From 35f5ea0db3ea6404d08ef6b073139165ce152e44 Mon Sep 17 00:00:00 2001 From: Brummel Date: Tue, 31 Mar 2026 11:19:56 +0200 Subject: [PATCH] Refactor Binder and Environment, remove fixed_scope_idx The `fixed_scope_idx` field has been removed from `Binder` and `Environment`. This field was used to enforce immutability on certain scopes during binding. The logic for handling the immutability of the root scope (scope 0) has been moved and refactored. Now, during the `Environment::new` bootstrap phase, the addresses within the RTL scope (scope 0) are directly translated from `Address::Local` to `Address::Global`. This ensures that the Binder inherently treats these as global and immutable without needing an explicit `fixed_scope_idx` check. The `Binder::bind_root` signature has been updated to reflect this change by removing the `fixed_scope_idx` parameter. Consequently, tests and other usages of `bind_root` have been adjusted. This change simplifies the binding process by centralizing the immutability handling to the environment setup phase, making the Binder's logic cleaner. --- docs/Analysis_Environment.md | 2 +- src/ast/compiler/binder.rs | 60 ++++-------------------------------- src/ast/compiler/macros.rs | 16 +++++----- src/ast/environment.rs | 25 +++++++++------ src/ast/rtl/prelude.myc | 2 +- 5 files changed, 31 insertions(+), 74 deletions(-) diff --git a/docs/Analysis_Environment.md b/docs/Analysis_Environment.md index 7783581..472cf73 100644 --- a/docs/Analysis_Environment.md +++ b/docs/Analysis_Environment.md @@ -7,7 +7,7 @@ Die `Environment`-Struktur fungiert im Projekt als zentraler "State-Manager" und * **Modulladung & Abhängigkeiten:** `preload_dependencies` und `discover_globals` lesen `#use`-Abhängigkeiten, durchsuchen den Code vorab nach globalen Definitionen (`def`) und Makros und laden die Standardbibliothek (`prelude.myc`). * **Kompilierungs-Pipeline:** Die Methoden `compile`, `compile_syntax`, `compile_pipeline` und `link` steuern den Code durch alle Compiler-Phasen: Macro-Expansion -> Binding -> Type-Checking -> Analysis -> Specialization -> Optimization -> Lowering. * **Makro-Evaluierung:** Die interne Struktur `RuntimeMacroEvaluator` wird genutzt, um AST-Knoten zur Compile-Zeit an eine VM zu übergeben und den Code für Makros auszuführen. -* **Laufzeit-Ausführung & RTL (Runtime Library):** Methoden wie `run_script`, `run_debug` und `instantiate` starten die `VM`. Zudem gibt es Methoden (`register_native`, `allocate_slot`), um native Rust-Funktionen (Intrinsics) im globalen Scope (`fixed_scope_idx = 0`) zu registrieren. +* **Laufzeit-Ausführung & RTL (Runtime Library):** Methoden wie `run_script`, `run_debug` und `instantiate` starten die `VM`. Zudem gibt es Methoden (`register_native`, `allocate_slot`), um native Rust-Funktionen (Intrinsics) im globalen RTL-Scope (Scope 0) zu registrieren. * **Dokumentations-Registry:** Es speichert sowohl RTL-Dokumentation als auch aus dem Source-Code extrahierte Kommentare (`myc_docs`). ## 2. Prüfung auf Boilerplate diff --git a/src/ast/compiler/binder.rs b/src/ast/compiler/binder.rs index ac0f06f..376291a 100644 --- a/src/ast/compiler/binder.rs +++ b/src/ast/compiler/binder.rs @@ -1,5 +1,5 @@ use crate::ast::nodes::{ - Address, AssignBinding, BoundPhase, DeclarationKind, DefBinding, GlobalIdx, + Address, AssignBinding, BoundPhase, DeclarationKind, DefBinding, IdentifierBinding, LambdaBinding, Node, NodeKind, SyntaxKind, SyntaxNode, Symbol, UpvalueIdx, VirtualId, }; @@ -124,19 +124,16 @@ pub type BindingResult = ( pub struct Binder { functions: Vec, capture_map: HashMap>, - fixed_scope_idx: i32, } impl Binder { pub fn new( initial_scopes: Vec, initial_slot_count: u32, - fixed_scope_idx: i32, ) -> Self { let mut binder = Self { functions: Vec::new(), capture_map: HashMap::new(), - fixed_scope_idx, }; binder.functions.push(FunctionCompiler::new( NodeIdentity::new(SourceLocation { @@ -152,11 +149,10 @@ impl Binder { pub fn bind_root( initial_scopes: Vec, initial_slot_count: u32, - fixed_scope_idx: i32, node: &SyntaxNode, diagnostics: &mut Diagnostics, ) -> Result { - let mut binder = Self::new(initial_scopes, initial_slot_count, fixed_scope_idx); + let mut binder = Self::new(initial_scopes, initial_slot_count); let bound = binder.bind(node, ExprContext::Expression, diagnostics); let final_captures = binder @@ -181,22 +177,6 @@ impl Binder { _kind: DeclarationKind, diag: &mut Diagnostics, ) -> Option> { - let current_fn_idx = self.functions.len() - 1; - - if current_fn_idx == 0 { - let current_scope_idx = self.functions[0].scopes.len() as i32 - 1; - if current_scope_idx <= self.fixed_scope_idx { - diag.push_error( - format!( - "Cannot define '{}': Scope {} is frozen/immutable.", - name.name, current_scope_idx - ), - None, - ); - return None; - } - } - let current_fn = self.functions.last_mut().unwrap(); match current_fn.define_variable(name, identity) { Ok(addr) => Some(addr), @@ -562,25 +542,13 @@ impl Binder { // 2. Try enclosing scopes (capture chain) for i in (0..current_fn_idx).rev() { - 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 Some((info, _scope_idx)) = self.functions[i].resolve_local(sym) { 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(); @@ -723,7 +691,7 @@ mod tests { let syntax = parser.parse_expression(); let mut diagnostics = Diagnostics::new(); - let (bound, captures, _scopes, _slots) = Binder::bind_root(vec![], 0, 0, &syntax, &mut diagnostics).unwrap(); + let (bound, captures, _scopes, _slots) = Binder::bind_root(vec![], 0, &syntax, &mut diagnostics).unwrap(); if let NodeKind::Lambda { body, .. } = &bound.kind { if let NodeKind::Block { exprs } = &body.kind { @@ -756,7 +724,7 @@ mod tests { let syntax = parser.parse_expression(); let mut diagnostics = Diagnostics::new(); - let (bound, captures, _scopes, _slots) = Binder::bind_root(vec![], 0, 0, &syntax, &mut diagnostics).unwrap(); + let (bound, captures, _scopes, _slots) = Binder::bind_root(vec![], 0, &syntax, &mut diagnostics).unwrap(); if let NodeKind::Lambda { body, .. } = &bound.kind { if let NodeKind::Block { exprs } = &body.kind { @@ -789,26 +757,10 @@ mod tests { let syntax = parser.parse_expression(); let mut diagnostics = Diagnostics::new(); - let _ = Binder::bind_root(vec![], 0, 0, &syntax, &mut diagnostics); + let _ = Binder::bind_root(vec![], 0, &syntax, &mut diagnostics); assert!(diagnostics.has_errors()); assert!(diagnostics.items.iter().any(|i| i.message.contains("already defined"))); } - #[test] - fn test_repro_global_redefinition() { - let source1 = "(def x 1) 1"; - let syntax1 = Parser::new(source1).parse_expression(); - let mut diagnostics = Diagnostics::new(); - let (_, _, scopes1, slots1) = Binder::bind_root(vec![], 0, -1, &syntax1, &mut diagnostics).unwrap(); - - let source2 = "(def x 2) 2"; - let syntax2 = 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, &syntax2, &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 13ef96f..038ce8d 100644 --- a/src/ast/compiler/macros.rs +++ b/src/ast/compiler/macros.rs @@ -633,7 +633,7 @@ mod tests { use crate::ast::compiler::binder::{CompilerScope, LocalInfo}; use crate::ast::compiler::Binder; use crate::ast::diagnostics::Diagnostics; - use crate::ast::nodes::{Address, VirtualId}; + use crate::ast::nodes::{Address, GlobalIdx, VirtualId}; use crate::ast::parser::Parser; use crate::ast::types::{NodeIdentity, Purity, SourceLocation, StaticType, Value}; @@ -778,7 +778,7 @@ mod tests { locals.insert( Symbol::from("*"), LocalInfo { - addr: Address::Local(VirtualId(0)), + addr: Address::Global(GlobalIdx(0)), identity: NodeIdentity::new(SourceLocation { line: 0, col: 0, @@ -790,8 +790,8 @@ mod tests { let initial_scopes = vec![CompilerScope { locals }]; let mut diag = Diagnostics::new(); - // fixed_scope_idx = 0 means scope 0 is frozen (Global) - let result = Binder::bind_root(initial_scopes, 1, 0, &expanded, &mut diag); + // Scope 0 simulates a frozen RTL scope with Global addresses + let result = Binder::bind_root(initial_scopes, 1, &expanded, &mut diag); assert!( result.is_ok(), "Should find global '*' Error: {:?}", @@ -815,7 +815,7 @@ mod tests { let expanded = expander.expand(syntax).unwrap(); let mut diag = Diagnostics::new(); - let result = Binder::bind_root(vec![], 0, 0, &expanded, &mut diag); + let result = Binder::bind_root(vec![], 0, &expanded, &mut diag); assert!(result.is_ok(), "Hygiene failed: {:?}", result.err()); } @@ -835,7 +835,7 @@ mod tests { let expanded = expander.expand(syntax).unwrap(); let mut diag = Diagnostics::new(); - let result = Binder::bind_root(vec![], 0, 0, &expanded, &mut diag); + let result = Binder::bind_root(vec![], 0, &expanded, &mut diag); assert!( result.is_ok(), "Explicit Hygiene with Backticks failed: {:?}", @@ -861,7 +861,7 @@ mod tests { let expanded = expander.expand(syntax).unwrap(); let mut diag = Diagnostics::new(); - let result = Binder::bind_root(vec![], 0, 0, &expanded, &mut diag); + let result = Binder::bind_root(vec![], 0, &expanded, &mut diag); assert!( result.is_ok(), "~nonparam should resolve to call-site variable: {:?}", @@ -887,7 +887,7 @@ mod tests { let expanded = expander.expand(syntax).unwrap(); let mut diag = Diagnostics::new(); - let result = Binder::bind_root(vec![], 0, 0, &expanded, &mut diag); + let result = Binder::bind_root(vec![], 0, &expanded, &mut diag); assert!( result.is_ok(), "Hygiene isolation for def should still hold: {:?}", diff --git a/src/ast/environment.rs b/src/ast/environment.rs index dcc73dd..8f92613 100644 --- a/src/ast/environment.rs +++ b/src/ast/environment.rs @@ -67,7 +67,6 @@ pub struct Environment { rtl_values: Rc<[Value]>, /// Mutable user-defined global slots. Indexed as `[0..)` i.e. offset from `rtl.slot_count`. user_values: Rc>>, - pub fixed_scope_idx: i32, pub root_scopes: Rc>>, pub root_slot_count: Rc>, pub function_registry: Rc>, @@ -110,7 +109,6 @@ struct RuntimeMacroEvaluator { root_types: Rc>>, globals: GlobalStore, root_purity: Rc>>, - fixed_scope_idx: i32, compiler_hooks: Rc>>, } @@ -130,7 +128,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, node, &mut diag)?; let bound_ast = CapturePass::apply(bound_ast, &captures); let checker = TypeChecker::new(self.root_types.clone(), Rc::clone(&self.compiler_hooks)); let typed_ast = checker.check(&bound_ast, &[], &mut diag); @@ -154,14 +152,12 @@ impl Default for Environment { impl Environment { pub fn new() -> Self { - // Phase 1: Bootstrap — fixed_scope_idx = -1 allows allocate_slot to - // write freely into scope 0 (the RTL scope). + // Phase 1: Bootstrap — allocate_slot writes freely into scope 0 (the RTL scope). let mut env = Self { root_types: Rc::new(RefCell::new(Vec::new())), root_purity: Rc::new(RefCell::new(Vec::new())), rtl_values: Rc::from([]), // placeholder — filled after freeze user_values: Rc::new(RefCell::new(Vec::new())), // bootstrap scratch during RTL phase - fixed_scope_idx: -1, root_scopes: Rc::new(RefCell::new(vec![CompilerScope::new()])), root_slot_count: Rc::new(RefCell::new(0)), function_registry: Rc::new(RefCell::new(HashMap::new())), @@ -191,11 +187,22 @@ impl Environment { rtl::register(&env); // Phase 2: Freeze scope 0 and snapshot the RTL state. - env.fixed_scope_idx = 0; let rtl_slot_count = *env.root_slot_count.borrow(); // user_values was used as the bootstrap scratch; freeze it into an immutable slice. let rtl_vals: Rc<[Value]> = env.user_values.borrow().clone().into(); let frozen_hooks = Rc::new(std::mem::take(&mut *env.compiler_hooks.borrow_mut())); + + // Rewrite RTL scope addresses: Local(VirtualId) -> Global(GlobalIdx) + // so the Binder never needs to do this translation at resolve time. + { + let mut scopes = env.root_scopes.borrow_mut(); + for info in scopes[0].locals.values_mut() { + if let Address::Local(vid) = info.addr { + info.addr = Address::Global(GlobalIdx(vid.0)); + } + } + } + env.rtl = Rc::new(Rtl { scope: env.root_scopes.borrow()[0].clone(), slot_count: rtl_slot_count, @@ -243,7 +250,6 @@ impl Environment { root_purity: Rc::new(RefCell::new(rtl.purity.clone())), rtl_values: Rc::clone(&rtl.values), // O(1) Rc increment — no clone of RTL values! user_values: Rc::new(RefCell::new(Vec::new())), - fixed_scope_idx: 0, root_scopes: Rc::new(RefCell::new(scopes)), root_slot_count: Rc::new(RefCell::new(rtl.slot_count)), function_registry: Rc::new(RefCell::new(HashMap::new())), @@ -318,7 +324,6 @@ impl Environment { root_types: self.root_types.clone(), globals: self.global_store(), root_purity: self.root_purity.clone(), - fixed_scope_idx: self.fixed_scope_idx, compiler_hooks: Rc::clone(&self.rtl.compiler_hooks), }; MacroExpander::new(self.macro_registry.borrow().clone(), evaluator) @@ -453,7 +458,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, &expanded_ast, diagnostics) { Ok(res) => res, Err(e) => { diagnostics.push_error(e, None); diff --git a/src/ast/rtl/prelude.myc b/src/ast/rtl/prelude.myc index 4178b72..ce7e9cc 100644 --- a/src/ast/rtl/prelude.myc +++ b/src/ast/rtl/prelude.myc @@ -2,7 +2,7 @@ ;; Standard macros and core utilities. (do - (def func1 (print "Hello World")) + (def func1 (fn [] (print "Hello World"))) (macro while [cond body] `((fn [] (if ~cond