diff --git a/examples/echo.lysp b/examples/echo.lysp new file mode 100644 index 0000000..a3b1b45 --- /dev/null +++ b/examples/echo.lysp @@ -0,0 +1,4 @@ +;; vi:ft=lisp:sw=2:ts=2 + +(print (length *args*)) +(print *args*) diff --git a/examples/repl.lysp b/examples/repl.lysp new file mode 100644 index 0000000..611f8e3 --- /dev/null +++ b/examples/repl.lysp @@ -0,0 +1,36 @@ +;; vi:ft=lisp:sw=2:ts=2 + +(defun cadr (x) (car (cdr x))) + +(defun map-ok-err (f-ok f-err result) + (if (= (car result) 'ok) + `(ok ,(f-ok (cadr result))) + `(err ,(f-err (cadr result))) + ) + ) +(defun map-ok (f-ok result) (map-ok-err f-ok identity result)) +(defun map-err (f-err result) (map-ok-err identity f-err result)) + +(defun repl-print (value) + (print "==>" value) + ) +(defun repl-eval-print (expression) + (map-ok-err repl-print repl-eval-error (unquote (eval expression))) + ) + +(defun repl-eval-error (error) + (print "Evaluation error:") + (print error) + ) +(defun repl-read-error (error) + (print "Parse error:") + (print error) + ) + +(loop + (let (expression (read)) + (if expression NIL (return)) + (setq expression (unquote expression)) + (map-ok-err repl-eval-print repl-read-error expression) + ) + ) diff --git a/src/compile/module.rs b/src/compile/module.rs index aa95af7..20c497e 100644 --- a/src/compile/module.rs +++ b/src/compile/module.rs @@ -18,6 +18,7 @@ use crate::{ #[derive(Default)] pub struct CompilationModule { + pub(crate) name: Option>, pub(crate) constant_pool: Pool, pub(crate) local_functions: HashMap, pub(crate) options: CompileOptions, @@ -27,8 +28,9 @@ pub struct CompilationModule { } impl CompilationModule { - pub fn new(options: CompileOptions) -> Self { + pub fn new(name: Option>, options: CompileOptions) -> Self { Self { + name, options, ..Default::default() } @@ -70,6 +72,7 @@ impl CompilationModule { // Emit all function code first let mut function_offsets = HashMap::new(); let mut instructions = vec![]; + let name = self.name; let root = self.root.unwrap(); for (index, function) in self.local_functions.into_iter() { function_offsets.insert(index, instructions.len()); @@ -99,6 +102,7 @@ impl CompilationModule { .collect(); Ok(Module { + name, constants, instructions, entry, diff --git a/src/main.rs b/src/main.rs index 9796ce5..d965279 100644 --- a/src/main.rs +++ b/src/main.rs @@ -30,6 +30,8 @@ enum Error { pub enum Trace { Compile, Execute, + Call, + Return, } impl FromStr for Trace { @@ -39,6 +41,8 @@ impl FromStr for Trace { match s { "compile" => Ok(Self::Compile), "execute" => Ok(Self::Execute), + "call" => Ok(Self::Call), + "return" => Ok(Self::Return), _ => Err(format!("Unknown trace flag: {s:?}")), } } @@ -53,6 +57,7 @@ struct Args { )] trace: Vec, module: Option, + arguments: Vec, } fn print_syntax_errors(errors: &[ParseError]) { @@ -84,7 +89,7 @@ fn handle_eval_error(value: Option<&Value>, input: EvalError) -> Error { eprintln!(":: {}", error.error); eprintln!(); if let Some(ip) = error.ip.as_ref() { - ip.module.dump(Some(ip.address), 8); + ip.module.dump(Some(ip.address), 8, 2); } } EvalError::Compile(CompileError::Parse(errors)) => { @@ -119,7 +124,7 @@ fn eval( env: &mut Environment, value: Value, ) -> Option { - let result = vm.eval_value(options.clone(), env, value.clone()); + let result = vm.eval_value(options.clone(), env, value.clone(), false); match result { Ok(r) => Some(r), Err(error) => { @@ -162,14 +167,15 @@ fn run_module>( path: P, ) -> Result<(), Error> { let path = path.as_ref(); + let name = format!("{}", path.display()); let reader = BufReader::new(File::open(path)?); let module_reader = ModuleReader::new(reader); - let module = match module_reader.compile(compile_options, env) { + let module = match module_reader.compile(Some(name.into()), compile_options, env) { Ok(module) => module, Err(error) => return Err(handle_module_error(error)), }; - match vm.eval_module(env, module) { + match vm.eval_module(env, module, false) { Ok(_) => Ok(()), Err(error) => Err(handle_eval_error(None, error)), } @@ -182,8 +188,19 @@ fn main() -> ExitCode { trace_compile: args.trace.contains(&Trace::Compile), }; vm.trace_instructions = args.trace.contains(&Trace::Execute); + vm.trace_calls = args.trace.contains(&Trace::Call); + vm.trace_returns = args.trace.contains(&Trace::Return); let mut env = Environment::default(); prelude::load(&mut env); + let mut arguments = vec![]; + if let Some(script) = args.module.as_ref() { + arguments.push(format!("{}", script.display())); + } + arguments.extend(args.arguments); + env.set_global_value( + "*args*", + Value::list_or_nil(arguments.into_iter().map(|arg| Value::String(arg.into()))), + ); let result = match args.module.as_ref() { Some(module) => run_module(&compile_options, &mut vm, &mut env, module), None => run_interactive(&compile_options, &mut vm, &mut env), diff --git a/src/read.rs b/src/read.rs index 347b196..89ee4bd 100644 --- a/src/read.rs +++ b/src/read.rs @@ -120,7 +120,7 @@ impl ModuleReader { let expression = Expression::parse(&value).map_err(Either::Right)?; if let Expression::Defmacro(_) = expression.as_ref() { self.macro_machine - .eval_value(options.clone(), env, value) + .eval_value(options.clone(), env, value, false) .map_err(Either::Left)?; continue; } @@ -130,10 +130,11 @@ impl ModuleReader { pub fn compile( mut self, + module_name: Option>, options: &CompileOptions, env: &mut Environment, ) -> Result>> { - let mut module = CompilationModule::new(options.clone()); + let mut module = CompilationModule::new(module_name, options.clone()); let mut body = FunctionBody { head: vec![], tail: Rc::new(Expression::Nil), diff --git a/src/vm/machine.rs b/src/vm/machine.rs index fab48f5..020fdbf 100644 --- a/src/vm/machine.rs +++ b/src/vm/machine.rs @@ -33,6 +33,8 @@ pub struct Machine { value_stack: Stack, pub call_stack: Stack, pub trace_instructions: bool, + pub trace_calls: bool, + pub trace_returns: bool, // Top-level locals locals: HashMap, } @@ -53,6 +55,8 @@ impl Default for Machine { locals: HashMap::new(), trace_instructions: false, + trace_calls: false, + trace_returns: false, } } } @@ -123,10 +127,20 @@ impl Machine { }), locals: HashMap::new(), }; + let entry_ip = InstructionPointer { module, address }; + if self.trace_calls { + eprintln!("TRACE: Call bytecode function"); + if let Some(source_ip) = self.ip.as_ref() { + eprintln!("TRACE: From {source_ip}"); + } else { + eprintln!("TRACE: From "); + } + eprintln!("TRACE: To {entry_ip}"); + } if self.call_stack.push(frame).is_err() { return Err(self.error_at_ip(MachineErrorKind::CallStackOverflow)); } - self.ip = Some(InstructionPointer { module, address }); + self.ip = Some(entry_ip); Ok(()) } @@ -187,10 +201,26 @@ impl Machine { fn execute_return(&mut self) -> Result { let ip = self.ip.clone().unwrap(); + if self.trace_returns { + eprintln!("TRACE: Return"); + eprintln!("TRACE: From {ip}"); + ip.module.dump(Some(ip.address), 4, 0); + } + if let Some(frame) = self.call_stack.pop() { + if self.trace_returns { + if let Some(target_ip) = frame.return_address.as_ref() { + eprintln!("TRACE: To {target_ip}"); + } else { + eprintln!("TRACE: To "); + } + } self.ip = frame.return_address; Ok(frame.event) } else { + if self.trace_returns { + eprintln!("TRACE: To "); + } self.ip = None; Ok(ExecutionEvent::ModuleExit(ip.module)) } @@ -370,6 +400,36 @@ impl Machine { eprintln!(); } + fn unwind(&mut self, until: ExecutionEvent) { + if self.trace_returns { + eprintln!("TRACE: Begin unwind"); + if let Some(ip) = self.ip.as_ref() { + eprintln!("TRACE: <- {ip}"); + } else { + eprintln!("TRACE: <- "); + } + } + let mut ip = self.ip.clone(); + while let Some(frame) = self.call_stack.pop() { + if self.trace_returns { + eprintln!("TRACE: Unwind frame:"); + if let Some(ip) = frame.return_address.as_ref() { + eprintln!("TRACE: -> {ip}"); + } else { + eprintln!("TRACE: -> "); + } + } + ip = frame.return_address; + if frame.event == until { + break; + } + } + self.ip = ip; + if self.trace_returns { + eprintln!("TRACE: Finished unwind"); + } + } + pub fn execute_next( &mut self, environment: &mut Environment, @@ -469,23 +529,38 @@ impl Machine { Ok(value) } - pub fn load_module(&mut self, module: ModuleRef) -> Result { + pub fn load_module( + &mut self, + module: ModuleRef, + advance_on_return: bool, + ) -> Result { let entry = module.entry(); let entry_ip = InstructionPointer { module: module.clone(), address: entry, }; - if let Some(ip) = self.ip.clone() - && self - .call_stack - .push(CallFrame { - arguments: vec![], - return_address: Some(ip), - event: ExecutionEvent::ModuleExit(module.clone()), - locals: HashMap::new(), - }) - .is_err() - { + + let entry_frame = CallFrame { + arguments: vec![], + event: ExecutionEvent::ModuleExit(module.clone()), + locals: HashMap::new(), + return_address: self.ip.clone().map(|ip| InstructionPointer { + module: ip.module, + address: ip.address + advance_on_return as usize, + }), + }; + + if self.trace_calls { + eprintln!("TRACE: Enter module"); + if let Some(source_ip) = self.ip.as_ref() { + eprintln!("TRACE: From {source_ip}"); + } else { + eprintln!("TRACE: From "); + } + eprintln!("TRACE: To {entry_ip}"); + } + + if self.call_stack.push(entry_frame).is_err() { return Err(self.error_at_ip(MachineErrorKind::CallStackOverflow)); } self.ip = Some(entry_ip); @@ -496,8 +571,9 @@ impl Machine { &mut self, environment: &mut Environment, module: ModuleRef, + advance_on_return: bool, ) -> Result { - let module = match self.load_module(module) { + let module = match self.load_module(module, advance_on_return) { Ok(module) => module, Err(error) => return Err(EvalError::Machine(error)), }; @@ -505,7 +581,10 @@ impl Machine { loop { let event = match self.execute_next(environment) { Ok(event) => event, - Err(error) => return Err(EvalError::Machine(error)), + Err(error) => { + self.unwind(expect); + return Err(EvalError::Machine(error)); + } }; if event == expect { break; @@ -520,17 +599,22 @@ impl Machine { compile_options: CompileOptions, environment: &mut Environment, value: Value, + advance_on_return: bool, ) -> Result { let value = value.macro_expand(self, environment, false)?; let module = Module::compile_value(compile_options, &value)?; let module = ModuleRef::from(module); - self.eval_module(environment, module) + self.eval_module(environment, module, advance_on_return) } } impl fmt::Display for InstructionPointer { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{:p}:{}", self.module, self.address) + if let Some(name) = self.module.name.as_ref() { + write!(f, "<{} {:p}>:{}", name, self.module, self.address) + } else { + write!(f, ":{}", self.module, self.address) + } } } @@ -568,7 +652,7 @@ mod tests { build(i as u32, &mut builder); builder.add(Instruction::Return); let module = builder.build(); - values.push(machine.eval_module(&mut env, module.into()).unwrap()); + values.push(machine.eval_module(&mut env, module.into(), false).unwrap()); } (machine, values) } diff --git a/src/vm/module.rs b/src/vm/module.rs index 051adc4..961f534 100644 --- a/src/vm/module.rs +++ b/src/vm/module.rs @@ -41,6 +41,7 @@ impl Hash for ModuleRef { impl Eq for ModuleRef {} pub struct Module { + pub name: Option>, pub constants: HashMap, pub instructions: Vec, pub entry: usize, @@ -97,6 +98,7 @@ impl Deref for ModuleRef { impl Module { pub fn dummy() -> Self { Self { + name: None, constants: HashMap::new(), instructions: vec![0], entry: 0, @@ -127,7 +129,7 @@ impl Module { pub fn compile_value(options: CompileOptions, value: &Value) -> Result { let expression = Expression::parse(value).map_err(CompileError::Parse)?; - let mut module = CompilationModule::new(options); + let mut module = CompilationModule::new(None, options); module.compile_function( FunctionSignature::EMPTY, &FunctionBody { @@ -139,10 +141,10 @@ impl Module { module.compile_module() } - pub fn dump(&self, highlight: Option, context: usize) { + pub fn dump(&self, highlight: Option, context_backward: usize, context_forward: usize) { let window = highlight .map(|end| (end + 1).min(self.instructions.len())) - .map(|end| end.saturating_sub(context)..end) + .map(|end| end.saturating_sub(context_backward)..end.saturating_add(context_forward)) .unwrap_or(0..self.instructions.len()); let start = window.start; @@ -178,6 +180,7 @@ impl ModuleBuilder { pub fn build(self) -> Module { Module { + name: None, constants: self.constants.into_map(), instructions: self.instructions, entry: self.entry.unwrap(), diff --git a/src/vm/prelude/math.rs b/src/vm/prelude/math.rs index 531e590..b3822ff 100644 --- a/src/vm/prelude/math.rs +++ b/src/vm/prelude/math.rs @@ -200,6 +200,7 @@ pub(crate) fn builtin_cmp( (Value::Integer(a), Value::Integer(b)) => Ord::cmp(a, b), (Value::Boolean(a), Value::Boolean(b)) => Ord::cmp(a, b), (Value::String(a), Value::String(b)) => Ord::cmp(a, b), + (Value::Identifier(a), Value::Identifier(b)) => Ord::cmp(a, b), _ => Ordering::Less, } } diff --git a/src/vm/prelude/mod.rs b/src/vm/prelude/mod.rs index ca98916..f68faa7 100644 --- a/src/vm/prelude/mod.rs +++ b/src/vm/prelude/mod.rs @@ -2,6 +2,7 @@ use std::{rc::Rc, slice}; use crate::{ error::MachineErrorKind, + read::{self, InteractiveReader}, util::IteratorExt, vm::{ env::Environment, @@ -52,6 +53,24 @@ pub fn load(env: &mut Environment) { }); // lists + env.defun_native("car", |vm, _env, args| { + let [x] = args else { + return Err(vm.error_at_ip(MachineErrorKind::InvalidArgument)); + }; + let Value::Cons(cons) = x else { + return Err(vm.error_at_ip(MachineErrorKind::InvalidArgument)); + }; + Ok(cons.0.clone()) + }); + env.defun_native("cdr", |vm, _env, args| { + let [x] = args else { + return Err(vm.error_at_ip(MachineErrorKind::InvalidArgument)); + }; + let Value::Cons(cons) = x else { + return Err(vm.error_at_ip(MachineErrorKind::InvalidArgument)); + }; + Ok(cons.1.clone()) + }); env.defun_native("cons", |vm, _env, args| { let [car, cdr] = args else { return Err(vm.error_at_ip(MachineErrorKind::InvalidArgument)); @@ -85,6 +104,23 @@ pub fn load(env: &mut Environment) { let out = Value::list_or_nil(args.iter().cloned()); Ok(out) }); + env.defun_native("length", |vm, _, args| { + let [xs] = args else { + return Err(vm.error_at_ip(MachineErrorKind::InvalidArgument)); + }; + + let mut xs = xs; + let mut count = 0; + while !xs.is_nil() { + let Value::Cons(cons) = xs else { + break; + }; + let ConsCell(_, cdr) = cons.as_ref(); + count += 1; + xs = cdr; + } + Ok(Value::Integer(count)) + }); // functional env.defun_native("identity", |vm, _, args| { @@ -102,9 +138,15 @@ pub fn load(env: &mut Environment) { [_, _] => todo!(), _ => todo!(), }; - let value = match vm.eval_value(Default::default(), env, value.clone()) { - Ok(result) => result, - _ => todo!(), + let value = match vm.eval_value(Default::default(), env, value.clone(), true) { + Ok(result) => Value::Quote(Rc::new(Value::list_or_nil([ + Value::Identifier("ok".into()), + result, + ]))), + Err(error) => Value::Quote(Rc::new(Value::list_or_nil([ + Value::Identifier("err".into()), + Value::String(format!("{error}").into()), + ]))), }; Ok(value) }); @@ -121,6 +163,31 @@ pub fn load(env: &mut Environment) { }); // io + env.defun_native("read", |vm, env, _args| { + let mut reader = InteractiveReader::new("> ", ">> "); + let value = read::read(&mut reader, vm, env); + let value = match value { + Ok(Some(value)) => Value::Quote(Rc::new(Value::list_or_nil([ + Value::Identifier("ok".into()), + value, + ]))), + Ok(None) => Value::Nil, + Err(error) => Value::Quote(Rc::new(Value::list_or_nil([ + Value::Identifier("err".into()), + Value::String(format!("{error}").into()), + ]))), + }; + Ok(value) + }); + env.defun_native("unquote", |vm, _env, args| { + let [x] = args else { + return Err(vm.error_at_ip(MachineErrorKind::InvalidArgument)); + }; + let Value::Quote(value) = x else { + return Err(vm.error_at_ip(MachineErrorKind::InvalidArgument)); + }; + Ok(value.as_ref().clone()) + }); env.defun_native("print", |_, _, args| { for (i, arg) in args.iter().enumerate() { if i != 0 { diff --git a/tests/integration.rs b/tests/integration.rs index b88abdc..9184cc5 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -30,7 +30,7 @@ fn eval_str_in(code: &str, env: &mut Environment) -> Result { Err(error) => panic!("{error}"), }; - last_value = Some(machine.eval_value(Default::default(), env, value)); + last_value = Some(machine.eval_value(Default::default(), env, value, false)); } last_value.expect("no expressions evaluated")