Refactor Binder initialization and root binding

The `Binder` and `FunctionCompiler` initialization has been refactored
to accept initial scopes and slot counts. This allows for more flexible
management of the compiler's state, particularly for the root scope.

The `bind_root` function now returns the final scopes and slot count of
the root function compiler, enabling the `Environment` to update its
state with these new bindings.

The global variable handling has been integrated into the scope
management, removing the direct reliance on a shared `HashMap` for
globals. This promotes a more consistent approach to symbol resolution.
This commit is contained in:
Michael Schimmel
2026-03-12 14:40:13 +01:00
parent 08b5bba2c4
commit a220815bd6
3 changed files with 79 additions and 91 deletions
+45 -72
View File
@@ -37,11 +37,15 @@ struct FunctionCompiler {
} }
impl FunctionCompiler { impl FunctionCompiler {
fn new(identity: Identity) -> Self { fn new(identity: Identity, initial_scopes: Vec<CompilerScope>, initial_slot_count: u32) -> Self {
Self { Self {
identity, identity,
scopes: vec![CompilerScope::new()], scopes: if initial_scopes.is_empty() {
slot_count: 0, vec![CompilerScope::new()]
} else {
initial_scopes
},
slot_count: initial_slot_count,
upvalues: Vec::new(), upvalues: Vec::new(),
} }
} }
@@ -51,7 +55,9 @@ impl FunctionCompiler {
} }
fn pop_scope(&mut self) { fn pop_scope(&mut self) {
self.scopes.pop(); if self.scopes.len() > 1 {
self.scopes.pop();
}
} }
fn define_variable(&mut self, name: &Symbol, identity: Identity) -> Result<Address, String> { fn define_variable(&mut self, name: &Symbol, identity: Identity) -> Result<Address, String> {
@@ -104,19 +110,18 @@ pub enum ExprContext {
pub struct Binder { pub struct Binder {
functions: Vec<FunctionCompiler>, functions: Vec<FunctionCompiler>,
globals: Rc<RefCell<HashMap<Symbol, (GlobalIdx, Identity)>>>,
capture_map: HashMap<Identity, std::collections::HashSet<Identity>>, capture_map: HashMap<Identity, std::collections::HashSet<Identity>>,
fixed_scope_idx: i32, fixed_scope_idx: i32,
} }
impl Binder { impl Binder {
pub fn new( pub fn new(
globals: Rc<RefCell<HashMap<Symbol, (GlobalIdx, Identity)>>>, initial_scopes: Vec<CompilerScope>,
initial_slot_count: u32,
fixed_scope_idx: i32, fixed_scope_idx: i32,
) -> Self { ) -> Self {
let mut binder = Self { let mut binder = Self {
functions: Vec::new(), functions: Vec::new(),
globals,
capture_map: HashMap::new(), capture_map: HashMap::new(),
fixed_scope_idx, fixed_scope_idx,
}; };
@@ -125,17 +130,20 @@ impl Binder {
line: 0, line: 0,
col: 0, col: 0,
}), }),
initial_scopes,
initial_slot_count,
)); ));
binder binder
} }
pub fn bind_root( pub fn bind_root(
globals: Rc<RefCell<HashMap<Symbol, (GlobalIdx, Identity)>>>, initial_scopes: Vec<CompilerScope>,
initial_slot_count: u32,
fixed_scope_idx: i32, fixed_scope_idx: i32,
node: &Node<UntypedKind>, node: &Node<UntypedKind>,
diagnostics: &mut Diagnostics, diagnostics: &mut Diagnostics,
) -> Result<(BoundNode, HashMap<Identity, Vec<Identity>>), String> { ) -> Result<(BoundNode, HashMap<Identity, Vec<Identity>>, Vec<CompilerScope>, u32), String> {
let mut binder = Self::new(globals, fixed_scope_idx); let mut binder = Self::new(initial_scopes, initial_slot_count, fixed_scope_idx);
let bound = binder.bind(node, ExprContext::Expression, diagnostics); let bound = binder.bind(node, ExprContext::Expression, diagnostics);
let final_captures = binder let final_captures = binder
@@ -144,7 +152,13 @@ impl Binder {
.map(|(k, v)| (k, v.into_iter().collect())) .map(|(k, v)| (k, v.into_iter().collect()))
.collect(); .collect();
Ok((bound, final_captures)) let root_compiler = binder.functions.pop().unwrap();
Ok((
bound,
final_captures,
root_compiler.scopes,
root_compiler.slot_count,
))
} }
fn declare_variable( fn declare_variable(
@@ -168,36 +182,6 @@ impl Binder {
); );
return None; return None;
} }
if current_scope_idx == 0 {
let mut globals_map = self.globals.borrow_mut();
let addr = if let Some((idx, existing_id)) = globals_map.get(name) {
if *existing_id != identity {
diag.push_error(
format!("Variable '{}' is already defined in global scope.", name.name),
None,
);
return None;
}
Address::Global(*idx)
} else {
let idx = GlobalIdx(globals_map.len() as u32);
globals_map.insert(name.clone(), (idx, identity.clone()));
Address::Global(idx)
};
let current_scope = self.functions[0].scopes.last_mut().unwrap();
current_scope.locals.insert(
name.clone(),
LocalInfo {
addr,
identity,
_ty: StaticType::Any,
purity: Purity::Impure,
},
);
return Some(addr);
}
} }
let current_fn = self.functions.last_mut().unwrap(); let current_fn = self.functions.last_mut().unwrap();
@@ -359,7 +343,7 @@ impl Binder {
UntypedKind::Lambda { params, body } => { UntypedKind::Lambda { params, body } => {
let identity = node.identity.clone(); let identity = node.identity.clone();
self.functions self.functions
.push(FunctionCompiler::new(identity.clone())); .push(FunctionCompiler::new(identity.clone(), vec![], 0));
let params_bound = self.bind_pattern(params, DeclarationKind::Parameter, diag); let params_bound = self.bind_pattern(params, DeclarationKind::Parameter, diag);
let body_bound = self.bind(body, ExprContext::Expression, diag); let body_bound = self.bind(body, ExprContext::Expression, diag);
@@ -540,30 +524,22 @@ impl Binder {
) -> Option<Address> { ) -> Option<Address> {
let current_fn_idx = self.functions.len() - 1; let current_fn_idx = self.functions.len() - 1;
// 1. Try local in current function
if let Some((info, _)) = self.functions[current_fn_idx].resolve_local(sym) { if let Some((info, _)) = self.functions[current_fn_idx].resolve_local(sym) {
return Some(info.addr); return Some(info.addr);
} }
// 2. Try enclosing scopes (capture chain)
for i in (0..current_fn_idx).rev() { for i in (0..current_fn_idx).rev() {
if let Some((info, scope_idx)) = self.functions[i].resolve_local(sym) { if let Some((info, _)) = self.functions[i].resolve_local(sym) {
let is_frozen_root = i == 0 && (scope_idx as i32) <= self.fixed_scope_idx; // If the resolved address is already Global, we don't need to capture it as an upvalue
if let Address::Global(_) = info.addr { if let Address::Global(_) = info.addr {
return Some(info.addr); return Some(info.addr);
} }
let mut addr = info.addr; let mut addr = info.addr;
if is_frozen_root { // Record the capture for each lambda level in between
if let Address::Local(slot) = addr {
addr = Address::Global(GlobalIdx(slot.0));
}
}
if let Address::Global(_) = addr {
return Some(addr);
}
for k in (i + 1)..=current_fn_idx { for k in (i + 1)..=current_fn_idx {
let lambda_id = self.functions[k].identity.clone(); let lambda_id = self.functions[k].identity.clone();
self.capture_map self.capture_map
@@ -576,18 +552,19 @@ impl Binder {
} }
} }
let globals = self.globals.borrow(); // 3. Global Fallback (search in root level with context removed)
if let Some((idx, _)) = globals.get(sym) {
return Some(Address::Global(*idx));
}
if sym.context.is_some() { if sym.context.is_some() {
let fallback_sym = Symbol { let fallback_sym = Symbol {
name: sym.name.clone(), name: sym.name.clone(),
context: None, context: None,
}; };
if let Some((idx, _)) = globals.get(&fallback_sym) { // Search again in all functions, primarily we care about Root Scopes
return Some(Address::Global(*idx)); 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);
}
}
} }
} }
@@ -705,9 +682,8 @@ mod tests {
let mut parser = Parser::new(source); let mut parser = Parser::new(source);
let untyped = parser.parse_expression(); let untyped = parser.parse_expression();
let globals = Rc::new(RefCell::new(HashMap::new()));
let mut diagnostics = Diagnostics::new(); let mut diagnostics = Diagnostics::new();
let (bound, captures) = Binder::bind_root(globals, 0, &untyped, &mut diagnostics).unwrap(); let (bound, captures, _scopes, _slots) = Binder::bind_root(vec![], 0, 0, &untyped, &mut diagnostics).unwrap();
if let BoundKind::Lambda { body, .. } = &bound.kind { if let BoundKind::Lambda { body, .. } = &bound.kind {
if let BoundKind::Block { exprs } = &body.kind { if let BoundKind::Block { exprs } = &body.kind {
@@ -735,9 +711,8 @@ mod tests {
let mut parser = Parser::new(source); let mut parser = Parser::new(source);
let untyped = parser.parse_expression(); let untyped = parser.parse_expression();
let globals = Rc::new(RefCell::new(HashMap::new()));
let mut diagnostics = Diagnostics::new(); let mut diagnostics = Diagnostics::new();
let (bound, captures) = Binder::bind_root(globals, 0, &untyped, &mut diagnostics).unwrap(); let (bound, captures, _scopes, _slots) = Binder::bind_root(vec![], 0, 0, &untyped, &mut diagnostics).unwrap();
if let BoundKind::Lambda { body, .. } = &bound.kind { if let BoundKind::Lambda { body, .. } = &bound.kind {
if let BoundKind::Block { exprs } = &body.kind { if let BoundKind::Block { exprs } = &body.kind {
@@ -765,9 +740,8 @@ mod tests {
let mut parser = Parser::new(source); let mut parser = Parser::new(source);
let untyped = parser.parse_expression(); let untyped = parser.parse_expression();
let globals = Rc::new(RefCell::new(HashMap::new()));
let mut diagnostics = Diagnostics::new(); let mut diagnostics = Diagnostics::new();
let _ = Binder::bind_root(globals, 0, &untyped, &mut diagnostics); let _ = Binder::bind_root(vec![], 0, 0, &untyped, &mut diagnostics);
assert!(diagnostics.has_errors()); assert!(diagnostics.has_errors());
assert!(diagnostics.items.iter().any(|i| i.message.contains("already defined"))); assert!(diagnostics.items.iter().any(|i| i.message.contains("already defined")));
@@ -775,17 +749,16 @@ mod tests {
#[test] #[test]
fn test_repro_global_redefinition() { fn test_repro_global_redefinition() {
let globals = Rc::new(RefCell::new(HashMap::new()));
let source1 = "(def x 1) 1"; let source1 = "(def x 1) 1";
let untyped1 = Parser::new(source1).parse_expression(); let untyped1 = Parser::new(source1).parse_expression();
let mut diagnostics = Diagnostics::new(); let mut diagnostics = Diagnostics::new();
assert!(Binder::bind_root(globals.clone(), 0, &untyped1, &mut diagnostics).is_ok()); let (_, _, scopes1, slots1) = Binder::bind_root(vec![], 0, -1, &untyped1, &mut diagnostics).unwrap();
let source2 = "(def x 2) 2"; let source2 = "(def x 2) 2";
let untyped2 = Parser::new(source2).parse_expression(); let untyped2 = Parser::new(source2).parse_expression();
let mut diagnostics2 = Diagnostics::new(); let mut diagnostics2 = Diagnostics::new();
let _ = Binder::bind_root(globals.clone(), 0, &untyped2, &mut diagnostics2); // Here we simulate frozen scope by passing fixed_scope_idx = 0
let _ = Binder::bind_root(scopes1, slots1, 0, &untyped2, &mut diagnostics2);
assert!(diagnostics2.has_errors()); assert!(diagnostics2.has_errors());
assert!(diagnostics2.items.iter().any(|i| i.message.contains("frozen/immutable"))); assert!(diagnostics2.items.iter().any(|i| i.message.contains("frozen/immutable")));
+15 -12
View File
@@ -623,6 +623,7 @@ impl<E: MacroEvaluator> MacroExpander<E> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::ast::compiler::bound_nodes::Address;
use crate::ast::compiler::Binder; use crate::ast::compiler::Binder;
use crate::ast::parser::Parser; use crate::ast::parser::Parser;
use crate::ast::types::{Object, Value}; use crate::ast::types::{Object, Value};
@@ -764,21 +765,25 @@ mod tests {
let mut expander = MacroExpander::new(MacroRegistry::new(), SimpleEvaluator); let mut expander = MacroExpander::new(MacroRegistry::new(), SimpleEvaluator);
let expanded = expander.expand(untyped).unwrap(); let expanded = expander.expand(untyped).unwrap();
let mut global_names = HashMap::new(); // Convert test globals into a CompilerScope
global_names.insert( let mut locals = HashMap::new();
locals.insert(
Symbol::from("*"), Symbol::from("*"),
( crate::ast::compiler::binder::LocalInfo {
crate::ast::compiler::bound_nodes::GlobalIdx(0), addr: Address::Local(crate::ast::compiler::bound_nodes::LocalSlot(0)),
crate::ast::types::NodeIdentity::new(crate::ast::types::SourceLocation { identity: crate::ast::types::NodeIdentity::new(crate::ast::types::SourceLocation {
line: 0, line: 0,
col: 0, col: 0,
}), }),
), _ty: crate::ast::types::StaticType::Any,
purity: crate::ast::types::Purity::Pure,
},
); );
let globals = Rc::new(RefCell::new(global_names)); let initial_scopes = vec![crate::ast::compiler::binder::CompilerScope { locals }];
let mut diag = crate::ast::diagnostics::Diagnostics::new(); let mut diag = crate::ast::diagnostics::Diagnostics::new();
let result = Binder::bind_root(globals, 0, &expanded, &mut diag); // fixed_scope_idx = 0 means scope 0 is frozen (Global)
let result = Binder::bind_root(initial_scopes, 1, 0, &expanded, &mut diag);
assert!( assert!(
result.is_ok(), result.is_ok(),
"Should find global '*' Error: {:?}", "Should find global '*' Error: {:?}",
@@ -801,9 +806,8 @@ mod tests {
let mut expander = MacroExpander::new(MacroRegistry::new(), SimpleEvaluator); let mut expander = MacroExpander::new(MacroRegistry::new(), SimpleEvaluator);
let expanded = expander.expand(untyped).unwrap(); let expanded = expander.expand(untyped).unwrap();
let globals = Rc::new(RefCell::new(HashMap::new()));
let mut diag = crate::ast::diagnostics::Diagnostics::new(); let mut diag = crate::ast::diagnostics::Diagnostics::new();
let result = Binder::bind_root(globals, 0, &expanded, &mut diag); let result = Binder::bind_root(vec![], 0, 0, &expanded, &mut diag);
assert!(result.is_ok(), "Hygiene failed: {:?}", result.err()); assert!(result.is_ok(), "Hygiene failed: {:?}", result.err());
} }
@@ -822,9 +826,8 @@ mod tests {
let mut expander = MacroExpander::new(MacroRegistry::new(), SimpleEvaluator); let mut expander = MacroExpander::new(MacroRegistry::new(), SimpleEvaluator);
let expanded = expander.expand(untyped).unwrap(); let expanded = expander.expand(untyped).unwrap();
let globals = Rc::new(RefCell::new(HashMap::new()));
let mut diag = crate::ast::diagnostics::Diagnostics::new(); let mut diag = crate::ast::diagnostics::Diagnostics::new();
let result = Binder::bind_root(globals, 0, &expanded, &mut diag); let result = Binder::bind_root(vec![], 0, 0, &expanded, &mut diag);
assert!( assert!(
result.is_ok(), result.is_ok(),
"Explicit Hygiene with Backticks failed: {:?}", "Explicit Hygiene with Backticks failed: {:?}",
+19 -7
View File
@@ -112,7 +112,8 @@ impl FunctionRegistry for EnvFunctionRegistry {
} }
struct RuntimeMacroEvaluator { struct RuntimeMacroEvaluator {
global_names: Rc<RefCell<HashMap<Symbol, (GlobalIdx, Identity)>>>, root_scopes: Rc<RefCell<Vec<crate::ast::compiler::binder::CompilerScope>>>,
root_slot_count: Rc<RefCell<u32>>,
global_types: Rc<RefCell<HashMap<GlobalIdx, StaticType>>>, global_types: Rc<RefCell<HashMap<GlobalIdx, StaticType>>>,
global_values: Rc<RefCell<Vec<Value>>>, global_values: Rc<RefCell<Vec<Value>>>,
fixed_scope_idx: i32, fixed_scope_idx: i32,
@@ -131,7 +132,10 @@ impl MacroEvaluator for RuntimeMacroEvaluator {
} }
let mut diag = Diagnostics::new(); let mut diag = Diagnostics::new();
let (bound_ast, captures) = Binder::bind_root(self.global_names.clone(), self.fixed_scope_idx, node, &mut diag)?; 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 = crate::ast::compiler::captures::CapturePass::apply(bound_ast, &captures); let bound_ast = crate::ast::compiler::captures::CapturePass::apply(bound_ast, &captures);
let checker = TypeChecker::new(self.global_types.clone()); let checker = TypeChecker::new(self.global_types.clone());
let typed_ast = checker.check(&bound_ast, &[], &mut diag); let typed_ast = checker.check(&bound_ast, &[], &mut diag);
@@ -219,7 +223,8 @@ impl Environment {
fn get_expander(&self) -> MacroExpander<RuntimeMacroEvaluator> { fn get_expander(&self) -> MacroExpander<RuntimeMacroEvaluator> {
let evaluator = RuntimeMacroEvaluator { 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(), global_types: self.global_types.clone(),
global_values: self.global_values.clone(), global_values: self.global_values.clone(),
fixed_scope_idx: self.fixed_scope_idx, fixed_scope_idx: self.fixed_scope_idx,
@@ -433,8 +438,11 @@ impl Environment {
} }
}; };
let (bound_ast, captures) = let initial_scopes = self.root_scopes.borrow().clone();
match Binder::bind_root(self.global_names.clone(), self.fixed_scope_idx, &expanded_ast, diagnostics) { 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) {
Ok(res) => res, Ok(res) => res,
Err(e) => { Err(e) => {
diagnostics.push_error(e, None); diagnostics.push_error(e, None);
@@ -442,6 +450,10 @@ impl Environment {
} }
}; };
// Update environment state with new bindings from this script
*self.root_scopes.borrow_mut() = final_scopes;
*self.root_slot_count.borrow_mut() = final_slot_count;
let bound_ast = crate::ast::compiler::captures::CapturePass::apply(bound_ast, &captures); let bound_ast = crate::ast::compiler::captures::CapturePass::apply(bound_ast, &captures);
// Pre-allocate global slots to prevent out-of-bounds during specialization/optimization // Pre-allocate global slots to prevent out-of-bounds during specialization/optimization
@@ -520,7 +532,7 @@ impl Environment {
root_scopes[0].locals.insert( root_scopes[0].locals.insert(
Symbol::from(name), Symbol::from(name),
crate::ast::compiler::binder::LocalInfo { crate::ast::compiler::binder::LocalInfo {
addr: Address::Local(slot), addr: Address::Global(GlobalIdx(slot.0)),
identity, identity,
_ty: ty, _ty: ty,
purity: func.purity, purity: func.purity,
@@ -566,7 +578,7 @@ impl Environment {
root_scopes[0].locals.insert( root_scopes[0].locals.insert(
Symbol::from(name), Symbol::from(name),
crate::ast::compiler::binder::LocalInfo { crate::ast::compiler::binder::LocalInfo {
addr: Address::Local(slot), addr: Address::Global(GlobalIdx(slot.0)),
identity, identity,
_ty: ty, _ty: ty,
purity: Purity::Pure, purity: Purity::Pure,