diff --git a/.gitignore b/.gitignore index 8a8598d..b3c1d0b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,2 @@ /target Delphi -gemini.md diff --git a/examples/macro_hygiene.myc b/examples/macro_hygiene.myc new file mode 100644 index 0000000..8ca0e9d --- /dev/null +++ b/examples/macro_hygiene.myc @@ -0,0 +1,7 @@ +;; Output: 5 +(do +(def y 1) +(macro m1 [x] `(do (def y 10) (assign y 4))) +(m1 8) +(+ y (m1 8)) +) diff --git a/gemini.md b/gemini.md new file mode 100644 index 0000000..81094f4 --- /dev/null +++ b/gemini.md @@ -0,0 +1,40 @@ +## Rust-Portierung des Delphi-AST-Compilers + +* Dieses Repository enthält den Rust-Port des Delphi-Compilers. +* Das Ziel ist, die alte Delphi-Codebase nach Rust zu portieren. +* Die alte Delphi-Codebase ist nicht zu verändern. + +Wichtig: zentraler Einstiegspunkt ist +@Delphi/Myc.Ast.Environment.pas +und die dort referenzierten Units. + +### Design + +* Der AST soll 1st class citizen sein. Das Skript ist nur eine mögliche Art, ihn darzustellen. +* Ein geplante Darstellung des AST ist vollständige grafische Visualisierung. +* Es wird eine DSL für Finanzanalyse. +* Aufgrund der geforderten Visualisierbarkeit muss der untyped AST (das, was der User bearbeiten kann) möglichst simpel sein. Komplexität muss vom System übernommen werden. +* Closures sind Key-Feature. + +### Concurrency + +* Die Root-Scopes sind die Grenze für Multithreading. Nichts verlässt den Root-Scope und alles innerhalb des Scope ist Single-Threaded. +* Globals sind nach dem Bootstrapping immutable. +* Das das Skript single-threaded ist, kann auf atomare Operationen (Mutex, Arc) verzichtet werden! + +### Spezielle Datentypen + +* Serien sind "unendliche" Queues mit maximaler Länge (Lookback). Serie[0] ist das zuletzt gepushte Item. +* Streams sind virtuelle Konfigurationen aus Serien, die in der Lage sind einen neuen Item-Record zu propagieren. +* Pipes sind Streams mit einer weiteren Funktion: Sie können einen Stream als Input akzeptieren und einen anderen Stream als Output produzieren. + +## Portierungsregeln + +* Wir wollen die Sprachfeatures von Rust nutzen. +* Der Code soll aber so gut wie möglich nach Rust-Style-Guidelines geschrieben werden. +* Dokumentation nach Rust-Regeln. + +## Interaktion + +* Ich kann Rust noch nicht so gut. Erkläre mir, was du machst und welche Konzepte du nutzt. Ich will was dazulernen. +* Wenn clippy etwas vorschlägt, von dem du glaubst, dass es nicht stabil ist, dann überprüfe die aktuelle Version. Wahrscheinlich ist das Feature mittlerweile im trunk. Wir gehen immer davon aus, dass clippy recht hat. diff --git a/src/ast/compiler/binder.rs b/src/ast/compiler/binder.rs index 8a4cf8b..3c0d056 100644 --- a/src/ast/compiler/binder.rs +++ b/src/ast/compiler/binder.rs @@ -1,7 +1,7 @@ use std::collections::{HashMap, BTreeMap}; use std::rc::Rc; use std::cell::RefCell; -use crate::ast::nodes::{Node, UntypedKind}; +use crate::ast::nodes::{Node, UntypedKind, Symbol}; use crate::ast::compiler::bound_nodes::{BoundKind, Address}; use crate::ast::types::{Identity, StaticType, Value}; @@ -13,7 +13,7 @@ struct LocalInfo { #[derive(Debug, Clone)] struct CompilerScope { - locals: HashMap, + locals: HashMap, slot_count: u32, } @@ -25,18 +25,18 @@ impl CompilerScope { } } - fn define(&mut self, name: &str, ty: StaticType) -> Result { - if self.locals.contains_key(name) { - return Err(format!("Variable '{}' is already defined in this scope level.", name)); + fn define(&mut self, sym: &Symbol, ty: StaticType) -> Result { + if self.locals.contains_key(sym) { + return Err(format!("Variable '{}' is already defined in this scope level.", sym.name)); } let slot = self.slot_count; - self.locals.insert(name.to_string(), LocalInfo { slot, ty }); + self.locals.insert(sym.clone(), LocalInfo { slot, ty }); self.slot_count += 1; Ok(slot) } - fn resolve(&self, name: &str) -> Option { - self.locals.get(name).cloned() + fn resolve(&self, sym: &Symbol) -> Option { + self.locals.get(sym).cloned() } } @@ -65,18 +65,18 @@ impl FunctionCompiler { pub struct Binder { functions: Vec, - // Globals mapping: Name -> (Index, Type) - globals: Rc>>, + // Globals mapping: Symbol -> (Index, Type) + globals: Rc>>, // Map of Declaration Identity -> List of Lambda Identities that capture it capture_map: HashMap>, } impl Binder { - pub fn new(globals: Rc>>) -> Self { + pub fn new(globals: Rc>>) -> Self { Self::with_boxed(globals, HashMap::new()) } - fn with_boxed(globals: Rc>>, captures: HashMap>) -> Self { + fn with_boxed(globals: Rc>>, captures: HashMap>) -> Self { let mut binder = Self { functions: Vec::new(), globals, @@ -86,7 +86,7 @@ impl Binder { binder } - pub fn bind_root(globals: Rc>>, node: &Node) -> Result, String> { + pub fn bind_root(globals: Rc>>, node: &Node) -> Result, String> { let captures = crate::ast::compiler::upvalues::UpvalueAnalyzer::analyze(node); let mut binder = Self::with_boxed(globals, captures); binder.bind(node) @@ -100,8 +100,8 @@ impl Binder { Ok(self.make_node(node.identity.clone(), BoundKind::Constant(v.clone()), ty)) }, - UntypedKind::Identifier(name) => { - let (addr, ty) = self.resolve_variable(name)?; + UntypedKind::Identifier(sym) => { + let (addr, ty) = self.resolve_variable(sym)?; Ok(self.make_node(node.identity.clone(), BoundKind::Get(addr), ty)) }, @@ -138,11 +138,11 @@ impl Binder { // 1. Pre-declare name to support recursion let slot_or_idx = if self.functions.len() == 1 { let mut globals = self.globals.borrow_mut(); - if globals.contains_key(name.as_ref()) { - return Err(format!("Global variable '{}' is already defined.", name)); + if globals.contains_key(name) { + return Err(format!("Global variable '{}' is already defined.", name.name)); } let idx = globals.len() as u32; - globals.insert(name.to_string(), (idx, StaticType::Any)); + globals.insert(name.clone(), (idx, StaticType::Any)); idx } else { let current_fn = self.functions.last_mut().unwrap(); @@ -157,7 +157,7 @@ impl Binder { if self.functions.len() == 1 { { let mut globals = self.globals.borrow_mut(); - if let Some(entry) = globals.get_mut(name.as_ref()) { + if let Some(entry) = globals.get_mut(name) { entry.1 = ty.clone(); } } @@ -168,7 +168,7 @@ impl Binder { } else { { let current_fn = self.functions.last_mut().unwrap(); - if let Some(info) = current_fn.scope.locals.get_mut(name.as_ref()) { + if let Some(info) = current_fn.scope.locals.get_mut(name) { info.ty = ty.clone(); } } @@ -184,8 +184,8 @@ impl Binder { UntypedKind::Assign { target, value } => { let val_node = self.bind(value)?; - if let UntypedKind::Identifier(name) = &target.kind { - let (addr, target_ty) = self.resolve_variable(name)?; + if let UntypedKind::Identifier(sym) = &target.kind { + let (addr, target_ty) = self.resolve_variable(sym)?; // Type check: value must be compatible with target if target_ty != StaticType::Any && val_node.ty != StaticType::Any && target_ty != val_node.ty { @@ -322,17 +322,17 @@ impl Binder { } } - fn resolve_variable(&mut self, name: &str) -> Result<(Address, StaticType), String> { + fn resolve_variable(&mut self, sym: &Symbol) -> Result<(Address, StaticType), String> { let current_fn_idx = self.functions.len() - 1; // 1. Try local in current function - if let Some(info) = self.functions[current_fn_idx].scope.resolve(name) { + if let Some(info) = self.functions[current_fn_idx].scope.resolve(sym) { return Ok((Address::Local(info.slot), info.ty)); } // 2. Try enclosing scopes (capture chain) for i in (0..current_fn_idx).rev() { - if let Some(info) = self.functions[i].scope.resolve(name) { + if let Some(info) = self.functions[i].scope.resolve(sym) { let mut addr = Address::Local(info.slot); let ty = info.ty.clone(); @@ -345,11 +345,20 @@ impl Binder { // 3. Try Global let globals = self.globals.borrow(); - if let Some((idx, ty)) = globals.get(name) { + if let Some((idx, ty)) = globals.get(sym) { return Ok((Address::Global(*idx), ty.clone())); } - Err(format!("Undefined variable '{}'", name)) + // 4. Global Fallback: If not found with context, try without context + // (Allows macros to access global built-ins like *, +, etc.) + if sym.context.is_some() { + let fallback_sym = Symbol { name: sym.name.clone(), context: None }; + if let Some((idx, ty)) = globals.get(&fallback_sym) { + return Ok((Address::Global(*idx), ty.clone())); + } + } + + Err(format!("Undefined variable '{}'", sym.name)) } fn make_node(&self, identity: Identity, kind: BoundKind, ty: StaticType) -> Node { @@ -426,4 +435,22 @@ mod tests { assert!(result.is_err()); assert!(result.unwrap_err().contains("already defined")); } + + #[test] + fn test_repro_global_redefinition() { + let globals = Rc::new(RefCell::new(HashMap::new())); + + // First run: defines 'x' + let source1 = "(def x 1)"; + let untyped1 = Parser::new(source1).unwrap().parse_expression().unwrap(); + assert!(Binder::bind_root(globals.clone(), &untyped1).is_ok()); + + // Second run: attempts to redefine 'x' in the same global environment + let source2 = "(def x 2)"; + let untyped2 = Parser::new(source2).unwrap().parse_expression().unwrap(); + let result = Binder::bind_root(globals.clone(), &untyped2); + + assert!(result.is_err()); + assert!(result.unwrap_err().contains("already defined")); + } } diff --git a/src/ast/compiler/macros.rs b/src/ast/compiler/macros.rs index 58d228e..694558e 100644 --- a/src/ast/compiler/macros.rs +++ b/src/ast/compiler/macros.rs @@ -1,17 +1,31 @@ use std::collections::HashMap; use std::rc::Rc; -use crate::ast::nodes::{Node, UntypedKind}; +use crate::ast::nodes::{Node, UntypedKind, Symbol}; use crate::ast::types::{Value, Identity}; /// Trait for evaluating expressions during macro expansion. -/// Similar to TMacroEvaluatorProc in the Delphi implementation. pub trait MacroEvaluator { fn evaluate(&self, node: &Node, bindings: &HashMap, Node>) -> Result; } +/// Internal state for template expansion (Hygiene context + Parameter binding) +struct ExpansionState<'a> { + bindings: &'a HashMap, Node>, + /// The identity of the current expansion instance, used to "color" internal symbols. + expansion_id: Identity, +} + +type MacroMap = HashMap, (Vec>, Node)>; + /// A registry for macro declarations. pub struct MacroRegistry { - scopes: Vec, (Vec>, Node)>>, + scopes: Vec, +} + +impl Default for MacroRegistry { + fn default() -> Self { + Self::new() + } } impl MacroRegistry { @@ -64,7 +78,8 @@ impl MacroExpander { fn expand_recursive(&mut self, node: Node) -> Result, String> { match node.kind { UntypedKind::MacroDecl { name, params, body } => { - self.registry.define(name, params, *body); + let p_names: Vec> = params.into_iter().map(|s| s.name).collect(); + self.registry.define(name.name, p_names, *body); Ok(Node { identity: node.identity, kind: UntypedKind::Nop, @@ -73,15 +88,14 @@ impl MacroExpander { } UntypedKind::Call { callee, args } => { - if let UntypedKind::Identifier(ref name) = callee.kind { - if let Some((params, body)) = self.registry.lookup(name) { + if let UntypedKind::Identifier(ref sym) = callee.kind + && let Some((params, body)) = self.registry.lookup(&sym.name) { let mut expanded_args = Vec::new(); for arg in args { expanded_args.push(self.expand_recursive(arg)?); } - let expanded = self.expand_call(node.identity.clone(), name, params, expanded_args, body)?; + let expanded = self.expand_call(node.identity.clone(), &sym.name, params, expanded_args, body)?; return self.expand_recursive(expanded); - } } let expanded_callee = self.expand_recursive(*callee)?; @@ -208,9 +222,15 @@ impl MacroExpander { bindings.insert(p, a); } + // AST-Authority: Substitution and Hygiene ONLY happen if there is a Template node. let expanded_body = if let UntypedKind::Template(inner) = body.kind { - self.expand_template(*inner, &bindings)? + let mut state = ExpansionState { + bindings: &bindings, + expansion_id: identity.clone(), + }; + self.expand_template(*inner, &mut state)? } else { + // Static AST fragment: No substitution, no hygiene. body }; @@ -219,10 +239,10 @@ impl MacroExpander { kind: UntypedKind::Call { callee: Box::new(Node { identity: identity.clone(), - kind: UntypedKind::Identifier(Rc::from(name)), + kind: UntypedKind::Identifier(Symbol::from(name)), ty: (), }), - args: bindings.iter().map(|(_, v)| v.clone()).collect(), + args: bindings.into_values().collect(), }, ty: (), }; @@ -237,25 +257,62 @@ impl MacroExpander { }) } - fn expand_template(&self, node: Node, bindings: &HashMap, Node>) -> Result, String> { + fn expand_template(&self, node: Node, state: &mut ExpansionState) -> Result, String> { match node.kind { + UntypedKind::Identifier(mut sym) => { + // Inside a template, all internal identifiers are colored. + sym.context = Some(state.expansion_id.clone()); + Ok(Node { identity: node.identity, kind: UntypedKind::Identifier(sym), ty: () }) + } + UntypedKind::Placeholder(inner) => { - let val = self.evaluator.evaluate(&inner, bindings)?; + // Break out of template for substitution/evaluation + if let UntypedKind::Identifier(ref sym) = inner.kind + && let Some(arg) = state.bindings.get(&sym.name) { + return Ok(arg.clone()); + } + let val = self.evaluator.evaluate(&inner, state.bindings)?; Ok(self.value_to_node(val, node.identity)) } - UntypedKind::Splice(_) => { - Err("Splice node found outside of a container context".to_string()) + UntypedKind::Splice(inner) => { + // Splicing is also a form of substitution + if let UntypedKind::Identifier(ref sym) = inner.kind + && let Some(arg) = state.bindings.get(&sym.name) { + return Ok(arg.clone()); + } + let val = self.evaluator.evaluate(&inner, state.bindings)?; + Ok(self.value_to_node(val, node.identity)) + } + + UntypedKind::Def { mut name, value } => { + name.context = Some(state.expansion_id.clone()); + let val = self.expand_template(*value, state)?; + Ok(Node { + identity: node.identity, + kind: UntypedKind::Def { name, value: Box::new(val) }, + ty: (), + }) + } + + UntypedKind::Assign { target, value } => { + let target = self.expand_template(*target, state)?; + let value = self.expand_template(*value, state)?; + Ok(Node { + identity: node.identity, + kind: UntypedKind::Assign { target: Box::new(target), value: Box::new(value) }, + ty: (), + }) } UntypedKind::Tuple { elements } => { let mut new_elements = Vec::new(); for e in elements { if let UntypedKind::Splice(ref inner) = e.kind { - let val = self.evaluator.evaluate(inner, bindings)?; + let val = self.evaluator.evaluate(inner, state.bindings)?; self.handle_splice_value(val, node.identity.clone(), &mut new_elements)?; } else { - new_elements.push(self.expand_template(e, bindings)?); + new_elements.push(self.expand_template(e, state)?); } } Ok(Node { @@ -269,10 +326,10 @@ impl MacroExpander { let mut new_exprs = Vec::new(); for e in exprs { if let UntypedKind::Splice(ref inner) = e.kind { - let val = self.evaluator.evaluate(inner, bindings)?; + let val = self.evaluator.evaluate(inner, state.bindings)?; self.handle_splice_value(val, node.identity.clone(), &mut new_exprs)?; } else { - new_exprs.push(self.expand_template(e, bindings)?); + new_exprs.push(self.expand_template(e, state)?); } } Ok(Node { @@ -285,7 +342,7 @@ impl MacroExpander { UntypedKind::Map { entries } => { let mut new_entries = Vec::new(); for (k, v) in entries { - new_entries.push((self.expand_template(k, bindings)?, self.expand_template(v, bindings)?)); + new_entries.push((self.expand_template(k, state)?, self.expand_template(v, state)?)); } Ok(Node { identity: node.identity, @@ -295,14 +352,14 @@ impl MacroExpander { } UntypedKind::Call { callee, args } => { - let expanded_callee = self.expand_template(*callee, bindings)?; + let expanded_callee = self.expand_template(*callee, state)?; let mut expanded_args = Vec::new(); for arg in args { if let UntypedKind::Splice(ref inner) = arg.kind { - let val = self.evaluator.evaluate(inner, bindings)?; + let val = self.evaluator.evaluate(inner, state.bindings)?; self.handle_splice_value(val, node.identity.clone(), &mut expanded_args)?; } else { - expanded_args.push(self.expand_template(arg, bindings)?); + expanded_args.push(self.expand_template(arg, state)?); } } Ok(Node { @@ -313,10 +370,10 @@ impl MacroExpander { } UntypedKind::If { cond, then_br, else_br } => { - let cond = self.expand_template(*cond, bindings)?; - let then_br = self.expand_template(*then_br, bindings)?; + let cond = self.expand_template(*cond, state)?; + let then_br = self.expand_template(*then_br, state)?; let else_br = if let Some(e) = else_br { - Some(Box::new(self.expand_template(*e, bindings)?)) + Some(Box::new(self.expand_template(*e, state)?)) } else { None }; @@ -327,6 +384,18 @@ impl MacroExpander { }) } + UntypedKind::Lambda { mut params, body } => { + for p in &mut params { + p.context = Some(state.expansion_id.clone()); + } + let body = self.expand_template(Node::clone(&body), state)?; + Ok(Node { + identity: node.identity, + kind: UntypedKind::Lambda { params, body: Rc::new(body) }, + ty: (), + }) + } + _ => Ok(node), } } @@ -369,28 +438,51 @@ impl MacroExpander { #[cfg(test)] mod tests { use super::*; + use std::cell::RefCell; use crate::ast::parser::Parser; - use crate::ast::types::Value; + use crate::ast::types::{Value, StaticType, Object}; + use crate::ast::compiler::Binder; struct SimpleEvaluator; impl MacroEvaluator for SimpleEvaluator { fn evaluate(&self, node: &Node, bindings: &HashMap, Node>) -> Result { - if let UntypedKind::Identifier(ref name) = node.kind { - if let Some(arg) = bindings.get(name) { - return Ok(Value::Object(Rc::new(arg.clone()))); - } + if let UntypedKind::Identifier(ref sym) = node.kind + && let Some(arg) = bindings.get(&sym.name) { + return Ok(Value::Object(Rc::new(arg.clone()) as Rc)); } - // For real expressions, we would need Binder + VM here. Err(format!("SimpleEvaluator cannot evaluate complex expression: {:?}", node.kind)) } } #[test] - fn test_macro_expansion_unless() { + fn test_macro_expansion_hygiene() { let source = " (do - (macro unless [c b] `(if ~c void ~b)) - (unless true 42)) + (macro m [] `(def y 10)) + (m)) + "; + let mut parser = Parser::new(source).unwrap(); + let untyped = parser.parse_expression().unwrap(); + + let mut expander = MacroExpander::new(MacroRegistry::new(), SimpleEvaluator); + let expanded = expander.expand(untyped).unwrap(); + + if let UntypedKind::Block { exprs } = &expanded.kind { + if let UntypedKind::Expansion { expanded: result, call } = &exprs[1].kind { + if let UntypedKind::Def { name, .. } = &result.kind { + assert_eq!(name.context, Some(call.identity.clone())); + assert_eq!(name.name.as_ref(), "y"); + } else { panic!("Expected Def result, got {:?}", result.kind); } + } + } + } + + #[test] + fn test_macro_expansion_unless() { + let source = " + (do + (macro unless [c b] `(if ~c void ~b)) + (unless false 42)) "; let mut parser = Parser::new(source).unwrap(); let untyped = parser.parse_expression().unwrap(); @@ -399,28 +491,19 @@ mod tests { let expanded = expander.expand(untyped).unwrap(); if let UntypedKind::Block { exprs } = &expanded.kind { - assert_eq!(exprs.len(), 2); - assert!(matches!(exprs[0].kind, UntypedKind::Nop)); - if let UntypedKind::Expansion { call: _, expanded: result } = &exprs[1].kind { if let UntypedKind::If { cond, then_br, else_br } = &result.kind { if let UntypedKind::Constant(Value::Bool(b)) = &cond.kind { - assert_eq!(*b, true); - } else { panic!("Expected true, got {:?}", cond.kind); } + assert_eq!(*b, false); + } else { panic!("Expected false, got {:?}", cond.kind); } assert!(matches!(then_br.kind, UntypedKind::Constant(Value::Void))); if let Some(eb) = else_br { if let UntypedKind::Constant(Value::Int(n)) = &eb.kind { assert_eq!(*n, 42); } else { panic!("Expected 42, got {:?}", eb.kind); } } else { panic!("Else branch missing"); } - } else { - panic!("Expansion result should be an IF, got {:?}", result.kind); } - } else { - panic!("Second expression should be an Expansion, got {:?}", exprs[1].kind); } - } else { - panic!("Root should be a Block"); } } @@ -443,11 +526,71 @@ mod tests { if let UntypedKind::Tuple { elements } = &result.kind { assert_eq!(elements.len(), 5); if let UntypedKind::Constant(Value::Int(n)) = &elements[0].kind { assert_eq!(*n, 0); } - if let UntypedKind::Constant(Value::Int(n)) = &elements[1].kind { assert_eq!(*n, 1); } if let UntypedKind::Constant(Value::Int(n)) = &elements[4].kind { assert_eq!(*n, 4); } - } else { panic!("Expected Tuple result, got {:?}", result.kind); } - } else { panic!("Expected Expansion for t, got {:?}", value.kind); } + } + } } } } + + #[test] + fn test_repro_macro_global_lookup() { + let source = " + (do + (macro square [x] `(* ~x ~x)) + (square 3)) + "; + let mut parser = Parser::new(source).unwrap(); + let untyped = parser.parse_expression().unwrap(); + + let mut expander = MacroExpander::new(MacroRegistry::new(), SimpleEvaluator); + let expanded = expander.expand(untyped).unwrap(); + + let mut global_names = HashMap::new(); + global_names.insert(Symbol::from("*"), (0, StaticType::Any)); + let globals = Rc::new(RefCell::new(global_names)); + + let result = Binder::bind_root(globals, &expanded); + assert!(result.is_ok(), "Should find global '*' Error: {:?}", result.err()); + } + + #[test] + fn test_repro_macro_hygiene_success() { + let source = " + (do + (def y 1) + (macro m [] `(def y 10)) + (m) + y) + "; + let mut parser = Parser::new(source).unwrap(); + let untyped = parser.parse_expression().unwrap(); + + let mut expander = MacroExpander::new(MacroRegistry::new(), SimpleEvaluator); + 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()); + } + + #[test] + fn test_repro_macro_hygiene_clash_fixed() { + let source = " + (do + (def y 1) + (macro m1 [x] `(do (def y 10) (assign y 4))) + (m1 8) + y) + "; + let mut parser = Parser::new(source).unwrap(); + let untyped = parser.parse_expression().unwrap(); + + let mut expander = MacroExpander::new(MacroRegistry::new(), SimpleEvaluator); + 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(), "Explicit Hygiene with Backticks failed: {:?}", bound.err()); + } } diff --git a/src/ast/compiler/upvalues.rs b/src/ast/compiler/upvalues.rs index 0f43f81..f856eda 100644 --- a/src/ast/compiler/upvalues.rs +++ b/src/ast/compiler/upvalues.rs @@ -1,6 +1,5 @@ use std::collections::{HashMap, HashSet}; -use std::rc::Rc; -use crate::ast::nodes::{Node, UntypedKind}; +use crate::ast::nodes::{Node, UntypedKind, Symbol}; use crate::ast::types::Identity; /// Analyzes the AST to find which lambdas capture which variable declarations. @@ -22,15 +21,15 @@ impl UpvalueAnalyzer { fn visit( node: &Node, - scopes: &mut Vec, Identity>>, + scopes: &mut Vec>, capture_map: &mut HashMap>, current_lambda: Option ) { match &node.kind { - UntypedKind::Identifier(name) => { + 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(name) { + if let Some(decl_id) = scope.get(sym) { if depth > 0 { // Captured from an outer scope! if let Some(lambda_id) = ¤t_lambda { diff --git a/src/ast/environment.rs b/src/ast/environment.rs index c81d549..633c74d 100644 --- a/src/ast/environment.rs +++ b/src/ast/environment.rs @@ -2,7 +2,7 @@ use std::rc::Rc; use std::cell::RefCell; use std::collections::HashMap; use crate::ast::types::{Value, StaticType, Object}; -use crate::ast::nodes::{Node, UntypedKind}; +use crate::ast::nodes::{Node, UntypedKind, Symbol}; use crate::ast::parser::Parser; use crate::ast::compiler::binder::Binder; use crate::ast::compiler::BoundKind; @@ -13,29 +13,26 @@ use crate::ast::compiler::dumper::Dumper; use crate::ast::compiler::macros::{MacroExpander, MacroRegistry, MacroEvaluator}; pub struct Environment { - pub global_names: Rc>>, + pub global_names: Rc>>, pub global_values: Rc>>, pub debug_mode: bool, } /// Evaluator used during macro expansion to allow compile-time logic. struct RuntimeMacroEvaluator { - global_names: Rc>>, + global_names: Rc>>, global_values: Rc>>, } impl MacroEvaluator for RuntimeMacroEvaluator { fn evaluate(&self, node: &Node, bindings: &HashMap, Node>) -> Result { // 1. Check if it's a simple parameter substitution - if let UntypedKind::Identifier(name) = &node.kind { - if let Some(arg_node) = bindings.get(name) { + if let UntypedKind::Identifier(sym) = &node.kind + && let Some(arg_node) = bindings.get(&sym.name) { return Ok(Value::Object(Rc::new(arg_node.clone()) as Rc)); - } } // 2. Full evaluation for complex compile-time expressions - // Note: This evaluator currently doesn't see macro parameters in complex expressions - // unless they are explicitly substituted. For now, we support global-only logic. let bound_ast = Binder::bind_root(self.global_names.clone(), node)?; let mut vm = VM::new(self.global_values.clone()); vm.run(&bound_ast) @@ -76,7 +73,7 @@ impl Environment { let mut values = self.global_values.borrow_mut(); let idx = values.len() as u32; - names.insert(name.to_string(), (idx, ty)); + names.insert(Symbol::from(name), (idx, ty)); values.push(Value::Function(Rc::new(func))); } diff --git a/src/ast/nodes.rs b/src/ast/nodes.rs index 90fa71a..9c605fb 100644 --- a/src/ast/nodes.rs +++ b/src/ast/nodes.rs @@ -3,6 +3,27 @@ use std::fmt::Debug; use std::any::Any; use crate::ast::types::{Identity, Value, Object}; +/// A name with an optional context for macro hygiene. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct Symbol { + pub name: Rc, + /// Points to the identity of the Expansion node if this symbol + /// was created/referenced inside a macro expansion. + pub context: Option, +} + +impl From> for Symbol { + fn from(name: Rc) -> Self { + Self { name, context: None } + } +} + +impl From<&str> for Symbol { + fn from(name: &str) -> Self { + Self { name: Rc::from(name), context: None } + } +} + /// A generic AST Node wrapper to preserve identity and metadata #[derive(Debug, Clone)] pub struct Node { @@ -36,14 +57,14 @@ impl Clone for Box { pub enum UntypedKind { Nop, Constant(Value), - Identifier(Rc), + Identifier(Symbol), If { cond: Box>, then_br: Box>, else_br: Option>>, }, Def { - name: Rc, + name: Symbol, value: Box>, }, Assign { @@ -51,7 +72,7 @@ pub enum UntypedKind { value: Box>, }, Lambda { - params: Vec>, + params: Vec, body: Rc>, }, Call { @@ -69,8 +90,8 @@ pub enum UntypedKind { }, /// A macro declaration that can be expanded at compile time. MacroDecl { - name: Rc, - params: Vec>, + name: Symbol, + params: Vec, body: Box>, }, /// A template for AST nodes, allowing for substitutions. (Quasiquote) diff --git a/src/ast/parser.rs b/src/ast/parser.rs index 12d935c..176a316 100644 --- a/src/ast/parser.rs +++ b/src/ast/parser.rs @@ -1,7 +1,7 @@ use std::rc::Rc; use crate::ast::lexer::{Lexer, Token, TokenKind}; use crate::ast::types::{Identity, NodeIdentity, Value, Keyword}; -use crate::ast::nodes::{Node, UntypedKind}; +use crate::ast::nodes::{Node, UntypedKind, Symbol}; pub struct Parser<'a> { lexer: Lexer<'a>, @@ -95,7 +95,7 @@ impl<'a> Parser<'a> { "true" => UntypedKind::Constant(Value::Bool(true)), "false" => UntypedKind::Constant(Value::Bool(false)), "void" => UntypedKind::Constant(Value::Void), - _ => UntypedKind::Identifier(id), + _ => UntypedKind::Identifier(id.into()), } } _ => return Err(format!("Unexpected token in atom: {:?} at {:?}", token.kind, token.location)), @@ -114,8 +114,8 @@ impl<'a> Parser<'a> { let head = self.parse_expression()?; - let result = if let UntypedKind::Identifier(name) = &head.kind { - match name.as_ref() { + let result = if let UntypedKind::Identifier(ref sym) = head.kind { + match sym.name.as_ref() { "if" => self.parse_if(identity), "fn" => self.parse_fn(identity), "def" => self.parse_def(identity), @@ -151,7 +151,7 @@ impl<'a> Parser<'a> { fn parse_def(&mut self, identity: Identity) -> Result, String> { let name_node = self.parse_expression()?; let name = match name_node.kind { - UntypedKind::Identifier(s) => s, + UntypedKind::Identifier(sym) => sym, _ => return Err("Expected identifier for def name".to_string()), }; @@ -205,7 +205,7 @@ impl<'a> Parser<'a> { fn parse_macro_decl(&mut self, identity: Identity) -> Result, String> { let name_node = self.parse_expression()?; let name = match name_node.kind { - UntypedKind::Identifier(s) => s, + UntypedKind::Identifier(sym) => sym, _ => return Err("Expected identifier for macro name".to_string()), }; let params = self.parse_param_vector()?; @@ -217,7 +217,7 @@ impl<'a> Parser<'a> { }) } - fn parse_param_vector(&mut self) -> Result>, String> { + fn parse_param_vector(&mut self) -> Result, String> { if *self.peek() != TokenKind::LeftBracket { return Err(format!("Expected parameter vector [...] for fn, found {:?}", self.peek())); } @@ -227,7 +227,7 @@ impl<'a> Parser<'a> { while *self.peek() != TokenKind::RightBracket { let token = self.advance()?; match token.kind { - TokenKind::Identifier(name) => params.push(name), + TokenKind::Identifier(name) => params.push(name.into()), _ => return Err(format!("Expected identifier in param vector, found {:?}", token.kind)), } } @@ -313,7 +313,7 @@ impl<'a> Parser<'a> { fn make_id_node(&self, name: &str, identity: Identity) -> Node { Node { identity, - kind: UntypedKind::Identifier(Rc::from(name)), + kind: UntypedKind::Identifier(Symbol::from(name)), ty: (), } } diff --git a/src/ast/tester.rs b/src/ast/tester.rs index 6b5dcdd..292cf0f 100644 --- a/src/ast/tester.rs +++ b/src/ast/tester.rs @@ -20,10 +20,10 @@ pub struct BenchmarkResult { pub fn run_functional_tests() -> Vec { let mut results = Vec::new(); let entries = fs::read_dir("examples").unwrap(); - let env = Environment::new(); let output_re = Regex::new(r";; Output: (.*)").unwrap(); for entry in entries.filter_map(|e| e.ok()) { + let env = Environment::new(); // Fresh environment per test file let path = entry.path(); if path.extension().is_some_and(|ext| ext == "myc") { let content = fs::read_to_string(&path).unwrap(); diff --git a/src/ast/vm.rs b/src/ast/vm.rs index 8e97000..aaa751b 100644 --- a/src/ast/vm.rs +++ b/src/ast/vm.rs @@ -200,7 +200,7 @@ macro_rules! dispatch_eval { loop { match func_val { - Value::Function(f) => return Ok(f(arg_vals)), + Value::Function(f) => break Ok(f(arg_vals)), Value::Object(obj) => { if let Some(closure) = obj.as_any().downcast_ref::() { let old_stack_top = $self.stack.len(); @@ -225,14 +225,13 @@ macro_rules! dispatch_eval { arg_vals = next_args; continue; }, - Ok(val) => return Ok(val), - Err(e) => return Err(e), + res => break res, } } else { - return Err(format!("Object is not a closure: {}", obj.type_name())); + break Err(format!("Object is not a closure: {}", obj.type_name())); } }, - _ => return Err(format!("Attempt to call non-function: {}", func_val)), + _ => break Err(format!("Attempt to call non-function: {}", func_val)), } } }, @@ -315,7 +314,13 @@ impl VM { /// The observed path for debugging. fn eval_observed(&mut self, observer: &mut O, node: &Node) -> Result { observer.before_eval(self, node); - let result = dispatch_eval!(self, node, eval_observed, observer); + + // Wrap in a closure to ensure after_eval is called even on early returns (e.g. from ?) + let wrapper = |vm: &mut Self, obs: &mut O| -> Result { + dispatch_eval!(vm, node, eval_observed, obs) + }; + + let result = wrapper(self, observer); observer.after_eval(self, node, &result); result } diff --git a/src/main.rs b/src/main.rs index 7cda528..94c4a7e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -87,6 +87,9 @@ impl CompilerApp { let start_total = std::time::Instant::now(); self.trace_logs.clear(); + // Reset environment for a fresh run + self.env = Environment::new(); + let res = (|| -> Result<(myc::ast::types::Value, std::time::Duration, std::time::Duration, std::time::Duration), String> { let start_compile = std::time::Instant::now(); let compiled = self.env.compile(&self.source_code)?; @@ -110,11 +113,10 @@ impl CompilerApp { } for line in &mut logs { - if line.len() > 255 { - if let Some((idx, _)) = line.char_indices().nth(255) { + if line.len() > 255 + && let Some((idx, _)) = line.char_indices().nth(255) { line.truncate(idx); line.push_str("..."); - } } } @@ -154,6 +156,7 @@ impl CompilerApp { } fn dump_ast(&mut self) { + self.env = Environment::new(); match self.env.dump_ast(&self.source_code) { Ok(dump) => { self.output_log = format!( @@ -367,6 +370,7 @@ impl eframe::App for CompilerApp { // Log Output Area egui::ScrollArea::vertical() .id_salt("output_log_scroll") + .auto_shrink([false, true]) // Don't shrink horizontally, fill width .show(ui, |ui| { match self.active_tab { AppTab::Output => { @@ -379,14 +383,12 @@ impl eframe::App for CompilerApp { ); }, AppTab::Trace => { - egui::ScrollArea::vertical() - .id_salt("trace_log_scroll") - .show(ui, |ui| { - ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace); - for line in &self.trace_logs { - ui.label(line); - } - }); + ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace); + ui.with_layout(egui::Layout::top_down_justified(egui::Align::LEFT), |ui| { + for line in &self.trace_logs { + ui.label(line); + } + }); } } });