diff --git a/src/ast/lexer.rs b/src/ast/lexer.rs index acd8cd6..35057eb 100644 --- a/src/ast/lexer.rs +++ b/src/ast/lexer.rs @@ -11,7 +11,8 @@ pub enum TokenKind { Quote, Backtick, Tilde, At, Identifier(Arc), Keyword(Arc), - Number(f64), + Integer(i64), + Float(f64), String(Arc), EOF, } @@ -69,8 +70,20 @@ impl<'a> Lexer<'a> { '"' => TokenKind::String(Arc::from(self.read_string()?)), c if c.is_ascii_digit() || (c == '-' && self.input.peek().is_some_and(|p| p.is_ascii_digit())) => { let mut num_str = String::from(c); - num_str.push_str(&self.read_while(|c| c.is_ascii_digit() || c == '.')); - TokenKind::Number(num_str.parse().map_err(|_| "Invalid number format")?) + while let Some(&next) = self.peek() { + if next.is_ascii_digit() || next == '.' { + num_str.push(self.input.next().unwrap()); + self.col += 1; + } else { + break; + } + } + + if num_str.contains('.') { + TokenKind::Float(num_str.parse().map_err(|_| "Invalid float format")?) + } else { + TokenKind::Integer(num_str.parse().map_err(|_| "Invalid integer format")?) + } } c if is_ident_start(c) => { let mut id = String::from(c); diff --git a/src/ast/parser.rs b/src/ast/parser.rs index 1ee7731..e7cbd9d 100644 --- a/src/ast/parser.rs +++ b/src/ast/parser.rs @@ -51,7 +51,8 @@ impl<'a> Parser<'a> { let identity = Arc::new(NodeIdentity { location: token.location }); let kind = match token.kind { - TokenKind::Number(n) => UntypedKind::Constant(Value::Float(n)), + TokenKind::Integer(n) => UntypedKind::Constant(Value::Int(n)), + TokenKind::Float(n) => UntypedKind::Constant(Value::Float(n)), TokenKind::String(s) => UntypedKind::Constant(Value::Text(s)), TokenKind::Keyword(k) => UntypedKind::Constant(Value::Keyword(Keyword::intern(&k))), TokenKind::Identifier(id) => { diff --git a/src/ast/types.rs b/src/ast/types.rs index 51d8c5b..01236d5 100644 --- a/src/ast/types.rs +++ b/src/ast/types.rs @@ -68,6 +68,7 @@ pub enum Value { Record(Arc>), Function(Arc) -> Value + Send + Sync>), Object(Arc), // For compiled Closures and other opaque types + Cell(Arc>), // Boxed value for captures } #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -93,6 +94,7 @@ impl Value { match self { Value::Void => false, Value::Bool(b) => *b, + Value::Cell(c) => c.lock().unwrap().is_truthy(), _ => true, } } @@ -109,6 +111,7 @@ impl Value { Value::Record(_) => StaticType::Record(Arc::new(BTreeMap::new())), // Empty for now Value::Function { .. } => StaticType::Any, // Dynamic function Value::Object(o) => StaticType::Object(o.type_name()), + Value::Cell(c) => c.lock().unwrap().static_type(), } } } @@ -126,6 +129,7 @@ impl fmt::Display for Value { Value::Record(r) => write!(f, "", r.len()), Value::Function(_) => write!(f, ""), Value::Object(o) => write!(f, "<{}>", o.type_name()), + Value::Cell(c) => write!(f, "{}", c.lock().unwrap()), } } } diff --git a/src/ast/vm.rs b/src/ast/vm.rs index 4912061..96f32c7 100644 --- a/src/ast/vm.rs +++ b/src/ast/vm.rs @@ -7,7 +7,7 @@ use crate::ast::types::{Value, Object, StaticType}; #[derive(Debug, Clone)] pub struct Closure { pub function_node: Arc>, - pub upvalues: Vec, + pub upvalues: Vec>>, } impl Object for Closure { @@ -102,7 +102,7 @@ impl VM { BoundKind::Lambda { param_count: _, upvalues, body } => { let mut captured = Vec::with_capacity(upvalues.len()); for addr in upvalues { - captured.push(self.get_value(*addr)?); + captured.push(self.capture_upvalue(*addr)?); } let closure = Closure { @@ -151,13 +151,53 @@ impl VM { } } + fn capture_upvalue(&mut self, addr: Address) -> Result>, String> { + match addr { + Address::Local(idx) => { + let frame = self.frames.last().ok_or("No call frame")?; + let abs_index = frame.stack_base + (idx as usize); + if abs_index < self.stack.len() { + // Check if already boxed + if let Value::Cell(cell) = &self.stack[abs_index] { + Ok(cell.clone()) + } else { + // Box it + let val = self.stack[abs_index].clone(); + let cell = Arc::new(Mutex::new(val)); + self.stack[abs_index] = Value::Cell(cell.clone()); + Ok(cell) + } + } else { + Err(format!("Stack underflow capture local {}", idx)) + } + }, + Address::Upvalue(idx) => { + let frame = self.frames.last().ok_or("No call frame")?; + if let Some(closure) = &frame.closure { + let idx = idx as usize; + if idx < closure.upvalues.len() { + Ok(closure.upvalues[idx].clone()) + } else { + Err(format!("Upvalue access out of bounds capture {}", idx)) + } + } else { + Err("Current frame has no closure".to_string()) + } + }, + Address::Global(_) => Err("Cannot capture global directly".to_string()), + } + } + fn get_value(&self, addr: Address) -> Result { match addr { Address::Local(idx) => { let frame = self.frames.last().ok_or("No call frame")?; let abs_index = frame.stack_base + (idx as usize); if abs_index < self.stack.len() { - Ok(self.stack[abs_index].clone()) + match &self.stack[abs_index] { + Value::Cell(cell) => Ok(cell.lock().unwrap().clone()), + val => Ok(val.clone()), + } } else { Err(format!("Stack underflow access local {}", idx)) } @@ -176,7 +216,7 @@ impl VM { if let Some(closure) = &frame.closure { let idx = idx as usize; if idx < closure.upvalues.len() { - Ok(closure.upvalues[idx].clone()) + Ok(closure.upvalues[idx].lock().unwrap().clone()) } else { Err(format!("Upvalue access out of bounds {}", idx)) } @@ -192,14 +232,16 @@ impl VM { Address::Local(idx) => { let frame = self.frames.last().ok_or("No call frame")?; let abs_index = frame.stack_base + (idx as usize); - if abs_index >= self.stack.len() { - if abs_index == self.stack.len() { - self.stack.push(value); - } else { - return Err(format!("Stack gap write local {}", idx)); - } + if abs_index < self.stack.len() { + if let Value::Cell(cell) = &self.stack[abs_index] { + *cell.lock().unwrap() = value; + } else { + self.stack[abs_index] = value; + } + } else if abs_index == self.stack.len() { + self.stack.push(value); } else { - self.stack[abs_index] = value; + return Err(format!("Stack gap write local {}", idx)); } Ok(()) }, @@ -212,7 +254,127 @@ impl VM { globals[idx] = value; Ok(()) }, - Address::Upvalue(_) => Err("Cannot assign to upvalue (Closure Upvalues are immutable)".to_string()), + Address::Upvalue(idx) => { + let frame = self.frames.last().ok_or("No call frame")?; + if let Some(closure) = &frame.closure { + let idx = idx as usize; + if idx < closure.upvalues.len() { + *closure.upvalues[idx].lock().unwrap() = value; + Ok(()) + } else { + Err(format!("Upvalue assignment out of bounds {}", idx)) + } + } else { + Err("Current frame has no closure".to_string()) + } + }, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::ast::types::{SourceLocation, NodeIdentity}; + + fn make_dummy_identity() -> Arc { + Arc::new(NodeIdentity { + location: SourceLocation { line: 0, col: 0 }, + }) + } + + #[test] + fn test_capture_boxing_modification() { + // Goal: + // var x = 10; (Local 0) + // var f = lambda { x = 20; }; (Local 1) + // f(); + // return x; -> Should be 20 + + let id = make_dummy_identity(); + + // 1. Define Lambda Body: x = 20 (Upvalue 0) + let lambda_body = Node { + identity: id.clone(), + ty: StaticType::Void, + kind: BoundKind::Set { + addr: Address::Upvalue(0), + value: Box::new(Node { + identity: id.clone(), + ty: StaticType::Int, + kind: BoundKind::Constant(Value::Int(20)), + }), + }, + }; + + // 2. Define Main Script + let root = Node { + identity: id.clone(), + ty: StaticType::Int, + kind: BoundKind::Block { + exprs: vec![ + // x = 10 (Local 0) - simulate definition by assignment to stack gap + Node { + identity: id.clone(), + ty: StaticType::Int, + kind: BoundKind::Set { + addr: Address::Local(0), + value: Box::new(Node { + identity: id.clone(), + ty: StaticType::Int, + kind: BoundKind::Constant(Value::Int(10)), + }), + }, + }, + // f = lambda capturing x (Local 1) + Node { + identity: id.clone(), + ty: StaticType::Any, + kind: BoundKind::Set { + addr: Address::Local(1), + value: Box::new(Node { + identity: id.clone(), + ty: StaticType::Any, + kind: BoundKind::Lambda { + param_count: 0, + upvalues: vec![Address::Local(0)], // Capture x + body: Arc::new(lambda_body), + }, + }), + }, + }, + // f() + Node { + identity: id.clone(), + ty: StaticType::Void, + kind: BoundKind::Call { + callee: Box::new(Node { + identity: id.clone(), + ty: StaticType::Any, + kind: BoundKind::Get(Address::Local(1)), + }), + args: vec![], + }, + }, + // return x (Local 0) + Node { + identity: id.clone(), + ty: StaticType::Int, + kind: BoundKind::Get(Address::Local(0)), + }, + ], + }, + }; + + let globals = Arc::new(Mutex::new(Vec::new())); + let mut vm = VM::new(globals); + + let result = vm.run(&root); + + match result { + Ok(Value::Int(val)) => assert_eq!(val, 20, "Captured variable should be modified to 20"), + Ok(val) => panic!("Expected Int(20), got {:?}", val), + Err(e) => panic!("VM Error: {}", e), } } } diff --git a/src/integration_test.rs b/src/integration_test.rs new file mode 100644 index 0000000..f83aa94 --- /dev/null +++ b/src/integration_test.rs @@ -0,0 +1,91 @@ +#[cfg(test)] +mod tests { + use std::sync::{Arc, Mutex}; + use crate::ast::parser::Parser; + use crate::ast::compiler::binder::Binder; + use crate::ast::vm::VM; + use crate::ast::types::Value; + + use crate::ast::nodes::UntypedKind; + + #[test] + fn test_parse_integer_constant() { + let source = "123"; + let mut parser = Parser::new(source).expect("Failed to create parser"); + let ast = parser.parse_expression().expect("Failed to parse"); + + if let UntypedKind::Constant(Value::Int(val)) = ast.kind { + assert_eq!(val, 123); + } else { + panic!("Expected Integer constant, got {:?}", ast.kind); + } + } + + #[test] + fn test_parse_negative_integer() { + let source = "-42"; + let mut parser = Parser::new(source).expect("Failed to create parser"); + let ast = parser.parse_expression().expect("Failed to parse"); + + if let UntypedKind::Constant(Value::Int(val)) = ast.kind { + assert_eq!(val, -42); + } else { + panic!("Expected Integer constant, got {:?}", ast.kind); + } + } + + #[test] + fn test_parse_float_constant() { + let source = "123.45"; + let mut parser = Parser::new(source).expect("Failed to create parser"); + let ast = parser.parse_expression().expect("Failed to parse"); + + if let UntypedKind::Constant(Value::Float(val)) = ast.kind { + assert_eq!(val, 123.45); + } else { + panic!("Expected Float constant, got {:?}", ast.kind); + } + } + + #[test] + fn test_parse_negative_float() { + let source = "-10.5"; + let mut parser = Parser::new(source).expect("Failed to create parser"); + let ast = parser.parse_expression().expect("Failed to parse"); + + if let UntypedKind::Constant(Value::Float(val)) = ast.kind { + assert_eq!(val, -10.5); + } else { + panic!("Expected Float constant, got {:?}", ast.kind); + } + } + + #[test] + fn test_closure_modification_from_source() { + let source = r#" + (do + (def x 10) + (def f (fn [] (assign x 20))) + (f) + x + ) + "#; + + let mut parser = Parser::new(source).expect("Failed to create parser"); + let ast = parser.parse_expression().expect("Failed to parse"); + + let globals = Arc::new(Mutex::new(std::collections::HashMap::new())); + let mut binder = Binder::new(globals.clone()); + let bound_ast = binder.bind(&ast).expect("Failed to bind"); + + let vm_globals = Arc::new(Mutex::new(Vec::new())); + let mut vm = VM::new(vm_globals); + let result = vm.run(&bound_ast); + + match result { + Ok(Value::Int(val)) => assert_eq!(val, 20, "Closure should modify outer variable"), + Ok(val) => panic!("Expected Int(20), got {:?}", val), + Err(e) => panic!("VM Error: {}", e), + } + } +} diff --git a/src/main.rs b/src/main.rs index b8ee6e0..905ba30 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,6 +2,8 @@ use eframe::egui; use crate::ast::environment::Environment; pub mod ast; +#[cfg(test)] +mod integration_test; fn main() -> eframe::Result { let options = eframe::NativeOptions {