refactoring, extensible types, basic file i/o

This commit is contained in:
TriMill 2022-09-23 14:55:18 -04:00
parent 297c060e48
commit 7173c5dda1
7 changed files with 187 additions and 60 deletions

View file

@ -9,3 +9,4 @@ num-rational = "0.4.1"
num-traits = "0.2.15" num-traits = "0.2.15"
strum = { version = "0.24.1", features = ["derive"] } strum = { version = "0.24.1", features = ["derive"] }
paste = "1.0.9" paste = "1.0.9"
lazy_static = "1.4.0"

View file

@ -224,7 +224,7 @@ pub fn eval_expr(expr: &Expr, env: EnvRef) -> Result<Value, RuntimeError> {
Ok(Value::Func(func)) Ok(Value::Func(func))
}, },
Expr::StructInit { ty, args, .. } => { Expr::StructInit { ty, args, .. } => {
let ty_val = eval_expr(&ty, env.clone())?; let ty_val = eval_expr(ty, env.clone())?;
let ty = match ty_val { let ty = match ty_val {
Value::Type(ty) => ty, Value::Type(ty) => ty,
_ => return Err(format!("'{}' is not a type", ty_val.repr()).into()) _ => return Err(format!("'{}' is not a type", ty_val.repr()).into())
@ -294,7 +294,7 @@ pub fn eval_assignment(lhs: &Expr, rhs: &Expr, op: &Token, env: EnvRef) -> Resul
// plain assignment // plain assignment
let r = eval_expr(rhs, env.clone())?; let r = eval_expr(rhs, env.clone())?;
env.borrow_mut() env.borrow_mut()
.set(name.clone(), r.clone()) .set(name, r.clone())
.map_err(|_| RuntimeError::new("Variable not declared before assignment", op.pos.clone()))?; .map_err(|_| RuntimeError::new("Variable not declared before assignment", op.pos.clone()))?;
Ok(r) Ok(r)
} else { } else {
@ -306,7 +306,7 @@ pub fn eval_assignment(lhs: &Expr, rhs: &Expr, op: &Token, env: EnvRef) -> Resul
let result = compound_assignment_inner(&prev_value, &r, op)?; let result = compound_assignment_inner(&prev_value, &r, op)?;
env.borrow_mut().set(name, result.clone()).expect("unreachable"); env.borrow_mut().set(name, result).expect("unreachable");
Ok(Value::Nil) Ok(Value::Nil)
} }
}, },
@ -321,7 +321,7 @@ pub fn eval_assignment(lhs: &Expr, rhs: &Expr, op: &Token, env: EnvRef) -> Resul
let prev_value = l.index(&idx).map_err(|e| RuntimeError::new(e, pos.clone()))?; let prev_value = l.index(&idx).map_err(|e| RuntimeError::new(e, pos.clone()))?;
let r = eval_expr(rhs, env)?; let r = eval_expr(rhs, env)?;
let result = compound_assignment_inner(&prev_value, &r, op)?; let result = compound_assignment_inner(&prev_value, &r, op)?;
l.assign_index(&idx, result.clone()).map_err(|e| RuntimeError::new(e, pos.clone()))?; l.assign_index(&idx, result).map_err(|e| RuntimeError::new(e, pos.clone()))?;
Ok(Value::Nil) Ok(Value::Nil)
} }
}, },
@ -403,7 +403,7 @@ pub fn eval_boolean(lhs: &Expr, rhs: &Expr, op: &Token, env: EnvRef) -> Result<V
fn mk_pipecolon_inner(f: Func, it: CIterator) -> Func { fn mk_pipecolon_inner(f: Func, it: CIterator) -> Func {
let it = RefCell::new(it); let it = RefCell::new(it);
return Func::BuiltinClosure{ Func::BuiltinClosure{
arg_count: 0, arg_count: 0,
func: Rc::new(move |_| { func: Rc::new(move |_| {
if let Some(next) = it.borrow_mut().next() { if let Some(next) = it.borrow_mut().next() {
@ -417,7 +417,7 @@ fn mk_pipecolon_inner(f: Func, it: CIterator) -> Func {
fn mk_pipequestion_inner(f: Func, it: CIterator) -> Func { fn mk_pipequestion_inner(f: Func, it: CIterator) -> Func {
let it = RefCell::new(it); let it = RefCell::new(it);
return Func::BuiltinClosure { Func::BuiltinClosure {
arg_count: 0, arg_count: 0,
func: Rc::new(move |_| { func: Rc::new(move |_| {
loop { loop {

View file

@ -410,8 +410,8 @@ impl Parser {
}; };
Ok(Expr::Range { Ok(Expr::Range {
start: Box::new(start), start: Box::new(start),
end: end.map(|x| Box::new(x)), end: end.map(Box::new),
step: step.map(|x| Box::new(x)), step: step.map(Box::new),
incl incl
}) })
} else { } else {

View file

@ -1,21 +1,24 @@
use std::io::Write; use std::{io::{Write, Read}, fs::{OpenOptions, File}, rc::Rc, cell::RefCell, fmt};
use crate::{env::Environment, declare_fn, value::Value, RuntimeError}; use crate::{env::Environment, declare_fn, value::{Value, Native, TypeData}, RuntimeError};
pub fn load(env: &mut Environment) { pub fn load(env: &mut Environment) {
declare_fn!(env, print, 1); declare_fn!(env, print, 1);
declare_fn!(env, println, 1); declare_fn!(env, println, 1);
declare_fn!(env, input, 0); declare_fn!(env, input, 0);
declare_fn!(env, open, 1);
declare_fn!(env, close, 1);
declare_fn!(env, read, 1);
} }
fn fn_print(args: Vec<Value>) -> Result<Value, RuntimeError> { fn fn_print(args: Vec<Value>) -> Result<Value, RuntimeError> {
print!("{}", args[0].to_string()); print!("{}", args[0]);
std::io::stdout().flush().map_err(|e| e.to_string())?; std::io::stdout().flush().map_err(|e| e.to_string())?;
Ok(Value::Nil) Ok(Value::Nil)
} }
fn fn_println(args: Vec<Value>) -> Result<Value, RuntimeError> { fn fn_println(args: Vec<Value>) -> Result<Value, RuntimeError> {
println!("{}", args[0].to_string()); println!("{}", args[0]);
Ok(Value::Nil) Ok(Value::Nil)
} }
@ -28,3 +31,88 @@ fn fn_input(_: Vec<Value>) -> Result<Value, RuntimeError> {
} }
Ok(Value::from(buffer)) Ok(Value::from(buffer))
} }
struct FileBox {
f: Option<File>
}
lazy_static::lazy_static! {
static ref FILE_TYPE_ID: usize = crate::value::generate_type_id();
}
thread_local!(static FILE_TYPE_NAME: Rc<str> = Rc::from("File"));
impl Native for FileBox {
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
self
}
fn get_type(&self) -> crate::value::Type {
crate::value::Type { name: FILE_TYPE_NAME.with(Rc::clone), id: *FILE_TYPE_ID, typedata: TypeData::None }
}
}
impl fmt::Debug for FileBox {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self, f)
}
}
impl fmt::Display for FileBox {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "<file {:?}>", self.f)
}
}
fn fn_open(args: Vec<Value>) -> Result<Value, RuntimeError> {
let fname = match &args[0] {
Value::String(s) => s,
_ => return Err(format!("Expected filename, got {}", args[0]).into())
};
let f = match OpenOptions::new().read(true).open(fname.as_ref()) {
Ok(f) => f,
Err(e) => return Err(format!("Could not open file '{}': {}", fname, e).into())
};
Ok(Value::Native(Rc::new(RefCell::new(FileBox { f: Some(f) }))))
}
fn fn_close(args: Vec<Value>) -> Result<Value, RuntimeError> {
match &args[0] {
Value::Native(s) => {
let mut bs = s.borrow_mut();
let f: &mut FileBox = match bs.as_any_mut().downcast_mut() {
Some(f) => f,
None => return Err(format!("Expected a file, got {}", args[0]).into())
};
drop(f.f.take());
Ok(Value::Nil)
},
_ => return Err(format!("Expected a file, got {}", args[0]).into())
}
}
fn fn_read(args: Vec<Value>) -> Result<Value, RuntimeError> {
match &args[0] {
Value::Native(s) => {
let mut bs = s.borrow_mut();
let f: &mut FileBox = match bs.as_any_mut().downcast_mut() {
Some(f) => f,
None => return Err(format!("Expected a file, got {}", args[0]).into())
};
if let Some(file) = &mut f.f {
let mut buf = String::new();
match file.read_to_string(&mut buf) {
Ok(_) => return Ok(Value::from(buf)),
Err(e) => return Err(format!("Error reading file {}: {}", f, e).into())
}
} else {
Err("Attempt to read file that has been closed".into())
}
},
_ => return Err(format!("Expected a file, got {}", args[0]).into())
}
}

View file

@ -56,9 +56,9 @@ fn try_into_floaty(v: &Value, name: &'static str) -> Result<Floaty, String> {
match v { match v {
Value::Int(n) => Ok((*n as f64).into()), Value::Int(n) => Ok((*n as f64).into()),
Value::Float(f) => Ok((*f).into()), Value::Float(f) => Ok((*f).into()),
Value::Rational(r) => Ok((r.to_f64().ok_or_else(|| "Could not convert rational to float")?).into()), Value::Rational(r) => Ok((r.to_f64().ok_or("Could not convert rational to float")?).into()),
Value::Complex(z) => Ok((*z).into()), Value::Complex(z) => Ok((*z).into()),
_ => Err(format!("Argument to {} must be numeric", name).into()) _ => Err(format!("Argument to {} must be numeric", name))
} }
} }

View file

@ -2,9 +2,9 @@ pub mod io;
pub mod iter; pub mod iter;
pub mod math; pub mod math;
use std::time::{SystemTime, UNIX_EPOCH}; use std::{time::{SystemTime, UNIX_EPOCH}, rc::Rc, cell::RefCell};
use crate::{value::Value, RuntimeError, env::Environment}; use crate::{value::{Value, func::Func}, RuntimeError, env::Environment};
#[macro_export] #[macro_export]
macro_rules! declare_fn { macro_rules! declare_fn {
@ -28,6 +28,7 @@ pub fn load(env: &mut Environment) {
declare_fn!(env, chr, 1); declare_fn!(env, chr, 1);
declare_fn!(env, has, 2); declare_fn!(env, has, 2);
declare_fn!(env, len, 1); declare_fn!(env, len, 1);
declare_fn!(env, args, 0);
declare_fn!(env, time, 0); declare_fn!(env, time, 0);
declare_fn!(env, list, 1); declare_fn!(env, list, 1);
declare_fn!(env, push, 2); declare_fn!(env, push, 2);
@ -99,6 +100,21 @@ fn fn_has(args: Vec<Value>) -> Result<Value, RuntimeError> {
} }
} }
fn fn_args(_: Vec<Value>) -> Result<Value, RuntimeError> {
let mut args = std::env::args();
args.next();
let args = RefCell::new(args);
Ok(Value::Func(Func::BuiltinClosure {
arg_count: 0,
func: Rc::new(move |_| {
match args.borrow_mut().next() {
Some(s) => Ok(Value::from(s)),
None => Ok(Value::Nil)
}
})
}))
}
fn fn_time(_: Vec<Value>) -> Result<Value, RuntimeError> { fn fn_time(_: Vec<Value>) -> Result<Value, RuntimeError> {
let time = SystemTime::now().duration_since(UNIX_EPOCH).map_err(|e| e.to_string())?; let time = SystemTime::now().duration_since(UNIX_EPOCH).map_err(|e| e.to_string())?;
Ok(Value::from(time.as_secs_f64())) Ok(Value::from(time.as_secs_f64()))
@ -122,7 +138,7 @@ fn fn_push(args: Vec<Value>) -> Result<Value, RuntimeError> {
fn fn_pop(args: Vec<Value>) -> Result<Value, RuntimeError> { fn fn_pop(args: Vec<Value>) -> Result<Value, RuntimeError> {
if let Value::List(l) = &args[0] { if let Value::List(l) = &args[0] {
l.as_ref().borrow_mut().pop().ok_or("Pop on empty list".into()) l.as_ref().borrow_mut().pop().ok_or_else(|| "Pop on empty list".into())
} else{ } else{
Err("First argument to pop must be a list".into()) Err("First argument to pop must be a list".into())
} }

View file

@ -1,4 +1,4 @@
use std::{rc::Rc, collections::HashMap, ops::*, cmp::Ordering, cell::RefCell, hash::Hash, sync::atomic::{AtomicUsize, self}}; use std::{rc::Rc, collections::HashMap, ops::*, cmp::Ordering, cell::RefCell, hash::Hash, sync::atomic::{AtomicUsize, self}, fmt::{self, Write}, any::Any};
use num_traits::{Zero, ToPrimitive, Pow}; use num_traits::{Zero, ToPrimitive, Pow};
use strum::{EnumCount, EnumDiscriminants, EnumIter, IntoEnumIterator, AsRefStr}; use strum::{EnumCount, EnumDiscriminants, EnumIter, IntoEnumIterator, AsRefStr};
@ -10,6 +10,12 @@ pub mod func;
pub type Rational = num_rational::Ratio<i64>; pub type Rational = num_rational::Ratio<i64>;
pub type Complex = num_complex::Complex64; pub type Complex = num_complex::Complex64;
pub trait Native: Any + std::fmt::Debug + std::fmt::Display {
fn as_any(&self) -> &dyn Any;
fn as_any_mut(&mut self) -> &mut dyn Any;
fn get_type(&self) -> Type;
}
static TYPE_COUNTER: AtomicUsize = AtomicUsize::new(Value::COUNT); static TYPE_COUNTER: AtomicUsize = AtomicUsize::new(Value::COUNT);
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@ -33,6 +39,10 @@ impl PartialEq for Type {
impl Eq for Type {} impl Eq for Type {}
pub fn generate_type_id() -> usize {
TYPE_COUNTER.fetch_add(1, atomic::Ordering::Relaxed)
}
pub fn generate_struct_type(name: Rc<str>, fields: Vec<String>) -> Type { pub fn generate_struct_type(name: Rc<str>, fields: Vec<String>) -> Type {
Type { Type {
name, name,
@ -55,21 +65,25 @@ pub fn generate_builtin_types() -> Vec<Type> {
fn fmt_list(list: &Vec<Value>) -> String { fn fmt_list(list: &Vec<Value>) -> String {
let mut result: String = "[".into(); let mut result: String = "[".into();
if list.len() > 0 {
for v in list { for v in list {
result += &(v.repr() + ", "); result += &(v.repr() + ", ");
} }
result.pop(); result.pop();
result.pop(); result.pop();
}
result + "]" result + "]"
} }
fn fmt_map(map: &HashMap<Value, Value>) -> String { fn fmt_map(map: &HashMap<Value, Value>) -> String {
let mut result: String = "{".into(); let mut result: String = "{".into();
if map.len() > 0 {
for (k, v) in map { for (k, v) in map {
result += &(k.repr() + ": " + &v.repr() + ", "); result += &(k.repr() + ": " + &v.repr() + ", ");
} }
result.pop(); result.pop();
result.pop(); result.pop();
}
result + "}" result + "}"
} }
@ -140,6 +154,7 @@ pub enum Value {
Map(Rc<RefCell<HashMap<Value,Value>>>), Map(Rc<RefCell<HashMap<Value,Value>>>),
Func(Func), Func(Func),
Struct(CxprStruct), Struct(CxprStruct),
Native(Rc<RefCell<dyn Native>>),
} }
impl Value { impl Value {
@ -150,9 +165,9 @@ impl Value {
Float(f) => *f != 0.0, Float(f) => *f != 0.0,
Complex(z) => !z.is_zero(), Complex(z) => !z.is_zero(),
Rational(r) => !r.is_zero(), Rational(r) => !r.is_zero(),
String(s) => !s.len() == 0, String(s) => s.len() != 0,
List(l) => !l.borrow().len() == 0, List(l) => l.borrow().len() != 0,
Map(m) => !m.borrow().len() == 0, Map(m) => m.borrow().len() != 0,
Char(c) => *c != '\0', Char(c) => *c != '\0',
_ => true _ => true
} }
@ -180,35 +195,6 @@ impl Value {
} }
} }
pub fn to_string(&self) -> String {
match self {
Self::Nil => "nil".into(),
Self::Bool(b) => b.to_string(),
Self::Int(n) => n.to_string(),
Self::Float(f) => f.to_string(),
Self::Rational(r) => r.to_string(),
Self::Complex(z) => z.to_string(),
Self::Char(c) => c.to_string(),
Self::String(s) => s.as_ref().to_owned(),
Self::List(l) => fmt_list(&l.borrow()),
Self::Map(m) => fmt_map(&m.borrow()),
Self::Type(t) => format!("<type {}>", t.name),
Self::Func(Func::Builtin { name, func, .. }) => format!("<builtin fn {} at {:?}>", name, *func as *const ()),
Self::Func(Func::BuiltinClosure { .. }) => format!("<builtin anonymous fn>"),
Self::Func(f @ Func::Partial { .. }) => match f.name() {
Some(name) => format!("<partial of fn {}>", name),
None => "<partial of anonymous fn>".into(),
}
Self::Func(Func::Func { name, .. }) => match name {
Some(name) => format!("<fn {}>", name),
None => "<anonymous fn>".into(),
},
Self::Struct(CxprStruct { ty, data })
=> format!("{} {{ {} }}", ty.name,
data.borrow().iter().map(|(k, v)| format!("{}: {}", k, v.repr()))
.collect::<Vec<String>>().join(", "))
}
}
pub fn repr(&self) -> String { pub fn repr(&self) -> String {
match self { match self {
@ -290,8 +276,10 @@ impl Value {
pub fn get_type(&self) -> Type { pub fn get_type(&self) -> Type {
let discr = ValueDiscriminants::from(self); let discr = ValueDiscriminants::from(self);
if let Self::Struct(_) = self { if let Self::Struct(s) = self {
todo!() s.ty.clone()
} else if let Self::Native(n) = self {
n.borrow().get_type()
} else { } else {
Type { Type {
name: Rc::from(discr.as_ref()), name: Rc::from(discr.as_ref()),
@ -302,6 +290,39 @@ impl Value {
} }
} }
impl fmt::Display for Value {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Nil => f.write_str("nil"),
Self::Bool(b) => f.write_str(&b.to_string()),
Self::Int(n) => f.write_str(&n.to_string()),
Self::Float(x) => f.write_str(&x.to_string()),
Self::Rational(r) => f.write_str(&r.to_string()),
Self::Complex(z) => f.write_str(&z.to_string()),
Self::Char(c) => f.write_char(*c),
Self::String(s) => f.write_str(s.as_ref()),
Self::List(l) => f.write_str(&fmt_list(&l.borrow())),
Self::Map(m) => f.write_str(&fmt_map(&m.borrow())),
Self::Type(t) => write!(f, "<type {}>", t.name),
Self::Func(Func::Builtin { name, func, .. }) => write!(f, "<builtin fn {} at {:?}>", name, *func as *const ()),
Self::Func(Func::BuiltinClosure { .. }) => f.write_str("<builtin anonymous fn>"),
Self::Func(func @ Func::Partial { .. }) => match func.name() {
Some(name) => write!(f, "<partial of fn {}>", name),
None => f.write_str("<partial of anonymous fn>"),
}
Self::Func(Func::Func { name, .. }) => match name {
Some(name) => write!(f, "<fn {}>", name),
None => f.write_str("<anonymous fn>"),
},
Self::Struct(CxprStruct { ty, data })
=> write!(f, "{} {{ {} }}", ty.name,
data.borrow().iter().map(|(k, v)| format!("{}: {}", k, v.repr()))
.collect::<Vec<String>>().join(", ")),
Self::Native(x) => f.write_str(&x.borrow().to_string()),
}
}
}
#[allow(clippy::ptr_eq)] // provided fix does not work #[allow(clippy::ptr_eq)] // provided fix does not work
impl PartialEq for Value { impl PartialEq for Value {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
@ -415,6 +436,7 @@ impl Hash for Value {
Self::Map(_) => todo!(), Self::Map(_) => todo!(),
Self::Func(f) => f.hash(state), Self::Func(f) => f.hash(state),
Self::Struct(_) => todo!(), Self::Struct(_) => todo!(),
Self::Native(_) => todo!(),
} }
} }