diff --git a/complexpr-bin/src/main.rs b/complexpr-bin/src/main.rs index dc45b14..a86bd12 100644 --- a/complexpr-bin/src/main.rs +++ b/complexpr-bin/src/main.rs @@ -6,6 +6,7 @@ use rustyline::{self, error::ReadlineError}; const C_RESET: &str = "\x1b[0m"; const C_BLUE: &str = "\x1b[94m"; +const C_RED: &str = "\x1b[91m"; const PROMPT: &str = "\x1b[94m>> \x1b[0m"; fn panic_hook(info: &PanicInfo) { @@ -32,7 +33,7 @@ fn main() -> Result<(), Box> { let src = fs::read_to_string(fname)?; let res = interpret(&src, Some(fname.into()), None, false); if let Err(e) = res { - println!("{}", e); + eprintln!("Error: {}", e); } } else { repl()?; @@ -53,7 +54,7 @@ fn repl() -> Result<(), Box> { match result { Ok(Value::Nil) => (), Ok(value) => println!("{}", value.repr()), - Err(e) => print!("{}", e) + Err(e) => eprintln!("{}Error: {}{}", C_RED, C_RESET, e) } } Err(ReadlineError::Eof) => break, diff --git a/complexpr/src/env.rs b/complexpr/src/env.rs index 9b1b21c..f81c4e7 100644 --- a/complexpr/src/env.rs +++ b/complexpr/src/env.rs @@ -37,6 +37,7 @@ impl Environment { self.map.insert(name, value); } + #[allow(clippy::result_unit_err)] pub fn set(&mut self, name: Rc, value: Value) -> Result<(),()> { match self.map.contains_key(&name) { true => { self.map.insert(name, value); Ok(()) }, diff --git a/complexpr/src/eval.rs b/complexpr/src/eval.rs index 3528b58..74f572c 100644 --- a/complexpr/src/eval.rs +++ b/complexpr/src/eval.rs @@ -138,7 +138,7 @@ pub fn eval_expr(expr: &Expr, env: EnvRef) -> Result { => eval_pipeline(lhs, rhs, op, env), o => todo!("{:?}", o) // TODO other operations }, - Expr::Ternary { .. } => todo!(), + Expr::Ternary { arg1, arg2, arg3, op } => eval_ternary(arg1, arg2, arg3, op, env), Expr::Unary { arg, op } => eval_unary(arg, op, env), Expr::List { items } => { let mut list = Vec::with_capacity(items.len()); @@ -200,7 +200,7 @@ pub fn eval_ident(token: &Token, env: EnvRef) -> Result { if let Token { ty: TokenType::Ident(name), ..} = token { env.borrow_mut() .get(name) - .ok_or_else(|| RuntimeError::new("Variable not defined in scope", token.pos.clone())) + .ok_or_else(|| RuntimeError::new(format!("Variable {} not defined in scope", name), token.pos.clone())) } else { unreachable!() } @@ -407,6 +407,34 @@ fn eval_pipeline_inner(l: Value, r: Value, op: &Token) -> Result Result { + match op.ty { + TokenType::PipeSlash => { + let iter = eval_expr(arg1, env.clone())?; + let mut result = eval_expr(arg2, env.clone())?; + let func = eval_expr(arg3, env)?; + for v in iter.iter().map_err(|e| RuntimeError::new(e, op.pos.clone()))? { + let v = v.map_err(|e| e.complete(op.pos.clone()))?; + result = func.call(vec![result, v]).map_err(|e| e.complete(op.pos.clone()))?; + } + Ok(result) + }, + TokenType::PipeBackslash => { + let iter = eval_expr(arg1, env.clone())?; + let mut result = eval_expr(arg2, env.clone())?; + let func = eval_expr(arg3, env)?; + let lst = iter.iter().map_err(|e| RuntimeError::new(e, op.pos.clone()))?.collect::>>(); + for v in lst.into_iter().rev() { + let v = v.map_err(|e| e.complete(op.pos.clone()))?; + result = func.call(vec![v, result]).map_err(|e| e.complete(op.pos.clone()))?; + } + Ok(result) + }, + _ => unreachable!() + } +} + pub fn eval_unary(arg: &Expr, op: &Token, env: EnvRef) -> Result { let a = eval_expr(arg, env)?; match op.ty { diff --git a/complexpr/src/lexer.rs b/complexpr/src/lexer.rs index e02e0b9..effb2f7 100644 --- a/complexpr/src/lexer.rs +++ b/complexpr/src/lexer.rs @@ -63,7 +63,11 @@ impl Lexer { file: self.filename.clone(), pos: self.start, line: self.line, - col: self.col - (self.current - self.start) + col: if self.col < (self.current - self.start) { + 0 + } else { + self.col - (self.current - self.start) + } } }); } @@ -144,7 +148,7 @@ impl Lexer { Some('=') => self.add_token(TokenType::PlusEqual, "+="), _ => self.add_token(TokenType::Plus, "+"), }, - '-' => match self.expect(&['=', '>']) { + '-' => match self.expect(&['=']) { Some('=') => self.add_token(TokenType::MinusEqual, "-="), _ => self.add_token(TokenType::Minus, "-"), }, @@ -198,12 +202,12 @@ impl Lexer { Some('>') => self.add_token(TokenType::PipePoint, "|>"), Some('&') => self.add_token(TokenType::PipeAmper, "|&"), Some('/') => match self.expect(&['/']) { - Some(_) => self.add_token(TokenType::PipeDoubleSlash, "|//"), - None => self.add_token(TokenType::PipeSlash, "|/") + Some('/') => self.add_token(TokenType::PipeDoubleSlash, "|//"), + _ => self.add_token(TokenType::PipeSlash, "|/") }, Some('\\') => match self.expect(&['\\']) { - Some(_) => self.add_token(TokenType::PipeDoubleBackslash, "|\\\\"), - None => self.add_token(TokenType::PipeBackslash, "|\\") + Some('\\') => self.add_token(TokenType::PipeDoubleBackslash, "|\\\\"), + _ => self.add_token(TokenType::PipeBackslash, "|\\") }, _ => self.add_token(TokenType::Pipe, "|"), }, diff --git a/complexpr/src/lib.rs b/complexpr/src/lib.rs index 535eca8..4c3660d 100644 --- a/complexpr/src/lib.rs +++ b/complexpr/src/lib.rs @@ -91,7 +91,7 @@ impl From<&str> for RuntimeError { impl fmt::Display for ParserError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "Error: {}\n In {} at {},{}\n", + write!(f, "{}\n In {} at {},{}", self.message, self.pos.file.as_ref().map(|o| o.as_ref()).unwrap_or(""), self.pos.line, @@ -102,9 +102,9 @@ impl fmt::Display for ParserError { impl fmt::Display for RuntimeError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - writeln!(f, "Error: {}", self.message)?; + writeln!(f, "{}", self.message)?; for frame in &self.stacktrace { - writeln!(f, " In {} at {}:{}:{}", + write!(f, "\n In {} at {}:{}:{}", frame.fn_name.as_ref().map(|o| o.as_ref()).unwrap_or(""), frame.pos.file.as_ref().map(|o| o.as_ref()).unwrap_or(""), frame.pos.line, diff --git a/complexpr/src/parser.rs b/complexpr/src/parser.rs index c2e33e7..434dd5f 100644 --- a/complexpr/src/parser.rs +++ b/complexpr/src/parser.rs @@ -277,6 +277,7 @@ impl Parser { let op = self.next(); let right = self.logical_or()?; if op.ty == TokenType::PipeSlash || op.ty == TokenType::PipeBackslash { + self.err_on_eof()?; let next = self.next(); if next.ty != TokenType::Comma { return Err(self.mk_error("Expected comma after first argument")) diff --git a/complexpr/src/stdlib/mod.rs b/complexpr/src/stdlib/mod.rs index 99d532d..a94f440 100644 --- a/complexpr/src/stdlib/mod.rs +++ b/complexpr/src/stdlib/mod.rs @@ -36,6 +36,16 @@ pub fn load(env: &mut Environment) { env.declare(name.clone(), Value::Func(Func::Builtin { func: fn_list, arg_count: 1, name })); name = Rc::from("take"); env.declare(name.clone(), Value::Func(Func::Builtin { func: fn_take, arg_count: 2, name })); + name = Rc::from("skip"); + env.declare(name.clone(), Value::Func(Func::Builtin { func: fn_skip, arg_count: 2, name })); + name = Rc::from("forall"); + env.declare(name.clone(), Value::Func(Func::Builtin { func: fn_forall, arg_count: 2, name })); + name = Rc::from("exists"); + env.declare(name.clone(), Value::Func(Func::Builtin { func: fn_exists, arg_count: 2, name })); + name = Rc::from("min"); + env.declare(name.clone(), Value::Func(Func::Builtin { func: fn_min, arg_count: 2, name })); + name = Rc::from("max"); + env.declare(name.clone(), Value::Func(Func::Builtin { func: fn_max, arg_count: 2, name })); } fn fn_str(args: Vec) -> Result { @@ -185,3 +195,71 @@ fn fn_take(args: Vec) -> Result { func: take_inner })) } + +fn skip_inner(_: Vec, data: Rc>>, iter_data: Rc>>) -> Result { + let mut d = if let Value::Int(d) = data.borrow()[0] { d } else { + unreachable!() // checked by fn_skip() + }; + while d > 0 { + iter_data.borrow_mut()[0].next(); + d -= 1; + } + data.borrow_mut()[0] = Value::Int(d); + match iter_data.borrow_mut()[0].next() { + None => Ok(Value::Nil), + Some(x) => x + } +} + +fn fn_skip(args: Vec) -> Result { + let n = match args[0] { + Value::Int(n) if n <= 0 => return Err(RuntimeError::new_incomplete("First argument to skip must be nonnegative")), + Value::Int(n) => n, + _ => return Err(RuntimeError::new_incomplete("First argument to skip must be an integer")) + }; + let it = args[1].iter()?; + Ok(Value::Func(Func::BuiltinClosure { + arg_count: 0, + data: Rc::new(RefCell::new(vec![Value::Int(n)])), + iter_data: Rc::new(RefCell::new(vec![it])), + func: skip_inner + })) +} + +fn fn_forall(args: Vec) -> Result { + let func = &args[0]; + for item in args[1].iter()? { + let item = item?; + if !func.call(vec![item])?.truthy() { + return Ok(Value::Bool(false)) + } + } + Ok(Value::Bool(true)) +} + +fn fn_exists(args: Vec) -> Result { + let func = &args[0]; + for item in args[1].iter()? { + let item = item?; + if func.call(vec![item])?.truthy() { + return Ok(Value::Bool(true)) + } + } + Ok(Value::Bool(false)) +} + +fn fn_min(args: Vec) -> Result { + match args[0].partial_cmp(&args[1]) { + None => Err("Arguments to min must be comparable".into()), + Some(Ordering::Greater) => Ok(args[1].clone()), + _ => Ok(args[0].clone()) + } +} + +fn fn_max(args: Vec) -> Result { + match args[0].partial_cmp(&args[1]) { + None => Err("Arguments to max must be comparable".into()), + Some(Ordering::Less) => Ok(args[1].clone()), + _ => Ok(args[0].clone()) + } +} diff --git a/complexpr/src/value.rs b/complexpr/src/value.rs index 00bdb57..2c11cfc 100644 --- a/complexpr/src/value.rs +++ b/complexpr/src/value.rs @@ -108,9 +108,17 @@ impl Func { filled_args.append(&mut arg_values); inner.call(filled_args) } - } - Ordering::Less => { - Ok(Value::Func(Func::Partial { inner: Box::new(self.clone()), filled_args: arg_values })) + }, + Ordering::Less if arg_values.is_empty() => Err(RuntimeError::new_incomplete( + format!("Cannot call this function with zero arguments: expected {}", self.arg_count()) + )), + Ordering::Less => match self { + Self::Partial { inner, filled_args } => { + let mut args = filled_args.clone(); + args.append(&mut arg_values); + Ok(Value::Func(Func::Partial { inner: inner.clone(), filled_args: args })) + } + f => Ok(Value::Func(Func::Partial { inner: Box::new(f.clone()), filled_args: arg_values })) }, Ordering::Greater => Err(RuntimeError::new_incomplete( format!("Too many arguments for function: expected {}, got {}", self.arg_count(), arg_values.len()) @@ -323,6 +331,10 @@ impl Value { v => Err(format!("{:?} has no length", v)) } } + + pub fn is_empty(&self) -> Result { + Ok(self.len()? == 0) + } pub fn fracdiv(&self, other: &Value) -> Result { use Value::*;