diff --git a/Cargo.lock b/Cargo.lock index 462e306..a26e5bb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,12 +2,42 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "addr2line" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + [[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "backtrace" +version = "0.3.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab84319d616cfb654d03394f38ab7e6f0919e181b1b57e1fd15e7fb4077d9a7" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -41,6 +71,7 @@ dependencies = [ name = "complexpr" version = "0.1.0" dependencies = [ + "backtrace", "lazy_static", "num-complex", "num-rational", @@ -128,6 +159,12 @@ dependencies = [ "wasi", ] +[[package]] +name = "gimli" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d" + [[package]] name = "io-lifetimes" version = "0.7.3" @@ -167,6 +204,15 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +[[package]] +name = "miniz_oxide" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34" +dependencies = [ + "adler", +] + [[package]] name = "nibble_vec" version = "0.1.0" @@ -238,6 +284,15 @@ dependencies = [ "autocfg", ] +[[package]] +name = "object" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21158b2c33aa6d4561f1c0a6ea283ca92bc54802a93b263e910746d679a7eb53" +dependencies = [ + "memchr", +] + [[package]] name = "proc-macro2" version = "1.0.43" @@ -286,6 +341,12 @@ dependencies = [ "thiserror", ] +[[package]] +name = "rustc-demangle" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" + [[package]] name = "rustix" version = "0.35.9" diff --git a/Cargo.toml b/Cargo.toml index a7dd19d..c3b8954 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,3 +9,4 @@ num-complex = "0.4.1" num-rational = "0.4.0" num-traits = "0.2.15" rustyline = "10.0.0" +backtrace = "0.3.66" diff --git a/examples/cat.cxpr b/examples/cat.cxpr index d46429d..ffaf3c9 100644 --- a/examples/cat.cxpr +++ b/examples/cat.cxpr @@ -1,3 +1,3 @@ while true { print(input()); -} \ No newline at end of file +} diff --git a/examples/collatz.cxpr b/examples/collatz.cxpr index f2fb77a..ae0d830 100644 --- a/examples/collatz.cxpr +++ b/examples/collatz.cxpr @@ -7,4 +7,4 @@ while n != 1 { n = 3*n+1; } } -println(n); \ No newline at end of file +println(n); diff --git a/examples/factorial.cxpr b/examples/factorial.cxpr new file mode 100644 index 0000000..cfc5a5e --- /dev/null +++ b/examples/factorial.cxpr @@ -0,0 +1,11 @@ +fn factorial(n) { + if n <= 1 { + return 1; + } else { + return n * factorial(n-1); + } +} + +for n : range(0, 10) { + println(factorial(n)); +} diff --git a/examples/fib.cxpr b/examples/fib.cxpr index 4e39cd3..4f34716 100644 --- a/examples/fib.cxpr +++ b/examples/fib.cxpr @@ -6,4 +6,4 @@ while z < 1000000 { z = y; y = x; println(z); -} \ No newline at end of file +} diff --git a/examples/helloworld.cxpr b/examples/helloworld.cxpr index ce50212..0b449fa 100644 --- a/examples/helloworld.cxpr +++ b/examples/helloworld.cxpr @@ -1,2 +1,2 @@ # Prints the string "Hello, world!" followed by a newline -println("Hello, world!"); \ No newline at end of file +println("Hello, world!"); diff --git a/examples/mbrot.cxpr b/examples/mbrot.cxpr index ec6a8c8..f958182 100644 --- a/examples/mbrot.cxpr +++ b/examples/mbrot.cxpr @@ -36,4 +36,4 @@ for y: range(0,yscale) { } } println(""); -} \ No newline at end of file +} diff --git a/src/bin/main.rs b/src/bin/main.rs index 117378e..539f23d 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -1,5 +1,6 @@ -use std::{rc::Rc, cell::RefCell, fs}; +use std::{rc::Rc, cell::RefCell, fs, panic::{self, PanicInfo}, thread::Thread}; +use backtrace::Backtrace; use complexpr::{eval::Environment, interpreter::interpret, value::Value, stdlib}; use rustyline::{self, error::ReadlineError}; @@ -7,7 +8,24 @@ const C_RESET: &str = "\x1b[0m"; const C_BLUE: &str = "\x1b[94m"; const PROMPT: &str = "\x1b[94m>> \x1b[0m"; +fn panic_hook(info: &PanicInfo) { + eprintln!("{:?}", Backtrace::new()); + eprintln!("!!! Internal interpreter error occured !!!"); + if let Some(s) = std::thread::current().name() { + eprintln!("Thread: {}", s); + } + if let Some(loc) = info.location() { + eprintln!("Location: {}:{}:{}", loc.file(), loc.line(), loc.column()) + } + if let Some(s) = info.payload().downcast_ref::<&str>() { + eprintln!("Message: {}", s); + } else if let Some(s) = info.payload().downcast_ref::() { + eprintln!("Message: {}", s); + } +} + fn main() -> Result<(), Box> { + panic::set_hook(Box::new(panic_hook)); let args: Vec = std::env::args().collect(); if args.len() == 2 { let fname = &args[1]; @@ -35,7 +53,7 @@ fn repl() -> Result<(), Box> { match result { Ok(Value::Nil) => (), Ok(value) => println!("{}", value.repr()), - Err(e) => println!("{}", e) + Err(e) => print!("{}", e) } } Err(ReadlineError::Eof) => break, diff --git a/src/eval.rs b/src/eval.rs index 30b4d4a..b3c26b2 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -1,6 +1,6 @@ use std::{collections::HashMap, rc::Rc, cell::RefCell}; -use crate::{value::{Value, Complex}, expr::{Stmt, Expr}, token::{TokenType, Token, OpType}, RuntimeError, Position}; +use crate::{value::{Value, Complex, Func}, expr::{Stmt, Expr}, token::{TokenType, Token, OpType}, RuntimeError, Position}; #[derive(Debug)] pub struct Environment { @@ -54,17 +54,21 @@ impl Default for Environment { } } +#[derive(Debug)] pub enum Unwind { - Continue{pos: Position}, Break{pos: Position}, Return{pos: Position, value: Value}, Error(RuntimeError) + Continue{pos: Position}, + Break{pos: Position}, + Return{pos: Position, value: Value}, + Error(RuntimeError) } impl Unwind { pub fn as_error(self) -> RuntimeError { match self { Self::Error(e) => e, - Self::Continue { pos } => RuntimeError { message: "continue statement outside of loop".into(), pos }, - Self::Break { pos } => RuntimeError { message: "break statement outside of loop".into(), pos }, - Self::Return { pos, .. } => RuntimeError { message: "return statement outside of function".into(), pos }, + Self::Continue { pos } => RuntimeError::new("continue statement outside of loop", pos), + Self::Break { pos } => RuntimeError::new("break statement outside of loop", pos), + Self::Return { pos, .. } => RuntimeError::new("return statement outside of function", pos), } } } @@ -86,7 +90,7 @@ fn unwrap_ident_token(tok: &Token) -> &Rc { pub fn eval_stmt(stmt: &Stmt, env: EnvRef) -> Result<(), Unwind> { match stmt { Stmt::Expr{ expr } - => drop(eval_expr(expr, env)), + => drop(eval_expr(expr, env)?), Stmt::Let { lhs, rhs: None } => env.borrow_mut().declare(unwrap_ident_token(lhs).clone(), Value::Nil), Stmt::Let { lhs, rhs: Some(rhs) } => { @@ -95,8 +99,8 @@ pub fn eval_stmt(stmt: &Stmt, env: EnvRef) -> Result<(), Unwind> { }, Stmt::Block { stmts } => { let block_env = Environment::extend(env).wrap(); - for stmt in stmts { - eval_stmt(stmt, block_env.clone())? + for stmt in stmts { + eval_stmt(stmt, block_env.clone())?; } }, Stmt::If { if_clauses, else_clause } => { @@ -116,7 +120,7 @@ pub fn eval_stmt(stmt: &Stmt, env: EnvRef) -> Result<(), Unwind> { env.borrow_mut().declare(name.clone(), Value::Nil); let iterator = iter.iter(); if let Err(e) = iterator { - return Err(RuntimeError { message: e, pos: var.pos.clone() }.into()) + return Err(RuntimeError::new(e, var.pos.clone()).into()) } if let Ok(i) = iterator { for v in i { @@ -145,8 +149,22 @@ pub fn eval_stmt(stmt: &Stmt, env: EnvRef) -> Result<(), Unwind> { } } }, + Stmt::Fn { name, args, body } => { + let name = name.ty.clone().as_ident().unwrap(); + let func = Func { + name: Some(name.clone()), + args: args.into_iter().map(|a| a.ty.clone().as_ident().unwrap()).collect(), + env: env.clone(), + func: body.as_ref().clone() + }; + env.borrow_mut().declare(name, Value::Func(func)); + }, Stmt::Break { tok } => return Err(Unwind::Break { pos: tok.pos.clone() }), Stmt::Continue { tok } => return Err(Unwind::Continue { pos: tok.pos.clone() }), + Stmt::Return { tok, expr } => { + let value = eval_expr(expr, env)?; + return Err(Unwind::Return { pos: tok.pos.clone(), value }) + } } Ok(()) } @@ -186,7 +204,16 @@ pub fn eval_expr(expr: &Expr, env: EnvRef) -> Result { Expr::Index { lhs, index, pos } => { let l = eval_expr(lhs, env.clone())?; let idx = eval_expr(index, env)?; - l.index(&idx).map_err(|e| RuntimeError { message: e, pos: pos.clone() }) + l.index(&idx).map_err(|e| RuntimeError::new(e, pos.clone())) + }, + Expr::Fn { args, body } => { + let func = Func { + name: None, + args: args.into_iter().map(|a| a.ty.clone().as_ident().unwrap()).collect(), + env: env.clone(), + func: body.as_ref().clone() + }; + Ok(Value::Func(func)) }, } } @@ -209,7 +236,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 { message: "Variable not defined in scope".into(), pos: token.pos.clone() }) + .ok_or_else(|| RuntimeError::new("Variable not defined in scope", token.pos.clone())) } else { unreachable!() } @@ -223,13 +250,13 @@ pub fn eval_assignment(lhs: &Expr, rhs: &Expr, op: &Token, env: EnvRef) -> Resul let r = eval_expr(rhs, env.clone())?; env.borrow_mut() .set(name.clone(), r) - .map_err(|_| RuntimeError { message: "Variable not declared before assignment".into(), pos: op.pos.clone() })?; + .map_err(|_| RuntimeError::new("Variable not declared before assignment", op.pos.clone()))?; Ok(Value::Nil) } else { // compound assignment let prev_value = env.borrow_mut() .get(name) - .ok_or_else(|| RuntimeError { message: "Variable not defined in scope".into(), pos: op.pos.clone()})?; + .ok_or_else(|| RuntimeError::new("Variable not defined in scope", op.pos.clone()))?; let r = eval_expr(rhs, env.clone())?; let result = match op.ty { @@ -239,7 +266,7 @@ pub fn eval_assignment(lhs: &Expr, rhs: &Expr, op: &Token, env: EnvRef) -> Resul TokenType::SlashEqual => &prev_value / &r, TokenType::PercentEqual => &prev_value % &r, _ => todo!() // TODO more operations - }.map_err(|e| RuntimeError { message: e, pos: op.pos.clone() })?; + }.map_err(|e| RuntimeError::new(e, op.pos.clone()))?; env.borrow_mut() .set(name.clone(), result.clone()).expect("unreachable"); @@ -250,10 +277,10 @@ pub fn eval_assignment(lhs: &Expr, rhs: &Expr, op: &Token, env: EnvRef) -> Resul let idx = eval_expr(index, env.clone())?; if op.ty == TokenType::Equal { let r = eval_expr(rhs, env)?; - l.assign_index(&idx, r.clone()).map_err(|e| RuntimeError { message: e, pos: pos.clone() })?; + l.assign_index(&idx, r.clone()).map_err(|e| RuntimeError::new(e, pos.clone()))?; Ok(r) } else { - let prev_value = l.index(&idx).map_err(|e| RuntimeError { message: e, pos: pos.clone() })?; + let prev_value = l.index(&idx).map_err(|e| RuntimeError::new(e, pos.clone()))?; let r = eval_expr(rhs, env)?; let result = match op.ty { TokenType::PlusEqual => &prev_value + &r, @@ -262,8 +289,8 @@ pub fn eval_assignment(lhs: &Expr, rhs: &Expr, op: &Token, env: EnvRef) -> Resul TokenType::SlashEqual => &prev_value / &r, TokenType::PercentEqual => &prev_value % &r, _ => todo!() // TODO more operations - }.map_err(|e| RuntimeError { message: e, pos: op.pos.clone() })?; - l.assign_index(&idx, result.clone()).map_err(|e| RuntimeError { message: e, pos: pos.clone() })?; + }.map_err(|e| RuntimeError::new(e, op.pos.clone()))?; + l.assign_index(&idx, result.clone()).map_err(|e| RuntimeError::new(e, pos.clone()))?; Ok(result) } } else { @@ -281,7 +308,7 @@ pub fn eval_binary(lhs: &Expr, rhs: &Expr, op: &Token, env: EnvRef) -> Result &l / &r, TokenType::Percent => &l % &r, _ => todo!() // TODO other operations - }.map_err(|e| RuntimeError { message: e, pos: op.pos.clone() }) + }.map_err(|e| RuntimeError::new(e, op.pos.clone())) } pub fn eval_boolean(lhs: &Expr, rhs: &Expr, op: &Token, env: EnvRef) -> Result { @@ -304,10 +331,10 @@ pub fn eval_boolean(lhs: &Expr, rhs: &Expr, op: &Token, env: EnvRef) -> Result Result { let l = eval_expr(lhs, env.clone())?; let r = eval_expr(rhs, env)?; - let mk_err = || RuntimeError { - message: format!("Cannot compare {:?} with {:?}", l, r), - pos: op.pos.clone() - }; + let mk_err = || RuntimeError::new( + format!("Cannot compare {:?} with {:?}", l, r), + op.pos.clone() + ); match op.ty { TokenType::DoubleEqual => Ok(Value::Bool(l == r)), TokenType::BangEqual => Ok(Value::Bool(l != r)), @@ -336,5 +363,5 @@ pub fn eval_unary(arg: &Expr, op: &Token, env: EnvRef) -> Result -a, TokenType::Bang => Ok(Value::Bool(!a.truthy())), _ => todo!(), - }.map_err(|e| RuntimeError { message: e, pos: op.pos.clone() }) + }.map_err(|e| RuntimeError::new(e, op.pos.clone())) } diff --git a/src/expr.rs b/src/expr.rs index 0d98b5c..8d35107 100644 --- a/src/expr.rs +++ b/src/expr.rs @@ -2,6 +2,7 @@ use std::fmt; use crate::{token::{Token, OpType}, Position}; +#[derive(Clone)] pub enum Stmt { Expr { expr: Expr }, Let { lhs: Token, rhs: Option }, @@ -11,6 +12,8 @@ pub enum Stmt { While { expr: Expr, stmt: Box }, Break { tok: Token }, Continue { tok: Token }, + Return { tok: Token, expr: Expr }, + Fn { name: Token, args: Vec, body: Box }, } impl fmt::Debug for Stmt { @@ -18,11 +21,13 @@ impl fmt::Debug for Stmt { match self { Self::Expr { expr } => write!(f, "{:?}", expr), Self::Let { lhs, rhs } => write!(f, "(let {:?} = {:?})", lhs, rhs), + Self::Block { stmts } => write!(f, "(block {:?})", stmts), _ => todo!() } } } +#[derive(Clone)] pub enum Expr { Binary { lhs: Box, rhs: Box, op: Token }, Unary { arg: Box, op: Token }, @@ -31,6 +36,7 @@ pub enum Expr { List { items: Vec }, FuncCall { func: Box, args: Vec, pos: Position }, Index { lhs: Box, index: Box, pos: Position }, + Fn { args: Vec, body: Box }, } impl fmt::Debug for Expr { @@ -43,6 +49,7 @@ impl fmt::Debug for Expr { Self::List { items } => write!(f, "(list {:?})", 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/interpreter.rs b/src/interpreter.rs index 928ab0c..36cfefc 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -3,7 +3,8 @@ use std::{cell::RefCell, rc::Rc}; use crate::{value::Value, lexer::Lexer, parser::Parser, eval::{Environment, eval_stmt, eval_expr, EnvRef}, expr::Stmt, stdlib}; pub fn interpret(src: &str, fname: Option, env: Option, repl: bool) -> Result> { - let mut lexer = Lexer::new(src, fname); + let ctx_name = if repl { "" } else { fname.as_ref().map(|s| s.as_ref()).unwrap_or("") }; + let mut lexer = Lexer::new(src, fname.clone()); lexer.lex()?; let mut parser = Parser::new(lexer.into_tokens(), repl); let ast = parser.parse()?; @@ -18,9 +19,9 @@ pub fn interpret(src: &str, fname: Option, env: Option, repl: bo let mut result = Value::Nil; for stmt in ast { if let Stmt::Expr{expr} = stmt { - result = eval_expr(&expr, environ.clone())?; + result = eval_expr(&expr, environ.clone()).map_err(|e| e.finish(Some(Rc::from(ctx_name))))?; } else { - eval_stmt(&stmt, environ.clone()).map_err(|e| e.as_error())?; + eval_stmt(&stmt, environ.clone()).map_err(|e| e.as_error().finish(Some(Rc::from(ctx_name))))?; result = Value::Nil; } } diff --git a/src/lexer.rs b/src/lexer.rs index 02fcb83..8c57632 100644 --- a/src/lexer.rs +++ b/src/lexer.rs @@ -268,6 +268,7 @@ impl Lexer { "else" => TokenType::Else, "while" => TokenType::While, "for" => TokenType::For, + "fn" => TokenType::Fn, "let" => TokenType::Let, "break" => TokenType::Break, "continue" => TokenType::Continue, diff --git a/src/lib.rs b/src/lib.rs index f38afa2..8bf4e1f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -24,10 +24,40 @@ pub struct ParserError { pub pos: Position } +#[derive(Debug)] +pub struct Stackframe { + pub pos: Position, + pub fn_name: Option>, +} + #[derive(Debug)] pub struct RuntimeError { pub message: String, - pub pos: Position + pub stacktrace: Vec, + pub last_pos: Option, +} + +impl RuntimeError { + pub fn new(message: S, pos: Position) -> Self + where S: Into { + Self { + message: message.into(), + stacktrace: vec![], + last_pos: Some(pos) + } + } + + pub fn exit_fn(mut self, fn_name: Option>, pos: Position) -> Self { + self.stacktrace.push(Stackframe { pos: self.last_pos.unwrap(), fn_name }); + self.last_pos = Some(pos); + self + } + + pub fn finish(mut self, ctx_name: Option>) -> Self { + self.stacktrace.push(Stackframe { pos: self.last_pos.unwrap(), fn_name: ctx_name }); + self.last_pos = None; + self + } } impl fmt::Display for ParserError { @@ -43,12 +73,16 @@ impl fmt::Display for ParserError { impl fmt::Display for RuntimeError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "Error: {}\n In {} at {},{}", - self.message, - self.pos.file.as_ref().map(|o| o.as_ref()).unwrap_or(""), - self.pos.line, - self.pos.col - ) + writeln!(f, "Error: {}", self.message)?; + for frame in &self.stacktrace { + writeln!(f, " 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, + frame.pos.col + )?; + } + Ok(()) } } diff --git a/src/parser.rs b/src/parser.rs index 13c530c..20d138c 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -82,11 +82,20 @@ impl Parser { TokenType::Break => { let tok = self.next(); self.terminate_stmt(Stmt::Break{ tok }) - } + }, TokenType::Continue => { let tok = self.next(); self.terminate_stmt(Stmt::Continue{ tok }) - } + }, + TokenType::Return => { + let tok = self.next(); + let expr = self.assignment()?; + self.terminate_stmt(Stmt::Return{ tok, expr }) + }, + TokenType::Fn => { + let tok = self.next(); + self.fndef() + }, _ => { // fallback to an expression terminated with a semicolon let expr = self.assignment()?; @@ -201,6 +210,36 @@ impl Parser { Ok(Stmt::While{ expr, stmt: Box::new(stmt) }) } + fn fndef(&mut self) -> Result { + self.err_on_eof()?; + let name = self.next(); + let name = if let TokenType::Ident(_) = name.ty { + name + } else { + return Err(ParserError { message: "Expected identifer in function declaration".into(), pos: name.pos }) + }; + 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(); + self.err_on_eof()?; + let body = self.statement()?; + Ok(Stmt::Fn { name, args, body: Box::new(body) }) + } + + fn ident(&mut self) -> Result { + let next = self.next(); + match next.ty { + TokenType::Ident(_) => Ok(Expr::Ident{ value: next }), + _ => Err(ParserError { message: "Expected identifier".into(), pos: next.pos }) + } + } + fn block(&mut self) -> Result { let mut stmts = vec![]; while !self.at_end() && self.peek().ty != TokenType::RBrace { @@ -352,6 +391,19 @@ 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::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(); + self.err_on_eof()?; + let body = self.statement()?; + Ok(Expr::Fn { args, body: Box::new(body) }) } else { Err(self.mk_error(format!("Unexpected token: {:?}", next.ty))) } diff --git a/src/stdlib/mod.rs b/src/stdlib/mod.rs index ef103d8..5127f84 100644 --- a/src/stdlib/mod.rs +++ b/src/stdlib/mod.rs @@ -2,32 +2,32 @@ use std::{rc::Rc, io::Write, cmp::Ordering}; use num_traits::ToPrimitive; -use crate::{value::{Value, BuiltinFn}, eval::Environment}; +use crate::{value::{Value, BuiltinFunc}, eval::Environment}; pub fn load(env: &mut Environment) { let mut name: Rc; name = Rc::from("str"); - env.declare(name.clone(), Value::BuiltinFn(BuiltinFn { func: fn_str, arg_count: 1, name })); + env.declare(name.clone(), Value::BuiltinFunc(BuiltinFunc { func: fn_str, arg_count: 1, name })); name = Rc::from("repr"); - env.declare(name.clone(), Value::BuiltinFn(BuiltinFn { func: fn_repr, arg_count: 1, name })); + env.declare(name.clone(), Value::BuiltinFunc(BuiltinFunc { func: fn_repr, arg_count: 1, name })); name = Rc::from("print"); - env.declare(name.clone(), Value::BuiltinFn(BuiltinFn { func: fn_print, arg_count: 1, name })); + env.declare(name.clone(), Value::BuiltinFunc(BuiltinFunc { func: fn_print, arg_count: 1, name })); name = Rc::from("println"); - env.declare(name.clone(), Value::BuiltinFn(BuiltinFn { func: fn_println, arg_count: 1, name })); + env.declare(name.clone(), Value::BuiltinFunc(BuiltinFunc { func: fn_println, arg_count: 1, name })); name = Rc::from("input"); - env.declare(name.clone(), Value::BuiltinFn(BuiltinFn { func: fn_input, arg_count: 0, name })); + env.declare(name.clone(), Value::BuiltinFunc(BuiltinFunc { func: fn_input, arg_count: 0, name })); name = Rc::from("ord"); - env.declare(name.clone(), Value::BuiltinFn(BuiltinFn { func: fn_ord, arg_count: 1, name })); + env.declare(name.clone(), Value::BuiltinFunc(BuiltinFunc { func: fn_ord, arg_count: 1, name })); name = Rc::from("chr"); - env.declare(name.clone(), Value::BuiltinFn(BuiltinFn { func: fn_chr, arg_count: 1, name })); + env.declare(name.clone(), Value::BuiltinFunc(BuiltinFunc { func: fn_chr, arg_count: 1, name })); name = Rc::from("range"); - env.declare(name.clone(), Value::BuiltinFn(BuiltinFn { func: fn_range, arg_count: 2, name })); + env.declare(name.clone(), Value::BuiltinFunc(BuiltinFunc { func: fn_range, arg_count: 2, name })); name = Rc::from("len"); - env.declare(name.clone(), Value::BuiltinFn(BuiltinFn { func: fn_len, arg_count: 1, name })); + env.declare(name.clone(), Value::BuiltinFunc(BuiltinFunc { func: fn_len, arg_count: 1, name })); name = Rc::from("re"); - env.declare(name.clone(), Value::BuiltinFn(BuiltinFn { func: fn_re, arg_count: 1, name })); + env.declare(name.clone(), Value::BuiltinFunc(BuiltinFunc { func: fn_re, arg_count: 1, name })); name = Rc::from("im"); - env.declare(name.clone(), Value::BuiltinFn(BuiltinFn { func: fn_im, arg_count: 1, name })); + env.declare(name.clone(), Value::BuiltinFunc(BuiltinFunc { func: fn_im, arg_count: 1, name })); } fn fn_str(args: Vec) -> Result { diff --git a/src/token.rs b/src/token.rs index 1a2430b..771c376 100644 --- a/src/token.rs +++ b/src/token.rs @@ -35,7 +35,7 @@ pub enum TokenType { True, False, Nil, If, Elif, Else, For, While, - Let, Break, Continue, Return + Fn, Let, Break, Continue, Return } impl TokenType { @@ -63,6 +63,13 @@ impl TokenType { _ => None } } + + pub fn as_ident(self) -> Option> { + match self { + Self::Ident(s) => Some(s), + _ => None + } + } } #[derive(Clone,Copy,Debug,PartialEq,Eq)] diff --git a/src/value.rs b/src/value.rs index ba7a80d..136f921 100644 --- a/src/value.rs +++ b/src/value.rs @@ -2,24 +2,32 @@ use std::{rc::Rc, collections::HashMap, ops::*, fmt, cmp::Ordering, cell::RefCel use num_traits::{Zero, ToPrimitive}; -use crate::{RuntimeError, Position}; +use crate::{RuntimeError, Position, eval::{EnvRef, eval_stmt, Environment, Unwind}, expr::Stmt}; pub type Rational = num_rational::Ratio; pub type Complex = num_complex::Complex64; #[derive(Clone)] -pub struct BuiltinFn { +pub struct BuiltinFunc { pub name: Rc, pub func: fn(Vec) -> Result, pub arg_count: usize } -impl fmt::Debug for BuiltinFn { +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 Func { + pub name: Option>, + pub args: Vec>, + pub env: EnvRef, + pub func: Stmt +} + #[derive(Clone, Debug)] pub struct Data { pub ty: usize, @@ -42,7 +50,8 @@ pub enum Value { Char(char), String(Rc), List(Rc>>), Map(Rc>), - BuiltinFn(BuiltinFn), + BuiltinFunc(BuiltinFunc), + Func(Func), Data(Data), } @@ -73,21 +82,44 @@ impl Value { pub fn call(&self, args: Vec, pos: &Position) -> Result { match self { - Value::BuiltinFn(f) => { + Value::BuiltinFunc(f) => { match args.len().cmp(&f.arg_count) { Ordering::Equal => - (f.func)(args).map_err(|e| RuntimeError { message: e, pos: pos.clone() }), - Ordering::Less => Err(RuntimeError { - message: format!("Not enough arguments for function: expected {}, got {}", f.arg_count, args.len()), - pos: pos.clone() - }), - Ordering::Greater => Err(RuntimeError { - message: format!("Too many arguments for function: expected {}, got {}", f.arg_count, args.len()), - pos: pos.clone() - }) + (f.func)(args).map_err(|e| RuntimeError::new(e, pos.clone())), + Ordering::Less => Err(RuntimeError::new( + format!("Not enough arguments for function: expected {}, got {}", f.arg_count, args.len()), + pos.clone() + )), + Ordering::Greater => Err(RuntimeError::new( + format!("Too many arguments for function: expected {}, got {}", f.arg_count, args.len()), + pos.clone() + )) } - } - _ => Err(RuntimeError { message: "Cannot call".into(), pos: pos.clone() }) + }, + Value::Func(f) => { + match args.len().cmp(&f.args.len()) { + Ordering::Equal => { + let mut env = Environment::extend(f.env.clone()); + for (k, v) in f.args.iter().zip(args.iter()) { + env.declare(k.clone(), v.clone()); + } + match eval_stmt(&f.func, env.wrap()) { + Ok(()) => Ok(Value::Nil), + Err(Unwind::Return{ value, .. }) => Ok(value), + Err(e) => Err(e.as_error().exit_fn(f.name.clone(), pos.clone())) + } + }, + Ordering::Less => Err(RuntimeError::new( + format!("Not enough arguments for function: expected {}, got {}", f.args.len(), args.len()), + pos.clone() + )), + Ordering::Greater => Err(RuntimeError::new( + format!("Too many arguments for function: expected {}, got {}", f.args.len(), args.len()), + pos.clone() + )) + } + }, + _ => Err(RuntimeError::new("Cannot call", pos.clone())) } } @@ -104,7 +136,11 @@ impl Value { Self::List(l) => Rc::from(format!("{:?}", l)), // TODO fix Self::Map(m) => Rc::from(format!("{:?}", m)), // TODO fix Self::Type(_) => todo!(), - Self::BuiltinFn(bf) => Rc::from(format!("", bf.name, bf.func as *const ())), + Self::BuiltinFunc(bf) => Rc::from(format!("", bf.name, bf.func as *const ())), + Self::Func(f) => match &f.name { + Some(name) => Rc::from(format!("", name)), + None => Rc::from(""), + }, Self::Data(_) => todo!(), } } @@ -122,7 +158,11 @@ impl Value { Self::List(l) => Rc::from(format!("{:?}", l.borrow())), // TODO fix Self::Map(m) => Rc::from(format!("{:?}", m)), // TODO fix Self::Type(_) => todo!(), - Self::BuiltinFn(bf) => Rc::from(format!("", bf.name, bf.func as *const ())), + Self::BuiltinFunc(bf) => Rc::from(format!("", bf.name, bf.func as *const ())), + Self::Func(f) => match &f.name { + Some(name) => Rc::from(format!("", name)), + None => Rc::from(""), + }, Self::Data(_) => todo!(), } } @@ -195,7 +235,7 @@ impl PartialEq for Value { (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::BuiltinFn(a), Self::BuiltinFn(b)) + (Self::BuiltinFunc(a), Self::BuiltinFunc(b)) => (a.func as *const ()) == (b.func as *const ()) && a.arg_count == b.arg_count, (Self::Data(_), Self::Data(_)) => todo!("Can't compare data yet"), _ => false