Add AST dumper and improve upvalue analysis
The `UpvalueAnalyzer` now correctly identifies which lambdas capture which variable declarations, rather than just marking declarations that need boxing. This information is stored in a `capture_map`. The `Binder` has been updated to use this new `capture_map` instead of a `HashSet` of boxed declarations. The `DefLocal` node in `bound_nodes.rs` now stores a `captured_by` field, which is a list of lambda identities that capture the local variable. A new `dumper` module has been added to provide a human-readable representation of the bound AST, including information about captured variables. The `VM` has been updated to use the `captured_by` field to determine if a local variable needs to be stored in a `Cell`, rather than relying on a boolean `is_boxed` flag. An example script `extreme_capture.myc` has been added to test deep nesting and variable capture. A new "Dump AST" button has been added to the UI, which uses the new `Dumper` to display the bound AST.
This commit is contained in:
+14
-14
@@ -1,4 +1,4 @@
|
||||
use std::collections::{HashMap, BTreeMap, HashSet};
|
||||
use std::collections::{HashMap, BTreeMap};
|
||||
use std::rc::Rc;
|
||||
use std::cell::RefCell;
|
||||
use crate::ast::nodes::{Node, UntypedKind};
|
||||
@@ -64,28 +64,28 @@ pub struct Binder {
|
||||
functions: Vec<FunctionCompiler>,
|
||||
// Globals mapping: Name -> (Index, Type)
|
||||
globals: Rc<RefCell<HashMap<String, (u32, StaticType)>>>,
|
||||
// Identities of definitions that need boxing (pre-calculated)
|
||||
boxed_declarations: HashSet<Identity>,
|
||||
// Map of Declaration Identity -> List of Lambda Identities that capture it
|
||||
capture_map: HashMap<Identity, Vec<Identity>>,
|
||||
}
|
||||
|
||||
impl Binder {
|
||||
pub fn new(globals: Rc<RefCell<HashMap<String, (u32, StaticType)>>>) -> Self {
|
||||
Self::with_boxed(globals, HashSet::new())
|
||||
Self::with_boxed(globals, HashMap::new())
|
||||
}
|
||||
|
||||
fn with_boxed(globals: Rc<RefCell<HashMap<String, (u32, StaticType)>>>, boxed: HashSet<Identity>) -> Self {
|
||||
fn with_boxed(globals: Rc<RefCell<HashMap<String, (u32, StaticType)>>>, captures: HashMap<Identity, Vec<Identity>>) -> Self {
|
||||
let mut binder = Self {
|
||||
functions: Vec::new(),
|
||||
globals,
|
||||
boxed_declarations: boxed,
|
||||
capture_map: captures,
|
||||
};
|
||||
binder.functions.push(FunctionCompiler::new());
|
||||
binder
|
||||
}
|
||||
|
||||
pub fn bind_root(globals: Rc<RefCell<HashMap<String, (u32, StaticType)>>>, node: &Node<UntypedKind>) -> Result<Node<BoundKind, StaticType>, String> {
|
||||
let boxed = crate::ast::compiler::upvalues::UpvalueAnalyzer::analyze(node);
|
||||
let mut binder = Self::with_boxed(globals, boxed);
|
||||
let captures = crate::ast::compiler::upvalues::UpvalueAnalyzer::analyze(node);
|
||||
let mut binder = Self::with_boxed(globals, captures);
|
||||
binder.bind(node)
|
||||
}
|
||||
|
||||
@@ -170,11 +170,11 @@ impl Binder {
|
||||
info.ty = ty.clone();
|
||||
}
|
||||
}
|
||||
let is_boxed = self.boxed_declarations.contains(&node.identity);
|
||||
let captured_by = self.capture_map.get(&node.identity).cloned().unwrap_or_default();
|
||||
Ok(self.make_node(node.identity.clone(), BoundKind::DefLocal {
|
||||
slot: slot_or_idx,
|
||||
value: Box::new(val_node) ,
|
||||
is_boxed
|
||||
captured_by
|
||||
}, ty))
|
||||
}
|
||||
},
|
||||
@@ -361,8 +361,8 @@ mod tests {
|
||||
if let BoundKind::Lambda { body, .. } = &bound.kind {
|
||||
if let BoundKind::Block { exprs } = &body.kind {
|
||||
let x_decl = &exprs[0];
|
||||
if let BoundKind::DefLocal { is_boxed, .. } = &x_decl.kind {
|
||||
assert!(is_boxed, "Variable 'x' should be marked as boxed because it is captured by lambda 'f'");
|
||||
if let BoundKind::DefLocal { captured_by, .. } = &x_decl.kind {
|
||||
assert!(!captured_by.is_empty(), "Variable 'x' should have capturers because it is used in lambda 'f'");
|
||||
} else {
|
||||
panic!("First expression in block should be DefLocal, got {:?}", x_decl.kind);
|
||||
}
|
||||
@@ -386,8 +386,8 @@ mod tests {
|
||||
if let BoundKind::Lambda { body, .. } = &bound.kind {
|
||||
if let BoundKind::Block { exprs } = &body.kind {
|
||||
let x_decl = &exprs[0];
|
||||
if let BoundKind::DefLocal { is_boxed, .. } = &x_decl.kind {
|
||||
assert!(!is_boxed, "Variable 'x' should NOT be boxed as it is not captured");
|
||||
if let BoundKind::DefLocal { captured_by, .. } = &x_decl.kind {
|
||||
assert!(captured_by.is_empty(), "Variable 'x' should NOT have any capturers");
|
||||
} else {
|
||||
panic!("First expression should be DefLocal");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user