diff --git a/src/ast/compiler/binder.rs b/src/ast/compiler/binder.rs index d6ef01d..194b5dd 100644 --- a/src/ast/compiler/binder.rs +++ b/src/ast/compiler/binder.rs @@ -21,6 +21,12 @@ pub struct CompilerScope { pub locals: HashMap, } +impl Default for CompilerScope { + fn default() -> Self { + Self::new() + } +} + impl CompilerScope { pub fn new() -> Self { Self { @@ -108,8 +114,16 @@ pub enum ExprContext { Statement, } +pub type BindingResult = ( + BoundNode, + HashMap>, + Vec, + u32, +); + pub struct Binder { functions: Vec, + globals: Rc>>, capture_map: HashMap>, fixed_scope_idx: i32, } @@ -119,9 +133,11 @@ impl Binder { initial_scopes: Vec, initial_slot_count: u32, fixed_scope_idx: i32, + globals: Rc>>, ) -> Self { let mut binder = Self { functions: Vec::new(), + globals, capture_map: HashMap::new(), fixed_scope_idx, }; @@ -140,10 +156,11 @@ impl Binder { initial_scopes: Vec, initial_slot_count: u32, fixed_scope_idx: i32, + globals: Rc>>, node: &Node, diagnostics: &mut Diagnostics, - ) -> Result<(BoundNode, HashMap>, Vec, u32), String> { - let mut binder = Self::new(initial_scopes, initial_slot_count, fixed_scope_idx); + ) -> Result { + let mut binder = Self::new(initial_scopes, initial_slot_count, fixed_scope_idx, globals); let bound = binder.bind(node, ExprContext::Expression, diagnostics); let final_captures = binder @@ -531,14 +548,25 @@ impl Binder { // 2. Try enclosing scopes (capture chain) for i in (0..current_fn_idx).rev() { - if let Some((info, _)) = self.functions[i].resolve_local(sym) { - // If the resolved address is already Global, we don't need to capture it as an upvalue + if let Some((info, scope_idx)) = self.functions[i].resolve_local(sym) { + // If it's in the root script and within a frozen scope, translate to Global + let is_frozen_root = i == 0 && (scope_idx as i32) <= self.fixed_scope_idx; + if let Address::Global(_) = info.addr { return Some(info.addr); } let mut addr = info.addr; + if is_frozen_root + && let Address::Local(slot) = addr { + addr = Address::Global(GlobalIdx(slot.0)); + } + + if let Address::Global(_) = addr { + return Some(addr); + } + // 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(); @@ -552,7 +580,13 @@ impl Binder { } } - // 3. Global Fallback (search in root level with context removed) + // 3. Global Fallback + let globals = self.globals.borrow(); + if let Some((idx, _)) = globals.get(sym) { + return Some(Address::Global(*idx)); + } + + // 4. Global Fallback (search in root level with context removed) if sym.context.is_some() { let fallback_sym = Symbol { name: sym.name.clone(), @@ -560,12 +594,15 @@ impl Binder { }; // Search again in all functions, primarily we care about Root Scopes for i in (0..=current_fn_idx).rev() { - if let Some((info, _)) = self.functions[i].resolve_local(&fallback_sym) { - if let Address::Global(_) = info.addr { - return Some(info.addr); - } + if let Some((info, _)) = self.functions[i].resolve_local(&fallback_sym) + && let Address::Global(_) = info.addr + { + return Some(info.addr); } } + if let Some((idx, _)) = globals.get(&fallback_sym) { + return Some(Address::Global(*idx)); + } } diag.push_error( @@ -682,8 +719,9 @@ mod tests { let mut parser = Parser::new(source); let untyped = parser.parse_expression(); + let globals = Rc::new(RefCell::new(HashMap::new())); let mut diagnostics = Diagnostics::new(); - let (bound, captures, _scopes, _slots) = Binder::bind_root(vec![], 0, 0, &untyped, &mut diagnostics).unwrap(); + let (bound, captures, _scopes, _slots) = Binder::bind_root(vec![], 0, 0, globals, &untyped, &mut diagnostics).unwrap(); if let BoundKind::Lambda { body, .. } = &bound.kind { if let BoundKind::Block { exprs } = &body.kind { @@ -711,8 +749,9 @@ mod tests { let mut parser = Parser::new(source); let untyped = parser.parse_expression(); + let globals = Rc::new(RefCell::new(HashMap::new())); let mut diagnostics = Diagnostics::new(); - let (bound, captures, _scopes, _slots) = Binder::bind_root(vec![], 0, 0, &untyped, &mut diagnostics).unwrap(); + let (bound, captures, _scopes, _slots) = Binder::bind_root(vec![], 0, 0, globals, &untyped, &mut diagnostics).unwrap(); if let BoundKind::Lambda { body, .. } = &bound.kind { if let BoundKind::Block { exprs } = &body.kind { @@ -740,8 +779,9 @@ mod tests { let mut parser = Parser::new(source); let untyped = parser.parse_expression(); + let globals = Rc::new(RefCell::new(HashMap::new())); let mut diagnostics = Diagnostics::new(); - let _ = Binder::bind_root(vec![], 0, 0, &untyped, &mut diagnostics); + let _ = Binder::bind_root(vec![], 0, 0, globals, &untyped, &mut diagnostics); assert!(diagnostics.has_errors()); assert!(diagnostics.items.iter().any(|i| i.message.contains("already defined"))); @@ -749,16 +789,18 @@ mod tests { #[test] fn test_repro_global_redefinition() { + let globals = Rc::new(RefCell::new(HashMap::new())); + let source1 = "(def x 1) 1"; let untyped1 = Parser::new(source1).parse_expression(); let mut diagnostics = Diagnostics::new(); - let (_, _, scopes1, slots1) = Binder::bind_root(vec![], 0, -1, &untyped1, &mut diagnostics).unwrap(); + let (_, _, scopes1, slots1) = Binder::bind_root(vec![], 0, -1, globals.clone(), &untyped1, &mut diagnostics).unwrap(); let source2 = "(def x 2) 2"; let untyped2 = Parser::new(source2).parse_expression(); let mut diagnostics2 = Diagnostics::new(); // Here we simulate frozen scope by passing fixed_scope_idx = 0 - let _ = Binder::bind_root(scopes1, slots1, 0, &untyped2, &mut diagnostics2); + let _ = Binder::bind_root(scopes1, slots1, 0, globals.clone(), &untyped2, &mut diagnostics2); assert!(diagnostics2.has_errors()); assert!(diagnostics2.items.iter().any(|i| i.message.contains("frozen/immutable"))); diff --git a/src/ast/compiler/macros.rs b/src/ast/compiler/macros.rs index 73d93fd..e3a9110 100644 --- a/src/ast/compiler/macros.rs +++ b/src/ast/compiler/macros.rs @@ -783,7 +783,7 @@ mod tests { let mut diag = crate::ast::diagnostics::Diagnostics::new(); // fixed_scope_idx = 0 means scope 0 is frozen (Global) - let result = Binder::bind_root(initial_scopes, 1, 0, &expanded, &mut diag); + let result = Binder::bind_root(initial_scopes, 1, 0, Rc::new(RefCell::new(HashMap::new())), &expanded, &mut diag); assert!( result.is_ok(), "Should find global '*' Error: {:?}", @@ -807,7 +807,7 @@ mod tests { let expanded = expander.expand(untyped).unwrap(); let mut diag = crate::ast::diagnostics::Diagnostics::new(); - let result = Binder::bind_root(vec![], 0, 0, &expanded, &mut diag); + let result = Binder::bind_root(vec![], 0, 0, Rc::new(RefCell::new(HashMap::new())), &expanded, &mut diag); assert!(result.is_ok(), "Hygiene failed: {:?}", result.err()); } @@ -827,7 +827,7 @@ mod tests { let expanded = expander.expand(untyped).unwrap(); let mut diag = crate::ast::diagnostics::Diagnostics::new(); - let result = Binder::bind_root(vec![], 0, 0, &expanded, &mut diag); + let result = Binder::bind_root(vec![], 0, 0, Rc::new(RefCell::new(HashMap::new())), &expanded, &mut diag); assert!( result.is_ok(), "Explicit Hygiene with Backticks failed: {:?}", diff --git a/src/ast/compiler/type_checker.rs b/src/ast/compiler/type_checker.rs index d8b9675..9e0b9f1 100644 --- a/src/ast/compiler/type_checker.rs +++ b/src/ast/compiler/type_checker.rs @@ -717,7 +717,7 @@ mod tests { assert!(result.is_err()); assert_eq!( result.unwrap_err(), - "Statement 'def' cannot be used as an expression.\nCannot define 'x': Scope 0 is frozen/immutable.\nCannot destructure type int as a tuple/vector" + "Statement 'def' cannot be used as an expression.\nCannot destructure type int as a tuple/vector" ); } diff --git a/src/ast/environment.rs b/src/ast/environment.rs index 4524a27..78307a3 100644 --- a/src/ast/environment.rs +++ b/src/ast/environment.rs @@ -112,6 +112,7 @@ impl FunctionRegistry for EnvFunctionRegistry { } struct RuntimeMacroEvaluator { + global_names: Rc>>, root_scopes: Rc>>, root_slot_count: Rc>, global_types: Rc>>, @@ -135,7 +136,7 @@ impl MacroEvaluator for RuntimeMacroEvaluator { let initial_scopes = self.root_scopes.borrow().clone(); let initial_slot_count = *self.root_slot_count.borrow(); - let (bound_ast, captures, _, _) = Binder::bind_root(initial_scopes, initial_slot_count, self.fixed_scope_idx, node, &mut diag)?; + let (bound_ast, captures, _, _) = Binder::bind_root(initial_scopes, initial_slot_count, self.fixed_scope_idx, self.global_names.clone(), node, &mut diag)?; 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, &[], &mut diag); @@ -186,6 +187,8 @@ impl Environment { let mut env = env; env.fixed_scope_idx = 0; + // Push the first mutable user scope (Level 1) + env.root_scopes.borrow_mut().push(crate::ast::compiler::binder::CompilerScope::new()); // Automatically add standard search paths (CWD and CWD/rtl) if let Ok(cwd) = std::env::current_dir() { @@ -223,6 +226,7 @@ impl Environment { fn get_expander(&self) -> MacroExpander { let evaluator = RuntimeMacroEvaluator { + global_names: self.global_names.clone(), root_scopes: self.root_scopes.clone(), root_slot_count: self.root_slot_count.clone(), global_types: self.global_types.clone(), @@ -398,10 +402,23 @@ impl Environment { match &node.kind { UntypedKind::Def { target, .. } => { if let UntypedKind::Identifier(sym) = &target.kind { - let mut names = self.global_names.borrow_mut(); - if !names.contains_key(sym) { - let idx = GlobalIdx(names.len() as u32); - names.insert(sym.clone(), (idx, node.identity.clone())); + let mut root_scopes = self.root_scopes.borrow_mut(); + let last_idx = root_scopes.len() - 1; + let current_scope = &mut root_scopes[last_idx]; + + if !current_scope.locals.contains_key(sym) { + let mut slot_count = self.root_slot_count.borrow_mut(); + let slot = crate::ast::compiler::bound_nodes::LocalSlot(*slot_count); + current_scope.locals.insert( + sym.clone(), + crate::ast::compiler::binder::LocalInfo { + addr: Address::Local(slot), + identity: node.identity.clone(), + _ty: StaticType::Any, + purity: Purity::Impure, + }, + ); + *slot_count += 1; } } } @@ -442,7 +459,7 @@ impl Environment { let initial_slot_count = *self.root_slot_count.borrow(); let (bound_ast, captures, final_scopes, final_slot_count) = - match Binder::bind_root(initial_scopes, initial_slot_count, self.fixed_scope_idx, &expanded_ast, diagnostics) { + match Binder::bind_root(initial_scopes, initial_slot_count, self.fixed_scope_idx, self.global_names.clone(), &expanded_ast, diagnostics) { Ok(res) => res, Err(e) => { diagnostics.push_error(e, None); @@ -459,10 +476,9 @@ impl Environment { // Pre-allocate global slots to prevent out-of-bounds during specialization/optimization { let mut values = self.global_values.borrow_mut(); - let names = self.global_names.borrow(); - let max_idx = names.values().map(|(idx, _)| idx.0).max().unwrap_or(0); - if (max_idx as usize) >= values.len() { - values.resize((max_idx + 1) as usize, Value::Void); + let count = *self.root_slot_count.borrow(); + if (count as usize) > values.len() { + values.resize(count as usize, Value::Void); } }