From bad52a57c3374ce611ccac97da46df402ab4985d Mon Sep 17 00:00:00 2001 From: Mark Poliakov Date: Wed, 6 May 2026 09:46:34 +0300 Subject: [PATCH] Implement setq/let/let* bindings --- lysp/example0.lysp | 12 ++ src/compile/block.rs | 143 ++++++++++++++-- src/compile/error.rs | 2 + src/compile/mod.rs | 5 +- src/compile/syntax/binding.rs | 293 ++++++++++++++++++++++++++++++++ src/compile/syntax/condition.rs | 2 +- src/compile/syntax/error.rs | 18 +- src/compile/syntax/mod.rs | 12 ++ src/compile/value.rs | 3 +- src/error.rs | 2 + src/lib.rs | 8 +- src/vm/instruction.rs | 7 +- src/vm/machine.rs | 60 ++++++- src/vm/module.rs | 4 +- src/vm/prelude.rs | 8 +- src/vm/stack.rs | 8 + src/vm/value.rs | 41 +++-- 17 files changed, 584 insertions(+), 44 deletions(-) create mode 100644 src/compile/syntax/binding.rs diff --git a/lysp/example0.lysp b/lysp/example0.lysp index cb86c54..aebe489 100644 --- a/lysp/example0.lysp +++ b/lysp/example0.lysp @@ -12,3 +12,15 @@ (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 + (let (a 1) + (let (b 2) + (+ a b 3) + ) + ) +)) +(assert (/= (let (a 2) a) a)) diff --git a/src/compile/block.rs b/src/compile/block.rs index 7d4a85b..08c42f1 100644 --- a/src/compile/block.rs +++ b/src/compile/block.rs @@ -8,7 +8,7 @@ use crate::{ module::CompilationModule, syntax::{ CallExpression, CondExpression, DefunExpression, Expression, FunctionBody, - IfExpression, LambdaExpression, + IfExpression, LambdaExpression, LetExpression, SetqExpression, }, value::{BuiltinFunction, CompileConstant, CompileValue}, }, @@ -19,18 +19,49 @@ pub struct CompiledFunction { pub(crate) instructions: Vec, } +struct LocalScope { + start_index: u32, + locals: Vec>, +} + pub struct FunctionBlock { pub(crate) instructions: Vec, labels: HashMap, last_label: u32, signature: FunctionSignature, + locals_stack: Vec, + max_local_index: u32, } pub struct LocalBlock<'a> { // TODO local bindings function: &'a mut FunctionBlock, module: &'a mut CompilationModule, - parent: Option<&'a mut LocalBlock<'a>>, +} + +impl LocalScope { + pub fn get_or_insert(&mut self, value: Rc) -> Result, CompileError> { + let index = if let Some(index) = self.locals.iter().position(|v| *v == value) { + index + } else { + let index = self.locals.len(); + self.locals.push(value); + index + } + self.start_index as usize; + Ok(U::new(index as u32).unwrap()) + } + + pub fn get(&self, value: &str) -> Option> { + self.locals + .iter() + .position(|v| v.as_ref() == value) + .map(|v| v + self.start_index as usize) + .and_then(|v| U::new(v as u32)) + } + + fn end_index(&self) -> u32 { + self.start_index + self.locals.len() as u32 + } } impl FunctionBlock { @@ -40,6 +71,8 @@ impl FunctionBlock { labels: HashMap::new(), last_label: 0, signature, + locals_stack: Vec::new(), + max_local_index: 0, } } @@ -50,6 +83,43 @@ impl FunctionBlock { id } + fn push_local_scope(&mut self) -> &mut LocalScope { + let start_index = self + .local_scope_ref() + .map(LocalScope::end_index) + .unwrap_or(0); + let scope = LocalScope { + locals: vec![], + start_index, + }; + self.locals_stack.push_mut(scope) + } + + fn pop_local_scope(&mut self) { + let scope = self + .locals_stack + .pop() + .expect("Local scope stack underflow"); + self.max_local_index = self.max_local_index.max(scope.end_index()); + } + + fn local_scope_mut(&mut self) -> Option<&mut LocalScope> { + self.locals_stack.last_mut() + } + + fn local_scope_ref(&self) -> Option<&LocalScope> { + self.locals_stack.last() + } + + fn lookup_local(&self, identifier: &str) -> Option> { + for scope in self.locals_stack.iter().rev() { + if let Some(index) = scope.get(identifier) { + return Some(index); + } + } + None + } + pub fn adjust_label(&mut self, label: u32) { self.labels.insert(label, self.instructions.len()); } @@ -105,11 +175,7 @@ impl FunctionBlock { impl<'a> LocalBlock<'a> { fn root(function: &'a mut FunctionBlock, module: &'a mut CompilationModule) -> Self { - Self { - function, - module, - parent: None, - } + Self { function, module } } fn compile_set_global( @@ -126,6 +192,21 @@ impl<'a> LocalBlock<'a> { Ok(()) } + fn compile_set_local( + &mut self, + identifier: &Rc, + value: CompileValue, + ) -> Result<(), CompileError> { + let index = self + .function + .local_scope_mut() + .unwrap() + .get_or_insert(identifier.clone())?; + self.compile_push(value)?; + self.function.emit(Instruction::SetLocal(index)); + Ok(()) + } + fn compile_push(&mut self, value: CompileValue) -> Result<(), CompileError> { match value { CompileValue::Nil => { @@ -138,6 +219,9 @@ impl<'a> LocalBlock<'a> { self.function.emit(Instruction::PushConstant(value)); self.function.emit(Instruction::GetGlobal); } + CompileValue::Local(index) => { + self.function.emit(Instruction::GetLocal(index)); + } CompileValue::Boolean(value) => { self.function.emit(Instruction::PushBool(value)); } @@ -188,6 +272,9 @@ impl<'a> LocalBlock<'a> { } fn compile_identifier(&mut self, identifier: &Rc) -> Result { + if let Some(local) = self.function.lookup_local(identifier) { + return Ok(CompileValue::Local(local)); + } if let Some(argument) = self.function.signature.argument(identifier) { return Ok(CompileValue::Argument(argument)); } @@ -316,6 +403,34 @@ impl<'a> LocalBlock<'a> { Ok(CompileValue::Stack) } + fn compile_let(&mut self, binding: &LetExpression) -> Result { + self.function.push_local_scope(); + for pair in &binding.bindings { + let value = self.compile_expression(&pair.value)?; + self.compile_set_local(&pair.identifier, value)?; + } + for expr in &binding.body.head { + self.compile_statement(expr)?; + } + let value = self.compile_expression(&binding.body.tail)?; + if let CompileValue::Local(_) = value { + self.compile_push(value)?; + self.function.pop_local_scope(); + Ok(CompileValue::Stack) + } else { + self.function.pop_local_scope(); + Ok(value) + } + } + + fn compile_setq(&mut self, setq: &SetqExpression) -> Result { + for pair in setq.pairs.iter() { + let value = self.compile_expression(&pair.value)?; + self.compile_set_global(&pair.identifier, value)?; + } + Ok(CompileValue::Nil) + } + pub fn compile_expression( &mut self, expression: &Expression, @@ -330,6 +445,8 @@ impl<'a> LocalBlock<'a> { Expression::Call(call) => self.compile_call(call), Expression::If(condition) => self.compile_if(condition), Expression::Cond(condition) => self.compile_cond(condition), + Expression::Let(binding) => self.compile_let(binding), + Expression::Setq(setq) => self.compile_setq(setq), Expression::SyntaxError(_) => unreachable!(), } @@ -351,7 +468,7 @@ mod tests { }, value::{CompileConstant, CompileValue}, }, - vm::instruction::{Instruction, U}, + vm::instruction::{Instruction, MathInstruction, U}, }; fn test_compile( @@ -367,6 +484,8 @@ mod tests { labels: HashMap::new(), last_label: 0, instructions: vec![], + locals_stack: vec![], + max_local_index: 0, }; let mut local = LocalBlock::root(&mut function, &mut module); let value = local.compile_expression(expression).unwrap(); @@ -400,7 +519,7 @@ mod tests { Instruction::PushInteger(U::truncate(1)), Instruction::PushConstant(U::truncate(1)), Instruction::GetGlobal, - Instruction::Compare(false, Comparison::Gt, U::truncate(2)), + Instruction::Math(MathInstruction::Gt, U::truncate(2)), ] ); assert_eq!(v, CompileValue::Stack); @@ -518,7 +637,7 @@ mod tests { &[ Instruction::PushArgument(U::truncate(1)), Instruction::PushArgument(U::truncate(0)), - Instruction::Add(U::truncate(2)), + Instruction::Math(MathInstruction::Add, U::truncate(2)), Instruction::Return ] ); @@ -561,7 +680,7 @@ mod tests { &[ Instruction::PushInteger(U::truncate(1)), Instruction::PushArgument(U::truncate(0)), - Instruction::Add(U::truncate(2)), + Instruction::Math(MathInstruction::Add, U::truncate(2)), Instruction::Return ] ); @@ -571,7 +690,7 @@ mod tests { Instruction::PushInteger(U::truncate(2)), Instruction::PushConstant(U::truncate(1)), Instruction::Call(U::truncate(0)), - Instruction::Add(U::truncate(2)) + Instruction::Math(MathInstruction::Add, U::truncate(2)) ] ); } diff --git a/src/compile/error.rs b/src/compile/error.rs index c26a2b7..929d7f6 100644 --- a/src/compile/error.rs +++ b/src/compile/error.rs @@ -1,3 +1,5 @@ +#![coverage(off)] + use crate::compile::syntax::ParseError; #[derive(Debug, thiserror::Error)] diff --git a/src/compile/mod.rs b/src/compile/mod.rs index b6661ef..42dcbb9 100644 --- a/src/compile/mod.rs +++ b/src/compile/mod.rs @@ -9,4 +9,7 @@ mod value; pub use error::CompileError; pub use function::FunctionSignature; pub use module::CompilationModule; -pub use syntax::{CallExpression, Expression, FunctionBody, LambdaExpression, ParseError}; +pub use syntax::{ + CallExpression, ExpectedWhat, ExpectedWhere, Expression, FunctionBody, LambdaExpression, + ParseError, ParseErrorKind, +}; diff --git a/src/compile/syntax/binding.rs b/src/compile/syntax/binding.rs new file mode 100644 index 0000000..78a7ee7 --- /dev/null +++ b/src/compile/syntax/binding.rs @@ -0,0 +1,293 @@ +use std::rc::Rc; + +use crate::{ + compile::{ + ExpectedWhat, ExpectedWhere, Expression, FunctionBody, ParseError, ParseErrorKind, + syntax::CollectErrors, + }, + vm::value::{ConsCell, Keyword, Value}, +}; + +#[derive(Debug, PartialEq)] +pub struct Assignment { + pub identifier: Rc, + pub value: Expression, +} + +#[derive(Debug, PartialEq)] +pub struct SetqExpression { + pub pairs: Vec, +} + +#[derive(Debug, PartialEq)] +pub struct LetExpression { + pub sequential: bool, + pub bindings: Vec, + pub body: FunctionBody, +} + +impl Assignment { + fn parse_list(value: &Value, input: &Value, after: Keyword) -> Result, ParseError> { + let mut iter = value.syntax_iter(ExpectedWhere::InAssignment); + let mut pairs = vec![]; + loop { + let [identifier, value] = match iter.next_chunk::<2>() { + Ok(pair) => pair, + Err(rest) => { + if !rest.is_empty() { + return Err(ParseError { + input: input.clone(), + error: ParseErrorKind::try_collect_extraneous( + rest.map(|x| x.cloned()), + )?, + }); + } + break; + } + }; + let Value::Identifier(identifier) = identifier? else { + return Err(ParseError { + input: input.clone(), + error: ParseErrorKind::Expected( + ExpectedWhat::Identifier, + ExpectedWhere::InAssignment, + ), + }); + }; + let value = Expression::parse_inner(value?); + pairs.push(Self { + identifier: identifier.clone(), + value, + }); + } + if pairs.is_empty() { + return Err(ParseError { + input: input.clone(), + error: ParseErrorKind::Expected( + ExpectedWhat::AtLeastOneBinding, + ExpectedWhere::AfterKeyword(after), + ), + }); + } + Ok(pairs) + } +} + +impl LetExpression { + pub(super) fn parse( + value: &Value, + input: &Value, + keyword: Keyword, + ) -> Result { + let Value::Cons(value) = value else { + return Err(ParseError { + input: input.clone(), + error: ParseErrorKind::Expected( + ExpectedWhat::ProperList, + ExpectedWhere::AfterKeyword(keyword), + ), + }); + }; + let ConsCell(bindings, cdr) = value.as_ref(); + let bindings = Assignment::parse_list(bindings, input, keyword)?; + let body = FunctionBody::parse(cdr, input, keyword)?; + let sequential = keyword == Keyword::LetStar; + Ok(Self { + sequential, + bindings, + body, + }) + } +} + +impl SetqExpression { + pub(super) fn parse(value: &Value, input: &Value) -> Result { + let pairs = Assignment::parse_list(value, input, Keyword::Setq)?; + Ok(Self { pairs }) + } +} + +impl CollectErrors for SetqExpression { + fn collect_errors(&self, errors: &mut Vec) -> bool { + self.pairs.iter().fold(false, |acc, v| { + let r = v.collect_errors(errors); + acc | r + }) + } +} + +impl CollectErrors for LetExpression { + fn collect_errors(&self, errors: &mut Vec) -> bool { + let r0 = self.bindings.iter().fold(false, |acc, v| { + let r = v.collect_errors(errors); + acc | r + }); + let r1 = self.body.collect_errors(errors); + r0 | r1 + } +} + +impl CollectErrors for Assignment { + fn collect_errors(&self, errors: &mut Vec) -> bool { + self.value.collect_errors(errors) + } +} + +#[cfg(test)] +mod tests { + use std::rc::Rc; + + use crate::{ + compile::{ + ExpectedWhat, ExpectedWhere, Expression, FunctionBody, ParseError, ParseErrorKind, + syntax::{Assignment, LetExpression, SetqExpression}, + }, + vm::value::{ConsCell, Keyword, Value}, + }; + + #[test] + fn test_parse_let() { + let v = Value::list_or_nil([ + Value::Keyword(Keyword::Let), + Value::list_or_nil([Value::Identifier("a".into()), Value::Integer(1)]), + Value::Nil, + ]); + let e = Expression::parse(&v).unwrap(); + assert_eq!( + e, + Expression::Let(LetExpression { + sequential: false, + bindings: vec![Assignment { + identifier: "a".into(), + value: Expression::IntegerLiteral(1) + },], + body: FunctionBody { + head: vec![], + tail: Rc::new(Expression::Nil) + }, + }) + ); + let v = Value::list_or_nil([ + Value::Keyword(Keyword::LetStar), + Value::list_or_nil([ + Value::Identifier("a".into()), + Value::Integer(1), + Value::Identifier("b".into()), + Value::Integer(2), + ]), + Value::Nil, + ]); + let e = Expression::parse(&v).unwrap(); + assert_eq!( + e, + Expression::Let(LetExpression { + sequential: true, + bindings: vec![ + Assignment { + identifier: "a".into(), + value: Expression::IntegerLiteral(1) + }, + Assignment { + identifier: "b".into(), + value: Expression::IntegerLiteral(2) + } + ], + body: FunctionBody { + head: vec![], + tail: Rc::new(Expression::Nil) + }, + }) + ); + + // syntax errors + let v = Value::Cons(Rc::new(ConsCell( + Value::Keyword(Keyword::Let), + Value::Identifier("a".into()), + ))); + let e = Expression::parse(&v).unwrap_err(); + assert_eq!( + &e[..], + &[ParseError { + input: v, + error: ParseErrorKind::Expected( + ExpectedWhat::ProperList, + ExpectedWhere::AfterKeyword(Keyword::Let) + ) + }] + ); + } + + #[test] + fn test_parse_setq() { + let v = Value::list_or_nil([ + Value::Keyword(Keyword::Setq), + Value::Identifier("a".into()), + Value::Identifier("b".into()), + Value::Identifier("c".into()), + Value::Identifier("d".into()), + ]); + let e = Expression::parse(&v).unwrap(); + assert_eq!( + e, + Expression::Setq(SetqExpression { + pairs: vec![ + Assignment { + identifier: "a".into(), + value: Expression::Identifier("b".into()), + }, + Assignment { + identifier: "c".into(), + value: Expression::Identifier("d".into()), + } + ] + }) + ); + + // syntax errors + let v = Value::list_or_nil([Value::Keyword(Keyword::Setq)]); + let e = Expression::parse(&v).unwrap_err(); + assert_eq!( + &e[..], + &[ParseError { + input: v, + error: ParseErrorKind::Expected( + ExpectedWhat::AtLeastOneBinding, + ExpectedWhere::AfterKeyword(Keyword::Setq) + ) + }] + ); + let v = Value::list_or_nil([ + Value::Keyword(Keyword::Setq), + Value::Identifier("a".into()), + Value::Integer(1), + Value::Identifier("b".into()), + ]); + let e = Expression::parse(&v).unwrap_err(); + assert_eq!( + &e[..], + &[ParseError { + input: v, + error: ParseErrorKind::ExtraneousExpressions(Rc::new(ConsCell( + Value::Identifier("b".into()), + Value::Nil + ))) + }] + ); + let v = Value::list_or_nil([ + Value::Keyword(Keyword::Setq), + Value::Integer(1), + Value::Identifier("a".into()), + ]); + let e = Expression::parse(&v).unwrap_err(); + assert_eq!( + &e[..], + &[ParseError { + input: v, + error: ParseErrorKind::Expected( + ExpectedWhat::Identifier, + ExpectedWhere::InAssignment + ) + }] + ); + } +} diff --git a/src/compile/syntax/condition.rs b/src/compile/syntax/condition.rs index 4723fc1..834fb69 100644 --- a/src/compile/syntax/condition.rs +++ b/src/compile/syntax/condition.rs @@ -481,7 +481,7 @@ mod tests { e, vec![ParseError { input: v, - error: ParseErrorKind::ExtraneousExpressionList(Rc::new(ConsCell::end( + error: ParseErrorKind::ExtraneousExpressions(Rc::new(ConsCell::end( Value::Integer(3) ))) }] diff --git a/src/compile/syntax/error.rs b/src/compile/syntax/error.rs index 536d469..a31692f 100644 --- a/src/compile/syntax/error.rs +++ b/src/compile/syntax/error.rs @@ -19,7 +19,7 @@ pub enum ParseErrorKind { #[error("Extraneous expression: {0}")] ExtraneousExpression(Value), #[error("Extraneous expression(s): {0}")] - ExtraneousExpressionList(Rc), + ExtraneousExpressions(Rc), } #[derive(Debug, Clone, PartialEq, thiserror::Error)] @@ -42,6 +42,8 @@ pub enum ExpectedWhat { ProperList, #[error("an identifier")] Identifier, + #[error("at least one binding pair")] + AtLeastOneBinding, } #[derive(Debug, Clone, PartialEq, thiserror::Error)] @@ -62,6 +64,8 @@ pub enum ExpectedWhere { InConditionArm, #[error("in the function definition")] InFunctionDefinition, + #[error("in assignment")] + InAssignment, } pub(super) trait CollectErrors { @@ -84,8 +88,18 @@ impl ParseErrorKind { pub fn extraneous(value: &Value) -> Self { match value { Value::Nil => unreachable!(), - Value::Cons(cons) => Self::ExtraneousExpressionList(cons.clone()), + Value::Cons(cons) => Self::ExtraneousExpressions(cons.clone()), _ => Self::ExtraneousExpression(value.clone()), } } + + pub fn collect_extraneous>(iter: I) -> Self { + Self::extraneous(&Value::list_or_nil(iter)) + } + + pub fn try_collect_extraneous>>( + iter: I, + ) -> Result { + Ok(Self::extraneous(&Value::try_list_or_nil(iter)?)) + } } diff --git a/src/compile/syntax/mod.rs b/src/compile/syntax/mod.rs index 754282d..28ff932 100644 --- a/src/compile/syntax/mod.rs +++ b/src/compile/syntax/mod.rs @@ -2,12 +2,14 @@ use std::rc::Rc; use crate::vm::value::{ConsCell, Keyword, Value}; +mod binding; mod call; mod condition; mod error; mod function; mod lambda; +pub use binding::*; pub use call::*; pub use condition::*; pub use error::*; @@ -25,6 +27,8 @@ pub enum Expression { Call(CallExpression), If(IfExpression), Cond(CondExpression), + Setq(SetqExpression), + Let(LetExpression), SyntaxError(ParseError), } @@ -67,6 +71,12 @@ impl Expression { Value::Keyword(Keyword::Defun) => { Self::map_or(DefunExpression::parse(cdr, value), Expression::Defun) } + Value::Keyword(keyword @ (Keyword::Let | Keyword::LetStar)) => { + Self::map_or(LetExpression::parse(cdr, value, *keyword), Expression::Let) + } + Value::Keyword(Keyword::Setq) => { + Self::map_or(SetqExpression::parse(cdr, value), Expression::Setq) + } _ => Self::map_or(CallExpression::parse(cons, value), Expression::Call), } } @@ -90,6 +100,8 @@ impl CollectErrors for Expression { Self::Lambda(lambda) => lambda.collect_errors(errors), Self::Defun(defun) => defun.collect_errors(errors), Self::Call(call) => call.collect_errors(errors), + Self::Let(let_) => let_.collect_errors(errors), + Self::Setq(setq) => setq.collect_errors(errors), Self::Nil | Self::IntegerLiteral(_) | Self::Identifier(_) | Self::BooleanLiteral(_) => { false } diff --git a/src/compile/value.rs b/src/compile/value.rs index 078a65f..130b566 100644 --- a/src/compile/value.rs +++ b/src/compile/value.rs @@ -1,12 +1,13 @@ use std::rc::Rc; -use crate::vm::instruction::MathInstruction; +use crate::vm::instruction::{MathInstruction, U}; #[derive(Debug, PartialEq)] pub enum CompileValue { Integer(i64), Boolean(bool), LocalFunction(u32, usize), + Local(U<16>), Argument(usize), Global(Rc), Stack, diff --git a/src/error.rs b/src/error.rs index d55c0b1..e1e98ec 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,3 +1,5 @@ +#![coverage(off)] + use crate::{compile::CompileError, vm::machine::MachineError}; #[derive(Debug, thiserror::Error)] diff --git a/src/lib.rs b/src/lib.rs index b2a9124..75fd073 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,10 @@ -#![feature(coverage_attribute, debug_closure_helpers, unboxed_closures)] +#![feature( + coverage_attribute, + debug_closure_helpers, + unboxed_closures, + iter_next_chunk, + exact_size_is_empty +)] pub mod compile; pub mod convert; diff --git a/src/vm/instruction.rs b/src/vm/instruction.rs index 430f00c..a743505 100644 --- a/src/vm/instruction.rs +++ b/src/vm/instruction.rs @@ -80,6 +80,8 @@ pub enum Instruction { Drop, SetGlobal, GetGlobal, + SetLocal(U<16>), + GetLocal(U<16>), Call(U<6>), Return, Math(MathInstruction, U<6>), @@ -145,6 +147,8 @@ impl From for u32 { Instruction::Return => 0b0000_0000_0000_0100, Instruction::SetGlobal => 0b0000_0000_0000_0101, Instruction::GetGlobal => 0b0000_0000_0000_0110, + Instruction::GetLocal(value) => 0b0000_0010_0000_0000 | value.0, + Instruction::SetLocal(value) => 0b0000_0011_0000_0000 | value.0, Instruction::Call(count) => 0b0000_0000_0100_0000 | count.0, Instruction::PushInteger(value) => 0b0001_0000_0000_0000 | value.0, Instruction::PushConstant(index) => 0b0010_0000_0000_0000 | index.0, @@ -181,7 +185,8 @@ impl TryFrom for Instruction { "0000_0000_11??_????" => todo!(), "0000_0001_????_????" => todo!(), - "0000_001?_????_????" => todo!(), + "0000_0010_xxxx_xxxx" => Ok(Instruction::GetLocal(U(x))), + "0000_0011_xxxx_xxxx" => Ok(Instruction::SetLocal(U(x))), "0000_01xx_xxyy_yyyy" => Ok(Instruction::Math(x.try_into()?, U(y))), "0000_10xx_xxxx_xxxx" => Ok(Instruction::Branch(U(x))), "0000_11xx_xxxx_xxxx" => Ok(Instruction::Jump(U(x))), diff --git a/src/vm/machine.rs b/src/vm/machine.rs index a220539..15e6b79 100644 --- a/src/vm/machine.rs +++ b/src/vm/machine.rs @@ -54,6 +54,7 @@ pub struct CallFrame { arguments: Vec, return_address: InstructionPointer, event: ExecutionEvent, + locals: HashMap, } pub struct Machine { @@ -61,6 +62,8 @@ pub struct Machine { ip: Option, value_stack: Stack, call_stack: Stack, + // Top-level locals + locals: HashMap, } #[derive(Debug, Clone, PartialEq)] @@ -77,6 +80,7 @@ impl Default for Machine { ip: None, value_stack: Stack::new(1024), call_stack: Stack::new(32), + locals: HashMap::new(), } } } @@ -90,6 +94,22 @@ impl Machine { 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, + None => &mut self.locals, + }; + locals.insert(index, value); + } + + fn get_local(&self, index: u32) -> Option<&Value> { + let locals = match self.call_stack.current() { + Some(frame) => &frame.locals, + None => &self.locals, + }; + locals.get(&index) + } + pub fn defun_native(&mut self, identifier: S, function: F) where S: Into>, @@ -136,6 +156,7 @@ impl Machine { module: source_ip.module, address: source_ip.address + 1, }, + locals: HashMap::new(), }; if self.call_stack.push(frame).is_err() { return Err(MachineError::CallStackOverflow); @@ -322,6 +343,23 @@ impl Machine { Ok(()) } + fn execute_get_local(&mut self, index: u32) -> Result<(), MachineError> { + let value = self.get_local(index).cloned(); + if let Some(value) = value { + self.push(value.clone())?; + } else { + eprintln!(":: Warning: local #{index} referenced before assignment"); + self.push(Value::Nil)?; + } + Ok(()) + } + + fn execute_set_local(&mut self, index: u32) -> Result<(), MachineError> { + let value = self.pop()?; + self.set_local(index, value); + Ok(()) + } + pub fn execute_next(&mut self) -> Result { let ip = self.ip.clone().unwrap(); let instruction = ip @@ -356,6 +394,12 @@ impl Machine { Instruction::SetGlobal => { self.execute_set_global()?; } + Instruction::SetLocal(index) => { + self.execute_set_local(index.into())?; + } + Instruction::GetLocal(index) => { + self.execute_get_local(index.into())?; + } Instruction::Return => { advance = false; event = self.execute_return()?; @@ -418,6 +462,7 @@ impl Machine { arguments: vec![], return_address: ip, event: ExecutionEvent::ModuleExit(module.clone()), + locals: HashMap::new(), }) .is_err() { @@ -492,10 +537,9 @@ mod tests { use std::sync::atomic::{AtomicI64, Ordering}; use crate::vm::{ - instruction::{Instruction, U}, + instruction::{Instruction, MathInstruction, U}, machine::{InstructionPointer, Machine}, module::{Module, ModuleBuilder, ModuleConstant, ModuleRef}, - native::NativeFunction, value::Value, }; @@ -534,9 +578,9 @@ mod tests { builder.add_all([ Instruction::PushInteger(U::truncate(1)), Instruction::PushInteger(U::truncate(2)), - Instruction::Add(U::truncate(2)), + Instruction::Math(MathInstruction::Add, U::truncate(2)), Instruction::PushConstant(c0), - Instruction::Add(U::truncate(2)), + Instruction::Math(MathInstruction::Add, U::truncate(2)), ]); }, |_| {}, @@ -551,7 +595,7 @@ mod tests { let (m, vs) = execute_all( 1, |_, builder| { - let c0 = builder.constant(ModuleConstant::LocalFunction(4)); + let c0 = builder.constant(ModuleConstant::LocalFunction(4, 1)); builder.add_all([ // main Instruction::PushInteger(U::truncate(34)), @@ -561,7 +605,7 @@ mod tests { // c0 Instruction::PushArgument(U::truncate(0)), Instruction::PushInteger(U::truncate(1200)), - Instruction::Add(U::truncate(2)), + Instruction::Math(MathInstruction::Add, U::truncate(2)), Instruction::Return, ]); }, @@ -579,7 +623,7 @@ mod tests { 2, |id, builder| match id { 1 => { - let c0 = builder.constant(ModuleConstant::LocalFunction(4)); + let c0 = builder.constant(ModuleConstant::LocalFunction(4, 1)); let c1 = builder.constant(ModuleConstant::Identifier("extern-function".into())); builder.add_all([ // main: (local 1) @@ -600,7 +644,7 @@ mod tests { 0 => { let c0 = builder.constant(ModuleConstant::Integer(3)); let c1 = builder.constant(ModuleConstant::Identifier("native".into())); - let c2 = builder.constant(ModuleConstant::LocalFunction(4)); + let c2 = builder.constant(ModuleConstant::LocalFunction(4, 2)); let c3 = builder.constant(ModuleConstant::Identifier("extern-function".into())); builder.add_all([ // main diff --git a/src/vm/module.rs b/src/vm/module.rs index cf96976..2281018 100644 --- a/src/vm/module.rs +++ b/src/vm/module.rs @@ -198,7 +198,7 @@ impl fmt::Display for ModuleConstant { #[cfg(test)] mod tests { use crate::vm::{ - instruction::{Instruction, U}, + instruction::{Instruction, MathInstruction, U}, module::Module, value::Value, }; @@ -215,7 +215,7 @@ mod tests { let is = [ Instruction::PushInteger(U::truncate(2)), Instruction::PushInteger(U::truncate(1)), - Instruction::Add(U::truncate(2)), + Instruction::Math(MathInstruction::Add, U::truncate(2)), Instruction::Return, ] .into_iter() diff --git a/src/vm/prelude.rs b/src/vm/prelude.rs index ce46631..84e9e26 100644 --- a/src/vm/prelude.rs +++ b/src/vm/prelude.rs @@ -216,7 +216,7 @@ pub fn load(vm: &mut Machine) { return Err(MachineError::InvalidArgument); }; let f = AnyFunction::try_from_value(f)?; - let xs = xs.proper_iter(); + let xs = xs.proper_iter(MachineError::InvalidArgument); let out = Value::try_list_or_nil(xs.map(|v| f.invoke(vm, slice::from_ref(v?))))?; Ok(out) }); @@ -225,7 +225,9 @@ pub fn load(vm: &mut Machine) { return Err(MachineError::InvalidArgument); }; let f = AnyFunction::try_from_value(f)?; - let xs = xs.proper_iter().map(|x| x.cloned()); + let xs = xs + .proper_iter(MachineError::InvalidArgument) + .map(|x| x.cloned()); let out = Value::try_list_or_nil(xs.try_filter(|v| { let result = f.invoke(vm, slice::from_ref(v))?; bool::try_from_value(&result) @@ -252,7 +254,7 @@ pub fn load(vm: &mut Machine) { }; let f = AnyFunction::try_from_value(f)?; let args = xs - .proper_iter() + .proper_iter(MachineError::InvalidArgument) .map(|x| x.cloned()) .collect::, _>>()?; f.invoke(vm, &args[..]) diff --git a/src/vm/stack.rs b/src/vm/stack.rs index 80910b1..c900d1c 100644 --- a/src/vm/stack.rs +++ b/src/vm/stack.rs @@ -41,6 +41,14 @@ impl Stack { } } + pub fn current_mut(&mut self) -> Option<&mut T> { + if self.pointer >= self.data.len() { + None + } else { + Some(unsafe { self.data[self.pointer].assume_init_mut() }) + } + } + pub fn push(&mut self, value: T) -> Result<(), T> { if self.pointer > 0 { self.pointer -= 1; diff --git a/src/vm/value.rs b/src/vm/value.rs index 3dbd8a8..76430a1 100644 --- a/src/vm/value.rs +++ b/src/vm/value.rs @@ -1,6 +1,9 @@ use std::{any::Any, fmt, rc::Rc}; -use crate::vm::{machine::MachineError, module::ModuleRef, native::NativeFunction}; +use crate::{ + compile::{ExpectedWhat, ExpectedWhere, ParseError, ParseErrorKind}, + vm::{machine::MachineError, module::ModuleRef, native::NativeFunction}, +}; #[derive(Debug, Clone, PartialEq)] pub struct BytecodeFunction { @@ -14,8 +17,9 @@ pub struct OpaqueValue { inner: Rc, } -pub struct ProperListIter<'a> { +pub struct ProperListIter<'a, E> { head: Option<&'a Value>, + error: Option, } #[derive(Debug, Clone, PartialEq)] @@ -33,8 +37,8 @@ pub enum Value { OpaqueValue(OpaqueValue), } -impl<'a> Iterator for ProperListIter<'a> { - type Item = Result<&'a Value, MachineError>; +impl<'a, E> Iterator for ProperListIter<'a, E> { + type Item = Result<&'a Value, E>; fn next(&mut self) -> Option { let head = self.head.take()?; @@ -45,7 +49,7 @@ impl<'a> Iterator for ProperListIter<'a> { Some(Ok(car)) } Value::Nil => None, - _ => Some(Err(MachineError::InvalidArgument)), + _ => self.error.take().map(Err), } } } @@ -127,7 +131,10 @@ impl_keyword! { While => "while", Otherwise => "&otherwise", Optional => "&optional", - Rest => "&rest" + Rest => "&rest", + Setq => "setq", + Let => "let", + LetStar => "let*", } } @@ -135,8 +142,18 @@ impl_keyword! { pub struct ConsCell(pub Value, pub Value); impl Value { - pub fn proper_iter(&self) -> ProperListIter<'_> { - ProperListIter { head: Some(self) } + 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 { @@ -154,9 +171,9 @@ impl Value { Self::Cons(Rc::new(ConsCell(self, cdr))) } - pub fn try_list_or_nil>>( + pub fn try_list_or_nil>>( items: I, - ) -> Result { + ) -> Result { Self::try_list_or_nil_inner(&mut items.into_iter()) } @@ -164,9 +181,9 @@ impl Value { Self::list_or_nil_inner(&mut items.into_iter()) } - fn try_list_or_nil_inner>>( + fn try_list_or_nil_inner>>( items: &mut I, - ) -> Result { + ) -> Result { match items.next() { Some(value) => Ok(value?.cons(Self::try_list_or_nil_inner(items)?)), None => Ok(Self::Nil),