Introduce boxing for captured variables

The binder now uses an `UpvalueAnalyzer` to identify local variables
that are captured by nested functions. These identified variables are
marked with `is_boxed: true` during `DefLocal` node creation. The VM
then uses this flag to wrap such variables in a `Value::Cell` (using
`Rc<RefCell<_>>`) to ensure they can be mutated across function calls.
feat: Introduce boxing for captured variables

Add UpvalueAnalyzer to identify variables captured by nested lambdas.
Modify Binder to use the analyzer and mark captured local variables for
boxing.
Update BoundKind::DefLocal to include an `is_boxed` flag.
Update VM to box captured variables when they are defined.
Add tests for upvalue capture detection.
This commit is contained in:
Michael Schimmel
2026-02-18 00:09:46 +01:00
parent 83f6cfb8e1
commit 4d9adccab5
8 changed files with 208 additions and 9 deletions
+22
View File
@@ -82,6 +82,28 @@ impl VM {
Ok(val)
},
BoundKind::DefLocal { slot, value, is_boxed } => {
let val = self.eval(value)?;
let final_val = if *is_boxed {
Value::Cell(Rc::new(RefCell::new(val)))
} else {
val
};
let frame = self.frames.last().ok_or("No call frame")?;
let abs_index = frame.stack_base + (*slot as usize);
// If it's the exact top of stack, push it. Otherwise, set it.
if abs_index == self.stack.len() {
self.stack.push(final_val.clone());
} else if abs_index < self.stack.len() {
self.stack[abs_index] = final_val.clone();
} else {
return Err(format!("Stack gap at local {}", slot));
}
Ok(final_val)
},
BoundKind::If { cond, then_br, else_br } => {
let c = self.eval(cond)?;
if c.is_truthy() {