diff --git a/Cargo.lock b/Cargo.lock index a26e5bb..7718d9d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -246,9 +246,9 @@ dependencies = [ [[package]] name = "num-complex" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97fbc387afefefd5e9e39493299f3069e14a140dd34dc19b4c1c1a8fddb6a790" +checksum = "7ae39348c8bc5fbd7f40c727a9925f03517afd2ab27d46702108b6a7e5414c19" dependencies = [ "num-traits", ] @@ -265,9 +265,9 @@ dependencies = [ [[package]] name = "num-rational" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d41702bd167c2df5520b384281bc111a4b5efcf7fbc4c9c222c815b07e0a6a6a" +checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" dependencies = [ "autocfg", "num-bigint", diff --git a/Cargo.toml b/Cargo.toml index c3b8954..55701a3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,8 +5,8 @@ edition = "2021" [dependencies] lazy_static = "1.4.0" -num-complex = "0.4.1" -num-rational = "0.4.0" +num-complex = "0.4.2" +num-rational = "0.4.1" num-traits = "0.2.15" rustyline = "10.0.0" backtrace = "0.3.66" diff --git a/examples/fib_recur.cxpr b/examples/fib_recur.cxpr new file mode 100644 index 0000000..65749ca --- /dev/null +++ b/examples/fib_recur.cxpr @@ -0,0 +1,36 @@ +fn memoize(f) { + let map = {}; + return fn(x) { + if has(map, x) { + return map[x]; + } else { + let result = f(x); + map[x] = result; + return result; + } + }; +} + +fn fib(n) { + if n <= 1 { + return n; + } else { + return fib(n-1) + fib(n-2); + } +} + +for n: [10,20,25] { + let start_time = time(); + let result = fib(n); + let elapsed = time() - start_time; + println("time for fib(" + str(n) + ")=" + str(result) + ": " + str(elapsed)); +} + +let fib = memoize(fib); + +for n: [10,20,25] { + let start_time = time(); + let result = fib(n); + let elapsed = time() - start_time; + println("time for memoized fib(" + str(n) + ")=" + str(result) + ": " + str(elapsed)); +} diff --git a/src/eval.rs b/src/eval.rs index 45a0abb..1fd803a 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -193,6 +193,15 @@ pub fn eval_expr(expr: &Expr, env: EnvRef) -> Result { } Ok(Value::from(list)) }, + Expr::Map { items } => { + let mut map = HashMap::with_capacity(items.len()); + for (k, v) in items { + let key = eval_expr(k, env.clone())?; + let value = eval_expr(v, env.clone())?; + map.insert(key, value); + } + Ok(Value::from(map)) + }, Expr::FuncCall { func, args, pos } => { let func = eval_expr(func, env.clone())?; let mut arg_values = Vec::with_capacity(args.len()); diff --git a/src/expr.rs b/src/expr.rs index 1f53417..9067f3b 100644 --- a/src/expr.rs +++ b/src/expr.rs @@ -40,6 +40,7 @@ pub enum Expr { Ident { value: Token }, Literal { value: Token }, List { items: Vec }, + Map { items: Vec<(Expr,Expr)> }, FuncCall { func: Box, args: Vec, pos: Position }, Index { lhs: Box, index: Box, pos: Position }, Fn { args: Vec, body: Box }, @@ -53,6 +54,7 @@ impl fmt::Debug for Expr { Self::Ident { value } => write!(f, "(ident {:?})", value), Self::Literal { value } => write!(f, "(lit {:?})", value), Self::List { items } => write!(f, "(list {:?})", items), + Self::Map { items } => write!(f, "(map {:?})", items), Self::FuncCall { func, args, .. } => write!(f, "(call {:?} {:?})", func, args), Self::Index { lhs, index, .. } => write!(f, "(index {:?} {:?})", lhs, index), Self::Fn { args, body } => write!(f, "(fn {:?} {:?})", args, body) diff --git a/src/parser.rs b/src/parser.rs index 6f45780..fe2d146 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -223,19 +223,16 @@ impl Parser { if let TokenType::LParen = next.ty {} else { return Err(ParserError { message: "Expected left parenthesis to start arguments list".into(), pos: next.pos }) } - let args = self.commalist(TokenType::RParen, Self::ident)?.into_iter().map(|e| { - if let Expr::Ident { value: i } = e { i } - else { panic!() } - }).collect(); + let args = self.commalist(TokenType::RParen, Self::ident)?; self.err_on_eof()?; let body = self.statement()?; Ok(Stmt::Fn { name, args, body: Box::new(body) }) } - fn ident(&mut self) -> Result { + fn ident(&mut self) -> Result { let next = self.next(); match next.ty { - TokenType::Ident(_) => Ok(Expr::Ident{ value: next }), + TokenType::Ident(_) => Ok(next), _ => Err(ParserError { message: "Expected identifier".into(), pos: next.pos }) } } @@ -261,7 +258,7 @@ impl Parser { Ok(expr) } - fn commalist(&mut self, terminator: TokenType, parse_item: fn(&mut Parser) -> Result) -> Result, ParserError> { + fn commalist(&mut self, terminator: TokenType, parse_item: fn(&mut Parser) -> Result) -> Result, ParserError> { let mut items = vec![]; while !self.at_end() && self.peek().ty != terminator { let expr = parse_item(self)?; @@ -370,6 +367,18 @@ impl Parser { Ok(Expr::Index { lhs: Box::new(expr), index: Box::new(index), pos: lbrack.pos }) } + fn kv_pair(&mut self) -> Result<(Expr, Expr), ParserError> { + let key = self.assignment()?; + self.err_on_eof()?; + let next = self.next(); + if next.ty != TokenType::Colon { + return Err(ParserError { message: "Expected colon in key-value pair".into(), pos: next.pos }) + } + self.err_on_eof()?; + let value = self.assignment()?; + Ok((key, value)) + } + fn expr_base(&mut self) -> Result { self.err_on_eof()?; let next = self.next(); @@ -391,16 +400,16 @@ impl Parser { } else if next.ty == TokenType::LBrack { let items = self.commalist(TokenType::RBrack, Self::assignment)?; Ok(Expr::List { items }) + } else if next.ty == TokenType::LBrace { + let items = self.commalist(TokenType::RBrace, Self::kv_pair)?; + Ok(Expr::Map { items }) } else if next.ty == TokenType::Fn { self.err_on_eof()?; let next = self.next(); if let TokenType::LParen = next.ty {} else { return Err(ParserError { message: "Expected left parenthesis to start arguments list".into(), pos: next.pos }) } - let args = self.commalist(TokenType::RParen, Self::ident)?.into_iter().map(|e| { - if let Expr::Ident { value: i } = e { i } - else { panic!() } - }).collect(); + let args = self.commalist(TokenType::RParen, Self::ident)?; self.err_on_eof()?; let body = self.statement()?; Ok(Expr::Fn { args, body: Box::new(body) }) diff --git a/src/stdlib/mod.rs b/src/stdlib/mod.rs index 82c9d83..e8156fb 100644 --- a/src/stdlib/mod.rs +++ b/src/stdlib/mod.rs @@ -1,4 +1,4 @@ -use std::{rc::Rc, io::Write, cmp::Ordering}; +use std::{rc::Rc, io::Write, cmp::Ordering, time::{SystemTime, UNIX_EPOCH}}; use num_traits::ToPrimitive; @@ -22,12 +22,16 @@ pub fn load(env: &mut Environment) { env.declare(name.clone(), Value::Func(Func::Builtin { func: fn_chr, arg_count: 1, name })); name = Rc::from("range"); env.declare(name.clone(), Value::Func(Func::Builtin { func: fn_range, arg_count: 2, name })); + name = Rc::from("has"); + env.declare(name.clone(), Value::Func(Func::Builtin { func: fn_has, arg_count: 2, name })); name = Rc::from("len"); env.declare(name.clone(), Value::Func(Func::Builtin { func: fn_len, arg_count: 1, name })); name = Rc::from("re"); env.declare(name.clone(), Value::Func(Func::Builtin { func: fn_re, arg_count: 1, name })); name = Rc::from("im"); env.declare(name.clone(), Value::Func(Func::Builtin { func: fn_im, arg_count: 1, name })); + name = Rc::from("time"); + env.declare(name.clone(), Value::Func(Func::Builtin { func: fn_time, arg_count: 0, name })); } fn fn_str(args: Vec) -> Result { @@ -99,11 +103,18 @@ fn fn_len(args: Vec) -> Result { match &args[0] { Value::String(s) => Ok(Value::Int(s.len() as i64)), Value::List(l) => Ok(Value::Int(l.borrow().len() as i64)), - Value::Map(m) => Ok(Value::Int(m.len() as i64)), + Value::Map(m) => Ok(Value::Int(m.borrow().len() as i64)), v => Err(format!("{:?} has no length", v)) } } +fn fn_has(args: Vec) -> Result { + match &args[0] { + Value::Map(m) => Ok(Value::from(m.borrow().contains_key(&args[1]))), + v => Err(format!("Argument to has must be a map, got {:?} ", v)) + } +} + fn fn_re(args: Vec) -> Result { match &args[0] { Value::Int(x) => Ok(Value::Float(*x as f64)), @@ -122,3 +133,8 @@ fn fn_im(args: Vec) -> Result { x => Err(format!("Cannot get real part of {:?}", x)) } } + +fn fn_time(_: Vec) -> Result { + let time = SystemTime::now().duration_since(UNIX_EPOCH).map_err(|e| e.to_string())?; + Ok(Value::from(time.as_secs_f64())) +} diff --git a/src/value.rs b/src/value.rs index 36001c3..2168eff 100644 --- a/src/value.rs +++ b/src/value.rs @@ -1,4 +1,4 @@ -use std::{rc::Rc, collections::HashMap, ops::*, fmt, cmp::Ordering, cell::RefCell}; +use std::{rc::Rc, collections::HashMap, ops::*, fmt, cmp::Ordering, cell::RefCell, hash::Hash}; use num_traits::{Zero, ToPrimitive}; @@ -77,6 +77,22 @@ impl Func { } } +impl Hash for Func { + fn hash(&self, state: &mut H) { + match self { + Self::Builtin { name, arg_count, func } => { + name.hash(state); + arg_count.hash(state); + func.hash(state); + }, + Self::Func { name, args, .. } => { + name.hash(state); + args.hash(state); + } + } + } +} + pub enum EitherRteOrString { Rte(RuntimeError), String(String) } @@ -122,20 +138,6 @@ impl Iterator for Func { } } } -// -//#[derive(Clone)] -//pub struct BuiltinFunc { -// pub name: Rc, -// pub func: fn(Vec) -> Result, -// pub arg_count: usize -//} -// -//impl fmt::Debug for BuiltinFunc { -// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { -// f.debug_struct("BuiltinFn").field("name", &self.name).field("arg_count", &self.arg_count).finish() -// } -//} -// #[derive(Clone, Debug)] pub struct Data { @@ -158,7 +160,8 @@ pub enum Value { Bool(bool), Char(char), String(Rc), - List(Rc>>), Map(Rc>), + List(Rc>>), + Map(Rc>>), Func(Func), Data(Data), } @@ -173,7 +176,7 @@ impl Value { Rational(r) => !r.is_zero(), String(s) => !s.len() == 0, List(l) => !l.borrow().len() == 0, - Map(m) => !m.len() == 0, + Map(m) => !m.borrow().len() == 0, _ => true } } @@ -263,14 +266,13 @@ impl Value { Value::Int(i) => Err(format!("List index {} cannot be negative", i)), _ => Err(format!("Cannot index {:?} with {:?}", self, idx)) } - Self::Map(_) => todo!(), + Self::Map(m) => m.borrow().get(idx).cloned().ok_or_else(|| format!("Map does not contain key {:?}", idx)), v => Err(format!("Cannot index into {:?}", v)) } } pub fn assign_index(&self, idx: &Value, value: Value) -> Result<(), String> { match self { - Self::String(_) => todo!("Can't mutate strings yet"), Self::List(l) => match idx { Value::Int(i) if *i >= 0 && (*i as usize) < l.borrow().len() => { l.borrow_mut()[*i as usize] = value; @@ -280,8 +282,11 @@ impl Value { Value::Int(i) => Err(format!("List index {} cannot be negative", i)), _ => Err(format!("Cannot index {:?} with {:?}", self, idx)) } - Self::Map(_) => todo!(), - v => Err(format!("Cannot index into {:?}", v)) + Self::Map(m) => { + m.borrow_mut().insert(idx.clone(), value); + Ok(()) + } + v => Err(format!("Cannot assign to index in {:?}", v)) } } } @@ -314,7 +319,19 @@ impl PartialEq for Value { (Self::Char(a), Self::Char(b)) => a == b, (Self::String(a), Self::String(b)) => a == b, (Self::List(a), Self::List(b)) => a == b, - (Self::Map(_), Self::Map(_)) => todo!("Can't test maps for equality yet"), + (Self::Map(a), Self::Map(b)) => { + // prevent double borrow + if a.as_ref().as_ptr() == b.as_ref().as_ptr() { return true } + if a.borrow().len() != b.borrow().len() { return false } + for (k, v1) in a.borrow().iter() { + let bb = b.borrow(); + let v2 = bb.get(k); + if v2 != Some(v1) { + return false + } + } + return true + } (Self::Func(f1), Self::Func(f2)) => match (f1, f2) { ( Func::Builtin { func: f1, arg_count: c1, .. }, @@ -328,6 +345,8 @@ impl PartialEq for Value { } } +impl Eq for Value {} + impl PartialOrd for Value { fn partial_cmp(&self, other: &Self) -> Option { if self == other { @@ -352,6 +371,43 @@ impl PartialOrd for Value { } } +fn hash_f64(f: f64, state: &mut H) { + if f.is_nan() { + "NaN".hash(state) + } else if f == 0.0 { + "0.0".hash(state) + } else if f.is_infinite() { + if f > 0.0 { + "+inf".hash(state) + } else{ + "-inf".hash(state) + } + } else{ + unsafe { std::mem::transmute::(f).hash(state) } + } +} + +impl Hash for Value { + fn hash(&self, state: &mut H) { + match self { + Self::Nil => "nil".hash(state), + Self::Type(_) => todo!(), + Self::Int(i) => i.hash(state), + Self::Float(f) => hash_f64(*f, state), + Self::Complex(z) => { hash_f64(z.re, state); hash_f64(z.im, state); } + Self::Rational(r) => { r.numer().hash(state); r.denom().hash(state); } + Self::Bool(b) => b.hash(state), + Self::Char(c) => c.hash(state), + Self::String(s) => s.hash(state), + Self::List(l) => l.borrow().hash(state), + Self::Map(_) => todo!(), + Self::Func(f) => f.hash(state), + Self::Data(_) => todo!(), + + } + } +} + macro_rules! value_from { ($variant:ident, $($kind:ty)*) => { $( @@ -370,6 +426,12 @@ impl From> for Value { } } +impl From> for Value { + fn from(x: HashMap) -> Self { + Self::Map(RefCell::new(x).into()) + } +} + value_from!(Int, u8 u16 u32 i8 i16 i32 i64); value_from!(Float, f32 f64); value_from!(Complex, Complex); @@ -378,7 +440,7 @@ value_from!(Bool, bool); value_from!(String, String Rc); value_from!(List, RefCell>); value_from!(Char, char); -value_from!(Map, HashMap); +value_from!(Map, RefCell>); macro_rules! impl_numeric_op { @@ -419,6 +481,9 @@ impl Neg for Value { fn neg(self) -> Self::Output { match self { Value::Int(a) => Ok(Value::Int(-a)), + Value::Float(a) => Ok(Value::Float(-a)), + Value::Rational(a) => Ok(Value::Rational(-a)), + Value::Complex(a) => Ok(Value::Complex(-a)), _ => Err(format!("Unsupported operation 'neg' on {:?}", self)) } }