diff --git a/src/ast/compiler/binder.rs b/src/ast/compiler/binder.rs index 305bf1e..7579f38 100644 --- a/src/ast/compiler/binder.rs +++ b/src/ast/compiler/binder.rs @@ -8,6 +8,7 @@ use std::rc::Rc; #[derive(Debug, Clone)] struct LocalInfo { slot: u32, + identity: Identity, // Note: Binder doesn't strictly need the type anymore, // but it might be useful for built-ins during resolution. // For now we keep it as Any or Unknown. @@ -28,7 +29,7 @@ impl CompilerScope { } } - fn define(&mut self, sym: &Symbol) -> Result { + fn define(&mut self, sym: &Symbol, identity: Identity) -> Result { if self.locals.contains_key(sym) { return Err(format!( "Variable '{}' is already defined in this scope level.", @@ -40,6 +41,7 @@ impl CompilerScope { sym.clone(), LocalInfo { slot, + identity, _ty: StaticType::Any, }, ); @@ -59,14 +61,16 @@ enum ScopeKind { } struct FunctionCompiler { + identity: Identity, scope: CompilerScope, upvalues: Vec
, kind: ScopeKind, } impl FunctionCompiler { - fn new(kind: ScopeKind) -> Self { + fn new(kind: ScopeKind, identity: Identity) -> Self { Self { + identity, scope: CompilerScope::new(), upvalues: Vec::new(), kind, @@ -76,7 +80,8 @@ impl FunctionCompiler { fn define_variable( &mut self, name: &Symbol, - globals: &Rc>>, + identity: Identity, + globals: &Rc>>, ) -> Result { match self.kind { ScopeKind::Root => { @@ -88,11 +93,11 @@ impl FunctionCompiler { )); } let idx = globals_map.len() as u32; - globals_map.insert(name.clone(), idx); + globals_map.insert(name.clone(), (idx, identity)); Ok(Address::Global(idx)) } ScopeKind::Local => { - let slot = self.scope.define(name)?; + let slot = self.scope.define(name, identity)?; Ok(Address::Local(slot)) } } @@ -110,48 +115,53 @@ impl FunctionCompiler { pub struct Binder { functions: Vec, - // Globals mapping: Symbol -> Index - globals: Rc>>, + // Globals mapping: Symbol -> (Index, DefinitionIdentity) + globals: Rc>>, // Map of Declaration Identity -> List of Lambda Identities that capture it - capture_map: HashMap>, + capture_map: HashMap>, } impl Binder { - pub fn new(globals: Rc>>) -> Self { - Self::with_boxed(globals, HashMap::new()) - } - - fn with_boxed( - globals: Rc>>, - captures: HashMap>, - ) -> Self { + pub fn new(globals: Rc>>) -> Self { let mut binder = Self { functions: Vec::new(), globals, - capture_map: captures, + capture_map: HashMap::new(), }; - binder - .functions - .push(FunctionCompiler::new(ScopeKind::Root)); + binder.functions.push(FunctionCompiler::new( + ScopeKind::Root, + Rc::new(crate::ast::types::NodeIdentity { + location: crate::ast::types::SourceLocation { line: 0, col: 0 }, + }), + )); binder } pub fn bind_root( - globals: Rc>>, + globals: Rc>>, node: &Node, - ) -> Result { - let captures = crate::ast::compiler::upvalues::UpvalueAnalyzer::analyze(node); - let mut binder = Self::with_boxed(globals, captures); - binder.bind(node) + ) -> Result<(BoundNode, HashMap>), String> { + let mut binder = Self::new(globals); + let bound = binder.bind(node)?; + + // Convert HashSet to sorted Vec + let final_captures = binder + .capture_map + .into_iter() + .map(|(k, v)| (k, v.into_iter().collect())) + .collect(); + + Ok((bound, final_captures)) } fn declare_variable( &mut self, name: &Symbol, + identity: Identity, _kind: crate::ast::compiler::bound_nodes::DeclarationKind, ) -> Result { let current_fn = self.functions.last_mut().unwrap(); - current_fn.define_variable(name, &self.globals) + current_fn.define_variable(name, identity, &self.globals) } pub fn bind(&mut self, node: &Node) -> Result { @@ -203,14 +213,10 @@ impl Binder { if let UntypedKind::Parameter(ref name) = target.kind { let addr = self.declare_variable( name, + node.identity.clone(), // Identity of the Def node crate::ast::compiler::bound_nodes::DeclarationKind::Variable, )?; let val_node = self.bind(value)?; - let captured_by = self - .capture_map - .get(&target.identity) - .cloned() - .unwrap_or_default(); Ok(self.make_node( node.identity.clone(), @@ -219,7 +225,7 @@ impl Binder { addr, kind: crate::ast::compiler::bound_nodes::DeclarationKind::Variable, value: Box::new(val_node), - captured_by, + captured_by: Vec::new(), // Will be filled by post-pass }, )) } else { @@ -265,7 +271,10 @@ impl Binder { UntypedKind::Lambda { params, body } => { let identity = node.identity.clone(); - self.functions.push(FunctionCompiler::new(ScopeKind::Local)); + self.functions.push(FunctionCompiler::new( + ScopeKind::Local, + identity.clone(), + )); // 1. Bind the parameter pattern/tuple let params_bound = self.bind_pattern(params, DeclarationKind::Parameter)?; @@ -409,7 +418,13 @@ impl Binder { if let Some(info) = self.functions[i].scope.resolve(sym) { let mut addr = Address::Local(info.slot); + // Record the capture for each lambda level in between for k in (i + 1)..=current_fn_idx { + let lambda_id = self.functions[k].identity.clone(); + self.capture_map + .entry(info.identity.clone()) + .or_default() + .insert(lambda_id); addr = Address::Upvalue(self.functions[k].add_upvalue(addr)); } return Ok(addr); @@ -418,7 +433,7 @@ impl Binder { // 3. Try Global let globals = self.globals.borrow(); - if let Some(idx) = globals.get(sym) { + if let Some((idx, _)) = globals.get(sym) { return Ok(Address::Global(*idx)); } @@ -428,7 +443,7 @@ impl Binder { name: sym.name.clone(), context: None, }; - if let Some(idx) = globals.get(&fallback_sym) { + if let Some((idx, _)) = globals.get(&fallback_sym) { return Ok(Address::Global(*idx)); } } @@ -443,12 +458,7 @@ impl Binder { ) -> Result { match &node.kind { UntypedKind::Parameter(sym) => { - let addr = self.declare_variable(sym, kind)?; - let captured_by = self - .capture_map - .get(&node.identity) - .cloned() - .unwrap_or_default(); + let addr = self.declare_variable(sym, node.identity.clone(), kind)?; Ok(self.make_node( node.identity.clone(), @@ -457,7 +467,7 @@ impl Binder { addr, kind, value: Box::new(self.make_node(node.identity.clone(), BoundKind::Nop)), - captured_by, + captured_by: Vec::new(), // Filled by post-pass }, )) } @@ -530,19 +540,16 @@ mod tests { let untyped = parser.parse_expression().unwrap(); let globals = Rc::new(RefCell::new(HashMap::new())); - let bound = Binder::bind_root(globals, &untyped).unwrap(); + let (bound, captures) = Binder::bind_root(globals, &untyped).unwrap(); // Structure: Lambda -> Block -> [ Define(x), Define(f), Get(x) ] if let BoundKind::Lambda { body, .. } = &bound.kind { if let BoundKind::Block { exprs } = &body.kind { let x_decl = &exprs[0]; - if let BoundKind::Define { - captured_by, addr, .. - } = &x_decl.kind - { + if let BoundKind::Define { addr, .. } = &x_decl.kind { assert!(matches!(addr, Address::Local(_))); assert!( - !captured_by.is_empty(), + captures.contains_key(&x_decl.identity), "Variable 'x' should have capturers because it is used in lambda 'f'" ); } else { @@ -566,18 +573,15 @@ mod tests { let untyped = parser.parse_expression().unwrap(); let globals = Rc::new(RefCell::new(HashMap::new())); - let bound = Binder::bind_root(globals, &untyped).unwrap(); + let (bound, captures) = Binder::bind_root(globals, &untyped).unwrap(); if let BoundKind::Lambda { body, .. } = &bound.kind { if let BoundKind::Block { exprs } = &body.kind { let x_decl = &exprs[0]; - if let BoundKind::Define { - captured_by, addr, .. - } = &x_decl.kind - { + if let BoundKind::Define { addr, .. } = &x_decl.kind { assert!(matches!(addr, Address::Local(_))); assert!( - captured_by.is_empty(), + !captures.contains_key(&x_decl.identity), "Variable 'x' should NOT have any capturers" ); } else { diff --git a/src/ast/compiler/macros.rs b/src/ast/compiler/macros.rs index 9c2e14b..684c8c3 100644 --- a/src/ast/compiler/macros.rs +++ b/src/ast/compiler/macros.rs @@ -713,7 +713,15 @@ mod tests { let expanded = expander.expand(untyped).unwrap(); let mut global_names = HashMap::new(); - global_names.insert(Symbol::from("*"), 0); + global_names.insert( + Symbol::from("*"), + ( + 0, + Rc::new(crate::ast::types::NodeIdentity { + location: crate::ast::types::SourceLocation { line: 0, col: 0 }, + }), + ), + ); let globals = Rc::new(RefCell::new(global_names)); let result = Binder::bind_root(globals, &expanded); @@ -740,8 +748,8 @@ mod tests { let expanded = expander.expand(untyped).unwrap(); let globals = Rc::new(RefCell::new(HashMap::new())); - let bound = Binder::bind_root(globals, &expanded); - assert!(bound.is_ok(), "Hygiene failed: {:?}", bound.err()); + let result = Binder::bind_root(globals, &expanded); + assert!(result.is_ok(), "Hygiene failed: {:?}", result.err()); } #[test] @@ -760,11 +768,11 @@ mod tests { let expanded = expander.expand(untyped).unwrap(); let globals = Rc::new(RefCell::new(HashMap::new())); - let bound = Binder::bind_root(globals, &expanded); + let result = Binder::bind_root(globals, &expanded); assert!( - bound.is_ok(), + result.is_ok(), "Explicit Hygiene with Backticks failed: {:?}", - bound.err() + result.err() ); } } diff --git a/src/ast/compiler/mod.rs b/src/ast/compiler/mod.rs index 7d19b2a..2715725 100644 --- a/src/ast/compiler/mod.rs +++ b/src/ast/compiler/mod.rs @@ -1,6 +1,7 @@ pub mod analyzer; pub mod binder; pub mod bound_nodes; +pub mod captures; pub mod dumper; pub mod lambda_collector; pub mod macros; @@ -8,14 +9,13 @@ pub mod optimizer; pub mod specializer; pub mod tco; pub mod type_checker; -pub mod upvalues; pub use binder::*; pub use bound_nodes::*; +pub use captures::*; pub use dumper::*; pub use macros::*; pub use optimizer::*; pub use specializer::*; pub use tco::*; pub use type_checker::*; -pub use upvalues::*; diff --git a/src/ast/compiler/upvalues.rs b/src/ast/compiler/upvalues.rs deleted file mode 100644 index 8ee698f..0000000 --- a/src/ast/compiler/upvalues.rs +++ /dev/null @@ -1,135 +0,0 @@ -use crate::ast::nodes::{Node, Symbol, UntypedKind}; -use crate::ast::types::Identity; -use std::collections::{HashMap, HashSet}; - -/// Analyzes the AST to find which lambdas capture which variable declarations. -/// Returns a map: Declaration Identity -> List of Lambda Identities that capture it. -pub struct UpvalueAnalyzer; - -impl UpvalueAnalyzer { - pub fn analyze(root: &Node) -> HashMap> { - let mut capture_map: HashMap> = HashMap::new(); - let mut scopes = vec![HashMap::new()]; // Root scope - - Self::visit(root, &mut scopes, &mut capture_map, None); - - // Convert HashSet back to sorted Vec for the final result - capture_map - .into_iter() - .map(|(decl, lambdas)| (decl, lambdas.into_iter().collect())) - .collect() - } - - fn visit_params(node: &Node, scope: &mut HashMap) { - match &node.kind { - UntypedKind::Parameter(sym) => { - scope.insert(sym.clone(), node.identity.clone()); - } - UntypedKind::Tuple { elements } => { - for el in elements { - Self::visit_params(el, scope); - } - } - _ => {} // Ignore other nodes in parameter patterns for now - } - } - - fn visit( - node: &Node, - scopes: &mut Vec>, - capture_map: &mut HashMap>, - current_lambda: Option, - ) { - match &node.kind { - UntypedKind::Identifier(sym) => { - // Resolve name in scope stack (from inner to outer) - for (depth, scope) in scopes.iter().rev().enumerate() { - if let Some(decl_id) = scope.get(sym) { - if depth > 0 { - // Captured from an outer scope! - if let Some(lambda_id) = ¤t_lambda { - capture_map - .entry(decl_id.clone()) - .or_default() - .insert(lambda_id.clone()); - } - } - break; - } - } - } - UntypedKind::Def { target, value } => { - Self::visit(value, scopes, capture_map, current_lambda.clone()); - if let Some(current) = scopes.last_mut() { - Self::visit_params(target, current); - } - } - UntypedKind::Lambda { params, body } => { - let mut new_scope = HashMap::new(); - Self::visit_params(params, &mut new_scope); - - scopes.push(new_scope); - // The current node is the lambda causing captures in its body - Self::visit(body, scopes, capture_map, Some(node.identity.clone())); - scopes.pop(); - } - UntypedKind::MacroDecl { params, body, .. } => { - let mut new_scope = HashMap::new(); - Self::visit_params(params, &mut new_scope); - scopes.push(new_scope); - Self::visit(body, scopes, capture_map, current_lambda); - scopes.pop(); - } - UntypedKind::If { - cond, - then_br, - else_br, - } => { - Self::visit(cond, scopes, capture_map, current_lambda.clone()); - Self::visit(then_br, scopes, capture_map, current_lambda.clone()); - if let Some(e) = else_br { - Self::visit(e, scopes, capture_map, current_lambda.clone()); - } - } - UntypedKind::Assign { target, value } => { - Self::visit(target, scopes, capture_map, current_lambda.clone()); - Self::visit(value, scopes, capture_map, current_lambda.clone()); - } - UntypedKind::Call { callee, args } => { - Self::visit(callee, scopes, capture_map, current_lambda.clone()); - Self::visit(args, scopes, capture_map, current_lambda.clone()); - } - UntypedKind::Again { args } => { - Self::visit(args, scopes, capture_map, current_lambda.clone()); - } - UntypedKind::Block { exprs } => { - for expr in exprs { - Self::visit(expr, scopes, capture_map, current_lambda.clone()); - } - } - UntypedKind::Tuple { elements } => { - for el in elements { - Self::visit(el, scopes, capture_map, current_lambda.clone()); - } - } - UntypedKind::Record { fields } => { - for (k, v) in fields { - Self::visit(k, scopes, capture_map, current_lambda.clone()); - Self::visit(v, scopes, capture_map, current_lambda.clone()); - } - } - UntypedKind::Expansion { call: _, expanded } => { - Self::visit(expanded, scopes, capture_map, current_lambda); - } - UntypedKind::Template(body) - | UntypedKind::Placeholder(body) - | UntypedKind::Splice(body) => { - Self::visit(body, scopes, capture_map, current_lambda); - } - UntypedKind::Nop - | UntypedKind::Constant(_) - | UntypedKind::Extension(_) - | UntypedKind::Parameter(_) => {} - } - } -} diff --git a/src/ast/environment.rs b/src/ast/environment.rs index 46c7c42..c5259d4 100644 --- a/src/ast/environment.rs +++ b/src/ast/environment.rs @@ -17,10 +17,12 @@ use crate::ast::compiler::specializer::{FunctionRegistry, MonoCache, Specializer use crate::ast::compiler::tco::{ExecNode, TCO}; use crate::ast::rtl; use crate::ast::rtl::intrinsics; -use crate::ast::types::{Object, Purity, StaticType, Value}; +use crate::ast::types::{ + Identity, NodeIdentity, Object, Purity, SourceLocation, StaticType, Value, +}; pub struct Environment { - pub global_names: Rc>>, + pub global_names: Rc>>, pub global_types: Rc>>, pub global_purity: Rc>>, pub global_values: Rc>>, @@ -55,7 +57,7 @@ impl FunctionRegistry for EnvFunctionRegistry { } struct RuntimeMacroEvaluator { - global_names: Rc>>, + global_names: Rc>>, global_types: Rc>>, global_values: Rc>>, } @@ -72,7 +74,8 @@ impl MacroEvaluator for RuntimeMacroEvaluator { return Ok(Value::Object(Rc::new(arg_node.clone()) as Rc)); } - let bound_ast = Binder::bind_root(self.global_names.clone(), node)?; + let (bound_ast, captures) = Binder::bind_root(self.global_names.clone(), node)?; + let bound_ast = crate::ast::compiler::captures::CapturePass::apply(bound_ast, &captures); let checker = TypeChecker::new(self.global_types.clone()); let typed_ast = checker.check(bound_ast, &[])?; let exec_ast = TCO::optimize(Analyzer::analyze(&typed_ast, &HashMap::new())); // Minimal analysis for macro eval @@ -131,7 +134,10 @@ impl Environment { let mut purity = self.global_purity.borrow_mut(); let idx = values.len() as u32; - names.insert(Symbol::from(name), idx); + let identity = Rc::new(NodeIdentity { + location: SourceLocation { line: 0, col: 0 }, + }); + names.insert(Symbol::from(name), (idx, identity)); types.insert(idx, ty); purity.insert(idx, func.purity); values.push(Value::Function(func)); @@ -161,7 +167,10 @@ impl Environment { let mut purity = self.global_purity.borrow_mut(); let idx = values.len() as u32; - names.insert(Symbol::from(name), idx); + let identity = Rc::new(NodeIdentity { + location: SourceLocation { line: 0, col: 0 }, + }); + names.insert(Symbol::from(name), (idx, identity)); types.insert(idx, ty); purity.insert(idx, Purity::Pure); values.push(val); @@ -186,7 +195,9 @@ impl Environment { } let expanded_ast = self.get_expander().expand(untyped_ast)?; - let bound_ast = Binder::bind_root(self.global_names.clone(), &expanded_ast)?; + let (bound_ast, captures) = Binder::bind_root(self.global_names.clone(), &expanded_ast)?; + let bound_ast = crate::ast::compiler::captures::CapturePass::apply(bound_ast, &captures); + LambdaCollector::collect(&bound_ast, &mut self.function_registry.borrow_mut()); let checker = TypeChecker::new(self.global_types.clone());