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:
+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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user