refactoring, extensible types, basic file i/o
This commit is contained in:
parent
297c060e48
commit
7173c5dda1
7 changed files with 187 additions and 60 deletions
|
@ -9,3 +9,4 @@ num-rational = "0.4.1"
|
|||
num-traits = "0.2.15"
|
||||
strum = { version = "0.24.1", features = ["derive"] }
|
||||
paste = "1.0.9"
|
||||
lazy_static = "1.4.0"
|
||||
|
|
|
@ -224,7 +224,7 @@ pub fn eval_expr(expr: &Expr, env: EnvRef) -> Result<Value, RuntimeError> {
|
|||
Ok(Value::Func(func))
|
||||
},
|
||||
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 {
|
||||
Value::Type(ty) => ty,
|
||||
_ => 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
|
||||
let r = eval_expr(rhs, env.clone())?;
|
||||
env.borrow_mut()
|
||||
.set(name.clone(), r.clone())
|
||||
.set(name, r.clone())
|
||||
.map_err(|_| RuntimeError::new("Variable not declared before assignment", op.pos.clone()))?;
|
||||
Ok(r)
|
||||
} 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)?;
|
||||
|
||||
env.borrow_mut().set(name, result.clone()).expect("unreachable");
|
||||
env.borrow_mut().set(name, result).expect("unreachable");
|
||||
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 r = eval_expr(rhs, env)?;
|
||||
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)
|
||||
}
|
||||
},
|
||||
|
@ -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 {
|
||||
let it = RefCell::new(it);
|
||||
return Func::BuiltinClosure{
|
||||
Func::BuiltinClosure{
|
||||
arg_count: 0,
|
||||
func: Rc::new(move |_| {
|
||||
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 {
|
||||
let it = RefCell::new(it);
|
||||
return Func::BuiltinClosure {
|
||||
Func::BuiltinClosure {
|
||||
arg_count: 0,
|
||||
func: Rc::new(move |_| {
|
||||
loop {
|
||||
|
|
|
@ -410,8 +410,8 @@ impl Parser {
|
|||
};
|
||||
Ok(Expr::Range {
|
||||
start: Box::new(start),
|
||||
end: end.map(|x| Box::new(x)),
|
||||
step: step.map(|x| Box::new(x)),
|
||||
end: end.map(Box::new),
|
||||
step: step.map(Box::new),
|
||||
incl
|
||||
})
|
||||
} else {
|
||||
|
|
|
@ -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) {
|
||||
declare_fn!(env, print, 1);
|
||||
declare_fn!(env, println, 1);
|
||||
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> {
|
||||
print!("{}", args[0].to_string());
|
||||
print!("{}", args[0]);
|
||||
std::io::stdout().flush().map_err(|e| e.to_string())?;
|
||||
Ok(Value::Nil)
|
||||
}
|
||||
|
||||
fn fn_println(args: Vec<Value>) -> Result<Value, RuntimeError> {
|
||||
println!("{}", args[0].to_string());
|
||||
println!("{}", args[0]);
|
||||
Ok(Value::Nil)
|
||||
}
|
||||
|
||||
|
@ -28,3 +31,88 @@ fn fn_input(_: Vec<Value>) -> Result<Value, RuntimeError> {
|
|||
}
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -56,9 +56,9 @@ fn try_into_floaty(v: &Value, name: &'static str) -> Result<Floaty, String> {
|
|||
match v {
|
||||
Value::Int(n) => Ok((*n as f64).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()),
|
||||
_ => Err(format!("Argument to {} must be numeric", name).into())
|
||||
_ => Err(format!("Argument to {} must be numeric", name))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,9 +2,9 @@ pub mod io;
|
|||
pub mod iter;
|
||||
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_rules! declare_fn {
|
||||
|
@ -28,6 +28,7 @@ pub fn load(env: &mut Environment) {
|
|||
declare_fn!(env, chr, 1);
|
||||
declare_fn!(env, has, 2);
|
||||
declare_fn!(env, len, 1);
|
||||
declare_fn!(env, args, 0);
|
||||
declare_fn!(env, time, 0);
|
||||
declare_fn!(env, list, 1);
|
||||
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> {
|
||||
let time = SystemTime::now().duration_since(UNIX_EPOCH).map_err(|e| e.to_string())?;
|
||||
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> {
|
||||
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{
|
||||
Err("First argument to pop must be a list".into())
|
||||
}
|
||||
|
|
|
@ -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 strum::{EnumCount, EnumDiscriminants, EnumIter, IntoEnumIterator, AsRefStr};
|
||||
|
@ -10,6 +10,12 @@ pub mod func;
|
|||
pub type Rational = num_rational::Ratio<i64>;
|
||||
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);
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
@ -33,6 +39,10 @@ impl PartialEq 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 {
|
||||
Type {
|
||||
name,
|
||||
|
@ -55,21 +65,25 @@ pub fn generate_builtin_types() -> Vec<Type> {
|
|||
|
||||
fn fmt_list(list: &Vec<Value>) -> String {
|
||||
let mut result: String = "[".into();
|
||||
for v in list {
|
||||
result += &(v.repr() + ", ");
|
||||
if list.len() > 0 {
|
||||
for v in list {
|
||||
result += &(v.repr() + ", ");
|
||||
}
|
||||
result.pop();
|
||||
result.pop();
|
||||
}
|
||||
result.pop();
|
||||
result.pop();
|
||||
result + "]"
|
||||
}
|
||||
|
||||
fn fmt_map(map: &HashMap<Value, Value>) -> String {
|
||||
let mut result: String = "{".into();
|
||||
for (k, v) in map {
|
||||
result += &(k.repr() + ": " + &v.repr() + ", ");
|
||||
if map.len() > 0 {
|
||||
for (k, v) in map {
|
||||
result += &(k.repr() + ": " + &v.repr() + ", ");
|
||||
}
|
||||
result.pop();
|
||||
result.pop();
|
||||
}
|
||||
result.pop();
|
||||
result.pop();
|
||||
result + "}"
|
||||
}
|
||||
|
||||
|
@ -140,6 +154,7 @@ pub enum Value {
|
|||
Map(Rc<RefCell<HashMap<Value,Value>>>),
|
||||
Func(Func),
|
||||
Struct(CxprStruct),
|
||||
Native(Rc<RefCell<dyn Native>>),
|
||||
}
|
||||
|
||||
impl Value {
|
||||
|
@ -150,9 +165,9 @@ impl Value {
|
|||
Float(f) => *f != 0.0,
|
||||
Complex(z) => !z.is_zero(),
|
||||
Rational(r) => !r.is_zero(),
|
||||
String(s) => !s.len() == 0,
|
||||
List(l) => !l.borrow().len() == 0,
|
||||
Map(m) => !m.borrow().len() == 0,
|
||||
String(s) => s.len() != 0,
|
||||
List(l) => l.borrow().len() != 0,
|
||||
Map(m) => m.borrow().len() != 0,
|
||||
Char(c) => *c != '\0',
|
||||
_ => 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 {
|
||||
match self {
|
||||
|
@ -290,8 +276,10 @@ impl Value {
|
|||
|
||||
pub fn get_type(&self) -> Type {
|
||||
let discr = ValueDiscriminants::from(self);
|
||||
if let Self::Struct(_) = self {
|
||||
todo!()
|
||||
if let Self::Struct(s) = self {
|
||||
s.ty.clone()
|
||||
} else if let Self::Native(n) = self {
|
||||
n.borrow().get_type()
|
||||
} else {
|
||||
Type {
|
||||
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
|
||||
impl PartialEq for Value {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
|
@ -415,6 +436,7 @@ impl Hash for Value {
|
|||
Self::Map(_) => todo!(),
|
||||
Self::Func(f) => f.hash(state),
|
||||
Self::Struct(_) => todo!(),
|
||||
Self::Native(_) => todo!(),
|
||||
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue