From 0b242e5384a9ccbf2686b683722873287af411e1 Mon Sep 17 00:00:00 2001 From: Mark Poliakov Date: Fri, 8 May 2026 15:55:39 +0300 Subject: [PATCH] Improve error handling, better read/compile functions --- lysp/example0.lysp | 43 ----- lysp/example1.lysp | 12 -- src/compile/block.rs | 56 +++++- src/compile/module.rs | 3 +- src/compile/syntax/call.rs | 17 +- src/compile/syntax/macros.rs | 72 ++++--- src/compile/syntax/mod.rs | 42 ++-- src/compile/value.rs | 4 +- src/error.rs | 46 ++++- src/lib.rs | 5 +- src/main.rs | 293 ++++++++++------------------ src/parse.rs | 16 ++ src/read.rs | 254 ++++++++++++++++++++++++ src/util.rs | 11 ++ src/vm/env.rs | 54 +++++- src/vm/instruction.rs | 4 +- src/vm/machine.rs | 223 ++++++++-------------- src/vm/macros.rs | 110 +++++++++++ src/vm/mod.rs | 2 +- src/vm/module.rs | 22 ++- src/vm/native.rs | 59 ------ src/vm/prelude/math.rs | 63 +++--- src/vm/prelude/mod.rs | 141 +++++++------- src/vm/value.rs | 350 ---------------------------------- src/vm/value/bytecode.rs | 16 ++ src/vm/value/cons.rs | 33 ++++ src/{ => vm/value}/convert.rs | 44 +++-- src/vm/value/iter.rs | 23 +++ src/vm/value/keyword.rs | 52 +++++ src/vm/value/mod.rs | 180 +++++++++++++++++ src/vm/value/native.rs | 117 ++++++++++++ src/vm/value/string.rs | 30 +++ src/vm/value/vector.rs | 54 ++++++ tests/integration.rs | 174 +++++++++++++++++ 34 files changed, 1600 insertions(+), 1025 deletions(-) delete mode 100644 lysp/example0.lysp delete mode 100644 lysp/example1.lysp create mode 100644 src/read.rs create mode 100644 src/vm/macros.rs delete mode 100644 src/vm/native.rs delete mode 100644 src/vm/value.rs create mode 100644 src/vm/value/bytecode.rs create mode 100644 src/vm/value/cons.rs rename src/{ => vm/value}/convert.rs (83%) create mode 100644 src/vm/value/iter.rs create mode 100644 src/vm/value/keyword.rs create mode 100644 src/vm/value/mod.rs create mode 100644 src/vm/value/native.rs create mode 100644 src/vm/value/string.rs create mode 100644 src/vm/value/vector.rs create mode 100644 tests/integration.rs diff --git a/lysp/example0.lysp b/lysp/example0.lysp deleted file mode 100644 index aa5fbc0..0000000 --- a/lysp/example0.lysp +++ /dev/null @@ -1,43 +0,0 @@ -;; vi:ft=lisp:sw=2:ts=2 - -(defun return-1 () 1) -(assert (= 1 (return-1))) -(assert (< 1 2 3 4)) -(assert (<= 1 2 2 3)) -(assert (= 6 (+ 1 2 3) (apply + (list 1 2 3)))) -(assert (= (- 4) (- 1 2 3) (apply - (list 1 2 3)))) -(assert (= 6 (* 1 2 3) (apply * (list 1 2 3)))) -(assert (= 2 (/ 6 3 1) (apply / (list 6 3 1)))) -(assert (= 2 (% 6 4) (apply % (list 6 4)))) -(assert (= 7 (| 1 2 4) (apply | (list 1 2 4)))) -(assert (= 1 (& 1 3 5 7) (apply & (list 1 3 5 7)))) -(assert (= 0 (^ 1 3 5 7) (apply ^ (list 1 3 5 7)))) -(assert (= #t (&& #t #t) (apply && (list #t #t)))) -(assert (= #f (|| #f #f) (apply || (list #f #f)))) - -(setq a 1) -(assert (= a 1)) - - -(assert - (= - 6 - ;; Non-sequential let: bindings will be visible only inside the block - (let - (a 1) - (let - (a 100 b (+ a 2)) - (+ b 3) - ) - ) - ;; Sequential let: bindings are visible immediately in the asignments - (let - (a 100) - (let* - (a 1 b (+ a 2)) - (+ b 3) - ) - ) - ) -) - diff --git a/lysp/example1.lysp b/lysp/example1.lysp deleted file mode 100644 index e554101..0000000 --- a/lysp/example1.lysp +++ /dev/null @@ -1,12 +0,0 @@ -;; vi:ft=lisp:sw=2:ts=2 - -(defun fun1 (a b) (+ a (* b 3))) - -(assert-equal 2 (+ 1 #t #f)) -(assert-equal "test1" (+ "test" 1)) -(assert-equal "1test2" (+ 1 "test" 2)) -(assert-equal 2 (+ #t 1)) -(assert-equal 1 (+ #t #f)) - -(assert-equal "-1234" (int->string -1234) "int->string") -(assert-equal -1234 (string->int "-1234")) diff --git a/src/compile/block.rs b/src/compile/block.rs index f651916..5e5ad85 100644 --- a/src/compile/block.rs +++ b/src/compile/block.rs @@ -7,12 +7,15 @@ use crate::{ instruction::Emitted, module::CompilationModule, syntax::{ - CallExpression, CondExpression, DefunExpression, Expression, FunctionBody, - IfExpression, LambdaExpression, LetExpression, SetqExpression, + CallExpression, CondExpression, DefmacroExpression, DefunExpression, Expression, + FunctionBody, IfExpression, LambdaExpression, LetExpression, SetqExpression, }, value::{BuiltinFunction, CompileConstant, CompileValue}, }, - vm::instruction::{Instruction, LocalId, MathInstruction, U}, + vm::{ + instruction::{Instruction, LocalId, MathInstruction, U}, + value::Value, + }, }; pub struct CompiledFunction { @@ -201,6 +204,23 @@ impl<'a> LocalBlock<'a> { Ok(()) } + fn compile_export_macro( + &mut self, + identifier: &Rc, + index: u32, + argc: usize, + ) -> Result<(), CompileError> { + let macro_index = self + .module + .constant(CompileConstant::LocalFunction(index, argc))?; + let ident_index = self + .module + .constant(CompileConstant::Identifier(identifier.clone()))?; + self.function.emit(Instruction::PushConstant(ident_index)); + self.function.emit(Instruction::ExportMacro(macro_index)); + Ok(()) + } + fn compile_set_local( &mut self, identifier: &Rc, @@ -233,6 +253,10 @@ impl<'a> LocalBlock<'a> { let index = self.module.constant(CompileConstant::String(value))?; self.function.emit(Instruction::PushConstant(index)); } + CompileValue::Quote(value) => { + let index = self.module.constant(CompileConstant::Value(value))?; + self.function.emit(Instruction::PushConstant(index)); + } CompileValue::Local(index) => { self.function.emit(Instruction::GetLocal(index)); } @@ -315,6 +339,21 @@ impl<'a> LocalBlock<'a> { Ok(CompileValue::Nil) } + fn compile_defmacro( + &mut self, + defmacro: &DefmacroExpression, + ) -> Result { + let index = + self.module + .compile_function(defmacro.signature.clone(), &defmacro.body, false)?; + self.compile_export_macro( + &defmacro.name, + index, + defmacro.signature.required_arguments.len(), + )?; + Ok(CompileValue::Nil) + } + fn compile_builtin_math_generic( &mut self, math: MathInstruction, @@ -454,6 +493,10 @@ impl<'a> LocalBlock<'a> { Ok(CompileValue::Nil) } + fn compile_quote(&mut self, value: &Rc) -> Result { + Ok(CompileValue::Quote(value.clone())) + } + pub fn compile_expression( &mut self, expression: &Rc, @@ -471,7 +514,8 @@ impl<'a> LocalBlock<'a> { Expression::Cond(condition) => self.compile_cond(condition), Expression::Let(binding) => self.compile_let(binding), Expression::Setq(setq) => self.compile_setq(setq), - Expression::Defmacro(_defmacro) => todo!(), + Expression::Defmacro(defmacro) => self.compile_defmacro(defmacro), + Expression::Quote(quote) => self.compile_quote(quote), Expression::SyntaxError(_) => unreachable!(), } @@ -499,7 +543,7 @@ mod tests { fn test_compile(expression: Expression) -> (CompilationModule, CompiledFunction, CompileValue) { let mut module = CompilationModule::default(); module.define_macro(DefmacroExpression { - identifier: "test-macro-1".into(), + name: "test-macro-1".into(), signature: FunctionSignature { required_arguments: vec!["x".into()], optional_arguments: vec![], @@ -517,7 +561,7 @@ mod tests { }, }); module.define_macro(DefmacroExpression { - identifier: "test-macro-2".into(), + name: "test-macro-2".into(), signature: FunctionSignature { required_arguments: vec!["x".into(), "y".into()], optional_arguments: vec![], diff --git a/src/compile/module.rs b/src/compile/module.rs index f3ff6b4..4676445 100644 --- a/src/compile/module.rs +++ b/src/compile/module.rs @@ -26,7 +26,7 @@ pub struct CompilationModule { impl CompilationModule { pub fn define_macro(&mut self, defmacro: DefmacroExpression) { - self.macros.insert(defmacro.identifier.clone(), defmacro); + self.macros.insert(defmacro.name.clone(), defmacro); } pub fn constant(&mut self, value: CompileConstant) -> Result { @@ -83,6 +83,7 @@ impl CompilationModule { ModuleConstant::LocalFunction(address, required_count) } CompileConstant::String(value) => ModuleConstant::String(value), + CompileConstant::Value(value) => ModuleConstant::Value(value), }, ) }) diff --git a/src/compile/syntax/call.rs b/src/compile/syntax/call.rs index 49e8f86..7ab57d6 100644 --- a/src/compile/syntax/call.rs +++ b/src/compile/syntax/call.rs @@ -2,8 +2,8 @@ use std::rc::Rc; use crate::{ compile::{ - CompileError, Expression, ParseError, - syntax::{CollectErrors, ExpectedWhat, ExpectedWhere, ParseErrorKind, SubMap, Substitute}, + Expression, ParseError, + syntax::{CollectErrors, ExpectedWhat, ExpectedWhere, ParseErrorKind}, }, vm::value::{ConsCell, Value}, }; @@ -43,19 +43,6 @@ impl CallExpression { } } -impl Substitute for CallExpression { - fn substitute(&self, map: &SubMap) -> Result { - Ok(Self { - callee: self.callee.substitute(map)?, - arguments: self - .arguments - .iter() - .map(|arg| arg.substitute(map)) - .collect::>()?, - }) - } -} - impl CollectErrors for CallExpression { fn collect_errors(&self, errors: &mut Vec) -> bool { let mut r = self.callee.collect_errors(errors); diff --git a/src/compile/syntax/macros.rs b/src/compile/syntax/macros.rs index b0762e0..472a42b 100644 --- a/src/compile/syntax/macros.rs +++ b/src/compile/syntax/macros.rs @@ -1,52 +1,66 @@ -use std::{collections::HashMap, rc::Rc}; +use std::rc::Rc; use crate::{ compile::{ - CompileError, Expression, FunctionBody, FunctionSignature, ParseError, + ExpectedWhat, ExpectedWhere, FunctionBody, FunctionSignature, ParseError, ParseErrorKind, syntax::CollectErrors, }, - vm::value::Value, + vm::value::{ConsCell, Keyword, Value}, }; #[derive(Debug, PartialEq)] pub struct DefmacroExpression { - pub identifier: Rc, + pub name: Rc, pub signature: FunctionSignature, pub body: FunctionBody, } impl DefmacroExpression { - pub(super) fn parse(_value: &Value, _input: &Value) -> Result { - todo!() - } + pub(super) fn parse(value: &Value, input: &Value) -> Result { + let Value::Cons(value) = value else { + return Err(ParseError { + input: input.clone(), + error: ParseErrorKind::Expected( + ExpectedWhat::ProperList, + ExpectedWhere::InFunctionDefinition, + ), + }); + }; + let ConsCell(identifier, cdr) = value.as_ref(); - pub fn expand(&self, args: &[Rc]) -> Result, CompileError> { - // TODO emit progn? - if !self.body.head.is_empty() { - todo!() - } + let Value::Identifier(identifier) = identifier else { + return Err(ParseError { + input: input.clone(), + error: ParseErrorKind::Expected( + ExpectedWhat::Identifier, + ExpectedWhere::AfterKeyword(Keyword::Defun), + ), + }); + }; - let mut substitution_map = HashMap::new(); - for (i, required) in self.signature.required_arguments.iter().enumerate() { - let Some(substitution) = args.get(i) else { - todo!() - }; - substitution_map.insert(required.clone(), substitution.clone()); - } - for (i, optional) in self.signature.optional_arguments.iter().enumerate() { - if let Some(substitution) = args.get(i + self.signature.required_arguments.len()) { - substitution_map.insert(optional.clone(), substitution.clone()); - } else { - substitution_map.insert(optional.clone(), Expression::Nil.into()); - } - } + let Value::Cons(cdr) = cdr else { + return Err(ParseError { + input: input.clone(), + error: ParseErrorKind::Expected( + ExpectedWhat::ProperList, + ExpectedWhere::InFunctionDefinition, + ), + }); + }; + let ConsCell(car, cdr) = cdr.as_ref(); - self.body.tail.substitute(&substitution_map) + let signature = FunctionSignature::parse(car, input)?; + let body = FunctionBody::parse(cdr, input, Keyword::Lambda)?; + Ok(Self { + name: identifier.clone(), + signature, + body, + }) } } impl CollectErrors for DefmacroExpression { - fn collect_errors(&self, _errors: &mut Vec) -> bool { - todo!() + fn collect_errors(&self, errors: &mut Vec) -> bool { + self.body.collect_errors(errors) } } diff --git a/src/compile/syntax/mod.rs b/src/compile/syntax/mod.rs index 78a73d6..f8a6c5e 100644 --- a/src/compile/syntax/mod.rs +++ b/src/compile/syntax/mod.rs @@ -1,9 +1,6 @@ -use std::{collections::HashMap, rc::Rc}; +use std::rc::Rc; -use crate::{ - compile::CompileError, - vm::value::{ConsCell, Keyword, Value, ValueString}, -}; +use crate::vm::value::{ConsCell, Keyword, Value, ValueString}; mod binding; mod call; @@ -21,12 +18,6 @@ pub use function::*; pub use lambda::*; pub use macros::*; -pub type SubMap = HashMap, Rc>; - -pub trait Substitute: Sized { - fn substitute(&self, map: &SubMap) -> Result; -} - #[derive(Debug, PartialEq)] pub enum Expression { Nil, @@ -43,6 +34,7 @@ pub enum Expression { Let(LetExpression), Defmacro(DefmacroExpression), SyntaxError(ParseError), + Quote(Rc), } impl Expression { @@ -64,6 +56,7 @@ impl Expression { } fn parse_inner(value: &Value) -> Rc { + eprintln!("{}: parse_inner({value})", core::panic::Location::caller()); match value { Value::Nil => Rc::new(Self::Nil), Value::Boolean(value) => Rc::new(Self::BooleanLiteral(*value)), @@ -71,7 +64,9 @@ impl Expression { Value::String(value) => Rc::new(Self::StringLiteral(value.clone())), Value::Vector(_vector) => todo!(), Value::Identifier(value) => Rc::new(Self::Identifier(value.clone())), - Value::Quasi(_value) => todo!(), + Value::Quasi(_value) => todo!("{value}"), + Value::Unquote(_value) => todo!("Unquote {_value}"), + Value::Quote(value) => Rc::new(Self::Quote(value.clone())), Value::Cons(cons) => { let ConsCell(car, cdr) = cons.as_ref(); match car { @@ -96,6 +91,13 @@ impl Expression { Value::Keyword(Keyword::Defmacro) => { Self::map_or(DefmacroExpression::parse(cdr, value), Expression::Defmacro) } + Value::Keyword(Keyword::Quote) => { + let value = match cdr { + Value::Cons(cons) => cons.0.clone(), + _ => Value::Nil, + }; + Rc::new(Self::Quote(value.into())) + } _ => Self::map_or(CallExpression::parse(cons, value), Expression::Call), } } @@ -105,21 +107,6 @@ impl Expression { } } } - - fn substitute(self: &Rc, map: &SubMap) -> Result, CompileError> { - match self.as_ref() { - Self::Identifier(value) if let Some(sub) = map.get(value) => Ok(sub.clone()), - Self::Call(call) => call.substitute(map).map(Self::Call).map(Rc::new), - Self::If(_) => todo!(), - Self::Let(_) => todo!(), - Self::Cond(_) => todo!(), - Self::Setq(_) => todo!(), - Self::Defun(_) => todo!(), - Self::Defmacro(_) => todo!(), - Self::Lambda(_) => todo!(), - _ => Ok(self.clone()), - } - } } impl CollectErrors for Expression { @@ -141,6 +128,7 @@ impl CollectErrors for Expression { | Self::IntegerLiteral(_) | Self::Identifier(_) | Self::BooleanLiteral(_) + | Self::Quote(_) | Self::StringLiteral(_) => false, } } diff --git a/src/compile/value.rs b/src/compile/value.rs index 685a976..ede0fc0 100644 --- a/src/compile/value.rs +++ b/src/compile/value.rs @@ -2,7 +2,7 @@ use std::rc::Rc; use crate::vm::{ instruction::{LocalId, MathInstruction}, - value::ValueString, + value::{Value, ValueString}, }; #[derive(Debug, PartialEq)] @@ -14,6 +14,7 @@ pub enum CompileValue { Local(LocalId), Argument(usize), Global(Rc), + Quote(Rc), Stack, Nil, } @@ -24,6 +25,7 @@ pub enum CompileConstant { LocalFunction(u32, usize), Identifier(Rc), String(ValueString), + Value(Rc), } #[derive(Debug, PartialEq, Clone, Copy)] diff --git a/src/error.rs b/src/error.rs index e1e98ec..28aba0e 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,11 +1,53 @@ #![coverage(off)] -use crate::{compile::CompileError, vm::machine::MachineError}; +use std::{io, rc::Rc}; + +use crate::{ + compile::CompileError, + vm::{instruction::InstructionError, machine::InstructionPointer}, +}; #[derive(Debug, thiserror::Error)] pub enum EvalError { #[error("machine error: {0}")] - Machine(#[from] MachineError), + Machine(MachineError), #[error("compilation error: {0}")] Compile(#[from] CompileError), + #[error("syntax error: {0}")] + Syntax(nom::Err>, nom::error::Error>>), + #[error("I/O error: {0}")] + Io(#[from] io::Error), +} + +#[derive(Debug, thiserror::Error)] +#[error("{ip:?}: {error}")] +pub struct MachineError { + pub ip: Option, + pub error: MachineErrorKind, +} + +#[derive(Debug, thiserror::Error)] +pub enum MachineErrorKind { + #[error("Instruction error: {0}")] + Instruction(#[from] InstructionError), + #[error("Instruction out of bounds: {0}")] + InstructionOutOfBounds(InstructionPointer), + #[error("Instruction pointer is undefined")] + UndefinedInstructionPointer, + #[error("Data stack underflowed")] + ValueStackUnderflow, + #[error("Data stack overflowed")] + ValueStackOverflow, + #[error("Call stack underflowed")] + CallStackUnderflow, + #[error("Call stack overflowed")] + CallStackOverflow, + #[error("Unbound identifier: {0:?}")] + UnboundIdentifier(Rc), + #[error("Expected at least {0} arguments, got {1}")] + ArgumentCountMismatch(usize, usize), + #[error("Invalid argument provided in a call")] + InvalidArgument, + #[error("Aborted: {0}")] + Aborted(String), } diff --git a/src/lib.rs b/src/lib.rs index f670b2f..0d0c713 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,13 +3,12 @@ debug_closure_helpers, unboxed_closures, iter_next_chunk, - exact_size_is_empty, - try_trait_v2 + exact_size_is_empty )] pub mod compile; -pub mod convert; pub mod error; pub mod parse; +pub mod read; pub mod util; pub mod vm; diff --git a/src/main.rs b/src/main.rs index 0ef3d66..e0d6ced 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,34 +1,26 @@ use std::{ fs::File, - io::{self, BufRead, BufReader, Write, stdin, stdout}, + io::{self, BufReader}, path::{Path, PathBuf}, process::ExitCode, - rc::Rc, }; use clap::Parser; use lysp::{ - compile::{ - CompilationModule, CompileError, Expression, FunctionBody, FunctionSignature, ParseError, - }, - error::EvalError, - parse::{parse_value, skip_comment_and_whitespace}, - vm::{ - env::Environment, - machine::{EvalResult, Machine, MachineError}, - prelude, - }, + compile::{CompileError, ParseError}, + error::{EvalError, MachineErrorKind}, + read::{InteractiveReader, ModuleReader, read}, + util::Either, + vm::{env::Environment, machine::Machine, prelude, value::Value}, }; #[derive(Debug, thiserror::Error)] #[error("{0}")] enum Error { - Machine(#[from] MachineError), + Machine(#[from] MachineErrorKind), Eval(#[from] EvalError), Io(#[from] io::Error), Compile(#[from] CompileError), - #[error("Compilation errors ocurred")] - CompilationErrors, #[error("Error already printed")] Printed, } @@ -38,97 +30,92 @@ struct Args { module: Option, } +fn print_syntax_errors(errors: &[ParseError]) { + if errors.len() > 1 { + eprintln!("Syntax errors:"); + } else { + eprintln!("Syntax error:"); + } + eprintln!(); + for error in errors { + let ParseError { input, error } = error; + eprintln!(" * In expression:"); + eprintln!(); + eprintln!(" {input}"); + eprintln!(); + eprintln!(" :: {error}"); + } +} + +fn handle_eval_error(value: Option<&Value>, input: EvalError) -> Error { + match input { + EvalError::Machine(error) => { + if let Some(value) = value { + eprintln!("Error in expression:"); + eprintln!(); + eprintln!(" {value}:"); + eprintln!(); + } + eprintln!(":: {}", error.error); + eprintln!(); + if let Some(ip) = error.ip.as_ref() { + ip.module.dump(Some(ip.address), 8); + } + } + EvalError::Compile(CompileError::Parse(errors)) => { + print_syntax_errors(&errors); + } + error => { + if let Some(value) = value { + eprintln!("Error in expression:"); + eprintln!(); + eprintln!(" {value}:"); + eprintln!(); + } + eprintln!(":: {error}"); + } + } + Error::Printed +} + +fn handle_module_error(input: Either>) -> Error { + match input { + Either::Left(error) => handle_eval_error(None, error), + Either::Right(errors) => { + print_syntax_errors(&errors); + Error::Printed + } + } +} + +fn eval(vm: &mut Machine, env: &mut Environment, value: Value) -> Option { + let result = vm.eval_value(env, value.clone()); + match result { + Ok(r) => Some(r), + Err(error) => { + handle_eval_error(Some(&value), error); + None + } + } +} + fn run_interactive(vm: &mut Machine, env: &mut Environment) -> Result<(), Error> { - let mut input = String::new(); - let stdin = stdin(); - let mut stdout = stdout(); + let mut reader = InteractiveReader::new("> ", ">> "); loop { - if input.is_empty() { - print!("> "); + let value = match read(&mut reader, vm, env) { + Ok(Some(value)) => value, + Ok(None) => break, + Err(error) => { + eprintln!("{error}"); + continue; + } + }; + if let Some(value) = eval(vm, env, value) { + println!("== {value}"); } else { - print!(">>> "); + reader.reset(); } - stdout.flush().ok(); - - let len = stdin.read_line(&mut input)?; - if len == 0 { - break; - } - - let mut i = input.trim_start(); - while !i.is_empty() { - i = match skip_comment_and_whitespace(i) { - Ok((i, _)) => i, - Err(e) => { - todo!("{e:?}") - } - }; - let result = parse_value(i); - let (o, value) = match result { - Ok(r) => r, - Err(nom::Err::Incomplete(_)) => { - break; - } - Err(error) => { - eprintln!("Syntax error: {error}"); - i = ""; - break; - } - }; - - let result = vm.eval_value(env, value.clone()); - let result = match result { - EvalResult::Ok(r) => r, - EvalResult::Err(module, ip, error) => { - let ip_address = ip.map(|ip| ip.address); - eprintln!("Error in expression:"); - eprintln!(); - eprintln!(" {value}:"); - eprintln!(); - eprintln!(":: {error}"); - eprintln!(); - module.dump(ip_address, 8); - i = ""; - break; - } - EvalResult::LoadErr(error) => { - match error { - EvalError::Compile(CompileError::Parse(errors)) => { - if errors.len() > 1 { - eprintln!("Syntax errors:"); - } else { - eprintln!("Syntax error:"); - } - eprintln!(); - for error in errors { - let ParseError { input, error } = error; - eprintln!(" * In (sub)expression:"); - eprintln!(); - eprintln!(" {input}"); - eprintln!(); - eprintln!(" :: {error}"); - } - } - _ => { - eprintln!("Error in expression:"); - eprintln!(); - eprintln!(" {value}:"); - eprintln!(); - eprintln!(":: {error}") - } - } - i = ""; - break; - } - }; - - println!("{result}"); - - i = o.trim_start(); - } - - input = i.into(); } Ok(()) @@ -140,104 +127,16 @@ fn run_module>( path: P, ) -> Result<(), Error> { let path = path.as_ref(); - let mut input = String::new(); - let mut reader = BufReader::new(File::open(path)?); - - let mut module = CompilationModule::default(); - let mut body = FunctionBody { - head: vec![], - tail: Rc::new(Expression::Nil), + let reader = BufReader::new(File::open(path)?); + let module_reader = ModuleReader::new(reader); + let module = match module_reader.compile(env) { + Ok(module) => module, + Err(error) => return Err(handle_module_error(error)), }; - let mut fail = false; - - loop { - let len = reader.read_line(&mut input)?; - if len == 0 { - break; - } - - let mut i = input.trim_start(); - while !i.is_empty() { - i = match skip_comment_and_whitespace(i) { - Ok((i, _)) => i, - Err(e) => { - todo!("{e:?}") - } - }; - let result = parse_value(i); - let (o, value) = match result { - Ok(r) => r, - Err(nom::Err::Incomplete(_)) => { - break; - } - Err(error) => { - eprintln!("Syntax error: {error}"); - i = ""; - fail = true; - break; - } - }; - - let expression = match Expression::parse(&value) { - Ok(expression) => expression, - Err(errors) => { - if errors.len() > 1 { - eprintln!("Syntax errors:"); - } else { - eprintln!("Syntax error:"); - } - eprintln!(); - for error in errors { - let ParseError { input, error } = error; - eprintln!(" * In (sub)expression:"); - eprintln!(); - eprintln!(" {input}"); - eprintln!(); - eprintln!(" :: {error}"); - } - fail = true; - continue; - } - }; - - body.head.push(expression); - - i = o.trim_start(); - } - - input = i.into(); - } - - if fail { - return Err(Error::CompilationErrors); - } - - module.compile_function( - FunctionSignature { - required_arguments: vec![], - optional_arguments: vec![], - rest_argument: None, - }, - &body, - true, - )?; - let module = module.compile_module()?; - - match vm.eval_module(env, module.into()) { - EvalResult::Ok(_) => Ok(()), - EvalResult::Err(module, ip, error) => { - let ip_address = ip.map(|ip| ip.address); - eprintln!("Error in module"); - eprintln!(); - eprintln!(" {}:", path.display()); - eprintln!(); - eprintln!(":: {error}"); - eprintln!(); - module.dump(ip_address, 8); - Err(Error::Printed) - } - EvalResult::LoadErr(error) => Err(error.into()), + match vm.eval_module(env, module) { + Ok(_) => Ok(()), + Err(error) => Err(handle_eval_error(None, error)), } } diff --git a/src/parse.rs b/src/parse.rs index 18b9a4b..bf8e527 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -281,11 +281,27 @@ fn parse_quasi(input: &str) -> IResult<&str, Value> { .parse(input) } +fn parse_unquote(input: &str) -> IResult<&str, Value> { + preceded(char(','), parse_value) + .map(Rc::new) + .map(Value::Unquote) + .parse(input) +} + +fn parse_quote(input: &str) -> IResult<&str, Value> { + preceded(char('\''), parse_value) + .map(Rc::new) + .map(Value::Quote) + .parse(input) +} + pub fn parse_value(input: &str) -> IResult<&str, Value> { alt(( parse_list_or_nil, parse_boolean, + parse_quote, parse_quasi, + parse_unquote, parse_integer, parse_string, parse_identifier_or_keyword_or_nil, diff --git a/src/read.rs b/src/read.rs new file mode 100644 index 0000000..246e04d --- /dev/null +++ b/src/read.rs @@ -0,0 +1,254 @@ +use std::{ + borrow::Cow, + io::{BufRead, Write, stdin, stdout}, + rc::Rc, +}; + +use crate::{ + compile::{CompilationModule, Expression, FunctionBody, FunctionSignature, ParseError}, + error::EvalError, + parse::{self, parse_value}, + util::Either, + vm::{ + env::Environment, + machine::{InstructionPointer, Machine}, + macros::MacroExpand, + module::{Module, ModuleRef}, + value::Value, + }, +}; + +pub trait Reader { + type Error: Into; + + fn read(&mut self) -> Result, Self::Error>; +} + +pub struct InteractiveReader { + buffer: String, + prompt_empty: Cow<'static, str>, + prompt_continuation: Cow<'static, str>, +} + +pub struct FileReader { + reader: R, + buffer: String, +} + +pub struct ModuleReader { + reader: FileReader, + macro_machine: Machine, +} + +impl InteractiveReader { + pub fn new>, U: Into>>( + prompt_empty: T, + prompt_continuation: U, + ) -> Self { + Self { + buffer: String::new(), + prompt_empty: prompt_empty.into(), + prompt_continuation: prompt_continuation.into(), + } + } + + pub fn reset(&mut self) { + self.buffer.clear(); + } +} + +impl Reader for InteractiveReader { + type Error = EvalError; + + fn read(&mut self) -> Result, Self::Error> { + let stdin = stdin(); + read_inner( + &mut stdin.lock(), + &mut self.buffer, + Some(( + self.prompt_empty.as_ref(), + self.prompt_continuation.as_ref(), + )), + ) + } +} + +impl FileReader { + pub fn new(reader: R) -> Self { + Self { + buffer: String::new(), + reader, + } + } +} + +impl Reader for FileReader { + type Error = EvalError; + + fn read(&mut self) -> Result, Self::Error> { + read_inner(&mut self.reader, &mut self.buffer, None) + } +} + +impl ModuleReader { + pub fn new(reader: R) -> Self { + let mut macro_machine = Machine::default(); + let dummy = ModuleRef::from(Module::dummy()); + macro_machine.ip = Some(InstructionPointer { + module: dummy, + address: 0, + }); + Self { + reader: FileReader::new(reader), + macro_machine, + } + } + + pub fn read_expression( + &mut self, + env: &mut Environment, + ) -> Result>, Either>> { + loop { + let value = + read(&mut self.reader, &mut self.macro_machine, env).map_err(Either::Left)?; + let Some(value) = value else { + return Ok(None); + }; + let expression = Expression::parse(&value).map_err(Either::Right)?; + if let Expression::Defmacro(_) = expression.as_ref() { + self.macro_machine + .eval_value(env, value) + .map_err(Either::Left)?; + continue; + } + return Ok(Some(expression)); + } + } + + pub fn compile( + mut self, + env: &mut Environment, + ) -> Result>> { + let mut module = CompilationModule::default(); + let mut body = FunctionBody { + head: vec![], + tail: Rc::new(Expression::Nil), + }; + + let mut syntax_errors = vec![]; + + loop { + let expression = match self.read_expression(env) { + Ok(Some(expression)) => expression, + Ok(None) => break, + Err(Either::Left(error)) => return Err(Either::Left(error)), + Err(Either::Right(errors)) => { + syntax_errors.extend(errors); + continue; + } + }; + + body.head.push(expression); + } + + if !syntax_errors.is_empty() { + return Err(Either::Right(syntax_errors)); + } + + module + .compile_function( + FunctionSignature { + required_arguments: vec![], + optional_arguments: vec![], + rest_argument: None, + }, + &body, + true, + ) + .map_err(EvalError::from) + .map_err(Either::Left)?; + + let module = module + .compile_module() + .map_err(EvalError::from) + .map_err(Either::Left)?; + + Ok(module.into()) + } +} + +fn read_inner( + reader: &mut R, + buffer: &mut String, + prompt: Option<(&str, &str)>, +) -> Result, EvalError> { + loop { + let mut incomplete = None; + let mut i = buffer.trim_start(); + + while !i.is_empty() { + i = match parse::skip_comment_and_whitespace(i) { + Ok((i, _)) => i, + Err(_error) => { + buffer.clear(); + i = buffer.trim_start(); + continue; + } + }; + if i.is_empty() { + buffer.clear(); + break; + } + let result = parse_value(i); + let (tail, value) = match result { + Ok(r) => r, + Err(nom::Err::Incomplete(error)) => { + incomplete = Some(error); + break; + } + Err(error) => { + let error = EvalError::Syntax(error.map_input(|i| i.into())); + buffer.clear(); + return Err(error); + } + }; + + *buffer = tail.trim_start().into(); + return Ok(Some(value)); + } + + *buffer = buffer.trim_start().into(); + + if let Some((prompt_empty, prompt_continuation)) = prompt { + if buffer.is_empty() { + print!("{prompt_empty}"); + } else { + print!("{prompt_continuation}"); + } + stdout().flush().ok(); + } + + let len = reader.read_line(buffer)?; + if len == 0 { + return if let Some(incomplete) = incomplete { + Err(EvalError::Syntax(nom::Err::Incomplete(incomplete))) + } else { + assert!(buffer.is_empty()); + Ok(None) + }; + } + } +} + +pub fn read( + reader: &mut R, + vm: &mut Machine, + env: &mut Environment, +) -> Result, EvalError> { + let raw_value = reader.read().map_err(Into::into)?; + let Some(raw_value) = raw_value else { + return Ok(None); + }; + let exp_value = raw_value.macro_expand(vm, env, false)?; + Ok(Some(exp_value)) +} diff --git a/src/util.rs b/src/util.rs index 1f3cbe0..dac9742 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,3 +1,5 @@ +use std::fmt; + pub enum Either { Left(A), Right(B), @@ -20,6 +22,15 @@ pub trait IteratorExt { E2: From; } +impl fmt::Debug for Either { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Left(a) => fmt::Debug::fmt(a, f), + Self::Right(b) => fmt::Debug::fmt(b, f), + } + } +} + impl IteratorExt for I where I: Iterator>, diff --git a/src/vm/env.rs b/src/vm/env.rs index d89d7a3..190a362 100644 --- a/src/vm/env.rs +++ b/src/vm/env.rs @@ -1,14 +1,18 @@ -use std::{collections::HashMap, rc::Rc}; +use std::{cell::RefCell, collections::HashMap, rc::Rc}; -use crate::vm::{ - machine::{Machine, MachineError}, - native::NativeFunction, - value::Value, +use crate::{ + error::MachineError, + vm::{ + machine::Machine, + value::{BytecodeFunction, Macro, NativeFunction, NativeFunctionImpl, Value}, + }, }; #[derive(Default)] pub struct Environment { - globals: HashMap, Value>, + globals: RefCell, Value>>, + macros: RefCell, Macro>>, + parent: Option>, } impl Environment { @@ -22,11 +26,45 @@ impl Environment { self.set_global_value(identifier, Value::NativeFunction(native)); } + pub fn defmacro_native(&mut self, identifier: S, function: F) + where + S: Into>, + F: Fn(&mut Machine, &mut Environment, &[Value]) -> Result + 'static, + { + let identifier = identifier.into(); + let native: NativeFunctionImpl = Rc::new(function); + self.macros + .borrow_mut() + .insert(identifier, Macro::Builtin(native)); + } + + pub fn set_global_macro>>(&mut self, identifier: S, value: BytecodeFunction) { + let identifier = identifier.into(); + // println!("Export macro: {identifier}: {value}"); + self.macros + .borrow_mut() + .insert(identifier, Macro::Bytecode(value)); + } + pub fn global_value(&self, identifier: &str) -> Option { - self.globals.get(identifier).cloned() + self.globals.borrow().get(identifier).cloned().or_else(|| { + self.parent + .as_ref() + .and_then(|parent| parent.global_value(identifier)) + }) } pub fn set_global_value>, V: Into>(&mut self, identifier: S, value: V) { - self.globals.insert(identifier.into(), value.into()); + self.globals + .borrow_mut() + .insert(identifier.into(), value.into()); + } + + pub fn global_macro(&self, identifier: &str) -> Option { + self.macros.borrow().get(identifier).cloned().or_else(|| { + self.parent + .as_ref() + .and_then(|parent| parent.global_macro(identifier)) + }) } } diff --git a/src/vm/instruction.rs b/src/vm/instruction.rs index e3191d3..45b038a 100644 --- a/src/vm/instruction.rs +++ b/src/vm/instruction.rs @@ -82,6 +82,7 @@ pub enum Instruction { GetGlobal, SetLocal(LocalId), GetLocal(LocalId), + ExportMacro(ConstantId), Call(ArgumentCount), Return, Math(MathInstruction, ArgumentCount), @@ -171,6 +172,7 @@ impl From for u32 { Instruction::PushInteger(value) => 0b00010000_00000000 | value.0, Instruction::PushConstant(index) => 0b00100000_00000000 | index.0, Instruction::PushArgument(index) => 0b00000000_10000000 | index.0, + Instruction::ExportMacro(index) => 0b00110000_00000000 | index.0, Instruction::Branch(offset) => 0b00001000_00000000 | offset.0, Instruction::Jump(offset) => 0b00001100_00000000 | offset.0, Instruction::Math(math, count) => { @@ -211,7 +213,7 @@ impl TryFrom for Instruction { "0001xxxx_xxxxxxxx" => Ok(Instruction::PushInteger(U(x))), "0010xxxx_xxxxxxxx" => Ok(Instruction::PushConstant(U(x))), - "0011????_????????" => todo!(), + "0011xxxx_xxxxxxxx" => Ok(Instruction::ExportMacro(U(x))), "01??????_????????" => todo!(), "1???????_????????" => todo!("{value:032b}"), } diff --git a/src/vm/machine.rs b/src/vm/machine.rs index 573942a..1e10452 100644 --- a/src/vm/machine.rs +++ b/src/vm/machine.rs @@ -1,54 +1,18 @@ -use std::{ - collections::HashMap, - fmt, - ops::{ControlFlow, FromResidual, Try}, - rc::Rc, -}; +use std::{collections::HashMap, fmt}; use crate::{ - error::EvalError, + error::{EvalError, MachineError, MachineErrorKind}, vm::{ env::Environment, - instruction::{ConstantId, Instruction, InstructionError, MathInstruction}, + instruction::{ConstantId, Instruction, MathInstruction}, + macros::MacroExpand, module::{Module, ModuleConstant, ModuleRef}, - native::NativeFunction, prelude, stack::Stack, - value::{BytecodeFunction, Value}, + value::{BytecodeFunction, NativeFunction, TryFromValue, Value}, }, }; -#[derive(Debug, thiserror::Error)] -pub enum MachineError { - #[error("Instruction error: {0}")] - Instruction(#[from] InstructionError), - #[error("Instruction out of bounds: {0}")] - InstructionOutOfBounds(InstructionPointer), - #[error("Instruction pointer is undefined")] - UndefinedInstructionPointer, - #[error("Data stack underflowed")] - ValueStackUnderflow, - #[error("Data stack overflowed")] - ValueStackOverflow, - #[error("Call stack underflowed")] - CallStackUnderflow, - #[error("Call stack overflowed")] - CallStackOverflow, - #[error("Unbound identifier: {0:?}")] - UnboundIdentifier(Rc), - #[error("Expected at least {0} arguments, got {1}")] - ArgumentCountMismatch(usize, usize), - #[error("Invalid argument provided in a call")] - InvalidArgument, -} - -#[derive(Debug)] -pub enum EvalResult { - Ok(T), - Err(ModuleRef, Option, E), - LoadErr(E), -} - #[derive(Debug, Clone, PartialEq)] pub struct InstructionPointer { pub module: ModuleRef, @@ -57,16 +21,16 @@ pub struct InstructionPointer { #[derive(Debug, Clone, PartialEq)] pub struct CallFrame { - arguments: Vec, - return_address: InstructionPointer, - event: ExecutionEvent, - locals: HashMap, + pub arguments: Vec, + pub return_address: Option, + pub event: ExecutionEvent, + pub locals: HashMap, } pub struct Machine { - ip: Option, + pub ip: Option, value_stack: Stack, - call_stack: Stack, + pub call_stack: Stack, // Top-level locals locals: HashMap, } @@ -90,14 +54,17 @@ impl Default for Machine { } impl Machine { + pub fn error_at_ip(&self, kind: MachineErrorKind) -> MachineError { + MachineError { + ip: self.ip.clone(), + error: kind, + } + } + pub fn ip(&self) -> Option { self.ip.clone() } - // pub fn set_global>>(&mut self, identifier: S, value: Value) { - // self.globals.insert(identifier.into(), value); - // } - pub fn set_local(&mut self, index: u32, value: Value) { let locals = match self.call_stack.current_mut() { Some(frame) => &mut frame.locals, @@ -117,13 +84,13 @@ impl Machine { fn pop(&mut self) -> Result { self.value_stack .pop() - .ok_or(MachineError::ValueStackUnderflow) + .ok_or_else(|| self.error_at_ip(MachineErrorKind::ValueStackUnderflow)) } fn push(&mut self, value: Value) -> Result<(), MachineError> { self.value_stack .push(value) - .map_err(|_| MachineError::ValueStackOverflow) + .map_err(|_| self.error_at_ip(MachineErrorKind::ValueStackOverflow)) } fn enter_bytecode_function( @@ -131,29 +98,29 @@ impl Machine { bytecode: BytecodeFunction, arguments: Vec, ) -> Result<(), MachineError> { - let source_ip = self.ip.clone().unwrap(); + let source_ip = self.ip.clone(); let BytecodeFunction { module, required_count, address, } = bytecode.clone(); if required_count > arguments.len() { - return Err(MachineError::ArgumentCountMismatch( + return Err(self.error_at_ip(MachineErrorKind::ArgumentCountMismatch( required_count, arguments.len(), - )); + ))); } let frame = CallFrame { arguments, event: ExecutionEvent::BytecodeFunctionExit(bytecode), - return_address: InstructionPointer { - module: source_ip.module, - address: source_ip.address + 1, - }, + return_address: source_ip.map(|ip| InstructionPointer { + module: ip.module, + address: ip.address + 1, + }), locals: HashMap::new(), }; if self.call_stack.push(frame).is_err() { - return Err(MachineError::CallStackOverflow); + return Err(self.error_at_ip(MachineErrorKind::CallStackOverflow)); } self.ip = Some(InstructionPointer { module, address }); Ok(()) @@ -173,7 +140,7 @@ impl Machine { let callee = match callee { Value::BytecodeFunction(bytecode) => Callee::Bytecode(bytecode), Value::NativeFunction(native) => Callee::Native(native), - _ => todo!(), + _ => return Err(self.error_at_ip(MachineErrorKind::InvalidArgument)), }; let mut arguments = vec![]; for _ in 0..count { @@ -217,7 +184,7 @@ impl Machine { fn execute_return(&mut self) -> Result { let ip = self.ip.clone().unwrap(); if let Some(frame) = self.call_stack.pop() { - self.ip = Some(frame.return_address); + self.ip = frame.return_address; Ok(frame.event) } else { self.ip = None; @@ -254,12 +221,7 @@ impl Machine { fn execute_branch(&mut self, offset: usize) -> Result { let value = self.pop()?; - let do_branch = match value { - Value::Boolean(true) => false, - Value::Boolean(false) => true, - Value::Nil => todo!(), - _ => todo!(), - }; + let do_branch = !bool::try_from_value(&value).unwrap_or_default(); if do_branch { self.execute_jump(offset)?; } @@ -289,6 +251,7 @@ impl Machine { ModuleConstant::String(value) => Value::String(value), ModuleConstant::Integer(value) => Value::Integer(value), ModuleConstant::Identifier(identifier) => Value::Identifier(identifier), + ModuleConstant::Value(value) => value.as_ref().clone(), }; self.push(value) @@ -309,7 +272,7 @@ impl Machine { Value::Identifier(ident) => { let value = environment .global_value(&ident) - .ok_or(MachineError::UnboundIdentifier(ident))?; + .ok_or_else(|| self.error_at_ip(MachineErrorKind::UnboundIdentifier(ident)))?; self.push(value) } _ => todo!(), @@ -344,16 +307,39 @@ impl Machine { Ok(()) } + fn execute_export_macro( + &mut self, + environment: &mut Environment, + index: ConstantId, + ) -> Result<(), MachineError> { + let ip = self.ip.clone().unwrap(); + let ident = self.pop()?; + let Value::Identifier(ident) = ident else { + return Err(self.error_at_ip(MachineErrorKind::InvalidArgument)); + }; + let function = ip.module.constant(index).unwrap(); + let ModuleConstant::LocalFunction(address, required_count) = function else { + return Err(self.error_at_ip(MachineErrorKind::InvalidArgument)); + }; + let function = BytecodeFunction { + module: ip.module.clone(), + required_count, + address, + }; + environment.set_global_macro(ident, function); + Ok(()) + } + pub fn execute_next( &mut self, environment: &mut Environment, ) -> Result { let ip = self.ip.clone().unwrap(); - let instruction = ip - .module - .instruction(ip.address) - .ok_or_else(|| MachineError::InstructionOutOfBounds(ip.clone()))?; - let instruction = Instruction::try_from(instruction)?; + let instruction = ip.module.instruction(ip.address).ok_or_else(|| { + self.error_at_ip(MachineErrorKind::InstructionOutOfBounds(ip.clone())) + })?; + let instruction = Instruction::try_from(instruction) + .map_err(|e| self.error_at_ip(MachineErrorKind::Instruction(e)))?; let mut advance = true; let mut event = ExecutionEvent::None; match instruction { @@ -375,6 +361,9 @@ impl Machine { Instruction::Drop => { self.pop()?; } + Instruction::ExportMacro(index) => { + self.execute_export_macro(environment, index.into())?; + } Instruction::GetGlobal => { self.execute_get_global(environment)?; } @@ -415,11 +404,6 @@ impl Machine { Ok(event) } - fn macro_expand(&mut self, value: Value) -> EvalResult { - // TODO - EvalResult::Ok(value) - } - pub fn eval_bytecode_call( &mut self, environment: &mut Environment, @@ -429,11 +413,7 @@ impl Machine { let expect = ExecutionEvent::BytecodeFunctionExit(function.clone()); self.enter_bytecode_function(function, args.into())?; loop { - let event = match self.execute_next(environment) { - Ok(event) => event, - // TODO rework error handling - Err(_error) => todo!(), - }; + let event = self.execute_next(environment)?; if event == expect { break; } @@ -453,13 +433,13 @@ impl Machine { .call_stack .push(CallFrame { arguments: vec![], - return_address: ip, + return_address: Some(ip), event: ExecutionEvent::ModuleExit(module.clone()), locals: HashMap::new(), }) .is_err() { - return Err(MachineError::CallStackOverflow); + return Err(self.error_at_ip(MachineErrorKind::CallStackOverflow)); } self.ip = Some(entry_ip); Ok(module) @@ -469,85 +449,34 @@ impl Machine { &mut self, environment: &mut Environment, module: ModuleRef, - ) -> EvalResult { + ) -> Result { let module = match self.load_module(module) { Ok(module) => module, - Err(error) => return EvalResult::LoadErr(error), + Err(error) => return Err(EvalError::Machine(error)), }; let expect = ExecutionEvent::ModuleExit(module.clone()); loop { - let ip = self.ip.clone(); let event = match self.execute_next(environment) { Ok(event) => event, - Err(error) => return EvalResult::Err(module, ip, error), + Err(error) => return Err(EvalError::Machine(error)), }; if event == expect { break; } } - let ip = self.ip.clone(); - match self.pop() { - Ok(value) => EvalResult::Ok(value), - Err(error) => EvalResult::Err(module, ip, error), - } + + self.pop().map_err(EvalError::Machine) } pub fn eval_value( &mut self, environment: &mut Environment, value: Value, - ) -> EvalResult { - let value = self.macro_expand(value)?; - let module = match Module::compile_value(&value) { - Ok(module) => module, - Err(error) => return EvalResult::LoadErr(error.into()), - }; + ) -> Result { + let value = value.macro_expand(self, environment, false)?; + let module = Module::compile_value(&value)?; let module = ModuleRef::from(module); - match self.eval_module(environment, module) { - EvalResult::Ok(value) => EvalResult::Ok(value), - EvalResult::Err(module, ip, error) => EvalResult::Err(module, ip, error.into()), - EvalResult::LoadErr(error) => EvalResult::LoadErr(error.into()), - } - } -} - -impl Try for EvalResult { - type Output = T; - type Residual = EvalResult; - - fn branch(self) -> ControlFlow { - match self { - Self::Ok(result) => ControlFlow::Continue(result), - Self::Err(module, ip, error) => ControlFlow::Break(EvalResult::Err(module, ip, error)), - Self::LoadErr(error) => ControlFlow::Break(EvalResult::LoadErr(error)), - } - } - - fn from_output(output: Self::Output) -> Self { - Self::Ok(output) - } -} - -impl FromResidual for EvalResult { - fn from_residual(residual: ::Residual) -> Self { - residual - } -} - -impl EvalResult { - pub fn unwrap(self) -> T - where - E: fmt::Display, - { - match self { - Self::Ok(value) => value, - Self::Err(module, _, error) => { - panic!("Unwrap called on module {module:p} error: {error}"); - } - Self::LoadErr(error) => { - panic!("Unwrap called on error: {error}"); - } - } + self.eval_module(environment, module) } } diff --git a/src/vm/macros.rs b/src/vm/macros.rs new file mode 100644 index 0000000..c7fa0c8 --- /dev/null +++ b/src/vm/macros.rs @@ -0,0 +1,110 @@ +use std::rc::Rc; + +use crate::{ + error::EvalError, + vm::{ + env::Environment, + machine::Machine, + value::{ConsCell, Keyword, Macro, Value}, + }, +}; + +pub trait MacroExpand: Sized { + fn macro_expand( + &self, + vm: &mut Machine, + env: &mut Environment, + tail: bool, + ) -> Result; +} + +impl MacroExpand for Value { + fn macro_expand( + &self, + vm: &mut Machine, + env: &mut Environment, + tail: bool, + ) -> Result { + match self { + Self::Nil + | Self::Identifier(_) + | Self::Integer(_) + | Self::Boolean(_) + | Self::String(_) + | Self::Keyword(_) + | Self::OpaqueValue(_) + | Self::BytecodeFunction(_) + | Self::Quote(_) + | Self::Unquote(_) + | Self::NativeFunction(_) => Ok(self.clone()), + Self::Cons(cons) => { + let ConsCell(car, cdr) = cons.as_ref(); + let car = car.macro_expand(vm, env, false)?; + let cdr = cdr.macro_expand(vm, env, true)?; + + if tail { + let cons = Rc::new(ConsCell(car, cdr.clone())); + return Ok(Self::Cons(cons)); + } + + let (Self::Identifier(identifier), Some(args)) = (&car, cdr.as_proper_list()) + else { + let cons = Rc::new(ConsCell(car, cdr.clone())); + return Ok(Self::Cons(cons)); + }; + let Some(mac) = env.global_macro(identifier.as_ref()) else { + // eprintln!("{identifier} is not a macro"); + let cons = Rc::new(ConsCell(car, cdr.clone())); + return Ok(Self::Cons(cons)); + }; + + match mac { + Macro::Builtin(native) => { + let result = (native)(vm, env, &args[..]).expect("TODO"); + Ok(result) + } + Macro::Bytecode(bytecode) => { + let result = vm + .eval_bytecode_call(env, bytecode, &args[..]) + .expect("TODO"); + Ok(result) + } + } + } + Self::Quasi(value) => { + let value = expand_quasiquote(value); + value.macro_expand(vm, env, false) + } + Self::Vector(_) => todo!(), + } + } +} + +fn expand_quasiquote(value: &Value) -> Value { + match value { + Value::Nil => Value::Nil, + Value::Unquote(inner) => inner.as_ref().clone(), + Value::Cons(cons) => { + // x . y -> (cons ) + let ConsCell(car, cdr) = cons.as_ref(); + let exp_car = expand_quasiquote(car); + let exp_cdr = expand_quasiquote(cdr); + Value::Identifier("cons".into()).cons(exp_car.cons(exp_cdr.cons(Value::Nil))) + } + Value::Quote(value) => { + // (cons 'quote inner) + let cons = Value::Identifier("cons".into()); + let quote_kw = Value::Quote(Rc::new(Value::Keyword(Keyword::Quote))); + let quote_nil = Value::Quote(Rc::new(Value::Nil)); + + let exp_inner = expand_quasiquote(value); + + let cons_inner = cons + .clone() + .cons(exp_inner.cons(quote_nil.clone().cons(Value::Nil))); + + cons.cons(quote_kw.cons(cons_inner.cons(Value::Nil))) + } + _ => Value::Quote(Rc::new(value.clone())), + } +} diff --git a/src/vm/mod.rs b/src/vm/mod.rs index 2817199..6c15ce7 100644 --- a/src/vm/mod.rs +++ b/src/vm/mod.rs @@ -2,8 +2,8 @@ pub mod env; pub mod instruction; pub mod loader; pub mod machine; +pub mod macros; pub mod module; -pub mod native; pub mod pool; pub mod prelude; pub mod stack; diff --git a/src/vm/module.rs b/src/vm/module.rs index d2e9ccc..98760d8 100644 --- a/src/vm/module.rs +++ b/src/vm/module.rs @@ -1,4 +1,10 @@ -use std::{collections::HashMap, fmt, ops::Deref, rc::Rc}; +use std::{ + collections::HashMap, + fmt, + hash::{Hash, Hasher}, + ops::Deref, + rc::Rc, +}; use crate::{ compile::{CompilationModule, CompileError, Expression, FunctionBody, FunctionSignature}, @@ -9,12 +15,13 @@ use crate::{ }, }; -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, PartialEq, Hash, Eq)] pub enum ModuleConstant { Integer(i64), LocalFunction(usize, usize), Identifier(Rc), String(ValueString), + Value(Rc), } #[derive(Clone)] @@ -22,6 +29,14 @@ pub struct ModuleRef { inner: Rc, } +impl Hash for ModuleRef { + fn hash(&self, state: &mut H) { + (&raw const *self.inner.as_ref()).addr().hash(state) + } +} + +impl Eq for ModuleRef {} + pub struct Module { pub constants: HashMap, pub instructions: Vec, @@ -75,7 +90,7 @@ impl Deref for ModuleRef { } } -#[cfg(test)] +// #[cfg(test)] impl Module { pub fn dummy() -> Self { Self { @@ -193,6 +208,7 @@ impl fmt::Display for ModuleConstant { Self::Integer(value) => fmt::Display::fmt(value, f), Self::Identifier(ident) => write!(f, "ident {ident:?}"), Self::LocalFunction(address, _) => write!(f, "label {address}"), + Self::Value(value) => write!(f, "{value}"), } } } diff --git a/src/vm/native.rs b/src/vm/native.rs deleted file mode 100644 index cda3e8b..0000000 --- a/src/vm/native.rs +++ /dev/null @@ -1,59 +0,0 @@ -use std::{fmt, rc::Rc}; - -use crate::vm::{ - env::Environment, - machine::{Machine, MachineError}, - value::Value, -}; - -pub type NativeFunctionImpl = - Rc Result + 'static>; - -#[derive(Clone)] -pub struct NativeFunction { - name: Rc, - inner: NativeFunctionImpl, -} - -impl NativeFunction { - pub fn new(name: S, function: F) -> Self - where - S: Into>, - F: Fn(&mut Machine, &mut Environment, &[Value]) -> Result + 'static, - { - Self { - name: name.into(), - inner: Rc::new(function), - } - } - - pub fn invoke( - &self, - machine: &mut Machine, - environment: &mut Environment, - arguments: &[Value], - ) -> Result { - (self.inner)(machine, environment, arguments) - } -} - -impl PartialEq for NativeFunction { - fn eq(&self, other: &Self) -> bool { - self.name == other.name - } -} - -impl fmt::Debug for NativeFunction { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("NativeFunction") - .field("name", &self.name) - .field_with("inner", |f| write!(f, "{:p}", self.inner)) - .finish() - } -} - -impl fmt::Display for NativeFunction { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "", self.name, self.inner) - } -} diff --git a/src/vm/prelude/math.rs b/src/vm/prelude/math.rs index 6a162f1..531e590 100644 --- a/src/vm/prelude/math.rs +++ b/src/vm/prelude/math.rs @@ -4,15 +4,15 @@ use std::{ }; use crate::{ - convert::TryFromValue, + error::{MachineError, MachineErrorKind}, vm::{ env::Environment, - machine::{Machine, MachineError}, - value::Value, + machine::Machine, + value::{TryFromValue, Value}, }, }; -fn value_add(a: &Value, b: &Value) -> Result { +fn value_add(a: &Value, b: &Value) -> Result { match (a, b) { (Value::String(a), _) => { let b = b.stringify()?; @@ -26,7 +26,7 @@ fn value_add(a: &Value, b: &Value) -> Result { (Value::Integer(a), _) => Ok(Value::Integer(a.wrapping_add(i64::try_from_value(b)?))), (_, Value::Integer(b)) => Ok(Value::Integer(i64::try_from_value(a)?.wrapping_add(*b))), (Value::Boolean(a), Value::Boolean(b)) => Ok(Value::Integer(*a as i64 + *b as i64)), - _ => Err(MachineError::InvalidArgument), + _ => Err(MachineErrorKind::InvalidArgument), } } @@ -39,9 +39,13 @@ pub(crate) enum CompareOperation { Ge, } -fn builtin_fold(fold: F, mut accumulator: Value, args: &[Value]) -> Result +fn builtin_fold( + fold: F, + mut accumulator: Value, + args: &[Value], +) -> Result where - F: Fn(&Value, &Value) -> Result, + F: Fn(&Value, &Value) -> Result, { for (i, arg) in args.iter().enumerate() { if i == 0 { @@ -54,6 +58,7 @@ where } fn builtin_fold_t<'a, T, F>( + vm: &mut Machine, fold: F, mut accumulator: T, args: &'a [Value], @@ -63,26 +68,26 @@ where T: TryFromValue<'a> + Into, { for arg in args { - let arg = T::try_from_value(arg)?; + let arg = T::try_from_value(arg).map_err(|e| vm.error_at_ip(e))?; accumulator = fold(accumulator, arg); } Ok(accumulator.into()) } pub(crate) fn builtin_add( - _vm: &mut Machine, + vm: &mut Machine, _env: &mut Environment, args: &[Value], ) -> Result { - builtin_fold(value_add, Value::Integer(0), args) + builtin_fold(value_add, Value::Integer(0), args).map_err(|e| vm.error_at_ip(e)) } pub(crate) fn builtin_sub( - _vm: &mut Machine, + vm: &mut Machine, _env: &mut Environment, args: &[Value], ) -> Result { match args { - [] => Err(MachineError::InvalidArgument), + [] => Err(vm.error_at_ip(MachineErrorKind::InvalidArgument)), [x] if let &Value::Integer(x) = x => Ok(Value::Integer(-x)), [x] => todo!("Math for {x}"), [x, rest @ ..] => { @@ -103,32 +108,32 @@ pub(crate) fn builtin_sub( } } pub(crate) fn builtin_mul( - _vm: &mut Machine, + vm: &mut Machine, _env: &mut Environment, args: &[Value], ) -> Result { - builtin_fold_t(i64::wrapping_mul, 1, args) + builtin_fold_t(vm, i64::wrapping_mul, 1, args) } pub(crate) fn builtin_mod( - _vm: &mut Machine, + vm: &mut Machine, _env: &mut Environment, args: &[Value], ) -> Result { let [x, y] = args else { - return Err(MachineError::InvalidArgument); + return Err(vm.error_at_ip(MachineErrorKind::InvalidArgument)); }; let (&Value::Integer(x), &Value::Integer(y)) = (x, y) else { - return Err(MachineError::InvalidArgument); + return Err(vm.error_at_ip(MachineErrorKind::InvalidArgument)); }; Ok(Value::Integer(x % y)) } pub(crate) fn builtin_div( - _vm: &mut Machine, + vm: &mut Machine, _env: &mut Environment, args: &[Value], ) -> Result { match args { - [] => Err(MachineError::InvalidArgument), + [] => Err(vm.error_at_ip(MachineErrorKind::InvalidArgument)), [x] if let &Value::Integer(_x) = x => todo!("Fractionals"), [x] => todo!("Math for {x}"), [x, rest @ ..] => { @@ -150,39 +155,39 @@ pub(crate) fn builtin_div( } pub(crate) fn builtin_and( - _vm: &mut Machine, + vm: &mut Machine, _env: &mut Environment, args: &[Value], ) -> Result { - builtin_fold_t(BitAnd::bitand, true, args) + builtin_fold_t(vm, BitAnd::bitand, true, args) } pub(crate) fn builtin_or( - _vm: &mut Machine, + vm: &mut Machine, _env: &mut Environment, args: &[Value], ) -> Result { - builtin_fold_t(BitOr::bitor, false, args) + builtin_fold_t(vm, BitOr::bitor, false, args) } pub(crate) fn builtin_bitwise_and( - _vm: &mut Machine, + vm: &mut Machine, _env: &mut Environment, args: &[Value], ) -> Result { - builtin_fold_t(BitAnd::bitand, 1, args) + builtin_fold_t(vm, BitAnd::bitand, 1, args) } pub(crate) fn builtin_bitwise_or( - _vm: &mut Machine, + vm: &mut Machine, _env: &mut Environment, args: &[Value], ) -> Result { - builtin_fold_t(BitOr::bitor, 0, args) + builtin_fold_t(vm, BitOr::bitor, 0, args) } pub(crate) fn builtin_bitwise_xor( - _vm: &mut Machine, + vm: &mut Machine, _env: &mut Environment, args: &[Value], ) -> Result { - builtin_fold_t(BitXor::bitxor, 0, args) + builtin_fold_t(vm, BitXor::bitxor, 0, args) } pub(crate) fn builtin_cmp( diff --git a/src/vm/prelude/mod.rs b/src/vm/prelude/mod.rs index d4009e0..79dac2a 100644 --- a/src/vm/prelude/mod.rs +++ b/src/vm/prelude/mod.rs @@ -1,12 +1,11 @@ -use std::slice; +use std::{rc::Rc, slice}; use crate::{ - convert::{AnyFunction, TryFromValue}, + error::MachineErrorKind, util::IteratorExt, vm::{ env::Environment, - machine::MachineError, - value::{Value, ValueString}, + value::{AnyFunction, ConsCell, Keyword, TryFromValue, Value, ValueString}, }, }; @@ -35,44 +34,50 @@ pub fn load(env: &mut Environment) { env.defun_native("<=", builtin_cmp_le); // conversion - env.defun_native("string->int", |_, _, args| { + env.defun_native("string->int", |vm, _, args| { let [arg] = args else { - return Err(MachineError::InvalidArgument); + return Err(vm.error_at_ip(MachineErrorKind::InvalidArgument)); }; - let arg = ValueString::try_from_value(arg)?; + let arg = ValueString::try_from_value(arg).map_err(|e| vm.error_at_ip(e))?; let result = arg.parse::().map(Value::Integer).unwrap_or(Value::Nil); Ok(result) }); - env.defun_native("int->string", |_, _, args| { + env.defun_native("int->string", |vm, _, args| { let [arg] = args else { - return Err(MachineError::InvalidArgument); + return Err(vm.error_at_ip(MachineErrorKind::InvalidArgument)); }; - let arg = i64::try_from_value(arg)?; + let arg = i64::try_from_value(arg).map_err(|e| vm.error_at_ip(e))?; let result = Value::String(format!("{arg}").into()); Ok(result) }); // lists + env.defun_native("cons", |vm, _env, args| { + let [car, cdr] = args else { + return Err(vm.error_at_ip(MachineErrorKind::InvalidArgument)); + }; + Ok(Value::Cons(Rc::new(ConsCell(car.clone(), cdr.clone())))) + }); env.defun_native("map", |vm, env, args| { let [f, xs] = args else { - return Err(MachineError::InvalidArgument); + return Err(vm.error_at_ip(MachineErrorKind::InvalidArgument)); }; - let f = AnyFunction::try_from_value(f)?; - let xs = xs.proper_iter(MachineError::InvalidArgument); + let f = AnyFunction::try_from_value(f).map_err(|e| vm.error_at_ip(e))?; + let xs = xs.proper_iter(vm.error_at_ip(MachineErrorKind::InvalidArgument)); let out = Value::try_list_or_nil(xs.map(|v| f.invoke(vm, env, slice::from_ref(v?))))?; Ok(out) }); env.defun_native("filter", |vm, env, args| { let [f, xs] = args else { - return Err(MachineError::InvalidArgument); + return Err(vm.error_at_ip(MachineErrorKind::InvalidArgument)); }; - let f = AnyFunction::try_from_value(f)?; + let f = AnyFunction::try_from_value(f).map_err(|e| vm.error_at_ip(e))?; let xs = xs - .proper_iter(MachineError::InvalidArgument) + .proper_iter(vm.error_at_ip(MachineErrorKind::InvalidArgument)) .map(|x| x.cloned()); let out = Value::try_list_or_nil(xs.try_filter(|v| { let result = f.invoke(vm, env, slice::from_ref(v))?; - bool::try_from_value(&result) + Ok(bool::try_from_value(&result).unwrap_or_default()) }))?; Ok(out) }); @@ -82,74 +87,38 @@ pub fn load(env: &mut Environment) { }); // functional - env.defun_native("identity", |_, _, args| { + env.defun_native("identity", |vm, _, args| { let [arg] = args else { - return Err(MachineError::InvalidArgument); + return Err(vm.error_at_ip(MachineErrorKind::InvalidArgument)); }; Ok(arg.clone()) }); // eval + env.defun_native("eval", |vm, env, args| { + let value = match args { + [] => todo!(), + [value] => value, + [_, _] => todo!(), + _ => todo!(), + }; + let value = match vm.eval_value(env, value.clone()) { + Ok(result) => result, + _ => todo!(), + }; + Ok(value) + }); env.defun_native("apply", |vm, env, args| { let [f, xs] = args else { - return Err(MachineError::InvalidArgument); + return Err(vm.error_at_ip(MachineErrorKind::InvalidArgument)); }; - let f = AnyFunction::try_from_value(f)?; + let f = AnyFunction::try_from_value(f).map_err(|e| vm.error_at_ip(e))?; let args = xs - .proper_iter(MachineError::InvalidArgument) + .proper_iter(vm.error_at_ip(MachineErrorKind::InvalidArgument)) .map(|x| x.cloned()) .collect::, _>>()?; f.invoke(vm, env, &args[..]) }); - env.defun_native("assert", |vm, _, args| match args { - [] => Err(MachineError::InvalidArgument), - [cond] => { - let cond = bool::try_from_value(cond)?; - if !cond { - let ip = vm.ip(); - if let Some(ip) = ip { - eprintln!("Assertion failed at {ip}:"); - eprintln!(); - ip.module.dump(Some(ip.address), 8); - } - panic!("Assertion failed"); - } - Ok(Value::Nil) - } - _ => todo!(), - }); - env.defun_native("assert-equal", |vm, _, args| match args { - [] | [_] => Err(MachineError::InvalidArgument), - [a, b] => { - if a != b { - let ip = vm.ip(); - if let Some(ip) = ip { - eprintln!("Assertion failed at {ip}:"); - eprintln!(); - eprintln!(":: {a} ≠ {b}"); - eprintln!(); - ip.module.dump(Some(ip.address), 8); - } - panic!("Assertion failed"); - } - Ok(Value::Nil) - } - [a, b, msg] => { - if a != b { - let ip = vm.ip(); - if let Some(ip) = ip { - eprintln!("Assertion failed at {ip}: {msg}"); - eprintln!(); - eprintln!(":: {a} ≠ {b}"); - eprintln!(); - ip.module.dump(Some(ip.address), 8); - } - panic!("Assertion failed"); - } - Ok(Value::Nil) - } - _ => todo!(), - }); // io env.defun_native("print", |_, _, args| { @@ -162,4 +131,34 @@ pub fn load(env: &mut Environment) { println!(); Ok(Value::Nil) }); + env.defmacro_native("assert", |vm, _, args| match args { + [] => Err(vm.error_at_ip(MachineErrorKind::InvalidArgument)), + [cond] => { + let assertion_failure_msg = match vm.ip() { + Some(ip) => format!("{ip}: assertion failed: {cond}"), + None => format!(": assertion failed: {cond}"), + }; + let assertion_failure = Value::list_or_nil([ + Value::Identifier("abort".into()), + Value::String(assertion_failure_msg.into()), + ]); + Ok(Value::list_or_nil([ + Value::Keyword(Keyword::If), + cond.clone(), + Value::Nil, + assertion_failure, + ])) + } + _ => Err(vm.error_at_ip(MachineErrorKind::InvalidArgument)), + }); + env.defun_native("abort", |vm, _, args| { + let mut message = String::new(); + for (i, arg) in args.iter().enumerate() { + if i != 0 { + message.push(' '); + } + message.push_str(&format!("{arg}")); + } + Err(vm.error_at_ip(MachineErrorKind::Aborted(message))) + }); } diff --git a/src/vm/value.rs b/src/vm/value.rs deleted file mode 100644 index b0bcf46..0000000 --- a/src/vm/value.rs +++ /dev/null @@ -1,350 +0,0 @@ -use std::{any::Any, cell::RefCell, fmt, ops::Deref, rc::Rc}; - -use crate::{ - compile::{ExpectedWhat, ExpectedWhere, ParseError, ParseErrorKind}, - vm::{machine::MachineError, module::ModuleRef, native::NativeFunction}, -}; - -#[derive(Debug, Clone, PartialEq)] -pub struct BytecodeFunction { - pub module: ModuleRef, - pub required_count: usize, - pub address: usize, -} - -#[derive(Clone)] -pub struct OpaqueValue { - inner: Rc, -} - -pub struct ProperListIter<'a, E> { - head: Option<&'a Value>, - error: Option, -} - -#[derive(Debug, PartialEq)] -pub struct Vector(RefCell>); - -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct ValueString(Rc); - -#[derive(Debug, Clone, PartialEq)] -pub enum Value { - // "Expression" values - Nil, - Boolean(bool), - Integer(i64), - Identifier(Rc), - Cons(Rc), - Keyword(Keyword), - String(ValueString), - Vector(Rc), - Quasi(Rc), - // "Runtime" values - BytecodeFunction(BytecodeFunction), - NativeFunction(NativeFunction), - OpaqueValue(OpaqueValue), -} - -impl<'a, E> Iterator for ProperListIter<'a, E> { - type Item = Result<&'a Value, E>; - - fn next(&mut self) -> Option { - let head = self.head.take()?; - match head { - Value::Cons(cons) => { - let ConsCell(car, cdr) = cons.as_ref(); - self.head = Some(cdr); - Some(Ok(car)) - } - Value::Nil => None, - _ => self.error.take().map(Err), - } - } -} - -impl OpaqueValue { - pub fn cast(&self) -> Result<&T, MachineError> { - self.inner - .downcast_ref() - .ok_or(MachineError::InvalidArgument) - } -} - -impl fmt::Debug for OpaqueValue { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_tuple("OpaqueValue") - .field_with(|f| write!(f, "{:p}", self.inner)) - .finish() - } -} - -impl fmt::Display for OpaqueValue { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "", self.inner) - } -} - -impl PartialEq for OpaqueValue { - fn eq(&self, other: &Self) -> bool { - Rc::ptr_eq(&self.inner, &other.inner) - } -} - -impl From> for OpaqueValue { - fn from(value: Rc) -> Self { - Self { inner: value } - } -} - -macro_rules! impl_keyword { - ( - $vis:vis enum $name:ident { - $($variant:ident => $s:literal),* $(,)? - } - ) => { - #[derive(Debug, Clone, Copy, PartialEq, Eq)] - $vis enum $name { - $($variant),* - } - - impl $name { - pub fn parse(s: &str) -> Option { - match s { - $( - $s => Some(Self::$variant), - )* - _ => None, - } - } - } - - impl fmt::Display for $name { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(match self { - $( - Self::$variant => $s - ),* - }) - } - } - }; -} - -impl_keyword! { - pub enum Keyword { - Lambda => "lambda", - Defun => "defun", - If => "if", - Cond => "cond", - While => "while", - Otherwise => "&otherwise", - Optional => "&optional", - Rest => "&rest", - Setq => "setq", - Let => "let", - LetStar => "let*", - Defmacro => "defmacro", - } -} - -#[derive(Debug, Clone, PartialEq)] -pub struct ConsCell(pub Value, pub Value); - -impl Value { - pub fn proper_iter(&self, error: E) -> ProperListIter<'_, E> { - ProperListIter { - head: Some(self), - error: Some(error), - } - } - - pub fn syntax_iter(&self, location: ExpectedWhere) -> ProperListIter<'_, ParseError> { - self.proper_iter(ParseError { - input: self.clone(), - error: ParseErrorKind::Expected(ExpectedWhat::ProperList, location), - }) - } - - pub fn is_nil(&self) -> bool { - matches!(self, Self::Nil) - } - - pub fn as_opaque(&self) -> Result<&T, MachineError> { - match self { - Self::OpaqueValue(opaque) => opaque.cast(), - _ => Err(MachineError::InvalidArgument), - } - } - - pub fn stringify(&self) -> Result { - match self { - Self::Integer(value) => Ok(format!("{value}")), - Self::Boolean(true) => Ok("#t".into()), - Self::Boolean(false) => Ok("#f".into()), - Self::Identifier(value) => Ok(format!("{value}")), - Self::String(value) => Ok(format!("{value}")), - Self::Quasi(_) => todo!(), - Self::Nil => todo!(), - Self::Cons(_) => todo!(), - Self::Vector(_) => todo!(), - Self::Keyword(_) => todo!(), - Self::OpaqueValue(_) => todo!(), - Self::NativeFunction(_) | Self::BytecodeFunction(_) => todo!(), - } - } - - pub fn cons(self, cdr: Value) -> Self { - Self::Cons(Rc::new(ConsCell(self, cdr))) - } - - pub fn try_list_or_nil>>( - items: I, - ) -> Result { - Self::try_list_or_nil_inner(&mut items.into_iter()) - } - - pub fn list_or_nil>(items: I) -> Self { - Self::list_or_nil_inner(&mut items.into_iter()) - } - - fn try_list_or_nil_inner>>( - items: &mut I, - ) -> Result { - match items.next() { - Some(value) => Ok(value?.cons(Self::try_list_or_nil_inner(items)?)), - None => Ok(Self::Nil), - } - } - - fn list_or_nil_inner>(items: &mut I) -> Self { - match items.next() { - Some(value) => value.cons(Self::list_or_nil_inner(items)), - None => Self::Nil, - } - } -} - -impl From<&str> for ValueString { - fn from(value: &str) -> Self { - Self(value.into()) - } -} - -impl From for ValueString { - fn from(value: String) -> Self { - Self(value.into()) - } -} - -impl Deref for ValueString { - type Target = str; - - fn deref(&self) -> &Self::Target { - self.0.as_ref() - } -} - -impl Vector { - pub fn is_empty(&self) -> bool { - self.0.borrow().is_empty() - } - - pub fn len(&self) -> usize { - self.0.borrow().len() - } -} - -impl ConsCell { - pub fn end(value: Value) -> Self { - Self(value, Value::Nil) - } - - fn fmt_inner(&self, f: &mut fmt::Formatter<'_>, first: bool) -> fmt::Result { - let Self(car, cdr) = self; - if !first { - write!(f, " ")?; - } - write!(f, "{car}")?; - match cdr { - Value::Nil => Ok(()), - Value::Cons(cons) => cons.fmt_inner(f, false), - _ => { - write!(f, " . {cdr}") - } - } - } -} - -impl fmt::Display for ConsCell { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.fmt_inner(f, true) - } -} - -impl fmt::Display for BytecodeFunction { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "", self.module, self.address) - } -} - -impl fmt::Display for Vector { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "[")?; - for (i, element) in self.0.borrow().iter().enumerate() { - if i != 0 { - write!(f, " ")?; - } - write!(f, "{element}")?; - } - write!(f, "]") - } -} - -impl fmt::Display for ValueString { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Display::fmt(self.0.as_ref(), f) - } -} - -impl fmt::Display for Value { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Nil => write!(f, "NIL"), - Self::Boolean(true) => write!(f, "#T"), - Self::Boolean(false) => write!(f, "#F"), - Self::Integer(value) => write!(f, "{value}"), - Self::Identifier(value) => write!(f, "{value}"), - Self::Keyword(keyword) => write!(f, "{keyword}"), - Self::Cons(cons) => write!(f, "({cons})"), - Self::Quasi(value) => write!(f, "`{value}"), - Self::BytecodeFunction(bytecode) => write!(f, "{bytecode}"), - Self::NativeFunction(native) => write!(f, "{native}"), - Self::OpaqueValue(opaque) => write!(f, "{opaque}"), - Self::Vector(vector) => write!(f, "{vector}"), - Self::String(value) => write!(f, "{value}"), - } - } -} - -#[cfg(test)] -mod tests { - use crate::vm::value::{Keyword, Value}; - - #[test] - fn test_value_formatting() { - let v = Value::Nil; - assert_eq!(&format!("{v}"), "NIL"); - let v = Value::Integer(1234); - assert_eq!(&format!("{v}"), "1234"); - let v = Value::Boolean(true); - assert_eq!(&format!("{v}"), "#T"); - let v = Value::Boolean(false); - assert_eq!(&format!("{v}"), "#F"); - let v = Value::Boolean(false).cons(Value::Integer(1234)); - assert_eq!(&format!("{v}"), "(#F . 1234)"); - let v = Value::Boolean(false) - .cons(Value::Integer(1234).cons(Value::Keyword(Keyword::Lambda).cons(Value::Nil))); - assert_eq!(&format!("{v}"), "(#F 1234 lambda)"); - } -} diff --git a/src/vm/value/bytecode.rs b/src/vm/value/bytecode.rs new file mode 100644 index 0000000..25289ff --- /dev/null +++ b/src/vm/value/bytecode.rs @@ -0,0 +1,16 @@ +use std::fmt; + +use crate::vm::module::ModuleRef; + +#[derive(Debug, Clone, Hash, PartialEq, Eq)] +pub struct BytecodeFunction { + pub module: ModuleRef, + pub required_count: usize, + pub address: usize, +} + +impl fmt::Display for BytecodeFunction { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "", self.module, self.address) + } +} diff --git a/src/vm/value/cons.rs b/src/vm/value/cons.rs new file mode 100644 index 0000000..33316f7 --- /dev/null +++ b/src/vm/value/cons.rs @@ -0,0 +1,33 @@ +use std::fmt; + +use crate::vm::value::Value; + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct ConsCell(pub Value, pub Value); + +impl ConsCell { + pub fn end(value: Value) -> Self { + Self(value, Value::Nil) + } + + fn fmt_inner(&self, f: &mut fmt::Formatter<'_>, first: bool) -> fmt::Result { + let Self(car, cdr) = self; + if !first { + write!(f, " ")?; + } + write!(f, "{car}")?; + match cdr { + Value::Nil => Ok(()), + Value::Cons(cons) => cons.fmt_inner(f, false), + _ => { + write!(f, " . {cdr}") + } + } + } +} + +impl fmt::Display for ConsCell { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.fmt_inner(f, true) + } +} diff --git a/src/convert.rs b/src/vm/value/convert.rs similarity index 83% rename from src/convert.rs rename to src/vm/value/convert.rs index 6b99c86..f570626 100644 --- a/src/convert.rs +++ b/src/vm/value/convert.rs @@ -1,8 +1,10 @@ -use crate::vm::{ - env::Environment, - machine::{Machine, MachineError}, - native::NativeFunction, - value::{BytecodeFunction, ConsCell, Keyword, Value, ValueString}, +use crate::{ + error::{MachineError, MachineErrorKind}, + vm::{ + env::Environment, + machine::Machine, + value::{BytecodeFunction, ConsCell, Keyword, NativeFunction, Value, ValueString}, + }, }; pub enum AnyFunction { @@ -11,13 +13,13 @@ pub enum AnyFunction { } pub trait TryFromValue<'a>: Sized { - fn try_from_value(value: &'a Value) -> Result; + fn try_from_value(value: &'a Value) -> Result; - fn try_from_value_list(mut list_value: &'a Value) -> Result, MachineError> { + fn try_from_value_list(mut list_value: &'a Value) -> Result, MachineErrorKind> { let mut output = vec![]; while !list_value.is_nil() { let Value::Cons(cons) = list_value else { - return Err(MachineError::InvalidArgument); + return Err(MachineErrorKind::InvalidArgument); }; let ConsCell(car, cdr) = cons.as_ref(); output.push(Self::try_from_value(car)?); @@ -38,13 +40,13 @@ macro_rules! impl_primitive_value { } impl TryFromValue<'_> for $t { - fn try_from_value(value: &Value) -> Result { + fn try_from_value(value: &Value) -> Result { match value { #[allow(irrefutable_let_patterns)] &Value::Integer(value) if let Ok(value) = value.try_into() => Ok(value), &Value::Boolean(true) => Ok(1), &Value::Boolean(false) => Ok(0), - _ => Err(MachineError::InvalidArgument) + _ => Err(MachineErrorKind::InvalidArgument) } } } @@ -69,16 +71,16 @@ impl AnyFunction { } impl TryFromValue<'_> for Keyword { - fn try_from_value(value: &Value) -> Result { + fn try_from_value(value: &Value) -> Result { match value { Value::Keyword(value) => Ok(*value), - _ => Err(MachineError::InvalidArgument), + _ => Err(MachineErrorKind::InvalidArgument), } } } impl TryFromValue<'_> for bool { - fn try_from_value(value: &Value) -> Result { + fn try_from_value(value: &Value) -> Result { match value { Value::Nil => Ok(false), Value::Cons(_) => Ok(true), @@ -88,6 +90,8 @@ impl TryFromValue<'_> for bool { Value::Integer(value) => Ok(*value != 0), Value::Keyword(_) | Value::Quasi(_) + | Value::Unquote(_) + | Value::Quote(_) | Value::Identifier(_) | Value::OpaqueValue(_) | Value::NativeFunction(_) @@ -102,10 +106,10 @@ impl From for Value { } impl TryFromValue<'_> for ValueString { - fn try_from_value(value: &'_ Value) -> Result { + fn try_from_value(value: &'_ Value) -> Result { match value { Value::String(value) => Ok(value.clone()), - _ => Err(MachineError::InvalidArgument), + _ => Err(MachineErrorKind::InvalidArgument), } } } @@ -116,23 +120,23 @@ impl From for Value { } impl TryFromValue<'_> for Value { - fn try_from_value(value: &Value) -> Result { + fn try_from_value(value: &Value) -> Result { Ok(value.clone()) } } impl<'a> TryFromValue<'a> for &'a Value { - fn try_from_value(value: &'a Value) -> Result { + fn try_from_value(value: &'a Value) -> Result { Ok(value) } } impl TryFromValue<'_> for AnyFunction { - fn try_from_value(value: &Value) -> Result { + fn try_from_value(value: &Value) -> Result { match value { Value::BytecodeFunction(bytecode) => Ok(Self::Bytecode(bytecode.clone())), Value::NativeFunction(native) => Ok(Self::Native(native.clone())), - _ => Err(MachineError::InvalidArgument), + _ => Err(MachineErrorKind::InvalidArgument), } } } @@ -140,7 +144,7 @@ impl TryFromValue<'_> for AnyFunction { impl_primitive_value!(i8, i16, i32, i64); impl_primitive_value!(u8, u16, u32, u64); -impl From for Result { +impl From for Result { fn from(value: Value) -> Self { Ok(value) } diff --git a/src/vm/value/iter.rs b/src/vm/value/iter.rs new file mode 100644 index 0000000..7db5d76 --- /dev/null +++ b/src/vm/value/iter.rs @@ -0,0 +1,23 @@ +use crate::vm::value::{ConsCell, Value}; + +pub struct ProperListIter<'a, E> { + pub(super) head: Option<&'a Value>, + pub(super) error: Option, +} + +impl<'a, E> Iterator for ProperListIter<'a, E> { + type Item = Result<&'a Value, E>; + + fn next(&mut self) -> Option { + let head = self.head.take()?; + match head { + Value::Cons(cons) => { + let ConsCell(car, cdr) = cons.as_ref(); + self.head = Some(cdr); + Some(Ok(car)) + } + Value::Nil => None, + _ => self.error.take().map(Err), + } + } +} diff --git a/src/vm/value/keyword.rs b/src/vm/value/keyword.rs new file mode 100644 index 0000000..28559d8 --- /dev/null +++ b/src/vm/value/keyword.rs @@ -0,0 +1,52 @@ +macro_rules! impl_keyword { + ( + $vis:vis enum $name:ident { + $($variant:ident => $s:literal),* $(,)? + } + ) => { + #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] + $vis enum $name { + $($variant),* + } + + impl $name { + pub fn parse(s: &str) -> Option { + match s { + $( + $s => Some(Self::$variant), + )* + _ => None, + } + } + } + + impl std::fmt::Display for $name { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(match self { + $( + Self::$variant => $s + ),* + }) + } + } + }; +} + +impl_keyword! { + pub enum Keyword { + Lambda => "lambda", + Defun => "defun", + If => "if", + Cond => "cond", + While => "while", + Otherwise => "&otherwise", + Optional => "&optional", + Rest => "&rest", + Setq => "setq", + Let => "let", + LetStar => "let*", + Defmacro => "defmacro", + Quote => "quote", + // Cons => "cons", + } +} diff --git a/src/vm/value/mod.rs b/src/vm/value/mod.rs new file mode 100644 index 0000000..6978e8d --- /dev/null +++ b/src/vm/value/mod.rs @@ -0,0 +1,180 @@ +use std::{fmt, hash::Hash, rc::Rc}; + +use crate::{ + compile::{ExpectedWhat, ExpectedWhere, ParseError, ParseErrorKind}, + error::MachineErrorKind, + vm::value::iter::ProperListIter, +}; + +mod bytecode; +mod cons; +mod keyword; +mod native; +mod string; +mod vector; + +mod convert; +mod iter; + +pub use bytecode::BytecodeFunction; +pub use cons::ConsCell; +pub use keyword::Keyword; +pub use native::{NativeFunction, NativeFunctionImpl, OpaqueValue}; +pub use string::ValueString; +pub use vector::Vector; + +pub use convert::{AnyFunction, TryFromValue}; + +#[derive(Clone)] +pub enum Macro { + Builtin(NativeFunctionImpl), + Bytecode(BytecodeFunction), +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum Value { + // "Expression" values + Nil, + Boolean(bool), + Integer(i64), + Identifier(Rc), + Cons(Rc), + Keyword(Keyword), + String(ValueString), + Vector(Rc), + // Quoting + Quasi(Rc), + Unquote(Rc), + Quote(Rc), + // "Runtime" values + BytecodeFunction(BytecodeFunction), + NativeFunction(NativeFunction), + OpaqueValue(OpaqueValue), +} + +impl Value { + pub fn proper_iter(&self, error: E) -> ProperListIter<'_, E> { + ProperListIter { + head: Some(self), + error: Some(error), + } + } + + pub fn syntax_iter(&self, location: ExpectedWhere) -> ProperListIter<'_, ParseError> { + self.proper_iter(ParseError { + input: self.clone(), + error: ParseErrorKind::Expected(ExpectedWhat::ProperList, location), + }) + } + + pub fn as_proper_list(&self) -> Option> { + self.proper_iter(()) + .map(|x| x.cloned()) + .collect::>() + .ok() + } + + pub fn is_nil(&self) -> bool { + matches!(self, Self::Nil) + } + + pub fn as_opaque(&self) -> Result<&T, MachineErrorKind> { + match self { + Self::OpaqueValue(opaque) => opaque.cast(), + _ => Err(MachineErrorKind::InvalidArgument), + } + } + + pub fn stringify(&self) -> Result { + match self { + Self::Integer(value) => Ok(format!("{value}")), + Self::Boolean(true) => Ok("#t".into()), + Self::Boolean(false) => Ok("#f".into()), + Self::Identifier(value) => Ok(format!("{value}")), + Self::String(value) => Ok(format!("{value}")), + Self::Quasi(_) => todo!(), + Self::Quote(_) => todo!(), + Self::Unquote(_) => todo!(), + Self::Nil => todo!(), + Self::Cons(_) => todo!(), + Self::Vector(_) => todo!(), + Self::Keyword(_) => todo!(), + Self::OpaqueValue(_) => todo!(), + Self::NativeFunction(_) | Self::BytecodeFunction(_) => todo!(), + } + } + + pub fn cons(self, cdr: Value) -> Self { + Self::Cons(Rc::new(ConsCell(self, cdr))) + } + + pub fn try_list_or_nil>>( + items: I, + ) -> Result { + Self::try_list_or_nil_inner(&mut items.into_iter()) + } + + pub fn list_or_nil>(items: I) -> Self { + Self::list_or_nil_inner(&mut items.into_iter()) + } + + fn try_list_or_nil_inner>>( + items: &mut I, + ) -> Result { + match items.next() { + Some(value) => Ok(value?.cons(Self::try_list_or_nil_inner(items)?)), + None => Ok(Self::Nil), + } + } + + fn list_or_nil_inner>(items: &mut I) -> Self { + match items.next() { + Some(value) => value.cons(Self::list_or_nil_inner(items)), + None => Self::Nil, + } + } +} + +impl fmt::Display for Value { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Nil => write!(f, "NIL"), + Self::Boolean(true) => write!(f, "#T"), + Self::Boolean(false) => write!(f, "#F"), + Self::Integer(value) => write!(f, "{value}"), + Self::Identifier(value) => write!(f, "{value}"), + Self::Keyword(keyword) => write!(f, "{keyword}"), + Self::Cons(cons) => write!(f, "({cons})"), + Self::Quasi(value) => write!(f, "`{value}"), + Self::Unquote(value) => write!(f, ",{value}"), + Self::Quote(value) => write!(f, "'{value}"), + Self::BytecodeFunction(bytecode) => write!(f, "{bytecode}"), + Self::NativeFunction(native) => write!(f, "{native}"), + Self::OpaqueValue(opaque) => write!(f, "{opaque}"), + Self::Vector(vector) => write!(f, "{vector}"), + Self::String(value) => write!(f, "{value}"), + } + } +} + +#[cfg(test)] +mod tests { + use crate::vm::value::{Keyword, Value}; + + #[test] + fn test_value_formatting() { + let v = Value::Nil; + assert_eq!(&format!("{v}"), "NIL"); + let v = Value::Integer(1234); + assert_eq!(&format!("{v}"), "1234"); + let v = Value::Boolean(true); + assert_eq!(&format!("{v}"), "#T"); + let v = Value::Boolean(false); + assert_eq!(&format!("{v}"), "#F"); + let v = Value::Boolean(false).cons(Value::Integer(1234)); + assert_eq!(&format!("{v}"), "(#F . 1234)"); + let v = Value::Boolean(false) + .cons(Value::Integer(1234).cons(Value::Keyword(Keyword::Lambda).cons(Value::Nil))); + assert_eq!(&format!("{v}"), "(#F 1234 lambda)"); + } +} diff --git a/src/vm/value/native.rs b/src/vm/value/native.rs new file mode 100644 index 0000000..e3bc67b --- /dev/null +++ b/src/vm/value/native.rs @@ -0,0 +1,117 @@ +use std::{ + any::Any, + fmt, + hash::{Hash, Hasher}, + rc::Rc, +}; + +use crate::{ + error::{MachineError, MachineErrorKind}, + vm::{env::Environment, machine::Machine, value::Value}, +}; + +#[derive(Clone)] +pub struct OpaqueValue { + inner: Rc, +} + +pub type NativeFunctionImpl = + Rc Result + 'static>; + +#[derive(Clone)] +pub struct NativeFunction { + name: Rc, + inner: NativeFunctionImpl, +} + +impl OpaqueValue { + pub fn cast(&self) -> Result<&T, MachineErrorKind> { + self.inner + .downcast_ref() + .ok_or(MachineErrorKind::InvalidArgument) + } +} + +impl Hash for OpaqueValue { + fn hash(&self, state: &mut H) { + (&raw const *self.inner.as_ref()).addr().hash(state); + } +} + +impl Eq for OpaqueValue {} + +impl fmt::Debug for OpaqueValue { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("OpaqueValue") + .field_with(|f| write!(f, "{:p}", self.inner)) + .finish() + } +} +impl PartialEq for OpaqueValue { + fn eq(&self, other: &Self) -> bool { + Rc::ptr_eq(&self.inner, &other.inner) + } +} + +impl From> for OpaqueValue { + fn from(value: Rc) -> Self { + Self { inner: value } + } +} +impl fmt::Display for OpaqueValue { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "", self.inner) + } +} + +impl NativeFunction { + pub fn new(name: S, function: F) -> Self + where + S: Into>, + F: Fn(&mut Machine, &mut Environment, &[Value]) -> Result + 'static, + { + Self { + name: name.into(), + inner: Rc::new(function), + } + } + + pub fn invoke( + &self, + machine: &mut Machine, + environment: &mut Environment, + arguments: &[Value], + ) -> Result { + (self.inner)(machine, environment, arguments) + } +} + +impl Hash for NativeFunction { + fn hash(&self, state: &mut H) { + self.name.hash(state); + (&raw const *self.inner.as_ref()).addr().hash(state); + } +} + +impl PartialEq for NativeFunction { + fn eq(&self, other: &Self) -> bool { + self.name == other.name && Rc::ptr_eq(&self.inner, &other.inner) + } +} + +impl Eq for NativeFunction {} + +impl fmt::Debug for NativeFunction { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("NativeFunction") + .field("name", &self.name) + .field_with("inner", |f| write!(f, "{:p}", self.inner)) + .finish() + } +} + +impl fmt::Display for NativeFunction { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "", self.name, self.inner) + } +} diff --git a/src/vm/value/string.rs b/src/vm/value/string.rs new file mode 100644 index 0000000..222eea0 --- /dev/null +++ b/src/vm/value/string.rs @@ -0,0 +1,30 @@ +use std::{fmt, ops::Deref, rc::Rc}; + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct ValueString(Rc); + +impl From<&str> for ValueString { + fn from(value: &str) -> Self { + Self(value.into()) + } +} + +impl From for ValueString { + fn from(value: String) -> Self { + Self(value.into()) + } +} + +impl Deref for ValueString { + type Target = str; + + fn deref(&self) -> &Self::Target { + self.0.as_ref() + } +} + +impl fmt::Display for ValueString { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(self.0.as_ref(), f) + } +} diff --git a/src/vm/value/vector.rs b/src/vm/value/vector.rs new file mode 100644 index 0000000..fc3de68 --- /dev/null +++ b/src/vm/value/vector.rs @@ -0,0 +1,54 @@ +use std::{ + cell::RefCell, + fmt, + hash::{Hash, Hasher}, + ops::Deref, +}; + +use crate::vm::value::Value; + +#[derive(Debug, PartialEq, Eq)] +pub struct Vector(RefCell>); + +impl Hash for Vector { + fn hash(&self, state: &mut H) { + self.0.borrow().hash(state); + } +} + +impl Vector { + pub fn is_empty(&self) -> bool { + self.0.borrow().is_empty() + } + + pub fn len(&self) -> usize { + self.0.borrow().len() + } +} + +impl FromIterator for Vector { + fn from_iter>(iter: T) -> Self { + Self(RefCell::new(Vec::from_iter(iter))) + } +} + +impl Deref for Vector { + type Target = RefCell>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl fmt::Display for Vector { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "[")?; + for (i, element) in self.0.borrow().iter().enumerate() { + if i != 0 { + write!(f, " ")?; + } + write!(f, "{element}")?; + } + write!(f, "]") + } +} diff --git a/tests/integration.rs b/tests/integration.rs new file mode 100644 index 0000000..9fd6346 --- /dev/null +++ b/tests/integration.rs @@ -0,0 +1,174 @@ +use std::io::{self, BufReader, Read}; + +use lysp::{ + error::{EvalError, MachineError, MachineErrorKind}, + read::{FileReader, read}, + vm::{env::Environment, machine::Machine, prelude, value::Value}, +}; + +struct SliceReader<'a>(&'a [u8]); + +impl Read for SliceReader<'_> { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + let count = self.0.len().min(buf.len()); + buf[..count].copy_from_slice(&self.0[..count]); + self.0 = &self.0[count..]; + Ok(count) + } +} + +fn eval_str_in(code: &str, env: &mut Environment) -> Result { + let mut machine = Machine::default(); + let reader = BufReader::new(SliceReader(code.as_bytes())); + let mut reader = FileReader::new(reader); + + let mut last_value = None; + loop { + let value = match read(&mut reader, &mut machine, env) { + Ok(Some(value)) => value, + Ok(None) => break, + Err(error) => panic!("{error}"), + }; + + last_value = Some(machine.eval_value(env, value)); + } + + last_value.expect("no expressions evaluated") +} + +fn eval_str(code: &str) -> Value { + let mut env = Environment::default(); + prelude::load(&mut env); + eval_str_in(code, &mut env).expect("expression evaluation failed") +} + +fn eval_str_err(code: &str) -> EvalError { + let mut env = Environment::default(); + prelude::load(&mut env); + eval_str_in(code, &mut env).expect_err("expression was expected to fail") +} + +#[test] +fn test_math() { + // math + assert_eq!(eval_str("(+ 1 2 3)"), Value::Integer(6)); + assert_eq!(eval_str("(- 3 2 1)"), Value::Integer(0)); + assert_eq!(eval_str("(* 2 3 4)"), Value::Integer(24)); + assert_eq!(eval_str("(/ 16 4 2)"), Value::Integer(2)); + assert_eq!(eval_str("(% 35 16)"), Value::Integer(3)); + assert_eq!(eval_str("(| 1 2 4)"), Value::Integer(7)); + assert_eq!(eval_str("(& 1 2 4)"), Value::Integer(0)); + assert_eq!(eval_str("(& 1 3 7)"), Value::Integer(1)); + assert_eq!(eval_str("(^ 1 3 8)"), Value::Integer(10)); + // comparison + assert_eq!(eval_str("(< 1 2 3 4 5)"), Value::Boolean(true)); + assert_eq!(eval_str("(< 1 2 3 3 4 5)"), Value::Boolean(false)); + assert_eq!(eval_str("(> 1 2 3 4 5)"), Value::Boolean(false)); + assert_eq!(eval_str("(>= 5 5 4 3 2 1)"), Value::Boolean(true)); + assert_eq!(eval_str("(> 5 5 4 3 2 1)"), Value::Boolean(false)); + assert_eq!(eval_str("(<= 1 2 3 3 3 4)"), Value::Boolean(true)); + assert_eq!(eval_str("(/= 1 2 3 4)"), Value::Boolean(true)); + assert_eq!(eval_str("(/= 1 2 2 4)"), Value::Boolean(false)); + assert_eq!(eval_str("(= 1 2 3 4)"), Value::Boolean(false)); + assert_eq!(eval_str("(= 1 1 1 1)"), Value::Boolean(true)); + // logic + assert_eq!(eval_str("(&& 1 0)"), Value::Boolean(false)); + assert_eq!(eval_str("(&& #t 1 \"yes\")"), Value::Boolean(true)); + assert_eq!(eval_str("(|| #f NIL \"\" ())"), Value::Boolean(false)); + assert_eq!(eval_str("(|| #f '(1 2 3) \"\" ())"), Value::Boolean(true)); +} + +#[test] +fn test_abort() { + let err = eval_str_err("(assert (= 1 (+ 1 1)))"); + let EvalError::Machine(MachineError { + error: MachineErrorKind::Aborted(message), + .. + }) = err + else { + panic!("Invalid error returned") + }; + assert_eq!(&message, ": assertion failed: (= 1 (+ 1 1))"); + + let err = eval_str_err("(abort 1234)"); + let EvalError::Machine(MachineError { + error: MachineErrorKind::Aborted(message), + .. + }) = err + else { + panic!("Invalid error returned") + }; + assert_eq!(&message, "1234"); +} + +#[test] +fn test_lambda() { + assert_eq!(eval_str("((lambda (x) (+ x 1)) 1)"), Value::Integer(2)); + assert_eq!( + eval_str("((lambda (x) (+ ((lambda () 2)) 1)) 1)"), + Value::Integer(3) + ); +} + +#[test] +fn test_math_behaves_the_same_inside_apply() { + assert_eq!(eval_str("(apply + '(1 2 3 4))"), eval_str("(+ 1 2 3 4)")); + assert_eq!(eval_str("(apply * '(1 2 3 4))"), eval_str("(* 1 2 3 4)")); + assert_eq!(eval_str("(apply - '(1 2 3 4))"), eval_str("(- 1 2 3 4)")); + assert_eq!(eval_str("(apply / '(100 25 2))"), eval_str("(/ 100 25 2)")); + assert_eq!(eval_str("(apply % '(90 37))"), eval_str("(% 90 37)")); + assert_eq!(eval_str("(apply | '(1 2 4))"), eval_str("(| 1 2 4)")); + assert_eq!(eval_str("(apply & '(1 3 7))"), eval_str("(& 1 3 7)")); + assert_eq!(eval_str("(apply ^ '(1 3 7))"), eval_str("(^ 1 3 7)")); + assert_eq!(eval_str("(apply < '(1 2 3 4))"), eval_str("(< 1 2 3 4)")); + assert_eq!(eval_str("(apply > '(1 2 3 4))"), eval_str("(> 1 2 3 4)")); +} + +#[test] +fn test_setq() { + assert_eq!(eval_str("(setq a 1234) a\n"), Value::Integer(1234)); +} + +#[test] +fn test_let() { + // Should fail + eval_str_err("(let (a 1234 b (+ a 1)) NIL)"); + + assert_eq!( + eval_str("(let (a 1234 b 4321) (+ a b))"), + Value::Integer(5555) + ); + assert_eq!( + eval_str("(let* (a 1234 b (+ a 4321)) (+ b 4444))"), + Value::Integer(9999) + ); + + // Nested let + assert_eq!( + eval_str("(let (a 1234) (let (b 4321) (+ a b)))"), + Value::Integer(5555) + ); + // Doesn't shadow + assert_eq!( + eval_str("(let (a 1234) (let (a 9999 b (+ a 4321)) b))"), + Value::Integer(5555) + ); + // Does shadow + assert_eq!( + eval_str("(let (a 1234) (let* (a 9999 b (+ a 4321)) b))"), + Value::Integer(14320) + ); +} + +#[test] +fn test_macro() { + assert_eq!( + eval_str("(defmacro stringify (x) (cons 'list `,x)) (stringify (1 2 3 4))"), + Value::list_or_nil([ + Value::Integer(1), + Value::Integer(2), + Value::Integer(3), + Value::Integer(4) + ]) + ); +}