Introduce Value::Cell for mutable upvalues, allowing closures to
modify captured variables. Implement `capture_upvalue` and `get_value`/`set_value` methods in the `VM` to handle these mutable upvalues. Refactor number parsing to support integers and floats Separate the `TokenKind::Number` into `TokenKind::Integer` and `TokenKind::Float`. Update the lexer to distinguish between integer and float literals. Update the parser to correctly map these new token kinds to their respective `Value` types. Add integration tests for parsing integers and floats, and for closure modification of captured variables.
This commit is contained in:
+16
-3
@@ -11,7 +11,8 @@ pub enum TokenKind {
|
||||
Quote, Backtick, Tilde, At,
|
||||
Identifier(Arc<str>),
|
||||
Keyword(Arc<str>),
|
||||
Number(f64),
|
||||
Integer(i64),
|
||||
Float(f64),
|
||||
String(Arc<str>),
|
||||
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);
|
||||
|
||||
+2
-1
@@ -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) => {
|
||||
|
||||
@@ -68,6 +68,7 @@ pub enum Value {
|
||||
Record(Arc<HashMap<Keyword, Value>>),
|
||||
Function(Arc<dyn Fn(Vec<Value>) -> Value + Send + Sync>),
|
||||
Object(Arc<dyn Object>), // For compiled Closures and other opaque types
|
||||
Cell(Arc<Mutex<Value>>), // 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, "<record[{}]>", r.len()),
|
||||
Value::Function(_) => write!(f, "<native fn>"),
|
||||
Value::Object(o) => write!(f, "<{}>", o.type_name()),
|
||||
Value::Cell(c) => write!(f, "{}", c.lock().unwrap()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+174
-12
@@ -7,7 +7,7 @@ use crate::ast::types::{Value, Object, StaticType};
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Closure {
|
||||
pub function_node: Arc<Node<BoundKind, StaticType>>,
|
||||
pub upvalues: Vec<Value>,
|
||||
pub upvalues: Vec<Arc<Mutex<Value>>>,
|
||||
}
|
||||
|
||||
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<Arc<Mutex<Value>>, 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<Value, 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() {
|
||||
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<NodeIdentity> {
|
||||
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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user