From 5bc69c267b21ca943b6c70ea837ab12e3fbd22d8 Mon Sep 17 00:00:00 2001 From: Michael Schimmel Date: Mon, 2 Mar 2026 23:12:51 +0100 Subject: [PATCH] Refactor Binder to use Diagnostics The Binder and its helper functions have been updated to accept and propagate a `Diagnostics` struct. This allows for better error reporting during the binding phase, enabling the compiler to continue processing even after encountering errors and collect all issues before halting. The `BoundKind::Error` node and `StaticType::Error` are introduced as "poison" nodes/types. These nodes indicate that an error occurred during compilation, preventing further valid processing of that specific AST fragment but allowing the compiler to continue with other parts of the code. The `Parser` has also been updated to return a `Diagnostics` struct, enabling it to report errors during the initial parsing stage while still attempting to build a partial AST. This adheres to the same error recovery strategy. --- src/ast/compiler/analyzer.rs | 1 + src/ast/compiler/binder.rs | 1373 +++++++++++----------- src/ast/compiler/bound_nodes.rs | 15 +- src/ast/compiler/captures.rs | 3 +- src/ast/compiler/dumper.rs | 3 + src/ast/compiler/macros.rs | 1635 ++++++++++++++------------- src/ast/compiler/optimizer/utils.rs | 2 +- src/ast/compiler/tco.rs | 1 + src/ast/compiler/type_checker.rs | 129 +-- src/ast/diagnostics.rs | 54 + src/ast/environment.rs | 119 +- src/ast/mod.rs | 1 + src/ast/nodes.rs | 2 + src/ast/parser.rs | 342 +++--- src/ast/types.rs | 5 +- src/ast/vm.rs | 1 + src/bin/ast.rs | 17 +- src/integration_test.rs | 1307 +++++++++++---------- src/utils/tester.rs | 2 +- 19 files changed, 2678 insertions(+), 2334 deletions(-) create mode 100644 src/ast/diagnostics.rs diff --git a/src/ast/compiler/analyzer.rs b/src/ast/compiler/analyzer.rs index 7f2e3ba..648c17d 100644 --- a/src/ast/compiler/analyzer.rs +++ b/src/ast/compiler/analyzer.rs @@ -306,6 +306,7 @@ impl<'a> Analyzer<'a> { } BoundKind::Extension(_) => (BoundKind::Nop, Purity::Impure), + BoundKind::Error => (BoundKind::Error, Purity::Impure), }; crate::ast::nodes::Node { diff --git a/src/ast/compiler/binder.rs b/src/ast/compiler/binder.rs index be0527e..9f66ddf 100644 --- a/src/ast/compiler/binder.rs +++ b/src/ast/compiler/binder.rs @@ -1,665 +1,708 @@ -use crate::ast::compiler::bound_nodes::{ - Address, BoundKind, BoundNode, DeclarationKind, GlobalIdx, LocalSlot, UpvalueIdx, -}; -use crate::ast::nodes::{Node, Symbol, UntypedKind}; -use crate::ast::types::{Identity, StaticType}; -use std::cell::RefCell; -use std::collections::HashMap; -use std::rc::Rc; - -#[derive(Debug, Clone)] -struct LocalInfo { - slot: LocalSlot, - 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. - _ty: StaticType, -} - -#[derive(Debug, Clone)] -struct CompilerScope { - locals: HashMap, - slot_count: u32, -} - -impl CompilerScope { - fn new() -> Self { - Self { - locals: HashMap::new(), - slot_count: 0, - } - } - - 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.", - sym.name - )); - } - let slot = LocalSlot(self.slot_count); - self.locals.insert( - sym.clone(), - LocalInfo { - slot, - identity, - _ty: StaticType::Any, - }, - ); - self.slot_count += 1; - Ok(slot) - } - - fn resolve(&self, sym: &Symbol) -> Option { - self.locals.get(sym).cloned() - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum ScopeKind { - Root, - Local, -} - -struct FunctionCompiler { - identity: Identity, - scope: CompilerScope, - upvalues: Vec
, - kind: ScopeKind, -} - -impl FunctionCompiler { - fn new(kind: ScopeKind, identity: Identity) -> Self { - Self { - identity, - scope: CompilerScope::new(), - upvalues: Vec::new(), - kind, - } - } - - fn define_variable( - &mut self, - name: &Symbol, - identity: Identity, - globals: &Rc>>, - ) -> Result { - match self.kind { - ScopeKind::Root => { - let mut globals_map = globals.borrow_mut(); - if globals_map.contains_key(name) { - return Err(format!( - "Global variable '{}' is already defined.", - name.name - )); - } - let idx = globals_map.len() as u32; - globals_map.insert(name.clone(), (GlobalIdx(idx), identity)); - Ok(Address::Global(GlobalIdx(idx))) - } - ScopeKind::Local => { - let slot = self.scope.define(name, identity)?; - Ok(Address::Local(slot)) - } - } - } - - fn add_upvalue(&mut self, addr: Address) -> UpvalueIdx { - if let Some(idx) = self.upvalues.iter().position(|&a| a == addr) { - return UpvalueIdx(idx as u32); - } - let idx = UpvalueIdx(self.upvalues.len() as u32); - self.upvalues.push(addr); - idx - } -} - -pub struct Binder { - functions: Vec, - // Globals mapping: Symbol -> (Index, DefinitionIdentity) - globals: Rc>>, - // Map of Declaration Identity -> List of Lambda Identities that capture it - capture_map: HashMap>, -} - -impl Binder { - pub fn new(globals: Rc>>) -> Self { - let mut binder = Self { - functions: Vec::new(), - globals, - capture_map: HashMap::new(), - }; - binder.functions.push(FunctionCompiler::new( - ScopeKind::Root, - crate::ast::types::NodeIdentity::new(crate::ast::types::SourceLocation { - line: 0, - col: 0, - }), - )); - binder - } - - pub fn bind_root( - globals: Rc>>, - node: &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, identity, &self.globals) - } - - pub fn bind(&mut self, node: &Node) -> Result { - match &node.kind { - UntypedKind::Nop => Ok(self.make_node(node.identity.clone(), BoundKind::Nop)), - UntypedKind::Constant(v) => { - Ok(self.make_node(node.identity.clone(), BoundKind::Constant(v.clone()))) - } - - UntypedKind::Identifier(sym) => { - let addr = self.resolve_variable(sym)?; - Ok(self.make_node( - node.identity.clone(), - BoundKind::Get { - addr, - name: sym.clone(), - }, - )) - } - - UntypedKind::Parameter(_) => { - Err("Unexpected 'Parameter' node in general binder context. This should be handled via 'bind_pattern'.".to_string()) - } - - UntypedKind::FieldAccessor(k) => { - Ok(self.make_node(node.identity.clone(), BoundKind::FieldAccessor(*k))) - } - - UntypedKind::If { - cond, - then_br, - else_br, - } => { - let cond = self.bind(cond)?; - let then_br = self.bind(then_br)?; - let mut else_br_bound = None; - if let Some(e) = else_br { - else_br_bound = Some(Box::new(self.bind(e)?)); - } - - Ok(self.make_node( - node.identity.clone(), - BoundKind::If { - cond: Box::new(cond), - then_br: Box::new(then_br), - else_br: else_br_bound, - }, - )) - } - - UntypedKind::Def { target, value } => { - // Special case: Single identifier (to support recursion) - 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)?; - - Ok(self.make_node( - node.identity.clone(), - BoundKind::Define { - name: name.clone(), - addr, - kind: crate::ast::compiler::bound_nodes::DeclarationKind::Variable, - value: Box::new(val_node), - captured_by: Vec::new(), // Will be filled by post-pass - }, - )) - } else { - // Complex Destructuring Pattern - // NOTE: Destructuring definitions are NOT recursive by default - // (the variables are only available AFTER the definition) - let val_node = self.bind(value)?; - let target_node = self.bind_pattern(target, DeclarationKind::Variable)?; - - Ok(self.make_node( - node.identity.clone(), - BoundKind::Destructure { - pattern: Box::new(target_node), - value: Box::new(val_node), - }, - )) - } - } - - UntypedKind::Assign { target, value } => { - let val_node = self.bind(value)?; - - if let UntypedKind::Identifier(sym) = &target.kind { - let addr = self.resolve_variable(sym)?; - Ok(self.make_node( - node.identity.clone(), - BoundKind::Set { - addr, - value: Box::new(val_node), - }, - )) - } else { - let target_node = self.bind_assign_pattern(target)?; - Ok(self.make_node( - node.identity.clone(), - BoundKind::Destructure { - pattern: Box::new(target_node), - value: Box::new(val_node), - }, - )) - } - } - - UntypedKind::Pipe { inputs, lambda } => { - let mut bound_inputs = Vec::with_capacity(inputs.len()); - for input in inputs { - bound_inputs.push(self.bind(input)?); - } - let bound_lambda = Box::new(self.bind(lambda.as_ref())?); - Ok(self.make_node( - node.identity.clone(), - BoundKind::Pipe { - inputs: bound_inputs, - lambda: bound_lambda, - out_type: crate::ast::types::StaticType::Any, - }, - )) - } - - UntypedKind::Lambda { params, body } => { - let identity = node.identity.clone(); - self.functions.push(FunctionCompiler::new( - ScopeKind::Local, - identity.clone(), - )); - - // 1. Bind the parameter pattern/tuple - let params_bound = self.bind_pattern(params, DeclarationKind::Parameter)?; - - // 2. Bind the body - let body_bound = self.bind(body)?; - - let compiled_fn = self.functions.pop().unwrap(); - - // 3. Static optimization: count total parameters needed in flat argument list - fn count_params(node: &BoundNode) -> Option { - match &node.kind { - BoundKind::Define { - kind: DeclarationKind::Parameter, - .. - } => Some(1), - BoundKind::Tuple { elements } => { - let mut total = 0; - for e in elements { - total += count_params(e)?; - } - Some(total) - } - BoundKind::Nop => Some(0), - _ => None, - } - } - - let positional_count = count_params(¶ms_bound); - - Ok(self.make_node( - identity, - BoundKind::Lambda { - params: Rc::new(params_bound), - upvalues: compiled_fn.upvalues, - body: Rc::new(body_bound), - positional_count, - }, - )) - } - - UntypedKind::Call { callee, args } => { - let callee = self.bind(callee)?; - let args = self.bind(args)?; - - Ok(self.make_node( - node.identity.clone(), - BoundKind::Call { - callee: Box::new(callee), - args: Box::new(args), - }, - )) - } - - UntypedKind::Again { args } => { - if self.functions.len() <= 1 { - return Err("'again' is only allowed inside a function or lambda.".to_string()); - } - let args = self.bind(args)?; - Ok(self.make_node( - node.identity.clone(), - BoundKind::Again { - args: Box::new(args), - }, - )) - } - - UntypedKind::Block { exprs } => { - let mut bound_exprs = Vec::new(); - for expr in exprs { - bound_exprs.push(self.bind(expr)?); - } - Ok(self.make_node( - node.identity.clone(), - BoundKind::Block { exprs: bound_exprs }, - )) - } - - UntypedKind::Tuple { elements } => { - let mut bound_elems = Vec::new(); - for e in elements { - bound_elems.push(self.bind(e)?); - } - Ok(self.make_node( - node.identity.clone(), - BoundKind::Tuple { - elements: bound_elems, - }, - )) - } - - UntypedKind::Record { fields } => { - let mut bound_values = Vec::new(); - let mut layout_fields = Vec::new(); - - for (k, v) in fields { - let key_node = self.bind(k)?; - let val_node = self.bind(v)?; - - if let BoundKind::Constant(crate::ast::types::Value::Keyword(kw)) = key_node.kind { - layout_fields.push((kw, crate::ast::types::StaticType::Any)); - } else { - return Err(format!("Record keys must be keywords, found at {:?}", key_node.identity.location)); - } - bound_values.push(val_node); - } - - let layout = crate::ast::types::RecordLayout::get_or_create(layout_fields); - - Ok(self.make_node( - node.identity.clone(), - BoundKind::Record { - layout, - values: bound_values, - }, - )) - } - - UntypedKind::Expansion { call, expanded } => { - let bound_expanded = self.bind(expanded)?; - Ok(self.make_node( - node.identity.clone(), - BoundKind::Expansion { - original_call: Rc::from(call.as_ref().clone()), - bound_expanded: Box::new(bound_expanded), - }, - )) - } - - UntypedKind::Template(_) - | UntypedKind::Placeholder(_) - | UntypedKind::Splice(_) - | UntypedKind::MacroDecl { .. } => Err(format!( - "Macro construct {:?} found in Binder. Macros must be expanded before binding.", - node.kind - )), - - UntypedKind::Extension(_) => { - // Future: Delegate to extension binder - Err("Custom extensions not supported in Binder yet".to_string()) - } - } - } - - fn resolve_variable(&mut self, sym: &Symbol) -> Result { - 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(sym) { - return Ok(Address::Local(info.slot)); - } - - // 2. Try enclosing scopes (capture chain) - for i in (0..current_fn_idx).rev() { - 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); - } - } - - // 3. Try Global - let globals = self.globals.borrow(); - if let Some((idx, _)) = globals.get(sym) { - return Ok(Address::Global(*idx)); - } - - // 4. Global Fallback - if sym.context.is_some() { - let fallback_sym = Symbol { - name: sym.name.clone(), - context: None, - }; - if let Some((idx, _)) = globals.get(&fallback_sym) { - return Ok(Address::Global(*idx)); - } - } - - Err(format!("Undefined variable '{}'", sym.name)) - } - - fn bind_pattern( - &mut self, - node: &Node, - kind: DeclarationKind, - ) -> Result { - match &node.kind { - UntypedKind::Parameter(sym) => { - let addr = self.declare_variable(sym, node.identity.clone(), kind)?; - - Ok(self.make_node( - node.identity.clone(), - BoundKind::Define { - name: sym.clone(), - addr, - kind, - value: Box::new(self.make_node(node.identity.clone(), BoundKind::Nop)), - captured_by: Vec::new(), // Filled by post-pass - }, - )) - } - UntypedKind::Tuple { elements } => { - let mut bound_elems = Vec::new(); - for e in elements { - bound_elems.push(self.bind_pattern(e, kind)?); - } - Ok(self.make_node( - node.identity.clone(), - BoundKind::Tuple { - elements: bound_elems, - }, - )) - } - _ => Err(format!("Invalid node in pattern: {:?}", node.kind)), - } - } - - fn bind_assign_pattern(&mut self, node: &Node) -> Result { - match &node.kind { - UntypedKind::Identifier(sym) => { - let addr = self.resolve_variable(sym)?; - Ok(self.make_node( - node.identity.clone(), - BoundKind::Set { - addr, - value: Box::new(self.make_node(node.identity.clone(), BoundKind::Nop)), - }, - )) - } - UntypedKind::Tuple { elements } => { - let mut bound_elems = Vec::new(); - for e in elements { - bound_elems.push(self.bind_assign_pattern(e)?); - } - Ok(self.make_node( - node.identity.clone(), - BoundKind::Tuple { - elements: bound_elems, - }, - )) - } - _ => Err(format!( - "Invalid node in assignment pattern: {:?}", - node.kind - )), - } - } - - fn make_node(&self, identity: Identity, kind: BoundKind<()>) -> BoundNode { - Node { - identity, - kind, - ty: (), - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::ast::parser::Parser; - - #[test] - fn test_upvalue_capture_sets_is_boxed() { - // Wrap in a lambda to ensure 'x' is a local variable, not a global - let source = "(fn [] (do (def x 10) (def f (fn [] x)) x))"; - let mut parser = Parser::new(source).unwrap(); - let untyped = parser.parse_expression().unwrap(); - - let globals = Rc::new(RefCell::new(HashMap::new())); - 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 { addr, .. } = &x_decl.kind { - assert!(matches!(addr, Address::Local(_))); - assert!( - captures.contains_key(&x_decl.identity), - "Variable 'x' should have capturers because it is used in lambda 'f'" - ); - } else { - panic!( - "First expression in block should be Define, got {:?}", - x_decl.kind - ); - } - } else { - panic!("Lambda body should be a Block, got {:?}", body.kind); - } - } else { - panic!("Root should be a Lambda, got {:?}", bound.kind); - } - } - - #[test] - fn test_no_capture_not_boxed() { - let source = "(fn [] (do (def x 10) x))"; - let mut parser = Parser::new(source).unwrap(); - let untyped = parser.parse_expression().unwrap(); - - let globals = Rc::new(RefCell::new(HashMap::new())); - 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 { addr, .. } = &x_decl.kind { - assert!(matches!(addr, Address::Local(_))); - assert!( - !captures.contains_key(&x_decl.identity), - "Variable 'x' should NOT have any capturers" - ); - } else { - panic!("First expression should be Define"); - } - } else { - panic!("Lambda body should be a Block"); - } - } else { - panic!("Root should be a Lambda"); - } - } - - #[test] - fn test_redefinition_error() { - let source = "(do (def x 1) (def x 2))"; - let mut parser = Parser::new(source).unwrap(); - let untyped = parser.parse_expression().unwrap(); - - let globals = Rc::new(RefCell::new(HashMap::new())); - let result = Binder::bind_root(globals, &untyped); - - 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")); - } -} +use crate::ast::compiler::bound_nodes::{ + Address, BoundKind, BoundNode, DeclarationKind, GlobalIdx, LocalSlot, UpvalueIdx, +}; +use crate::ast::diagnostics::Diagnostics; +use crate::ast::nodes::{Node, Symbol, UntypedKind}; +use crate::ast::types::{Identity, StaticType}; +use std::cell::RefCell; +use std::collections::HashMap; +use std::rc::Rc; + +#[derive(Debug, Clone)] +struct LocalInfo { + slot: LocalSlot, + 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. + _ty: StaticType, +} + +#[derive(Debug, Clone)] +struct CompilerScope { + locals: HashMap, + slot_count: u32, +} + +impl CompilerScope { + fn new() -> Self { + Self { + locals: HashMap::new(), + slot_count: 0, + } + } + + 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.", + sym.name + )); + } + let slot = LocalSlot(self.slot_count); + self.locals.insert( + sym.clone(), + LocalInfo { + slot, + identity, + _ty: StaticType::Any, + }, + ); + self.slot_count += 1; + Ok(slot) + } + + fn resolve(&self, sym: &Symbol) -> Option { + self.locals.get(sym).cloned() + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum ScopeKind { + Root, + Local, +} + +struct FunctionCompiler { + identity: Identity, + scope: CompilerScope, + upvalues: Vec
, + kind: ScopeKind, +} + +impl FunctionCompiler { + fn new(kind: ScopeKind, identity: Identity) -> Self { + Self { + identity, + scope: CompilerScope::new(), + upvalues: Vec::new(), + kind, + } + } + + fn define_variable( + &mut self, + name: &Symbol, + identity: Identity, + globals: &Rc>>, + ) -> Result { + match self.kind { + ScopeKind::Root => { + let mut globals_map = globals.borrow_mut(); + if globals_map.contains_key(name) { + return Err(format!( + "Global variable '{}' is already defined.", + name.name + )); + } + let idx = globals_map.len() as u32; + globals_map.insert(name.clone(), (GlobalIdx(idx), identity)); + Ok(Address::Global(GlobalIdx(idx))) + } + ScopeKind::Local => { + let slot = self.scope.define(name, identity)?; + Ok(Address::Local(slot)) + } + } + } + + fn add_upvalue(&mut self, addr: Address) -> UpvalueIdx { + if let Some(idx) = self.upvalues.iter().position(|&a| a == addr) { + return UpvalueIdx(idx as u32); + } + let idx = UpvalueIdx(self.upvalues.len() as u32); + self.upvalues.push(addr); + idx + } +} + +pub struct Binder { + functions: Vec, + // Globals mapping: Symbol -> (Index, DefinitionIdentity) + globals: Rc>>, + // Map of Declaration Identity -> List of Lambda Identities that capture it + capture_map: HashMap>, +} + +impl Binder { + pub fn new(globals: Rc>>) -> Self { + let mut binder = Self { + functions: Vec::new(), + globals, + capture_map: HashMap::new(), + }; + binder.functions.push(FunctionCompiler::new( + ScopeKind::Root, + crate::ast::types::NodeIdentity::new(crate::ast::types::SourceLocation { + line: 0, + col: 0, + }), + )); + binder + } + + pub fn bind_root( + globals: Rc>>, + node: &Node, + diagnostics: &mut Diagnostics, + ) -> Result<(BoundNode, HashMap>), String> { + let mut binder = Self::new(globals); + let bound = binder.bind(node, diagnostics); + + // 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, + diag: &mut Diagnostics, + ) -> Option
{ + let current_fn = self.functions.last_mut().unwrap(); + match current_fn.define_variable(name, identity, &self.globals) { + Ok(addr) => Some(addr), + Err(e) => { + diag.push_error(e, None); + None + } + } + } + + pub fn bind(&mut self, node: &Node, diag: &mut Diagnostics) -> BoundNode { + match &node.kind { + UntypedKind::Nop => self.make_node(node.identity.clone(), BoundKind::Nop), + UntypedKind::Constant(v) => { + self.make_node(node.identity.clone(), BoundKind::Constant(v.clone())) + } + + UntypedKind::Identifier(sym) => { + if let Some(addr) = self.resolve_variable(sym, diag, &node.identity) { + self.make_node( + node.identity.clone(), + BoundKind::Get { + addr, + name: sym.clone(), + }, + ) + } else { + self.make_node(node.identity.clone(), BoundKind::Error) + } + } + + UntypedKind::Parameter(_) => { + diag.push_error("Unexpected 'Parameter' node in general binder context. This should be handled via 'bind_pattern'.", Some(node.identity.clone())); + self.make_node(node.identity.clone(), BoundKind::Error) + } + + UntypedKind::FieldAccessor(k) => { + self.make_node(node.identity.clone(), BoundKind::FieldAccessor(*k)) + } + + UntypedKind::If { + cond, + then_br, + else_br, + } => { + let cond = self.bind(cond, diag); + let then_br = self.bind(then_br, diag); + let mut else_br_bound = None; + if let Some(e) = else_br { + else_br_bound = Some(Box::new(self.bind(e, diag))); + } + + self.make_node( + node.identity.clone(), + BoundKind::If { + cond: Box::new(cond), + then_br: Box::new(then_br), + else_br: else_br_bound, + }, + ) + } + + UntypedKind::Def { target, value } => { + // Special case: Single identifier (to support recursion) + if let UntypedKind::Parameter(ref name) = target.kind { + let addr_opt = self.declare_variable( + name, + node.identity.clone(), // Identity of the Def node + crate::ast::compiler::bound_nodes::DeclarationKind::Variable, + diag, + ); + let val_node = self.bind(value, diag); + + if let Some(addr) = addr_opt { + self.make_node( + node.identity.clone(), + BoundKind::Define { + name: name.clone(), + addr, + kind: crate::ast::compiler::bound_nodes::DeclarationKind::Variable, + value: Box::new(val_node), + captured_by: Vec::new(), // Will be filled by post-pass + }, + ) + } else { + self.make_node(node.identity.clone(), BoundKind::Error) + } + } else { + // Complex Destructuring Pattern + // NOTE: Destructuring definitions are NOT recursive by default + // (the variables are only available AFTER the definition) + let val_node = self.bind(value, diag); + let target_node = self.bind_pattern(target, DeclarationKind::Variable, diag); + + self.make_node( + node.identity.clone(), + BoundKind::Destructure { + pattern: Box::new(target_node), + value: Box::new(val_node), + }, + ) + } + } + + UntypedKind::Assign { target, value } => { + let val_node = self.bind(value, diag); + + if let UntypedKind::Identifier(sym) = &target.kind { + if let Some(addr) = self.resolve_variable(sym, diag, &target.identity) { + self.make_node( + node.identity.clone(), + BoundKind::Set { + addr, + value: Box::new(val_node), + }, + ) + } else { + self.make_node(node.identity.clone(), BoundKind::Error) + } + } else { + let target_node = self.bind_assign_pattern(target, diag); + self.make_node( + node.identity.clone(), + BoundKind::Destructure { + pattern: Box::new(target_node), + value: Box::new(val_node), + }, + ) + } + } + + UntypedKind::Pipe { inputs, lambda } => { + let mut bound_inputs = Vec::with_capacity(inputs.len()); + for input in inputs { + bound_inputs.push(self.bind(input, diag)); + } + let bound_lambda = Box::new(self.bind(lambda.as_ref(), diag)); + self.make_node( + node.identity.clone(), + BoundKind::Pipe { + inputs: bound_inputs, + lambda: bound_lambda, + out_type: crate::ast::types::StaticType::Any, + }, + ) + } + + UntypedKind::Lambda { params, body } => { + let identity = node.identity.clone(); + self.functions.push(FunctionCompiler::new( + ScopeKind::Local, + identity.clone(), + )); + + // 1. Bind the parameter pattern/tuple + let params_bound = self.bind_pattern(params, DeclarationKind::Parameter, diag); + + // 2. Bind the body + let body_bound = self.bind(body, diag); + + let compiled_fn = self.functions.pop().unwrap(); + + // 3. Static optimization: count total parameters needed in flat argument list + fn count_params(node: &BoundNode) -> Option { + match &node.kind { + BoundKind::Define { + kind: DeclarationKind::Parameter, + .. + } => Some(1), + BoundKind::Tuple { elements } => { + let mut total = 0; + for e in elements { + total += count_params(e)?; + } + Some(total) + } + BoundKind::Nop => Some(0), + _ => None, + } + } + + let positional_count = count_params(¶ms_bound); + + self.make_node( + identity, + BoundKind::Lambda { + params: Rc::new(params_bound), + upvalues: compiled_fn.upvalues, + body: Rc::new(body_bound), + positional_count, + }, + ) + } + + UntypedKind::Call { callee, args } => { + let callee = self.bind(callee, diag); + let args = self.bind(args, diag); + + self.make_node( + node.identity.clone(), + BoundKind::Call { + callee: Box::new(callee), + args: Box::new(args), + }, + ) + } + + UntypedKind::Again { args } => { + if self.functions.len() <= 1 { + diag.push_error("'again' is only allowed inside a function or lambda.", Some(node.identity.clone())); + return self.make_node(node.identity.clone(), BoundKind::Error); + } + let args = self.bind(args, diag); + self.make_node( + node.identity.clone(), + BoundKind::Again { + args: Box::new(args), + }, + ) + } + + UntypedKind::Block { exprs } => { + let mut bound_exprs = Vec::new(); + for expr in exprs { + bound_exprs.push(self.bind(expr, diag)); + } + self.make_node( + node.identity.clone(), + BoundKind::Block { exprs: bound_exprs }, + ) + } + + UntypedKind::Tuple { elements } => { + let mut bound_elems = Vec::new(); + for e in elements { + bound_elems.push(self.bind(e, diag)); + } + self.make_node( + node.identity.clone(), + BoundKind::Tuple { + elements: bound_elems, + }, + ) + } + + UntypedKind::Record { fields } => { + let mut bound_values = Vec::new(); + let mut layout_fields = Vec::new(); + + for (k, v) in fields { + let key_node = self.bind(k, diag); + let val_node = self.bind(v, diag); + + if let BoundKind::Constant(crate::ast::types::Value::Keyword(kw)) = key_node.kind { + layout_fields.push((kw, crate::ast::types::StaticType::Any)); + } else { + diag.push_error(format!("Record keys must be keywords, found at {:?}", key_node.identity.location), Some(key_node.identity.clone())); + } + bound_values.push(val_node); + } + + let layout = crate::ast::types::RecordLayout::get_or_create(layout_fields); + + self.make_node( + node.identity.clone(), + BoundKind::Record { + layout, + values: bound_values, + }, + ) + } + + UntypedKind::Expansion { call, expanded } => { + let bound_expanded = self.bind(expanded, diag); + self.make_node( + node.identity.clone(), + BoundKind::Expansion { + original_call: Rc::from(call.as_ref().clone()), + bound_expanded: Box::new(bound_expanded), + }, + ) + } + + UntypedKind::Template(_) + | UntypedKind::Placeholder(_) + | UntypedKind::Splice(_) + | UntypedKind::MacroDecl { .. } => { + diag.push_error(format!("Macro construct {:?} found in Binder. Macros must be expanded before binding.", node.kind), Some(node.identity.clone())); + self.make_node(node.identity.clone(), BoundKind::Error) + } + + UntypedKind::Extension(_) => { + diag.push_error("Custom extensions not supported in Binder yet", Some(node.identity.clone())); + self.make_node(node.identity.clone(), BoundKind::Error) + } + UntypedKind::Error => crate::ast::compiler::bound_nodes::BoundNode { + identity: node.identity.clone(), + kind: crate::ast::compiler::bound_nodes::BoundKind::Error, + ty: (), + }, + } + } + + fn resolve_variable(&mut self, sym: &Symbol, diag: &mut Diagnostics, identity: &Identity) -> Option
{ + 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(sym) { + return Some(Address::Local(info.slot)); + } + + // 2. Try enclosing scopes (capture chain) + for i in (0..current_fn_idx).rev() { + 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 Some(addr); + } + } + + // 3. Try Global + let globals = self.globals.borrow(); + if let Some((idx, _)) = globals.get(sym) { + return Some(Address::Global(*idx)); + } + + // 4. Global Fallback + if sym.context.is_some() { + let fallback_sym = Symbol { + name: sym.name.clone(), + context: None, + }; + if let Some((idx, _)) = globals.get(&fallback_sym) { + return Some(Address::Global(*idx)); + } + } + + diag.push_error(format!("Undefined variable '{}'", sym.name), Some(identity.clone())); + None + } + + fn bind_pattern( + &mut self, + node: &Node, + kind: DeclarationKind, + diag: &mut Diagnostics, + ) -> BoundNode { + match &node.kind { + UntypedKind::Parameter(sym) => { + if let Some(addr) = self.declare_variable(sym, node.identity.clone(), kind, diag) { + self.make_node( + node.identity.clone(), + BoundKind::Define { + name: sym.clone(), + addr, + kind, + value: Box::new(self.make_node(node.identity.clone(), BoundKind::Nop)), + captured_by: Vec::new(), // Filled by post-pass + }, + ) + } else { + self.make_node(node.identity.clone(), BoundKind::Error) + } + } + UntypedKind::Tuple { elements } => { + let mut bound_elems = Vec::new(); + for e in elements { + bound_elems.push(self.bind_pattern(e, kind, diag)); + } + self.make_node( + node.identity.clone(), + BoundKind::Tuple { + elements: bound_elems, + }, + ) + } + _ => { + diag.push_error(format!("Invalid node in pattern: {:?}", node.kind), Some(node.identity.clone())); + self.make_node(node.identity.clone(), BoundKind::Error) + } + } + } + + fn bind_assign_pattern(&mut self, node: &Node, diag: &mut Diagnostics) -> BoundNode { + match &node.kind { + UntypedKind::Identifier(sym) => { + if let Some(addr) = self.resolve_variable(sym, diag, &node.identity) { + self.make_node( + node.identity.clone(), + BoundKind::Set { + addr, + value: Box::new(self.make_node(node.identity.clone(), BoundKind::Nop)), + }, + ) + } else { + self.make_node(node.identity.clone(), BoundKind::Error) + } + } + UntypedKind::Tuple { elements } => { + let mut bound_elems = Vec::new(); + for e in elements { + bound_elems.push(self.bind_assign_pattern(e, diag)); + } + self.make_node( + node.identity.clone(), + BoundKind::Tuple { + elements: bound_elems, + }, + ) + } + _ => { + diag.push_error(format!("Invalid node in assignment pattern: {:?}", node.kind), Some(node.identity.clone())); + self.make_node(node.identity.clone(), BoundKind::Error) + } + } + } + + fn make_node(&self, identity: Identity, kind: BoundKind<()>) -> BoundNode { + Node { + identity, + kind, + ty: (), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::ast::parser::Parser; + use crate::ast::diagnostics::Diagnostics; + + #[test] + fn test_upvalue_capture_sets_is_boxed() { + // Wrap in a lambda to ensure 'x' is a local variable, not a global + let source = "(fn [] (do (def x 10) (def f (fn [] x)) x))"; + 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) = Binder::bind_root(globals, &untyped, &mut diagnostics).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 { addr, .. } = &x_decl.kind { + assert!(matches!(addr, Address::Local(_))); + assert!( + captures.contains_key(&x_decl.identity), + "Variable 'x' should have capturers because it is used in lambda 'f'" + ); + } else { + panic!( + "First expression in block should be Define, got {:?}", + x_decl.kind + ); + } + } else { + panic!("Lambda body should be a Block, got {:?}", body.kind); + } + } else { + panic!("Root should be a Lambda, got {:?}", bound.kind); + } + } + + #[test] + fn test_no_capture_not_boxed() { + let source = "(fn [] (do (def x 10) x))"; + 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) = Binder::bind_root(globals, &untyped, &mut diagnostics).unwrap(); + + if let BoundKind::Lambda { body, .. } = &bound.kind { + if let BoundKind::Block { exprs } = &body.kind { + let x_decl = &exprs[0]; + if let BoundKind::Define { addr, .. } = &x_decl.kind { + assert!(matches!(addr, Address::Local(_))); + assert!( + !captures.contains_key(&x_decl.identity), + "Variable 'x' should NOT have any capturers" + ); + } else { + panic!("First expression should be Define"); + } + } else { + panic!("Lambda body should be a Block"); + } + } else { + panic!("Root should be a Lambda"); + } + } + + #[test] + fn test_redefinition_error() { + let source = "(do (def x 1) (def x 2))"; + 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(globals, &untyped, &mut diagnostics); + + assert!(diagnostics.has_errors()); + assert!(diagnostics.items[0].message.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).parse_expression(); + let mut diagnostics = Diagnostics::new(); + assert!(Binder::bind_root(globals.clone(), &untyped1, &mut diagnostics).is_ok()); + + // Second run: attempts to redefine 'x' in the same global environment + let source2 = "(def x 2)"; + let untyped2 = Parser::new(source2).parse_expression(); + let mut diagnostics2 = Diagnostics::new(); + let _ = Binder::bind_root(globals.clone(), &untyped2, &mut diagnostics2); + + assert!(diagnostics2.has_errors()); + assert!(diagnostics2.items[0].message.contains("already defined")); + } +} diff --git a/src/ast/compiler/bound_nodes.rs b/src/ast/compiler/bound_nodes.rs index 73eab09..725ab91 100644 --- a/src/ast/compiler/bound_nodes.rs +++ b/src/ast/compiler/bound_nodes.rs @@ -161,6 +161,9 @@ pub enum BoundKind { bound_expanded: Box>, }, + /// A diagnostic poison node, allowing compilation to continue after an error. + Error, + /// DSL-specific extension slot Extension(Box>), } @@ -260,14 +263,15 @@ where } ( BoundKind::Expansion { - original_call: oa, - bound_expanded: ba, + original_call: ca, + bound_expanded: ea, }, BoundKind::Expansion { - original_call: ob, - bound_expanded: bb, + original_call: cb, + bound_expanded: eb, }, - ) => Rc::ptr_eq(oa, ob) && ba == bb, + ) => Rc::ptr_eq(ca, cb) && ea == eb, + (BoundKind::Error, BoundKind::Error) => true, (BoundKind::Extension(_), BoundKind::Extension(_)) => false, // Currently no equality for extensions _ => false, } @@ -314,6 +318,7 @@ impl BoundKind { BoundKind::Record { values, .. } => format!("RECORD({})", values.len()), BoundKind::Expansion { .. } => "EXPANSION".to_string(), BoundKind::Extension(ext) => ext.display_name(), + BoundKind::Error => "ERROR".to_string(), } } } diff --git a/src/ast/compiler/captures.rs b/src/ast/compiler/captures.rs index 9dfd0a3..138edac 100644 --- a/src/ast/compiler/captures.rs +++ b/src/ast/compiler/captures.rs @@ -141,7 +141,8 @@ impl CapturePass { BoundKind::Nop | BoundKind::Constant(_) | BoundKind::Get { .. } - | BoundKind::Extension(_) => {} + | BoundKind::Extension(_) + | BoundKind::Error => {} } node } diff --git a/src/ast/compiler/dumper.rs b/src/ast/compiler/dumper.rs index 196e4ce..2dc767d 100644 --- a/src/ast/compiler/dumper.rs +++ b/src/ast/compiler/dumper.rs @@ -256,6 +256,9 @@ impl Dumper { BoundKind::Extension(ext) => { self.log(&ext.display_name(), node); } + BoundKind::Error => { + self.log("ERROR_NODE", node); + } } } } diff --git a/src/ast/compiler/macros.rs b/src/ast/compiler/macros.rs index de53c46..098d23f 100644 --- a/src/ast/compiler/macros.rs +++ b/src/ast/compiler/macros.rs @@ -1,816 +1,819 @@ -use crate::ast::nodes::{Node, Symbol, UntypedKind}; -use crate::ast::types::{Identity, Value}; -use std::collections::HashMap; -use std::rc::Rc; - -/// Trait for evaluating expressions during macro expansion. -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. -#[derive(Clone)] -pub struct MacroRegistry { - scopes: Vec, -} - -impl Default for MacroRegistry { - fn default() -> Self { - Self::new() - } -} - -impl MacroRegistry { - pub fn new() -> Self { - Self { - scopes: vec![HashMap::new()], - } - } - - pub fn push(&mut self) { - self.scopes.push(HashMap::new()); - } - - pub fn pop(&mut self) { - if self.scopes.len() > 1 { - self.scopes.pop(); - } - } - - pub fn define(&mut self, name: Rc, params: Vec>, body: Node) { - if let Some(current) = self.scopes.last_mut() { - current.insert(name, (params, body)); - } - } - - pub fn lookup(&self, name: &str) -> Option<(Vec>, Node)> { - for scope in self.scopes.iter().rev() { - if let Some(m) = scope.get(name) { - return Some(m.clone()); - } - } - None - } -} - -pub struct MacroExpander { - registry: MacroRegistry, - evaluator: E, -} - -impl MacroExpander { - pub fn new(registry: MacroRegistry, evaluator: E) -> Self { - Self { - registry, - evaluator, - } - } - - pub fn into_registry(self) -> MacroRegistry { - self.registry - } - - pub fn expand(&mut self, node: Node) -> Result, String> { - self.expand_recursive(node) - } - - fn expand_recursive(&mut self, node: Node) -> Result, String> { - match node.kind { - UntypedKind::MacroDecl { name, params, body } => { - let p_names = self.extract_param_names(¶ms)?; - self.registry.define(name.name, p_names, *body); - Ok(Node { - identity: node.identity, - kind: UntypedKind::Nop, - ty: (), - }) - } - - UntypedKind::Call { callee, args } => { - if let UntypedKind::Identifier(ref sym) = callee.kind - && let Some((params, body)) = self.registry.lookup(&sym.name) - { - let expanded_args = self.expand_recursive(*args)?; - - // Extract argument nodes from the expanded tuple - let arg_elements = if let UntypedKind::Tuple { elements } = expanded_args.kind { - elements - } else { - vec![expanded_args] - }; - - let expanded = self.expand_call( - node.identity.clone(), - &sym.name, - params, - arg_elements, - body, - )?; - return self.expand_recursive(expanded); - } - - let expanded_callee = self.expand_recursive(*callee)?; - let expanded_args = self.expand_recursive(*args)?; - - Ok(Node { - identity: node.identity, - kind: UntypedKind::Call { - callee: Box::new(expanded_callee), - args: Box::new(expanded_args), - }, - ty: (), - }) - } - - UntypedKind::Block { exprs } => { - self.registry.push(); - let mut expanded_exprs = Vec::new(); - for expr in exprs { - expanded_exprs.push(self.expand_recursive(expr)?); - } - self.registry.pop(); - - Ok(Node { - identity: node.identity, - kind: UntypedKind::Block { - exprs: expanded_exprs, - }, - ty: (), - }) - } - - UntypedKind::Pipe { inputs, lambda } => { - let mut expanded_inputs = Vec::with_capacity(inputs.len()); - for input in inputs { - expanded_inputs.push(self.expand_recursive(input)?); - } - let expanded_lambda = Box::new(self.expand_recursive(*lambda)?); - Ok(Node { - identity: node.identity, - kind: UntypedKind::Pipe { - inputs: expanded_inputs, - lambda: expanded_lambda, - }, - ty: (), - }) - } - - UntypedKind::Lambda { params, body } => { - self.registry.push(); - let expanded_params = self.expand_recursive(*params)?; - let expanded_body = self.expand_recursive(Node::clone(&body))?; - self.registry.pop(); - - Ok(Node { - identity: node.identity, - kind: UntypedKind::Lambda { - params: Box::new(expanded_params), - body: Rc::new(expanded_body), - }, - ty: (), - }) - } - - UntypedKind::If { - cond, - then_br, - else_br, - } => { - let cond = self.expand_recursive(*cond)?; - let then_br = self.expand_recursive(*then_br)?; - let else_br = if let Some(e) = else_br { - Some(Box::new(self.expand_recursive(*e)?)) - } else { - None - }; - Ok(Node { - identity: node.identity, - kind: UntypedKind::If { - cond: Box::new(cond), - then_br: Box::new(then_br), - else_br, - }, - ty: (), - }) - } - - UntypedKind::Def { target, value } => { - let target = self.expand_recursive(*target)?; - let value = self.expand_recursive(*value)?; - Ok(Node { - identity: node.identity, - kind: UntypedKind::Def { - target: Box::new(target), - value: Box::new(value), - }, - ty: (), - }) - } - - UntypedKind::Assign { target, value } => { - let target = self.expand_recursive(*target)?; - let value = self.expand_recursive(*value)?; - Ok(Node { - identity: node.identity, - kind: UntypedKind::Assign { - target: Box::new(target), - value: Box::new(value), - }, - ty: (), - }) - } - - UntypedKind::Tuple { elements } => { - let mut expanded_elements = Vec::new(); - for e in elements { - expanded_elements.push(self.expand_recursive(e)?); - } - Ok(Node { - identity: node.identity, - kind: UntypedKind::Tuple { - elements: expanded_elements, - }, - ty: (), - }) - } - - UntypedKind::Record { fields } => { - let mut expanded_fields = Vec::new(); - for (k, v) in fields { - expanded_fields.push((self.expand_recursive(k)?, self.expand_recursive(v)?)); - } - Ok(Node { - identity: node.identity, - kind: UntypedKind::Record { - fields: expanded_fields, - }, - ty: (), - }) - } - - UntypedKind::Expansion { call, expanded } => { - let expanded = self.expand_recursive(*expanded)?; - Ok(Node { - identity: node.identity, - kind: UntypedKind::Expansion { - call, - expanded: Box::new(expanded), - }, - ty: (), - }) - } - - _ => Ok(node), - } - } - - fn expand_call( - &self, - identity: Identity, - name: &str, - params: Vec>, - args: Vec>, - body: Node, - ) -> Result, String> { - if params.len() != args.len() { - return Err(format!( - "Macro {} expects {} arguments, but got {}", - name, - params.len(), - args.len() - )); - } - - let mut bindings = HashMap::new(); - for (p, a) in params.into_iter().zip(args.into_iter()) { - 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 { - 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 - }; - - let original_call = Node { - identity: identity.clone(), - kind: UntypedKind::Call { - callee: Box::new(Node { - identity: identity.clone(), - kind: UntypedKind::Identifier(Symbol::from(name)), - ty: (), - }), - args: Box::new(Node { - identity: identity.clone(), - kind: UntypedKind::Tuple { - elements: bindings.into_values().collect(), - }, - ty: (), - }), - }, - ty: (), - }; - - Ok(Node { - identity, - kind: UntypedKind::Expansion { - call: Box::new(original_call), - expanded: Box::new(expanded_body), - }, - ty: (), - }) - } - - fn extract_param_names(&self, node: &Node) -> Result>, String> { - match &node.kind { - UntypedKind::Parameter(sym) => Ok(vec![sym.name.clone()]), - UntypedKind::Tuple { elements } => { - let mut names = Vec::new(); - for el in elements { - names.extend(self.extract_param_names(el)?); - } - Ok(names) - } - _ => Err("Invalid parameter pattern in macro declaration".to_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::Parameter(mut sym) => { - sym.context = Some(state.expansion_id.clone()); - Ok(Node { - identity: node.identity, - kind: UntypedKind::Parameter(sym), - ty: (), - }) - } - - UntypedKind::Placeholder(inner) => { - // 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(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 { target, value } => { - let target = self.expand_template(*target, state)?; - let val = self.expand_template(*value, state)?; - Ok(Node { - identity: node.identity, - kind: UntypedKind::Def { - target: Box::new(target), - 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, state.bindings)?; - self.handle_splice_value(val, node.identity.clone(), &mut new_elements)?; - } else { - new_elements.push(self.expand_template(e, state)?); - } - } - Ok(Node { - identity: node.identity, - kind: UntypedKind::Tuple { - elements: new_elements, - }, - ty: (), - }) - } - - UntypedKind::Block { exprs } => { - let mut new_exprs = Vec::new(); - for e in exprs { - if let UntypedKind::Splice(ref inner) = e.kind { - 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, state)?); - } - } - Ok(Node { - identity: node.identity, - kind: UntypedKind::Block { exprs: new_exprs }, - ty: (), - }) - } - - UntypedKind::Record { fields } => { - let mut new_fields = Vec::new(); - for (k, v) in fields { - new_fields.push(( - self.expand_template(k, state)?, - self.expand_template(v, state)?, - )); - } - Ok(Node { - identity: node.identity, - kind: UntypedKind::Record { fields: new_fields }, - ty: (), - }) - } - - UntypedKind::Call { callee, args } => { - let expanded_callee = self.expand_template(*callee, state)?; - let expanded_args = self.expand_template(*args, state)?; - Ok(Node { - identity: node.identity, - kind: UntypedKind::Call { - callee: Box::new(expanded_callee), - args: Box::new(expanded_args), - }, - ty: (), - }) - } - - UntypedKind::If { - cond, - then_br, - else_br, - } => { - 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, state)?)) - } else { - None - }; - Ok(Node { - identity: node.identity, - kind: UntypedKind::If { - cond: Box::new(cond), - then_br: Box::new(then_br), - else_br, - }, - ty: (), - }) - } - - UntypedKind::Pipe { inputs, lambda } => { - let mut expanded_inputs = Vec::with_capacity(inputs.len()); - for input in inputs { - expanded_inputs.push(self.expand_template(input, state)?); - } - let expanded_lambda = Box::new(self.expand_template(*lambda, state)?); - Ok(Node { - identity: node.identity, - kind: UntypedKind::Pipe { - inputs: expanded_inputs, - lambda: expanded_lambda, - }, - ty: (), - }) - } - - UntypedKind::Lambda { params, body } => { - let expanded_params = self.expand_template(*params, state)?; - let body = self.expand_template(Node::clone(&body), state)?; - Ok(Node { - identity: node.identity, - kind: UntypedKind::Lambda { - params: Box::new(expanded_params), - body: Rc::new(body), - }, - ty: (), - }) - } - - _ => Ok(node), - } - } - - fn handle_splice_value( - &self, - val: Value, - _identity: Identity, - target: &mut Vec>, - ) -> Result<(), String> { - match val { - Value::Object(obj) => { - if let Some(node) = obj.as_any().downcast_ref::>() { - match &node.kind { - UntypedKind::Tuple { elements } => { - for e in elements { - target.push(e.clone()); - } - return Ok(()); - } - UntypedKind::Block { exprs } => { - for e in exprs { - target.push(e.clone()); - } - return Ok(()); - } - _ => {} - } - } - Err(format!( - "Strict Splicing: Expected Tuple or Block node, but found {}", - obj.type_name() - )) - } - _ => Err(format!( - "Strict Splicing: Expected sequence (Tuple/Block Node), but found Value: {}", - val - )), - } - } - - fn value_to_node(&self, val: Value, identity: Identity) -> Node { - match val { - Value::Object(obj) => { - if let Some(node) = obj.as_any().downcast_ref::>() { - return node.clone(); - } - Node { - identity, - kind: UntypedKind::Constant(Value::Object(obj)), - ty: (), - } - } - _ => Node { - identity, - kind: UntypedKind::Constant(val), - ty: (), - }, - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::ast::compiler::Binder; - use crate::ast::parser::Parser; - use crate::ast::types::{Object, Value}; - use std::cell::RefCell; - - struct SimpleEvaluator; - impl MacroEvaluator for SimpleEvaluator { - fn evaluate( - &self, - node: &Node, - bindings: &HashMap, Node>, - ) -> Result { - 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)); - } - Err(format!( - "SimpleEvaluator cannot evaluate complex expression: {:?}", - node.kind - )) - } - } - - #[test] - fn test_macro_expansion_hygiene() { - let source = " - (do - (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 - && let UntypedKind::Expansion { - expanded: result, - call, - } = &exprs[1].kind - { - if let UntypedKind::Def { target, .. } = &result.kind { - if let UntypedKind::Parameter(sym) = &target.kind { - assert_eq!(sym.context, Some(call.identity.clone())); - assert_eq!(sym.name.as_ref(), "y"); - } else { - panic!("Expected Parameter target, got {:?}", target.kind); - } - } else { - panic!("Expected Def result, got {:?}", result.kind); - } - } - } - - #[test] - fn test_macro_expansion_unless() { - let source = " - (do - (macro unless [c b] `(if ~c ... ~b)) - (unless false 42)) - "; - 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 - && let UntypedKind::Expansion { - call: _, - expanded: result, - } = &exprs[1].kind - && let UntypedKind::If { - cond, - then_br, - else_br, - } = &result.kind - { - if let UntypedKind::Identifier(sym) = &cond.kind { - assert_eq!(sym.name.as_ref(), "false"); - } else { - panic!("Expected identifier 'false', got {:?}", cond.kind); - } - assert!(matches!(then_br.kind, UntypedKind::Nop)); - 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"); - } - } - } - - #[test] - fn test_macro_expansion_splice() { - let source = " - (do - (macro wrap [items] `[0 ~@items 4]) - (def t (wrap [1 2 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(); - - if let UntypedKind::Block { exprs } = &expanded.kind - && let UntypedKind::Def { value, .. } = &exprs[1].kind - && let UntypedKind::Expansion { - expanded: result, .. - } = &value.kind - && 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[4].kind { - assert_eq!(*n, 4); - } - } - } - - #[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("*"), - ( - crate::ast::compiler::bound_nodes::GlobalIdx(0), - crate::ast::types::NodeIdentity::new(crate::ast::types::SourceLocation { - line: 0, - col: 0, - }), - ), - ); - 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 result = Binder::bind_root(globals, &expanded); - assert!(result.is_ok(), "Hygiene failed: {:?}", result.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 result = Binder::bind_root(globals, &expanded); - assert!( - result.is_ok(), - "Explicit Hygiene with Backticks failed: {:?}", - result.err() - ); - } -} +use crate::ast::nodes::{Node, Symbol, UntypedKind}; +use crate::ast::types::{Identity, Value}; +use std::collections::HashMap; +use std::rc::Rc; + +/// Trait for evaluating expressions during macro expansion. +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. +#[derive(Clone)] +pub struct MacroRegistry { + scopes: Vec, +} + +impl Default for MacroRegistry { + fn default() -> Self { + Self::new() + } +} + +impl MacroRegistry { + pub fn new() -> Self { + Self { + scopes: vec![HashMap::new()], + } + } + + pub fn push(&mut self) { + self.scopes.push(HashMap::new()); + } + + pub fn pop(&mut self) { + if self.scopes.len() > 1 { + self.scopes.pop(); + } + } + + pub fn define(&mut self, name: Rc, params: Vec>, body: Node) { + if let Some(current) = self.scopes.last_mut() { + current.insert(name, (params, body)); + } + } + + pub fn lookup(&self, name: &str) -> Option<(Vec>, Node)> { + for scope in self.scopes.iter().rev() { + if let Some(m) = scope.get(name) { + return Some(m.clone()); + } + } + None + } +} + +pub struct MacroExpander { + registry: MacroRegistry, + evaluator: E, +} + +impl MacroExpander { + pub fn new(registry: MacroRegistry, evaluator: E) -> Self { + Self { + registry, + evaluator, + } + } + + pub fn into_registry(self) -> MacroRegistry { + self.registry + } + + pub fn expand(&mut self, node: Node) -> Result, String> { + self.expand_recursive(node) + } + + fn expand_recursive(&mut self, node: Node) -> Result, String> { + match node.kind { + UntypedKind::MacroDecl { name, params, body } => { + let p_names = self.extract_param_names(¶ms)?; + self.registry.define(name.name, p_names, *body); + Ok(Node { + identity: node.identity, + kind: UntypedKind::Nop, + ty: (), + }) + } + + UntypedKind::Call { callee, args } => { + if let UntypedKind::Identifier(ref sym) = callee.kind + && let Some((params, body)) = self.registry.lookup(&sym.name) + { + let expanded_args = self.expand_recursive(*args)?; + + // Extract argument nodes from the expanded tuple + let arg_elements = if let UntypedKind::Tuple { elements } = expanded_args.kind { + elements + } else { + vec![expanded_args] + }; + + let expanded = self.expand_call( + node.identity.clone(), + &sym.name, + params, + arg_elements, + body, + )?; + return self.expand_recursive(expanded); + } + + let expanded_callee = self.expand_recursive(*callee)?; + let expanded_args = self.expand_recursive(*args)?; + + Ok(Node { + identity: node.identity, + kind: UntypedKind::Call { + callee: Box::new(expanded_callee), + args: Box::new(expanded_args), + }, + ty: (), + }) + } + + UntypedKind::Block { exprs } => { + self.registry.push(); + let mut expanded_exprs = Vec::new(); + for expr in exprs { + expanded_exprs.push(self.expand_recursive(expr)?); + } + self.registry.pop(); + + Ok(Node { + identity: node.identity, + kind: UntypedKind::Block { + exprs: expanded_exprs, + }, + ty: (), + }) + } + + UntypedKind::Pipe { inputs, lambda } => { + let mut expanded_inputs = Vec::with_capacity(inputs.len()); + for input in inputs { + expanded_inputs.push(self.expand_recursive(input)?); + } + let expanded_lambda = Box::new(self.expand_recursive(*lambda)?); + Ok(Node { + identity: node.identity, + kind: UntypedKind::Pipe { + inputs: expanded_inputs, + lambda: expanded_lambda, + }, + ty: (), + }) + } + + UntypedKind::Lambda { params, body } => { + self.registry.push(); + let expanded_params = self.expand_recursive(*params)?; + let expanded_body = self.expand_recursive(Node::clone(&body))?; + self.registry.pop(); + + Ok(Node { + identity: node.identity, + kind: UntypedKind::Lambda { + params: Box::new(expanded_params), + body: Rc::new(expanded_body), + }, + ty: (), + }) + } + + UntypedKind::If { + cond, + then_br, + else_br, + } => { + let cond = self.expand_recursive(*cond)?; + let then_br = self.expand_recursive(*then_br)?; + let else_br = if let Some(e) = else_br { + Some(Box::new(self.expand_recursive(*e)?)) + } else { + None + }; + Ok(Node { + identity: node.identity, + kind: UntypedKind::If { + cond: Box::new(cond), + then_br: Box::new(then_br), + else_br, + }, + ty: (), + }) + } + + UntypedKind::Def { target, value } => { + let target = self.expand_recursive(*target)?; + let value = self.expand_recursive(*value)?; + Ok(Node { + identity: node.identity, + kind: UntypedKind::Def { + target: Box::new(target), + value: Box::new(value), + }, + ty: (), + }) + } + + UntypedKind::Assign { target, value } => { + let target = self.expand_recursive(*target)?; + let value = self.expand_recursive(*value)?; + Ok(Node { + identity: node.identity, + kind: UntypedKind::Assign { + target: Box::new(target), + value: Box::new(value), + }, + ty: (), + }) + } + + UntypedKind::Tuple { elements } => { + let mut expanded_elements = Vec::new(); + for e in elements { + expanded_elements.push(self.expand_recursive(e)?); + } + Ok(Node { + identity: node.identity, + kind: UntypedKind::Tuple { + elements: expanded_elements, + }, + ty: (), + }) + } + + UntypedKind::Record { fields } => { + let mut expanded_fields = Vec::new(); + for (k, v) in fields { + expanded_fields.push((self.expand_recursive(k)?, self.expand_recursive(v)?)); + } + Ok(Node { + identity: node.identity, + kind: UntypedKind::Record { + fields: expanded_fields, + }, + ty: (), + }) + } + + UntypedKind::Expansion { call, expanded } => { + let expanded = self.expand_recursive(*expanded)?; + Ok(Node { + identity: node.identity, + kind: UntypedKind::Expansion { + call, + expanded: Box::new(expanded), + }, + ty: (), + }) + } + + _ => Ok(node), + } + } + + fn expand_call( + &self, + identity: Identity, + name: &str, + params: Vec>, + args: Vec>, + body: Node, + ) -> Result, String> { + if params.len() != args.len() { + return Err(format!( + "Macro {} expects {} arguments, but got {}", + name, + params.len(), + args.len() + )); + } + + let mut bindings = HashMap::new(); + for (p, a) in params.into_iter().zip(args.into_iter()) { + 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 { + 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 + }; + + let original_call = Node { + identity: identity.clone(), + kind: UntypedKind::Call { + callee: Box::new(Node { + identity: identity.clone(), + kind: UntypedKind::Identifier(Symbol::from(name)), + ty: (), + }), + args: Box::new(Node { + identity: identity.clone(), + kind: UntypedKind::Tuple { + elements: bindings.into_values().collect(), + }, + ty: (), + }), + }, + ty: (), + }; + + Ok(Node { + identity, + kind: UntypedKind::Expansion { + call: Box::new(original_call), + expanded: Box::new(expanded_body), + }, + ty: (), + }) + } + + fn extract_param_names(&self, node: &Node) -> Result>, String> { + match &node.kind { + UntypedKind::Parameter(sym) => Ok(vec![sym.name.clone()]), + UntypedKind::Tuple { elements } => { + let mut names = Vec::new(); + for el in elements { + names.extend(self.extract_param_names(el)?); + } + Ok(names) + } + _ => Err("Invalid parameter pattern in macro declaration".to_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::Parameter(mut sym) => { + sym.context = Some(state.expansion_id.clone()); + Ok(Node { + identity: node.identity, + kind: UntypedKind::Parameter(sym), + ty: (), + }) + } + + UntypedKind::Placeholder(inner) => { + // 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(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 { target, value } => { + let target = self.expand_template(*target, state)?; + let val = self.expand_template(*value, state)?; + Ok(Node { + identity: node.identity, + kind: UntypedKind::Def { + target: Box::new(target), + 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, state.bindings)?; + self.handle_splice_value(val, node.identity.clone(), &mut new_elements)?; + } else { + new_elements.push(self.expand_template(e, state)?); + } + } + Ok(Node { + identity: node.identity, + kind: UntypedKind::Tuple { + elements: new_elements, + }, + ty: (), + }) + } + + UntypedKind::Block { exprs } => { + let mut new_exprs = Vec::new(); + for e in exprs { + if let UntypedKind::Splice(ref inner) = e.kind { + 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, state)?); + } + } + Ok(Node { + identity: node.identity, + kind: UntypedKind::Block { exprs: new_exprs }, + ty: (), + }) + } + + UntypedKind::Record { fields } => { + let mut new_fields = Vec::new(); + for (k, v) in fields { + new_fields.push(( + self.expand_template(k, state)?, + self.expand_template(v, state)?, + )); + } + Ok(Node { + identity: node.identity, + kind: UntypedKind::Record { fields: new_fields }, + ty: (), + }) + } + + UntypedKind::Call { callee, args } => { + let expanded_callee = self.expand_template(*callee, state)?; + let expanded_args = self.expand_template(*args, state)?; + Ok(Node { + identity: node.identity, + kind: UntypedKind::Call { + callee: Box::new(expanded_callee), + args: Box::new(expanded_args), + }, + ty: (), + }) + } + + UntypedKind::If { + cond, + then_br, + else_br, + } => { + 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, state)?)) + } else { + None + }; + Ok(Node { + identity: node.identity, + kind: UntypedKind::If { + cond: Box::new(cond), + then_br: Box::new(then_br), + else_br, + }, + ty: (), + }) + } + + UntypedKind::Pipe { inputs, lambda } => { + let mut expanded_inputs = Vec::with_capacity(inputs.len()); + for input in inputs { + expanded_inputs.push(self.expand_template(input, state)?); + } + let expanded_lambda = Box::new(self.expand_template(*lambda, state)?); + Ok(Node { + identity: node.identity, + kind: UntypedKind::Pipe { + inputs: expanded_inputs, + lambda: expanded_lambda, + }, + ty: (), + }) + } + + UntypedKind::Lambda { params, body } => { + let expanded_params = self.expand_template(*params, state)?; + let body = self.expand_template(Node::clone(&body), state)?; + Ok(Node { + identity: node.identity, + kind: UntypedKind::Lambda { + params: Box::new(expanded_params), + body: Rc::new(body), + }, + ty: (), + }) + } + + _ => Ok(node), + } + } + + fn handle_splice_value( + &self, + val: Value, + _identity: Identity, + target: &mut Vec>, + ) -> Result<(), String> { + match val { + Value::Object(obj) => { + if let Some(node) = obj.as_any().downcast_ref::>() { + match &node.kind { + UntypedKind::Tuple { elements } => { + for e in elements { + target.push(e.clone()); + } + return Ok(()); + } + UntypedKind::Block { exprs } => { + for e in exprs { + target.push(e.clone()); + } + return Ok(()); + } + _ => {} + } + } + Err(format!( + "Strict Splicing: Expected Tuple or Block node, but found {}", + obj.type_name() + )) + } + _ => Err(format!( + "Strict Splicing: Expected sequence (Tuple/Block Node), but found Value: {}", + val + )), + } + } + + fn value_to_node(&self, val: Value, identity: Identity) -> Node { + match val { + Value::Object(obj) => { + if let Some(node) = obj.as_any().downcast_ref::>() { + return node.clone(); + } + Node { + identity, + kind: UntypedKind::Constant(Value::Object(obj)), + ty: (), + } + } + _ => Node { + identity, + kind: UntypedKind::Constant(val), + ty: (), + }, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::ast::compiler::Binder; + use crate::ast::parser::Parser; + use crate::ast::types::{Object, Value}; + use std::cell::RefCell; + + struct SimpleEvaluator; + impl MacroEvaluator for SimpleEvaluator { + fn evaluate( + &self, + node: &Node, + bindings: &HashMap, Node>, + ) -> Result { + 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)); + } + Err(format!( + "SimpleEvaluator cannot evaluate complex expression: {:?}", + node.kind + )) + } + } + + #[test] + fn test_macro_expansion_hygiene() { + let source = " + (do + (macro m [] `(def y 10)) + (m)) + "; + let mut parser = Parser::new(source); + let untyped = parser.parse_expression(); + + let mut expander = MacroExpander::new(MacroRegistry::new(), SimpleEvaluator); + let expanded = expander.expand(untyped).unwrap(); + + if let UntypedKind::Block { exprs } = &expanded.kind + && let UntypedKind::Expansion { + expanded: result, + call, + } = &exprs[1].kind + { + if let UntypedKind::Def { target, .. } = &result.kind { + if let UntypedKind::Parameter(sym) = &target.kind { + assert_eq!(sym.context, Some(call.identity.clone())); + assert_eq!(sym.name.as_ref(), "y"); + } else { + panic!("Expected Parameter target, got {:?}", target.kind); + } + } else { + panic!("Expected Def result, got {:?}", result.kind); + } + } + } + + #[test] + fn test_macro_expansion_unless() { + let source = " + (do + (macro unless [c b] `(if ~c ... ~b)) + (unless false 42)) + "; + let mut parser = Parser::new(source); + let untyped = parser.parse_expression(); + + let mut expander = MacroExpander::new(MacroRegistry::new(), SimpleEvaluator); + let expanded = expander.expand(untyped).unwrap(); + + if let UntypedKind::Block { exprs } = &expanded.kind + && let UntypedKind::Expansion { + call: _, + expanded: result, + } = &exprs[1].kind + && let UntypedKind::If { + cond, + then_br, + else_br, + } = &result.kind + { + if let UntypedKind::Identifier(sym) = &cond.kind { + assert_eq!(sym.name.as_ref(), "false"); + } else { + panic!("Expected identifier 'false', got {:?}", cond.kind); + } + assert!(matches!(then_br.kind, UntypedKind::Nop)); + 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"); + } + } + } + + #[test] + fn test_macro_expansion_splice() { + let source = " + (do + (macro wrap [items] `[0 ~@items 4]) + (def t (wrap [1 2 3]))) + "; + let mut parser = Parser::new(source); + let untyped = parser.parse_expression(); + + let mut expander = MacroExpander::new(MacroRegistry::new(), SimpleEvaluator); + let expanded = expander.expand(untyped).unwrap(); + + if let UntypedKind::Block { exprs } = &expanded.kind + && let UntypedKind::Def { value, .. } = &exprs[1].kind + && let UntypedKind::Expansion { + expanded: result, .. + } = &value.kind + && 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[4].kind { + assert_eq!(*n, 4); + } + } + } + + #[test] + fn test_repro_macro_global_lookup() { + let source = " + (do + (macro square [x] `(* ~x ~x)) + (square 3)) + "; + let mut parser = Parser::new(source); + let untyped = parser.parse_expression(); + + 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("*"), + ( + crate::ast::compiler::bound_nodes::GlobalIdx(0), + crate::ast::types::NodeIdentity::new(crate::ast::types::SourceLocation { + line: 0, + col: 0, + }), + ), + ); + let globals = Rc::new(RefCell::new(global_names)); + + let mut diag = crate::ast::diagnostics::Diagnostics::new(); + let result = Binder::bind_root(globals, &expanded, &mut diag); + 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); + let untyped = parser.parse_expression(); + + let mut expander = MacroExpander::new(MacroRegistry::new(), SimpleEvaluator); + let expanded = expander.expand(untyped).unwrap(); + + let globals = Rc::new(RefCell::new(HashMap::new())); + let mut diag = crate::ast::diagnostics::Diagnostics::new(); + let result = Binder::bind_root(globals, &expanded, &mut diag); + assert!(result.is_ok(), "Hygiene failed: {:?}", result.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); + let untyped = parser.parse_expression(); + + let mut expander = MacroExpander::new(MacroRegistry::new(), SimpleEvaluator); + let expanded = expander.expand(untyped).unwrap(); + + let globals = Rc::new(RefCell::new(HashMap::new())); + let mut diag = crate::ast::diagnostics::Diagnostics::new(); + let result = Binder::bind_root(globals, &expanded, &mut diag); + assert!( + result.is_ok(), + "Explicit Hygiene with Backticks failed: {:?}", + result.err() + ); + } +} diff --git a/src/ast/compiler/optimizer/utils.rs b/src/ast/compiler/optimizer/utils.rs index b8e2157..6b6486d 100644 --- a/src/ast/compiler/optimizer/utils.rs +++ b/src/ast/compiler/optimizer/utils.rs @@ -158,7 +158,7 @@ impl UsageInfo { } self.collect(lambda); } - BoundKind::Nop | BoundKind::FieldAccessor(_) | BoundKind::Extension(_) => {} + BoundKind::Nop | BoundKind::FieldAccessor(_) | BoundKind::Extension(_) | BoundKind::Error => {} } } diff --git a/src/ast/compiler/tco.rs b/src/ast/compiler/tco.rs index 775654e..7affe57 100644 --- a/src/ast/compiler/tco.rs +++ b/src/ast/compiler/tco.rs @@ -167,6 +167,7 @@ impl TCO { )), }, BoundKind::Extension(_) => BoundKind::Nop, + BoundKind::Error => BoundKind::Error, }; Node { diff --git a/src/ast/compiler/type_checker.rs b/src/ast/compiler/type_checker.rs index ff1687b..a78d979 100644 --- a/src/ast/compiler/type_checker.rs +++ b/src/ast/compiler/type_checker.rs @@ -1,4 +1,5 @@ use crate::ast::compiler::bound_nodes::{Address, BoundKind, BoundNode, GlobalIdx, TypedNode}; +use crate::ast::diagnostics::Diagnostics; use crate::ast::nodes::Node; use crate::ast::types::StaticType; use std::collections::HashMap; @@ -78,7 +79,7 @@ impl TypeChecker { Self { global_types } } - pub fn check(&self, node: BoundNode, arg_types: &[StaticType]) -> Result { + pub fn check(&self, node: BoundNode, arg_types: &[StaticType], diag: &mut Diagnostics) -> TypedNode { match node.kind { BoundKind::Lambda { params, @@ -104,11 +105,10 @@ impl TypeChecker { StaticType::Tuple(arg_types.to_vec()) }; - let params_typed = - self.check_params(params.as_ref().clone(), &arg_tuple_ty, &mut lambda_ctx)?; + let params_typed = self.check_params(params.as_ref().clone(), &arg_tuple_ty, &mut lambda_ctx, diag); // 4. Check body with the new types - let body_typed = self.check_node((*body).clone(), &mut lambda_ctx)?; + let body_typed = self.check_node((*body).clone(), &mut lambda_ctx, diag); let ret_ty = body_typed.ty.clone(); // 5. Construct specialized function type @@ -119,7 +119,7 @@ impl TypeChecker { ret: ret_ty, })); - Ok(Node { + Node { identity: node.identity, kind: BoundKind::Lambda { params: Rc::new(params_typed), @@ -128,7 +128,7 @@ impl TypeChecker { positional_count, }, ty: fn_ty, - }) + } } _ => { // Fallback: Wrap in implicit lambda @@ -146,7 +146,7 @@ impl TypeChecker { }, ty: (), }; - self.check(virtual_lambda, &[]) + self.check(virtual_lambda, &[], diag) } } } @@ -156,7 +156,8 @@ impl TypeChecker { node: BoundNode, specialized_ty: &StaticType, ctx: &mut TypeContext, - ) -> Result { + diag: &mut Diagnostics, + ) -> TypedNode { let (kind, ty) = match node.kind { BoundKind::Define { name, @@ -208,12 +209,15 @@ impl TypeChecker { | StaticType::Vector(_, _) | StaticType::Matrix(_, _) | StaticType::List(_) - | StaticType::Record(_) => {} + | StaticType::Record(_) + | StaticType::Error => {} _ => { - return Err(format!( - "Cannot destructure type {} as a tuple/vector", - specialized_ty - )); + diag.push_error(format!("Cannot destructure type {} as a tuple/vector", specialized_ty), Some(node.identity.clone())); + return Node { + identity: node.identity, + kind: BoundKind::Error, + ty: StaticType::Error, + }; } } @@ -231,9 +235,10 @@ impl TypeChecker { .get(i) .map(|(_, ty): &(crate::ast::types::Keyword, StaticType)| ty.clone()) .unwrap_or(StaticType::Any), + StaticType::Error => StaticType::Error, _ => StaticType::Any, }; - let t = self.check_params(el, &sub_ty, ctx)?; + let t = self.check_params(el, &sub_ty, ctx, diag); elem_types.push(t.ty.clone()); typed_elements.push(t); } @@ -244,17 +249,21 @@ impl TypeChecker { StaticType::Tuple(elem_types), ) } - _ => return Err("Invalid node in parameter list".to_string()), + BoundKind::Error => (BoundKind::Error, StaticType::Error), + _ => { + diag.push_error("Invalid node in parameter list", Some(node.identity.clone())); + (BoundKind::Error, StaticType::Error) + } }; - Ok(Node { + Node { identity: node.identity, kind, ty, - }) + } } - fn check_node(&self, node: BoundNode, ctx: &mut TypeContext) -> Result { + fn check_node(&self, node: BoundNode, ctx: &mut TypeContext, diag: &mut Diagnostics) -> TypedNode { let (kind, ty) = match node.kind { BoundKind::Nop => (BoundKind::Nop, StaticType::Void), @@ -270,7 +279,7 @@ impl TypeChecker { value, captured_by, } => { - let val_typed = self.check_node(*value, ctx)?; + let val_typed = self.check_node(*value, ctx, diag); let ty = val_typed.ty.clone(); ctx.set_type(addr, ty.clone()); @@ -300,22 +309,21 @@ impl TypeChecker { BoundKind::FieldAccessor(k) => (BoundKind::FieldAccessor(k), StaticType::FieldAccessor(k)), BoundKind::GetField { rec, field } => { - let rec_typed = self.check_node(*rec, ctx)?; + let rec_typed = self.check_node(*rec, ctx, diag); let field_ty = match &rec_typed.ty { StaticType::Record(layout) => { if let Some(idx) = layout.index_of(field) { layout.fields[idx].1.clone() } else { - return Err(format!("Record does not have field :{}", field.name())); + diag.push_error(format!("Record does not have field :{}", field.name()), Some(rec_typed.identity.clone())); + StaticType::Error } } StaticType::Any => StaticType::Any, + StaticType::Error => StaticType::Error, _ => { - return Err(format!( - "Cannot access field :{} on non-record type {}", - field.name(), - rec_typed.ty - )); + diag.push_error(format!("Cannot access field :{} on non-record type {}", field.name(), rec_typed.ty), Some(rec_typed.identity.clone())); + StaticType::Error } }; ( @@ -328,7 +336,7 @@ impl TypeChecker { } BoundKind::Set { addr, value } => { - let val_typed = self.check_node(*value, ctx)?; + let val_typed = self.check_node(*value, ctx, diag); let ty = val_typed.ty.clone(); ctx.set_type(addr, ty.clone()); @@ -342,8 +350,8 @@ impl TypeChecker { } BoundKind::Destructure { pattern, value } => { - let val_typed = self.check_node(*value, ctx)?; - let pat_typed = self.check_params(*pattern, &val_typed.ty, ctx)?; + let val_typed = self.check_node(*value, ctx, diag); + let pat_typed = self.check_params(*pattern, &val_typed.ty, ctx, diag); let ty = val_typed.ty.clone(); ( BoundKind::Destructure { @@ -359,14 +367,14 @@ impl TypeChecker { then_br, else_br, } => { - let cond_typed = self.check_node(*cond, ctx)?; - let then_typed = self.check_node(*then_br, ctx)?; + let cond_typed = self.check_node(*cond, ctx, diag); + let then_typed = self.check_node(*then_br, ctx, diag); let mut else_typed = None; let mut final_ty = then_typed.ty.clone(); if let Some(e) = else_br { - let et = self.check_node(*e, ctx)?; + let et = self.check_node(*e, ctx, diag); // Basic type promotion: if types differ, fall back to Any if et.ty != final_ty { final_ty = StaticType::Any; @@ -391,7 +399,7 @@ impl TypeChecker { let mut typed_inputs = Vec::with_capacity(inputs.len()); let mut arg_types = Vec::with_capacity(inputs.len()); for input in inputs { - let typed_input = self.check_node(input, ctx)?; + let typed_input = self.check_node(input, ctx, diag); // Unwrap Series(T) to T for the lambda argument let arg_ty = if let StaticType::Series(inner) = &typed_input.ty { *inner.clone() @@ -403,7 +411,7 @@ impl TypeChecker { } // Specialized check for the lambda using the input types! - let typed_lambda = self.check(*lambda, &arg_types)?; + let typed_lambda = self.check(*lambda, &arg_types, diag); let ret_ty = if let StaticType::Function(sig) = &typed_lambda.ty { // If the lambda returns an Optional(T), the pipeline filters Void and stores T! @@ -430,7 +438,7 @@ impl TypeChecker { let mut last_ty = StaticType::Void; for e in exprs { - let t = self.check_node(e, ctx)?; + let t = self.check_node(e, ctx, diag); last_ty = t.ty.clone(); typed_exprs.push(t); } @@ -455,13 +463,12 @@ impl TypeChecker { TypeContext::new(64, upvalue_types, ctx.global_types, Some(ctx)); // 3. Check parameters and body - let params_typed = - self.check_params(params.as_ref().clone(), &StaticType::Any, &mut lambda_ctx)?; + let params_typed = self.check_params(params.as_ref().clone(), &StaticType::Any, &mut lambda_ctx, diag); // Set current params type for 'again' validation lambda_ctx.current_params_ty = Some(params_typed.ty.clone()); - let body_typed = self.check_node((*body).clone(), &mut lambda_ctx)?; + let body_typed = self.check_node((*body).clone(), &mut lambda_ctx, diag); let ret_ty = body_typed.ty.clone(); // 4. Construct function type @@ -482,14 +489,14 @@ impl TypeChecker { } BoundKind::Call { callee, args } => { - let callee_typed = self.check_node(*callee, ctx)?; + let callee_typed = self.check_node(*callee, ctx, diag); // Manually check args (Tuple) to prevent Vector/Matrix promotion let args_typed = if let BoundKind::Tuple { elements } = args.kind { let mut typed_elements = Vec::new(); let mut elem_types = Vec::new(); for e in elements { - let t = self.check_node(e, ctx)?; + let t = self.check_node(e, ctx, diag); elem_types.push(t.ty.clone()); typed_elements.push(t); } @@ -502,16 +509,14 @@ impl TypeChecker { } } else { // Should not happen as parser wraps args in Tuple, but fallback safely - self.check_node(*args, ctx)? + self.check_node(*args, ctx, diag) }; let ret_ty = match callee_typed.ty.resolve_call(&args_typed.ty) { Some(ty) => ty, None => { - return Err(format!( - "Invalid arguments for function call. Expected {}, got {}", - callee_typed.ty, args_typed.ty - )); + diag.push_error(format!("Invalid arguments for function call. Expected {}, got {}", callee_typed.ty, args_typed.ty), Some(node.identity.clone())); + StaticType::Error } }; @@ -530,7 +535,7 @@ impl TypeChecker { let mut typed_elements = Vec::new(); let mut elem_types = Vec::new(); for e in elements { - let t = self.check_node(e, ctx)?; + let t = self.check_node(e, ctx, diag); elem_types.push(t.ty.clone()); typed_elements.push(t); } @@ -542,16 +547,13 @@ impl TypeChecker { ty: StaticType::Tuple(elem_types), } } else { - self.check_node(*args, ctx)? + self.check_node(*args, ctx, diag) }; if let Some(expected_ty) = &ctx.current_params_ty && !expected_ty.is_assignable_from(&args_typed.ty) { - return Err(format!( - "Type mismatch in 'again' call: expected {}, but got {}", - expected_ty, args_typed.ty - )); + diag.push_error(format!("Type mismatch in 'again' call: expected {}, but got {}", expected_ty, args_typed.ty), Some(node.identity.clone())); } ( @@ -565,7 +567,7 @@ impl TypeChecker { BoundKind::Tuple { elements } => { let mut typed_elements = Vec::new(); for e in elements { - typed_elements.push(self.check_node(e, ctx)?); + typed_elements.push(self.check_node(e, ctx, diag)); } let ty = if typed_elements.is_empty() { @@ -606,7 +608,7 @@ impl TypeChecker { let mut fields_ty = Vec::with_capacity(values.len()); for (i, v) in values.into_iter().enumerate() { - let vt = self.check_node(v, ctx)?; + let vt = self.check_node(v, ctx, diag); fields_ty.push((layout.fields[i].0, vt.ty.clone())); typed_values.push(vt); } @@ -625,7 +627,7 @@ impl TypeChecker { original_call, bound_expanded, } => { - let expanded_typed = self.check_node(*bound_expanded, ctx)?; + let expanded_typed = self.check_node(*bound_expanded, ctx, diag); let ty = expanded_typed.ty.clone(); ( BoundKind::Expansion { @@ -637,18 +639,17 @@ impl TypeChecker { } BoundKind::Extension(_ext) => { - return Err(format!( - "TypeChecking for extension '{}' not implemented", - _ext.display_name() - )); + diag.push_error(format!("TypeChecking for extension '{}' not implemented", _ext.display_name()), Some(node.identity.clone())); + (BoundKind::Error, StaticType::Error) } + BoundKind::Error => (BoundKind::Error, StaticType::Error), }; - Ok(Node { + Node { identity: node.identity, kind, ty, - }) + } } } @@ -659,13 +660,13 @@ mod tests { fn check_source(source: &str) -> TypedNode { let env = crate::ast::environment::Environment::new(); - env.compile(source).unwrap() + env.compile(source).into_result().unwrap() } #[test] fn test_destructuring_scalar_error() { let env = crate::ast::environment::Environment::new(); - let result = env.compile("(def [x] 5)"); + let result = env.compile("(def [x] 5)").into_result(); assert!(result.is_err()); assert_eq!( result.unwrap_err(), @@ -677,7 +678,7 @@ mod tests { fn test_call_argument_mismatch() { let env = crate::ast::environment::Environment::new(); // fn takes 1 arg, called with 0 - let result = env.compile("(do (def f (fn [x] x)) (f))"); + let result = env.compile("(do (def f (fn [x] x)) (f))").into_result(); assert!(result.is_err()); assert!( result @@ -690,7 +691,7 @@ mod tests { fn test_destructuring_vector_args() { let env = crate::ast::environment::Environment::new(); // ((fn [[x y]] (+ x y)) [10 20]) -> 30 - let result = env.compile("((fn [[x y]] (+ x y)) [10 20])"); + let result = env.compile("((fn [[x y]] (+ x y)) [10 20])").into_result(); assert!(result.is_ok()); assert_eq!(get_ret_type(&result.unwrap()), StaticType::Int); } diff --git a/src/ast/diagnostics.rs b/src/ast/diagnostics.rs new file mode 100644 index 0000000..5d53a9a --- /dev/null +++ b/src/ast/diagnostics.rs @@ -0,0 +1,54 @@ +use crate::ast::types::Identity; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum DiagnosticLevel { + Error, + Warning, + Hint, +} + +#[derive(Debug, Clone)] +pub struct Diagnostic { + pub level: DiagnosticLevel, + pub message: String, + pub identity: Option, +} + +#[derive(Debug, Clone, Default)] +pub struct Diagnostics { + pub items: Vec, +} + +impl Diagnostics { + pub fn new() -> Self { + Self { items: Vec::new() } + } + + pub fn has_errors(&self) -> bool { + self.items.iter().any(|d| d.level == DiagnosticLevel::Error) + } + + pub fn push_error(&mut self, msg: impl Into, id: Option) { + self.items.push(Diagnostic { + level: DiagnosticLevel::Error, + message: msg.into(), + identity: id, + }); + } + + pub fn push_warning(&mut self, msg: impl Into, id: Option) { + self.items.push(Diagnostic { + level: DiagnosticLevel::Warning, + message: msg.into(), + identity: id, + }); + } + + pub fn push_hint(&mut self, msg: impl Into, id: Option) { + self.items.push(Diagnostic { + level: DiagnosticLevel::Hint, + message: msg.into(), + identity: id, + }); + } +} diff --git a/src/ast/environment.rs b/src/ast/environment.rs index 7788df8..3173337 100644 --- a/src/ast/environment.rs +++ b/src/ast/environment.rs @@ -21,8 +21,49 @@ use crate::ast::types::{ Identity, NodeIdentity, Object, Purity, SourceLocation, StaticType, Value, }; +use crate::ast::diagnostics::{DiagnosticLevel, Diagnostics}; pub type PipelineGenerator = Box bool>; +pub struct CompilationResult { + pub ast: Option, + pub diagnostics: Diagnostics, +} + +impl CompilationResult { + pub fn success(ast: TypedNode) -> Self { + Self { + ast: Some(ast), + diagnostics: Diagnostics::new(), + } + } + + pub fn error(msg: impl Into) -> Self { + let mut diag = Diagnostics::new(); + diag.push_error(msg, None); + Self { + ast: None, + diagnostics: diag, + } + } + + pub fn into_result(self) -> Result { + if self.diagnostics.has_errors() { + let errors: Vec = self + .diagnostics + .items + .into_iter() + .filter(|d| d.level == DiagnosticLevel::Error) + .map(|d| d.message) + .collect(); + Err(errors.join("\n")) + } else if let Some(ast) = self.ast { + Ok(ast) + } else { + Err("Compilation failed without diagnostics".to_string()) + } + } +} + pub struct Environment { pub global_names: Rc>>, pub global_types: Rc>>, @@ -77,10 +118,16 @@ impl MacroEvaluator for RuntimeMacroEvaluator { return Ok(Value::Object(Rc::new(arg_node.clone()) as Rc)); } - let (bound_ast, captures) = Binder::bind_root(self.global_names.clone(), node)?; + let mut diag = Diagnostics::new(); + let (bound_ast, captures) = Binder::bind_root(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, &[])?; + let typed_ast = checker.check(bound_ast, &[], &mut diag); + + if diag.has_errors() { + return Err(diag.items.into_iter().map(|d| d.message).collect::>().join("\n")); + } + let exec_ast = TCO::optimize(Analyzer::analyze(&typed_ast, &HashMap::new())); // Minimal analysis for macro eval let mut vm = VM::new(self.global_values.clone()); @@ -141,8 +188,8 @@ impl Environment { } fn eval_prelude(&self, source: &str) { - let mut parser = crate::ast::parser::Parser::new(source).expect("Failed to parse prelude"); - let untyped_ast = parser.parse_expression().expect("Failed to parse prelude expression"); + let mut parser = crate::ast::parser::Parser::new(source); + let untyped_ast = parser.parse_expression(); let mut expander = self.get_expander(); let _ = expander.expand(untyped_ast).expect("Failed to expand prelude"); *self.macro_registry.borrow_mut() = expander.into_registry(); @@ -204,21 +251,40 @@ impl Environment { } pub fn dump_ast(&self, source: &str) -> Result { - let compiled = self.compile(source)?; + let compiled = self.compile(source).into_result()?; let linked = self.link(compiled); Ok(Dumper::dump(&linked)) } - pub fn compile(&self, source: &str) -> Result { - let mut parser = Parser::new(source)?; - let untyped_ast = parser.parse_expression()?; + pub fn compile(&self, source: &str) -> CompilationResult { + let mut parser = Parser::new(source); + let untyped_ast = parser.parse_expression(); if !parser.at_eof() { - return Err("Unexpected trailing expressions in script.".to_string()); + parser.diagnostics.push_error("Unexpected trailing expressions in script.", None); + return CompilationResult { + ast: None, + diagnostics: parser.diagnostics, + }; } + let mut diagnostics = parser.diagnostics; + + let expanded_ast = match self.get_expander().expand(untyped_ast) { + Ok(ast) => ast, + Err(e) => { + diagnostics.push_error(e, None); + return CompilationResult { ast: None, diagnostics }; + } + }; + + let (bound_ast, captures) = match Binder::bind_root(self.global_names.clone(), &expanded_ast, &mut diagnostics) { + Ok(res) => res, + Err(e) => { + diagnostics.push_error(e, None); + return CompilationResult { ast: None, diagnostics }; + } + }; - let expanded_ast = self.get_expander().expand(untyped_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); // Pre-allocate global slots to prevent out-of-bounds during specialization/optimization @@ -234,9 +300,12 @@ impl Environment { LambdaCollector::collect(&bound_ast, &mut self.function_registry.borrow_mut()); let checker = TypeChecker::new(self.global_types.clone()); - let typed_ast = checker.check(bound_ast, &[])?; + let typed_ast = checker.check(bound_ast, &[], &mut diagnostics); - Ok(typed_ast) + CompilationResult { + ast: Some(typed_ast), + diagnostics, + } } pub fn link(&self, node: TypedNode) -> ExecNode { @@ -335,8 +404,13 @@ impl Environment { move |func_template: BoundNode, arg_types: &[StaticType]| -> Result<(Value, StaticType), String> { + let mut diag = Diagnostics::new(); let checker = TypeChecker::new(global_types.clone()); - let retyped_ast = checker.check(func_template, arg_types)?; + let retyped_ast = checker.check(func_template, arg_types, &mut diag); + + if diag.has_errors() { + return Err(diag.items.into_iter().map(|d| d.message).collect::>().join("\n")); + } let analyzed = Analyzer::analyze(&retyped_ast, &global_purity.borrow()); @@ -391,17 +465,20 @@ impl Environment { } res } else { - let compiled = self.compile(source)?; - let linked = self.link(compiled); - let func = self.instantiate(linked); - let res = (func.func)(vec![]); - self.run_pipeline(); - Ok(res) + self.compile(source).into_result().and_then(|ast| self.run_script_compiled(ast)) } } + pub fn run_script_compiled(&self, compiled: TypedNode) -> Result { + let linked = self.link(compiled); + let func = self.instantiate(linked); + let res = (func.func)(vec![]); + self.run_pipeline(); + Ok(res) + } + pub fn run_debug(&self, source: &str) -> Result<(Result, Vec), String> { - let compiled = self.compile(source)?; + let compiled = self.compile(source).into_result()?; let linked = self.link(compiled); let mut vm = VM::new(self.global_values.clone()); let mut observer = TracingObserver::new(); diff --git a/src/ast/mod.rs b/src/ast/mod.rs index c93cac0..e76936a 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -1,4 +1,5 @@ pub mod compiler; +pub mod diagnostics; pub mod environment; pub mod lexer; pub mod nodes; diff --git a/src/ast/nodes.rs b/src/ast/nodes.rs index 3a0afbb..4094a4f 100644 --- a/src/ast/nodes.rs +++ b/src/ast/nodes.rs @@ -123,5 +123,7 @@ pub enum UntypedKind { /// The resulting AST after macro expansion. expanded: Box>, }, + /// A diagnostic poison node, allowing compilation to continue after an error. + Error, Extension(Box), } diff --git a/src/ast/parser.rs b/src/ast/parser.rs index 9efc07f..e3589a7 100644 --- a/src/ast/parser.rs +++ b/src/ast/parser.rs @@ -1,33 +1,55 @@ use crate::ast::lexer::{Lexer, Token, TokenKind}; use crate::ast::nodes::{Node, Symbol, UntypedKind}; use crate::ast::types::{Identity, Keyword, NodeIdentity, Value}; +use crate::ast::diagnostics::Diagnostics; use std::rc::Rc; pub struct Parser<'a> { lexer: Lexer<'a>, current_token: Token, + pub diagnostics: Diagnostics, } impl<'a> Parser<'a> { - pub fn new(input: &'a str) -> Result { + pub fn new(input: &'a str) -> Self { let mut lexer = Lexer::new(input); - let current_token = lexer.next_token()?; - Ok(Self { + let mut diagnostics = Diagnostics::new(); + let current_token = match lexer.next_token() { + Ok(t) => t, + Err(e) => { + diagnostics.push_error(e, None); + Token { + kind: TokenKind::EOF, + location: crate::ast::types::SourceLocation { line: 1, col: 1 }, + } + } + }; + Self { lexer, current_token, - }) + diagnostics, + } } - fn advance(&mut self) -> Result { - let prev = std::mem::replace(&mut self.current_token, self.lexer.next_token()?); - Ok(prev) + fn advance(&mut self) -> Token { + let next = match self.lexer.next_token() { + Ok(t) => t, + Err(e) => { + self.diagnostics.push_error(e, Some(NodeIdentity::new(self.current_token.location))); + Token { + kind: TokenKind::EOF, + location: self.current_token.location, + } + } + }; + std::mem::replace(&mut self.current_token, next) } fn peek(&self) -> &TokenKind { &self.current_token.kind } - pub fn parse_expression(&mut self) -> Result, String> { + pub fn parse_expression(&mut self) -> Node { let token_loc = self.current_token.location; let identity = NodeIdentity::new(token_loc); @@ -36,9 +58,9 @@ impl<'a> Parser<'a> { TokenKind::LeftBracket => self.parse_vector_literal(), TokenKind::LeftBrace => self.parse_record_literal(), TokenKind::Quote => { - self.advance()?; // consume ' - let expr = self.parse_expression()?; - Ok(Node { + self.advance(); // consume ' + let expr = self.parse_expression(); + Node { identity: identity.clone(), kind: UntypedKind::Call { callee: Box::new(self.make_id_node("quote", identity.clone())), @@ -51,34 +73,34 @@ impl<'a> Parser<'a> { }), }, ty: (), - }) + } } TokenKind::Backtick => { - self.advance()?; // consume ` - let expr = self.parse_expression()?; - Ok(Node { + self.advance(); // consume ` + let expr = self.parse_expression(); + Node { identity, kind: UntypedKind::Template(Box::new(expr)), ty: (), - }) + } } TokenKind::Tilde => { - self.advance()?; // consume ~ + self.advance(); // consume ~ if *self.peek() == TokenKind::At { - self.advance()?; // consume @ - let expr = self.parse_expression()?; - Ok(Node { + self.advance(); // consume @ + let expr = self.parse_expression(); + Node { identity, kind: UntypedKind::Splice(Box::new(expr)), ty: (), - }) + } } else { - let expr = self.parse_expression()?; - Ok(Node { + let expr = self.parse_expression(); + Node { identity, kind: UntypedKind::Placeholder(Box::new(expr)), ty: (), - }) + } } } _ => self.parse_atom(), @@ -89,8 +111,22 @@ impl<'a> Parser<'a> { matches!(self.current_token.kind, TokenKind::EOF) } - fn parse_atom(&mut self) -> Result, String> { - let token = self.advance()?; + fn synchronize(&mut self) { + while !self.at_eof() { + match self.peek() { + TokenKind::RightParen | TokenKind::RightBracket | TokenKind::RightBrace => { + self.advance(); + return; + } + _ => { + self.advance(); + } + } + } + } + + fn parse_atom(&mut self) -> Node { + let token = self.advance(); let identity = NodeIdentity::new(token.location); let kind = match token.kind { @@ -105,35 +141,43 @@ impl<'a> Parser<'a> { } _ => UntypedKind::Identifier(id.into()), }, + TokenKind::EOF => UntypedKind::Error, // Error already logged by advance _ => { - return Err(format!( - "Unexpected token in atom: {:?} at {:?}", - token.kind, token.location - )); + self.diagnostics.push_error( + format!("Unexpected token in atom: {:?}", token.kind), + Some(identity.clone()), + ); + UntypedKind::Error } }; - Ok(Node { + Node { identity, kind, ty: (), - }) + } } - fn parse_list(&mut self) -> Result, String> { - let start_loc = self.advance()?.location; // consume '(' + fn parse_list(&mut self) -> Node { + let start_loc = self.advance().location; // consume '(' let identity = NodeIdentity::new(start_loc); if *self.peek() == TokenKind::RightParen { - return Err(format!( - "Empty list () is not a valid expression at {:?}", - start_loc - )); + self.diagnostics.push_error( + "Empty list () is not a valid expression", + Some(identity.clone()), + ); + self.advance(); // consume ) + return Node { + identity, + kind: UntypedKind::Error, + ty: (), + }; } - let head = self.parse_expression()?; + let head = self.parse_expression(); - let result = if let UntypedKind::Identifier(ref sym) = head.kind { + let node = if let UntypedKind::Identifier(ref sym) = head.kind { match sym.name.as_ref() { "if" => self.parse_if(identity), "fn" => self.parse_fn(identity), @@ -149,15 +193,15 @@ impl<'a> Parser<'a> { self.parse_call(head, identity) }; - self.expect(TokenKind::RightParen)?; - result + self.expect(TokenKind::RightParen); + node } - fn parse_again(&mut self, identity: Identity) -> Result, String> { + fn parse_again(&mut self, identity: Identity) -> Node { let mut elements = Vec::new(); while *self.peek() != TokenKind::RightParen && *self.peek() != TokenKind::EOF { - elements.push(self.parse_expression()?); + elements.push(self.parse_expression()); } let args_node = Node { @@ -166,25 +210,25 @@ impl<'a> Parser<'a> { ty: (), }; - Ok(Node { + Node { identity, kind: UntypedKind::Again { args: Box::new(args_node), }, ty: (), - }) + } } - fn parse_if(&mut self, identity: Identity) -> Result, String> { - let cond = Box::new(self.parse_expression()?); - let then_br = Box::new(self.parse_expression()?); + fn parse_if(&mut self, identity: Identity) -> Node { + let cond = Box::new(self.parse_expression()); + let then_br = Box::new(self.parse_expression()); let mut else_br = None; if *self.peek() != TokenKind::RightParen { - else_br = Some(Box::new(self.parse_expression()?)); + else_br = Some(Box::new(self.parse_expression())); } - Ok(Node { + Node { identity, kind: UntypedKind::If { cond, @@ -192,82 +236,88 @@ impl<'a> Parser<'a> { else_br, }, ty: (), - }) + } } - fn parse_def(&mut self, identity: Identity) -> Result, String> { - let target = Box::new(self.parse_pattern()?); - let value = Box::new(self.parse_expression()?); + fn parse_def(&mut self, identity: Identity) -> Node { + let target = Box::new(self.parse_pattern()); + let value = Box::new(self.parse_expression()); - Ok(Node { + Node { identity, kind: UntypedKind::Def { target, value }, ty: (), - }) + } } - fn parse_assign(&mut self, identity: Identity) -> Result, String> { + fn parse_assign(&mut self, identity: Identity) -> Node { // (assign target value) - let target = Box::new(self.parse_expression()?); - let value = Box::new(self.parse_expression()?); + let target = Box::new(self.parse_expression()); + let value = Box::new(self.parse_expression()); - Ok(Node { + Node { identity, kind: UntypedKind::Assign { target, value }, ty: (), - }) + } } - fn parse_do(&mut self, identity: Identity) -> Result, String> { + fn parse_do(&mut self, identity: Identity) -> Node { let mut exprs = Vec::new(); while *self.peek() != TokenKind::RightParen && *self.peek() != TokenKind::EOF { - exprs.push(self.parse_expression()?); + exprs.push(self.parse_expression()); } - Ok(Node { + Node { identity, kind: UntypedKind::Block { exprs }, ty: (), - }) + } } - fn parse_pipe(&mut self, identity: Identity) -> Result, String> { - let inputs_node = self.parse_expression()?; + fn parse_pipe(&mut self, identity: Identity) -> Node { + let inputs_node = self.parse_expression(); let inputs = match inputs_node.kind { UntypedKind::Tuple { elements } => elements, _ => vec![inputs_node], }; - let lambda = Box::new(self.parse_expression()?); + let lambda = Box::new(self.parse_expression()); - Ok(Node { + Node { identity, kind: UntypedKind::Pipe { inputs, lambda }, ty: (), - }) + } } - fn parse_fn(&mut self, identity: Identity) -> Result, String> { - let params = Box::new(self.parse_param_vector()?); - let body = self.parse_expression()?; + fn parse_fn(&mut self, identity: Identity) -> Node { + let params = Box::new(self.parse_param_vector()); + let body = self.parse_expression(); - Ok(Node { + Node { identity, kind: UntypedKind::Lambda { params, body: Rc::new(body), }, ty: (), - }) + } } - fn parse_macro_decl(&mut self, identity: Identity) -> Result, String> { - let name_node = self.parse_expression()?; + fn parse_macro_decl(&mut self, identity: Identity) -> Node { + let name_node = self.parse_expression(); let name = match name_node.kind { UntypedKind::Identifier(sym) => sym, - _ => return Err("Expected identifier for macro name".to_string()), + _ => { + self.diagnostics.push_error( + "Expected identifier for macro name", + Some(name_node.identity.clone()), + ); + Symbol::from("error") + } }; - let params = Box::new(self.parse_param_vector()?); - let body = self.parse_expression()?; - Ok(Node { + let params = Box::new(self.parse_param_vector()); + let body = self.parse_expression(); + Node { identity, kind: UntypedKind::MacroDecl { name, @@ -275,54 +325,66 @@ impl<'a> Parser<'a> { body: Box::new(body), }, ty: (), - }) + } } - fn parse_param_vector(&mut self) -> Result, String> { + fn parse_param_vector(&mut self) -> Node { if *self.peek() != TokenKind::LeftBracket { - return Err(format!( - "Expected parameter vector [...] for fn, found {:?}", - self.peek() - )); + self.diagnostics.push_error( + format!("Expected parameter vector [...] for fn, found {:?}", self.peek()), + Some(NodeIdentity::new(self.current_token.location)), + ); + return Node { + identity: NodeIdentity::new(self.current_token.location), + kind: UntypedKind::Error, + ty: (), + }; } self.parse_pattern() } - fn parse_pattern(&mut self) -> Result, String> { + fn parse_pattern(&mut self) -> Node { let next = self.peek(); match next { TokenKind::Identifier(_) => { - let token = self.advance()?; + let token = self.advance(); let sym: Symbol = match token.kind { TokenKind::Identifier(s) => s.into(), _ => unreachable!(), }; - Ok(Node { + Node { identity: NodeIdentity::new(token.location), kind: UntypedKind::Parameter(sym), ty: (), - }) + } } TokenKind::LeftBracket => { - let token = self.advance()?; + let token = self.advance(); let identity = NodeIdentity::new(token.location); let mut elements = Vec::new(); - while *self.peek() != TokenKind::RightBracket { - elements.push(self.parse_pattern()?); + while *self.peek() != TokenKind::RightBracket && *self.peek() != TokenKind::EOF { + elements.push(self.parse_pattern()); } - self.expect(TokenKind::RightBracket)?; + self.expect(TokenKind::RightBracket); - Ok(Node { + Node { identity, kind: UntypedKind::Tuple { elements }, ty: (), - }) + } + } + _ => { + self.diagnostics.push_error( + format!("Expected identifier or pattern vector [...] for definition, found {:?}", next), + Some(NodeIdentity::new(self.current_token.location)), + ); + Node { + identity: NodeIdentity::new(self.current_token.location), + kind: UntypedKind::Error, + ty: (), + } } - _ => Err(format!( - "Expected identifier or pattern vector [...] for definition, found {:?}", - next - )), } } @@ -330,11 +392,11 @@ impl<'a> Parser<'a> { &mut self, callee: Node, identity: Identity, - ) -> Result, String> { + ) -> Node { let mut elements = Vec::new(); while *self.peek() != TokenKind::RightParen && *self.peek() != TokenKind::EOF { - elements.push(self.parse_expression()?); + elements.push(self.parse_expression()); } // The arguments are wrapped in a Tuple node, reusing the call's identity/location. @@ -344,77 +406,87 @@ impl<'a> Parser<'a> { ty: (), }; - Ok(Node { + Node { identity, kind: UntypedKind::Call { callee: Box::new(callee), args: Box::new(args_node), }, ty: (), - }) + } } - fn parse_vector_literal(&mut self) -> Result, String> { - let token = self.advance()?; + fn parse_vector_literal(&mut self) -> Node { + let token = self.advance(); let mut elements = Vec::new(); while *self.peek() != TokenKind::RightBracket && *self.peek() != TokenKind::EOF { - let expr = self.parse_expression()?; + let expr = self.parse_expression(); elements.push(expr); } - self.expect(TokenKind::RightBracket)?; + self.expect(TokenKind::RightBracket); - Ok(Node { + Node { identity: NodeIdentity::new(token.location), kind: UntypedKind::Tuple { elements }, ty: (), - }) + } } - fn parse_record_literal(&mut self) -> Result, String> { - let token = self.advance()?; + fn parse_record_literal(&mut self) -> Node { + let token = self.advance(); let mut fields = Vec::new(); - while *self.peek() != TokenKind::RightBrace { - if *self.peek() == TokenKind::EOF { - return Err("Unexpected EOF in record literal".to_string()); - } - - let key_node = self.parse_expression()?; + while *self.peek() != TokenKind::RightBrace && *self.peek() != TokenKind::EOF { + let key_node = self.parse_expression(); // We check for keyword kind here (syntactically) to avoid ambiguity, but // strictly we could allow any expression and check at runtime. // Delphi enforces keywords. We can do minimal check here. match &key_node.kind { UntypedKind::Constant(Value::Keyword(_)) => {} - _ => return Err("Record keys must be keywords (syntactically)".to_string()), + _ => { + self.diagnostics.push_error( + "Record keys must be keywords (syntactically)", + Some(key_node.identity.clone()), + ); + } } - if *self.peek() == TokenKind::RightBrace { - return Err("Record literal must have even number of forms".to_string()); + if *self.peek() == TokenKind::RightBrace || *self.peek() == TokenKind::EOF { + self.diagnostics.push_error( + "Record literal must have even number of forms", + Some(NodeIdentity::new(self.current_token.location)), + ); + break; } - let val_node = self.parse_expression()?; + let val_node = self.parse_expression(); fields.push((key_node, val_node)); } - self.expect(TokenKind::RightBrace)?; + self.expect(TokenKind::RightBrace); - Ok(Node { + Node { identity: NodeIdentity::new(token.location), kind: UntypedKind::Record { fields }, ty: (), - }) + } } - fn expect(&mut self, kind: TokenKind) -> Result<(), String> { - let token = self.advance()?; - if token.kind == kind { - Ok(()) + fn expect(&mut self, kind: TokenKind) -> Token { + if self.peek() == &kind { + self.advance() } else { - Err(format!( - "Expected {:?}, but found {:?} at {:?}", - kind, token.kind, token.location - )) + self.diagnostics.push_error( + format!("Expected {:?}, but found {:?}", kind, self.peek()), + Some(NodeIdentity::new(self.current_token.location)), + ); + // Recovery: skip until we find what we expected or a synchronization point + self.synchronize(); + Token { + kind, + location: self.current_token.location, + } } } diff --git a/src/ast/types.rs b/src/ast/types.rs index 8a5922d..d41f22c 100644 --- a/src/ast/types.rs +++ b/src/ast/types.rs @@ -306,6 +306,8 @@ pub enum StaticType { Function(Box), FunctionOverloads(Vec), Object(&'static str), + /// A diagnostic poison type, allowing type-checking to continue after an error. + Error, } impl fmt::Display for StaticType { @@ -361,6 +363,7 @@ impl fmt::Display for StaticType { write!(f, "overloads({} variants)", sigs.len()) } StaticType::Object(name) => write!(f, "{}", name), + StaticType::Error => write!(f, ""), } } } @@ -368,7 +371,7 @@ impl fmt::Display for StaticType { impl StaticType { /// Returns true if `other` can be assigned to a location of type `self`. pub fn is_assignable_from(&self, other: &StaticType) -> bool { - if self == other || matches!(self, StaticType::Any) || matches!(other, StaticType::Any) { + if self == other || matches!(self, StaticType::Any) || matches!(other, StaticType::Any) || matches!(self, StaticType::Error) || matches!(other, StaticType::Error) { return true; } diff --git a/src/ast/vm.rs b/src/ast/vm.rs index a6ac6d7..fa30536 100644 --- a/src/ast/vm.rs +++ b/src/ast/vm.rs @@ -658,6 +658,7 @@ impl VM { "Execution of extension '{}' not implemented yet", ext.display_name() )), + BoundKind::Error => Err("Cannot execute a poisoned AST node".to_string()), } } diff --git a/src/bin/ast.rs b/src/bin/ast.rs index 9b085bc..8fd0df3 100644 --- a/src/bin/ast.rs +++ b/src/bin/ast.rs @@ -100,10 +100,21 @@ fn main() { } fn execute(env: &Environment, source: &str) { - match env.run_script(source) { - Ok(result) => println!("{}", result), + let result = env.compile(source); + for diag in &result.diagnostics.items { + let level = format!("{:?}", diag.level).to_uppercase(); + let loc = diag.identity.as_ref().and_then(|id| id.location).map(|l| format!(" at {}:{}", l.line, l.col)).unwrap_or_default(); + eprintln!("{}{} : {}", level, loc, diag.message); + } + + if result.diagnostics.has_errors() { + std::process::exit(1); + } + + match env.run_script_compiled(result.ast.unwrap()) { + Ok(res) => println!("{}", res), Err(e) => { - eprintln!("Error: {}", e); + eprintln!("Runtime Error: {}", e); std::process::exit(1); } } diff --git a/src/integration_test.rs b/src/integration_test.rs index e9d751d..e23eb39 100644 --- a/src/integration_test.rs +++ b/src/integration_test.rs @@ -1,621 +1,686 @@ -#[cfg(test)] -mod tests { - use crate::ast::environment::Environment; - use crate::ast::nodes::UntypedKind; - use crate::ast::parser::Parser; - use crate::ast::types::Value; - - #[test] - fn test_parse_integer_constant() { - let source = "123"; - let mut parser = Parser::new(source).expect("Failed to create parser"); - let ast = parser.parse_expression().expect("Failed to parse"); - - if let UntypedKind::Constant(Value::Int(val)) = ast.kind { - assert_eq!(val, 123); - } else { - panic!("Expected Integer constant, got {:?}", ast.kind); - } - } - - #[test] - fn test_parse_negative_integer() { - let source = "-42"; - let mut parser = Parser::new(source).expect("Failed to create parser"); - let ast = parser.parse_expression().expect("Failed to parse"); - - if let UntypedKind::Constant(Value::Int(val)) = ast.kind { - assert_eq!(val, -42); - } else { - panic!("Expected Integer constant, got {:?}", ast.kind); - } - } - - #[test] - fn test_parse_float_constant() { - let source = "123.45"; - let mut parser = Parser::new(source).expect("Failed to create parser"); - let ast = parser.parse_expression().expect("Failed to parse"); - - if let UntypedKind::Constant(Value::Float(val)) = ast.kind { - assert_eq!(val, 123.45); - } else { - panic!("Expected Float constant, got {:?}", ast.kind); - } - } - - #[test] - fn test_parse_negative_float() { - let source = "-10.5"; - let mut parser = Parser::new(source).expect("Failed to create parser"); - let ast = parser.parse_expression().expect("Failed to parse"); - - if let UntypedKind::Constant(Value::Float(val)) = ast.kind { - assert_eq!(val, -10.5); - } else { - panic!("Expected Float constant, got {:?}", ast.kind); - } - } - - #[test] - fn test_closure_modification_from_source() { - let source = r#" - (do - (def x 10) - (def f (fn [] (assign x 20))) - (f) - x - ) - "#; - - let env = Environment::new(); - let compiled = env.compile(source).expect("Failed to compile"); - let linked = env.link(compiled); - let func = env.instantiate(linked); - let result: Result = Ok((func.func)(vec![])); - - match result { - Ok(Value::Int(20)) => (), - Ok(val) => panic!("Expected Int(20), got {:?}", val), - Err(e) => panic!("VM Error: {}", e), - } - } - - #[test] - fn test_examples() { - for opt in [false, true] { - let results = crate::utils::tester::run_functional_tests_with_optimization(opt); - for res in results { - assert!( - res.success, - "Example {} failed at opt {}: {}", - res.name, opt, res.message - ); - } - } - } - - #[test] - fn test_debug_mode_logging() { - let mut env = Environment::new(); - env.optimization = false; - let source = "(+ 10 20)"; - let result = env.run_debug(source).expect("Failed to run debug"); - - let (val, logs) = result; - - // 1. Check value - match val { - Ok(Value::Int(30)) => (), - _ => panic!("Expected Int(30), got {:?}", val), - } - - // 2. Check logs (should have entries for + and constants) - assert!(!logs.is_empty(), "Logs should not be empty"); - - // Look for typical trace patterns - let has_call = logs.iter().any(|l| l.contains("CALL")); - let has_const = logs.iter().any(|l| l.contains("CONST(10)")); - let has_result = logs.iter().any(|l| l.contains("} -> 30")); - - assert!(has_call, "Logs should contain CALL"); - assert!(has_const, "Logs should contain CONST(10)"); - assert!(has_result, "Logs should contain result 30"); - } - - #[test] - fn test_rtl_operators() { - let env = Environment::new(); - - // --- Arithmetic --- - assert_eq!(format!("{}", env.run_script("(+ 10 20)").unwrap()), "30"); - assert_eq!(format!("{}", env.run_script("(- 20 10)").unwrap()), "10"); - assert_eq!(format!("{}", env.run_script("(* 10 20)").unwrap()), "200"); - assert_eq!(format!("{}", env.run_script("(/ 20 10)").unwrap()), "2"); - assert_eq!(format!("{}", env.run_script("(// 20 3)").unwrap()), "6"); // 20 // 3 = 6 - assert_eq!(format!("{}", env.run_script("(% 20 3)").unwrap()), "2"); // 20 % 3 = 2 - - // --- Logic / Bitwise --- - assert_eq!( - format!("{}", env.run_script("(and true false)").unwrap()), - "false" - ); - assert_eq!( - format!("{}", env.run_script("(or true false)").unwrap()), - "true" - ); - assert_eq!( - format!("{}", env.run_script("(xor true false)").unwrap()), - "true" - ); - assert_eq!( - format!("{}", env.run_script("(not true)").unwrap()), - "false" - ); - - assert_eq!(format!("{}", env.run_script("(<< 1 2)").unwrap()), "4"); // 1 << 2 = 4 - assert_eq!(format!("{}", env.run_script("(>> 4 1)").unwrap()), "2"); // 4 >> 1 = 2 - assert_eq!(format!("{}", env.run_script("(and 3 1)").unwrap()), "1"); // 3 & 1 = 1 - - // --- Comparison --- - assert_eq!(format!("{}", env.run_script("(= 10 10)").unwrap()), "true"); - assert_eq!(format!("{}", env.run_script("(= 10 20)").unwrap()), "false"); - assert_eq!(format!("{}", env.run_script("(<> 10 20)").unwrap()), "true"); - assert_eq!(format!("{}", env.run_script("(< 10 20)").unwrap()), "true"); - assert_eq!(format!("{}", env.run_script("(> 10 20)").unwrap()), "false"); - assert_eq!(format!("{}", env.run_script("(<= 10 10)").unwrap()), "true"); - assert_eq!(format!("{}", env.run_script("(>= 10 10)").unwrap()), "true"); - - // --- NaN --- - assert_eq!(format!("{}", env.run_script("NaN").unwrap()), "NaN"); - } - - #[test] - fn test_random_isolation_between_environments() { - let env1 = Environment::new(); - let env2 = Environment::new(); - - // 1. Create a seeded generator in env1 - env1.run_script("(def rand (make-random 123))").unwrap(); - let val1_a = env1.run_script("(rand)").unwrap(); - - // 2. env2 should have its own default seed state for its generators - env2.run_script("(def rand (make-random))").unwrap(); - let val2_a = env2.run_script("(rand)").unwrap(); - - // They are highly unlikely to be equal by default, - // and seeding env1 MUST not have seeded env2. - assert_ne!( - val1_a, val2_a, - "Environments must have isolated PRNG states" - ); - - // 3. Create another generator in env2 with the same seed - env2.run_script("(def rand-same (make-random 123))").unwrap(); - let val2_b = env2.run_script("(rand-same)").unwrap(); - - // After same seeding, they should match (isolated but identical seed) - assert_eq!( - val1_a, val2_b, - "Different environments with the same seed must produce the same sequence" - ); - } - - #[test] - fn test_random_seeding_determinism() { - let env = Environment::new(); - - // 1. First run with seed 42 - env.run_script("(def rand1 (make-random 42))").unwrap(); - let val1 = env.run_script("(rand1)").unwrap(); - - // 2. Second run with same seed 42 - env.run_script("(def rand2 (make-random 42))").unwrap(); - let val2 = env.run_script("(rand2)").unwrap(); - - assert_eq!( - val1, val2, - "Random results must be identical for the same seed" - ); - - // 3. Third run with different seed - env.run_script("(def rand3 (make-random 123))").unwrap(); - let val3 = env.run_script("(rand3)").unwrap(); - assert_ne!(val1, val3, "Random results must differ for different seeds"); - } - - #[test] - fn test_now_function_not_folded() { - let env = Environment::new(); - let source = "(now)"; - - // 1. Check result type and value plausibility - let result = env.run_script(source).expect("Failed to run script"); - if let Value::DateTime(ts) = result { - let current = chrono::Utc::now().timestamp_millis(); - assert!(ts > 0); - assert!(ts <= current); - } else { - panic!("Expected DateTime, got {:?}", result); - } - - // 2. Verify it's NOT constant folded in the AST dump - let dump = env.dump_ast(source).expect("Failed to dump AST"); - assert!( - dump.contains("Call"), - "now() should remain a Call, not a Constant. Dump: \n{}", - dump - ); - assert!( - !dump.contains("Constant: #"), - "now() should NOT be folded into a specific timestamp constant. Dump: \n{}", - dump - ); - } - - #[test] - fn test_date_parsing() { - let env = Environment::new(); - let res = env.run_script("(date \"2023-01-01\")").unwrap(); - if let Value::DateTime(_) = res { - // OK - } else { - panic!("Expected DateTime, got {:?}", res); - } - } - - #[test] - #[should_panic(expected = "'again' is only allowed in tail position to avoid dead code.")] - fn test_again_non_tail_panic() { - let env = Environment::new(); - let source = "(do (def f (fn [x] (do (again (- x 1)) x))) (f 5))"; - // This will trigger the TCO pass which contains the validation logic - let _ = env.run_script(source); - } - - #[test] - fn test_dynamic_call_destructuring_underflow() { - let env = Environment::new(); - let source = "(do - (def call-dynamic (fn [f data] (f data))) - (def data [10 [20 30]]) - (def x (fn [[a [b c]]] (+ a (+ b c)))) - (call-dynamic x data))"; - - let result = env.run_script(source); - if let Err(e) = &result { - panic!("Failed: {}", e); - } - assert_eq!(format!("{}", result.unwrap()), "60"); - } - - #[test] - fn test_nested_destructuring_optimization() { - let env = Environment::new(); - - // 1. Tuple-to-Tuple - let source_tuple = "((fn [[x y]] (+ x y)) [10 20])"; - assert_eq!(format!("{}", env.run_script(source_tuple).unwrap()), "30"); - let dump_tuple = env.dump_ast(source_tuple).unwrap(); - assert!( - dump_tuple.contains("Constant: 30"), - "Nested tuple should be folded to 30. Dump:\n{}", - dump_tuple - ); - } - - #[test] - fn test_def_destructuring() { - let env = Environment::new(); - - // 1. Global destructuring - let source_global = "(do (def [a b] [1 2]) (+ a b))"; - assert_eq!(format!("{}", env.run_script(source_global).unwrap()), "3"); - - // 2. Local nested destructuring inside a function - let source_local = - "((fn [x] (do (def [a [[b c] d]] x) (+ a (+ b (+ c d))))) [1 [[2 3] 4]])"; - assert_eq!(format!("{}", env.run_script(source_local).unwrap()), "10"); - - // 3. Verify 'def' returns the assigned value - let source_return = "(def [x y] [7 8])"; - let res = env.run_script(source_return).unwrap(); - if let Value::Tuple(vals) = res { - assert_eq!(vals.len(), 2); - assert_eq!(format!("{}", vals[0]), "7"); - assert_eq!(format!("{}", vals[1]), "8"); - } else { - panic!("Expected tuple return from def, got {:?}", res); - } - } - - #[test] - fn test_assign_destructuring() { - // 1. Simple assignment destructuring - { - let env = Environment::new(); - let source_simple = "(do (def a 0) (def b 0) (assign [a b] [10 20]) (+ a b))"; - assert_eq!(format!("{}", env.run_script(source_simple).unwrap()), "30"); - } - - // 2. Nested assignment destructuring - { - let env = Environment::new(); - let source_nested = - "(do (def a 0) (def b 0) (def c 0) (assign [a [b c]] [1 [2 3]]) (+ a (+ b c)))"; - assert_eq!(format!("{}", env.run_script(source_nested).unwrap()), "6"); - } - - // 3. Assignment returns the assigned value - { - let env = Environment::new(); - let source_return = "(do (def a 0) (def b 0) (assign [a b] [5 6]))"; - let res = env.run_script(source_return).unwrap(); - if let Value::Tuple(vals) = res { - assert_eq!(vals.len(), 2); - assert_eq!(format!("{}", vals[0]), "5"); - assert_eq!(format!("{}", vals[1]), "6"); - } else { - panic!("Expected tuple return from assign, got {:?}", res); - } - } - } - - #[test] - fn test_pipeline_optional_type() { - let env = Environment::new(); - // The lambda uses an `if` without an `else` returning a float constant. - // The TypeChecker should deduce `Optional(Float)` for the lambda body, - // and correctly unwrap it to `Series(Float)` for the pipeline output. - let source = "(do - (def src (create-random-ohlc 42 10)) - (def filtered - (pipe [src] - (fn [tick] - (if true - 42.0 - ) - ) - ) - ) - filtered - )"; - let res = env.run_script(source); - if let Err(e) = &res { - panic!("Script failed to compile/run: {:?}", e); - } - - let val = res.unwrap(); - if let crate::ast::types::Value::Object(obj) = val { - assert_eq!(obj.type_name(), "PipelineNode"); - } else { - panic!("Expected an Object(PipelineNode)"); - } - } - - #[test] - fn test_multi_level_destructuring() { - let env = Environment::new(); - let source = "(do - (def process_data (fn [conf] - (do - (def [str s] conf) - (def [f ss] s) - [\"Symbol:\" str \"field:\" f \"id:\" ss] - ) - ) - ) - (process_data [\"btc\" [:close \"cls\"]]))"; - - let res = env.run_script(source).unwrap(); - assert_eq!( - format!("{}", res), - "[\"Symbol:\" \"btc\" \"field:\" :close \"id:\" \"cls\"]" - ); - } - - #[test] - fn test_closure_reassignment_optimization_bug() { - let env = Environment::new(); - // This test case reproduces a bug where the optimizer aggressively inlined a function - // ('f') even though its parameters ('x') were being assigned to in the body (by inner lambda). - // The fix ensures that such functions are NOT inlined. - let source = "(do (def f (fn [[x y]] (fn [] (assign x (+ x y))))) ((f [1 2])))"; - let res = env.run_script(source); - assert_eq!(format!("{}", res.unwrap()), "3"); - } - - #[test] - fn test_macro_inlining_identity_collision() { - let source = r#" - (do - (macro wrap [f] `(fn [x] (~f x))) - - (def add1 (fn [x] (+ x 1))) - (def add2 (fn [x] (+ x 2))) - - (def w1 (wrap add1)) - (def w2 (wrap add2)) - - (w1 (w2 10))) - "#; - - // 1. Verify the result is correct - let env_run = Environment::new(); - let res = env_run.run_script(source).expect("Failed to run script"); - assert_eq!(format!("{}", res), "13"); - - // 2. Verify that it was actually folded into a constant by the optimizer - let env_dump = Environment::new(); - let dump = env_dump.dump_ast(source).expect("Failed to dump AST"); - assert!( - dump.contains("Constant: 13"), - "Macro-wrapped calls should be fully folded to 13. Dump:\n{}", - dump - ); - // The definitions add1, add2, w1, w2 should be gone after dead code elimination - assert!( - !dump.contains("Define Variable"), - "Definitions should be removed by DCE" - ); - } - - #[test] - fn test_optimizer_upvalue_inlining_bug_repro() { - let env = Environment::new(); - let source = r#" - (do - (def make-counter (fn [init] - (do - (def val init) - { - :inc (fn [] (assign val (+ val 1))) - :get (fn [] val) - }))) - (def c (make-counter 10)) - ((.inc c)) - ((.get c))) - "#; - let res = env.run_script(source); - assert!(res.is_ok(), "Optimizer bug triggered: {:?}", res.err()); - assert_eq!(format!("{}", res.unwrap()), "11"); - } - - #[test] - fn test_optimizer_destructuring_inlining_and_mutation() { - let env = Environment::new(); - // 1. Test: Destructuring definition should allow inlining if not mutated - let source_inline = "(do (def [x y] [10 20]) (+ x y))"; - let res_inline = env.run_script(source_inline).unwrap(); - assert_eq!(format!("{}", res_inline), "30"); - - // 2. Test: Destructuring definition should NOT be inlined if mutated (Regression Test) - let source_mutation = r#" - (do - (def [a b] [1 2]) - (def f (fn [] (assign a (+ a b)))) - (f) - a) - "#; - let res_mutation = env.run_script(source_mutation).unwrap(); - assert_eq!(format!("{}", res_mutation), "3"); - } - - #[test] - fn test_record_basics() { - let env = Environment::new(); - let source = r#" - ((fn [user] [(.name user) (.age user)]) - {:name "Alice" :age 30}) - "#; - let res = env.run_script(source).unwrap(); - assert_eq!(format!("{}", res), "[\"Alice\" 30]"); - } - - #[test] - fn test_record_optimized_access() { - let env = Environment::new(); - let source_eval = "(.price {:id 1 :price 99.5})"; - - // 1. Check result (will be fully folded to a constant by the new optimization) - let res = env.run_script(source_eval).unwrap(); - assert_eq!(format!("{}", res), "99.5"); - - // 2. Verify optimization to GET_FIELD when the record contains non-constants - let source_ast = "(fn [id] (.price {:id id :price 99.5}))"; - let dump = env.dump_ast(source_ast).unwrap(); - assert!(dump.contains("GetField: .price"), "Should be optimized to GetField. Dump:\n{}", dump); - } - #[test] - fn test_first_class_field_accessor() { - let env = Environment::new(); - let source = r#" - (do - (def get-name .name) - ; Dynamic call to field accessor - (get-name {:name "Alice"})) - "#; - let res = env.run_script(source).unwrap(); - assert_eq!(format!("{}", res), "\"Alice\""); - } - - #[test] - fn test_record_constant_folding() { - let env = Environment::new(); - // Optimizer should fold (.x {:x 10}) into 10 - let source = "(.x {:x 10 :y 20})"; - let dump = env.dump_ast(source).unwrap(); - assert!(dump.contains("Constant: 10"), "Should evaluate GetField at compile time."); - } - - #[test] - fn test_record_literal_constant_folding() { - let env = Environment::new(); - let source = " {:a 1 :b 2} "; - let dump = env.dump_ast(source).unwrap(); - - // Ensure the record definition itself is folded into a Constant. - assert!(dump.contains("Constant: {:a 1, :b 2}"), "Should transform a pure record literal into a constant value."); - assert!(!dump.contains("Record {"), "Should not leave a runtime Record node in the AST."); - } - - #[test] - fn test_record_inlining_in_while_loop() { - let env_ast = Environment::new(); - // Ensure that constants (like the config record) are inherited into inner lambda scopes (like the body of a while loop). - let source = r#" - (do - (def loop-config {:start 0 :limit 10}) - (def loop-idx 0) - (while (< loop-idx (.limit loop-config)) - (assign loop-idx (+ loop-idx 1))) - loop-idx - ) - "#; - let dump = env_ast.dump_ast(source).unwrap(); - - // The optimizer should inline `loop-config`, resolving `(.limit loop-config)` to `10`. - // The GetField and the Get for 'loop-config' should vanish inside the condition. - assert!(!dump.contains("GetField: .limit"), "The record field should be completely inlined."); - assert!(dump.contains("Constant: 10"), "The limit should be resolved to a constant 10."); - - // The result of running it should obviously still be correct. - let env_run = Environment::new(); - let res = env_run.run_script(source).unwrap(); - assert_eq!(format!("{}", res), "10"); - } - - #[test] - fn test_record_errors() { - let env = Environment::new(); - - // Both cases are reported by the TypeChecker since it can't resolve the call - // for a missing field or a non-record argument. - - // 1. Missing field - let res_missing = env.run_script("(.missing {:a 1})"); - assert!(res_missing.is_err()); - assert!(res_missing.unwrap_err().contains("Invalid arguments")); - - // 2. Not a record - let res_not_rec = env.run_script("(.name 123)"); - assert!(res_not_rec.is_err()); - assert!(res_not_rec.unwrap_err().contains("Invalid arguments")); - } - - #[test] - fn test_record_layout_interning() { - let env = Environment::new(); - let source = r#" - (do - (def r1 {:a 1 :b 2}) - (def r2 {:a 10 :b 20}) - ; Identical layouts result in identical types - (= r1 r2)) - "#; - // This will be false because values differ, but let's just check if it compiles and runs. - // To really test interning, we'd need a way to check if layouts are the same Arc. - let res = env.run_script(source).unwrap(); - assert_eq!(format!("{}", res), "false"); - } -} +#[cfg(test)] +mod tests { + use crate::ast::environment::Environment; + use crate::ast::nodes::UntypedKind; + use crate::ast::parser::Parser; + use crate::ast::types::Value; + + #[test] + fn test_parse_integer_constant() { + let source = "123"; + let mut parser = Parser::new(source); + let ast = parser.parse_expression(); + + if let UntypedKind::Constant(Value::Int(val)) = ast.kind { + assert_eq!(val, 123); + } else { + panic!("Expected Integer constant, got {:?}", ast.kind); + } + } + + #[test] + fn test_parse_negative_integer() { + let source = "-42"; + let mut parser = Parser::new(source); + let ast = parser.parse_expression(); + + if let UntypedKind::Constant(Value::Int(val)) = ast.kind { + assert_eq!(val, -42); + } else { + panic!("Expected Integer constant, got {:?}", ast.kind); + } + } + + #[test] + fn test_parse_float_constant() { + let source = "123.45"; + let mut parser = Parser::new(source); + let ast = parser.parse_expression(); + + if let UntypedKind::Constant(Value::Float(val)) = ast.kind { + assert_eq!(val, 123.45); + } else { + panic!("Expected Float constant, got {:?}", ast.kind); + } + } + + #[test] + fn test_parse_negative_float() { + let source = "-10.5"; + let mut parser = Parser::new(source); + let ast = parser.parse_expression(); + + if let UntypedKind::Constant(Value::Float(val)) = ast.kind { + assert_eq!(val, -10.5); + } else { + panic!("Expected Float constant, got {:?}", ast.kind); + } + } + + #[test] + fn test_closure_modification_from_source() { + let source = r#" + (do + (def x 10) + (def f (fn [] (assign x 20))) + (f) + x + ) + "#; + + let env = Environment::new(); + let compiled = env.compile(source).into_result().expect("Failed to compile"); + let linked = env.link(compiled); + let func = env.instantiate(linked); + let result: Result = Ok((func.func)(vec![])); + + match result { + Ok(Value::Int(20)) => (), + Ok(val) => panic!("Expected Int(20), got {:?}", val), + Err(e) => panic!("VM Error: {}", e), + } + } + + #[test] + fn test_examples() { + for opt in [false, true] { + let results = crate::utils::tester::run_functional_tests_with_optimization(opt); + for res in results { + assert!( + res.success, + "Example {} failed at opt {}: {}", + res.name, opt, res.message + ); + } + } + } + + #[test] + fn test_debug_mode_logging() { + let mut env = Environment::new(); + env.optimization = false; + let source = "(+ 10 20)"; + let result = env.run_debug(source).expect("Failed to run debug"); + + let (val, logs) = result; + + // 1. Check value + match val { + Ok(Value::Int(30)) => (), + _ => panic!("Expected Int(30), got {:?}", val), + } + + // 2. Check logs (should have entries for + and constants) + assert!(!logs.is_empty(), "Logs should not be empty"); + + // Look for typical trace patterns + let has_call = logs.iter().any(|l| l.contains("CALL")); + let has_const = logs.iter().any(|l| l.contains("CONST(10)")); + let has_result = logs.iter().any(|l| l.contains("} -> 30")); + + assert!(has_call, "Logs should contain CALL"); + assert!(has_const, "Logs should contain CONST(10)"); + assert!(has_result, "Logs should contain result 30"); + } + + #[test] + fn test_rtl_operators() { + let env = Environment::new(); + + // --- Arithmetic --- + assert_eq!(format!("{}", env.run_script("(+ 10 20)").unwrap()), "30"); + assert_eq!(format!("{}", env.run_script("(- 20 10)").unwrap()), "10"); + assert_eq!(format!("{}", env.run_script("(* 10 20)").unwrap()), "200"); + assert_eq!(format!("{}", env.run_script("(/ 20 10)").unwrap()), "2"); + assert_eq!(format!("{}", env.run_script("(// 20 3)").unwrap()), "6"); // 20 // 3 = 6 + assert_eq!(format!("{}", env.run_script("(% 20 3)").unwrap()), "2"); // 20 % 3 = 2 + + // --- Logic / Bitwise --- + assert_eq!( + format!("{}", env.run_script("(and true false)").unwrap()), + "false" + ); + assert_eq!( + format!("{}", env.run_script("(or true false)").unwrap()), + "true" + ); + assert_eq!( + format!("{}", env.run_script("(xor true false)").unwrap()), + "true" + ); + assert_eq!( + format!("{}", env.run_script("(not true)").unwrap()), + "false" + ); + + assert_eq!(format!("{}", env.run_script("(<< 1 2)").unwrap()), "4"); // 1 << 2 = 4 + assert_eq!(format!("{}", env.run_script("(>> 4 1)").unwrap()), "2"); // 4 >> 1 = 2 + assert_eq!(format!("{}", env.run_script("(and 3 1)").unwrap()), "1"); // 3 & 1 = 1 + + // --- Comparison --- + assert_eq!(format!("{}", env.run_script("(= 10 10)").unwrap()), "true"); + assert_eq!(format!("{}", env.run_script("(= 10 20)").unwrap()), "false"); + assert_eq!(format!("{}", env.run_script("(<> 10 20)").unwrap()), "true"); + assert_eq!(format!("{}", env.run_script("(< 10 20)").unwrap()), "true"); + assert_eq!(format!("{}", env.run_script("(> 10 20)").unwrap()), "false"); + assert_eq!(format!("{}", env.run_script("(<= 10 10)").unwrap()), "true"); + assert_eq!(format!("{}", env.run_script("(>= 10 10)").unwrap()), "true"); + + // --- NaN --- + assert_eq!(format!("{}", env.run_script("NaN").unwrap()), "NaN"); + } + + #[test] + fn test_random_isolation_between_environments() { + let env1 = Environment::new(); + let env2 = Environment::new(); + + // 1. Create a seeded generator in env1 + env1.run_script("(def rand (make-random 123))").unwrap(); + let val1_a = env1.run_script("(rand)").unwrap(); + + // 2. env2 should have its own default seed state for its generators + env2.run_script("(def rand (make-random))").unwrap(); + let val2_a = env2.run_script("(rand)").unwrap(); + + // They are highly unlikely to be equal by default, + // and seeding env1 MUST not have seeded env2. + assert_ne!( + val1_a, val2_a, + "Environments must have isolated PRNG states" + ); + + // 3. Create another generator in env2 with the same seed + env2.run_script("(def rand-same (make-random 123))").unwrap(); + let val2_b = env2.run_script("(rand-same)").unwrap(); + + // After same seeding, they should match (isolated but identical seed) + assert_eq!( + val1_a, val2_b, + "Different environments with the same seed must produce the same sequence" + ); + } + + #[test] + fn test_random_seeding_determinism() { + let env = Environment::new(); + + // 1. First run with seed 42 + env.run_script("(def rand1 (make-random 42))").unwrap(); + let val1 = env.run_script("(rand1)").unwrap(); + + // 2. Second run with same seed 42 + env.run_script("(def rand2 (make-random 42))").unwrap(); + let val2 = env.run_script("(rand2)").unwrap(); + + assert_eq!( + val1, val2, + "Random results must be identical for the same seed" + ); + + // 3. Third run with different seed + env.run_script("(def rand3 (make-random 123))").unwrap(); + let val3 = env.run_script("(rand3)").unwrap(); + assert_ne!(val1, val3, "Random results must differ for different seeds"); + } + + #[test] + fn test_now_function_not_folded() { + let env = Environment::new(); + let source = "(now)"; + + // 1. Check result type and value plausibility + let result = env.run_script(source).expect("Failed to run script"); + if let Value::DateTime(ts) = result { + let current = chrono::Utc::now().timestamp_millis(); + assert!(ts > 0); + assert!(ts <= current); + } else { + panic!("Expected DateTime, got {:?}", result); + } + + // 2. Verify it's NOT constant folded in the AST dump + let dump = env.dump_ast(source).expect("Failed to dump AST"); + assert!( + dump.contains("Call"), + "now() should remain a Call, not a Constant. Dump: \n{}", + dump + ); + assert!( + !dump.contains("Constant: #"), + "now() should NOT be folded into a specific timestamp constant. Dump: \n{}", + dump + ); + } + + #[test] + fn test_date_parsing() { + let env = Environment::new(); + let res = env.run_script("(date \"2023-01-01\")").unwrap(); + if let Value::DateTime(_) = res { + // OK + } else { + panic!("Expected DateTime, got {:?}", res); + } + } + + #[test] + #[should_panic(expected = "'again' is only allowed in tail position to avoid dead code.")] + fn test_again_non_tail_panic() { + let env = Environment::new(); + let source = "(do (def f (fn [x] (do (again (- x 1)) x))) (f 5))"; + // This will trigger the TCO pass which contains the validation logic + let _ = env.run_script(source); + } + + #[test] + fn test_dynamic_call_destructuring_underflow() { + let env = Environment::new(); + let source = "(do + (def call-dynamic (fn [f data] (f data))) + (def data [10 [20 30]]) + (def x (fn [[a [b c]]] (+ a (+ b c)))) + (call-dynamic x data))"; + + let result = env.run_script(source); + if let Err(e) = &result { + panic!("Failed: {}", e); + } + assert_eq!(format!("{}", result.unwrap()), "60"); + } + + #[test] + fn test_nested_destructuring_optimization() { + let env = Environment::new(); + + // 1. Tuple-to-Tuple + let source_tuple = "((fn [[x y]] (+ x y)) [10 20])"; + assert_eq!(format!("{}", env.run_script(source_tuple).unwrap()), "30"); + let dump_tuple = env.dump_ast(source_tuple).unwrap(); + assert!( + dump_tuple.contains("Constant: 30"), + "Nested tuple should be folded to 30. Dump:\n{}", + dump_tuple + ); + } + + #[test] + fn test_def_destructuring() { + let env = Environment::new(); + + // 1. Global destructuring + let source_global = "(do (def [a b] [1 2]) (+ a b))"; + assert_eq!(format!("{}", env.run_script(source_global).unwrap()), "3"); + + // 2. Local nested destructuring inside a function + let source_local = + "((fn [x] (do (def [a [[b c] d]] x) (+ a (+ b (+ c d))))) [1 [[2 3] 4]])"; + assert_eq!(format!("{}", env.run_script(source_local).unwrap()), "10"); + + // 3. Verify 'def' returns the assigned value + let source_return = "(def [x y] [7 8])"; + let res = env.run_script(source_return).unwrap(); + if let Value::Tuple(vals) = res { + assert_eq!(vals.len(), 2); + assert_eq!(format!("{}", vals[0]), "7"); + assert_eq!(format!("{}", vals[1]), "8"); + } else { + panic!("Expected tuple return from def, got {:?}", res); + } + } + + #[test] + fn test_assign_destructuring() { + // 1. Simple assignment destructuring + { + let env = Environment::new(); + let source_simple = "(do (def a 0) (def b 0) (assign [a b] [10 20]) (+ a b))"; + assert_eq!(format!("{}", env.run_script(source_simple).unwrap()), "30"); + } + + // 2. Nested assignment destructuring + { + let env = Environment::new(); + let source_nested = + "(do (def a 0) (def b 0) (def c 0) (assign [a [b c]] [1 [2 3]]) (+ a (+ b c)))"; + assert_eq!(format!("{}", env.run_script(source_nested).unwrap()), "6"); + } + + // 3. Assignment returns the assigned value + { + let env = Environment::new(); + let source_return = "(do (def a 0) (def b 0) (assign [a b] [5 6]))"; + let res = env.run_script(source_return).unwrap(); + if let Value::Tuple(vals) = res { + assert_eq!(vals.len(), 2); + assert_eq!(format!("{}", vals[0]), "5"); + assert_eq!(format!("{}", vals[1]), "6"); + } else { + panic!("Expected tuple return from assign, got {:?}", res); + } + } + } + + #[test] + fn test_pipeline_optional_type() { + let env = Environment::new(); + // The lambda uses an `if` without an `else` returning a float constant. + // The TypeChecker should deduce `Optional(Float)` for the lambda body, + // and correctly unwrap it to `Series(Float)` for the pipeline output. + let source = "(do + (def src (create-random-ohlc 42 10)) + (def filtered + (pipe [src] + (fn [tick] + (if true + 42.0 + ) + ) + ) + ) + filtered + )"; + let res = env.run_script(source); + if let Err(e) = &res { + panic!("Script failed to compile/run: {:?}", e); + } + + let val = res.unwrap(); + if let crate::ast::types::Value::Object(obj) = val { + assert_eq!(obj.type_name(), "PipelineNode"); + } else { + panic!("Expected an Object(PipelineNode)"); + } + } + + #[test] + fn test_multi_level_destructuring() { + let env = Environment::new(); + let source = "(do + (def process_data (fn [conf] + (do + (def [str s] conf) + (def [f ss] s) + [\"Symbol:\" str \"field:\" f \"id:\" ss] + ) + ) + ) + (process_data [\"btc\" [:close \"cls\"]]))"; + + let res = env.run_script(source).unwrap(); + assert_eq!( + format!("{}", res), + "[\"Symbol:\" \"btc\" \"field:\" :close \"id:\" \"cls\"]" + ); + } + + #[test] + fn test_closure_reassignment_optimization_bug() { + let env = Environment::new(); + // This test case reproduces a bug where the optimizer aggressively inlined a function + // ('f') even though its parameters ('x') were being assigned to in the body (by inner lambda). + // The fix ensures that such functions are NOT inlined. + let source = "(do (def f (fn [[x y]] (fn [] (assign x (+ x y))))) ((f [1 2])))"; + let res = env.run_script(source); + assert_eq!(format!("{}", res.unwrap()), "3"); + } + + #[test] + fn test_macro_inlining_identity_collision() { + let source = r#" + (do + (macro wrap [f] `(fn [x] (~f x))) + + (def add1 (fn [x] (+ x 1))) + (def add2 (fn [x] (+ x 2))) + + (def w1 (wrap add1)) + (def w2 (wrap add2)) + + (w1 (w2 10))) + "#; + + // 1. Verify the result is correct + let env_run = Environment::new(); + let res = env_run.run_script(source).expect("Failed to run script"); + assert_eq!(format!("{}", res), "13"); + + // 2. Verify that it was actually folded into a constant by the optimizer + let env_dump = Environment::new(); + let dump = env_dump.dump_ast(source).expect("Failed to dump AST"); + assert!( + dump.contains("Constant: 13"), + "Macro-wrapped calls should be fully folded to 13. Dump:\n{}", + dump + ); + // The definitions add1, add2, w1, w2 should be gone after dead code elimination + assert!( + !dump.contains("Define Variable"), + "Definitions should be removed by DCE" + ); + } + + #[test] + fn test_optimizer_upvalue_inlining_bug_repro() { + let env = Environment::new(); + let source = r#" + (do + (def make-counter (fn [init] + (do + (def val init) + { + :inc (fn [] (assign val (+ val 1))) + :get (fn [] val) + }))) + (def c (make-counter 10)) + ((.inc c)) + ((.get c))) + "#; + let res = env.run_script(source); + assert!(res.is_ok(), "Optimizer bug triggered: {:?}", res.err()); + assert_eq!(format!("{}", res.unwrap()), "11"); + } + + #[test] + fn test_optimizer_destructuring_inlining_and_mutation() { + let env = Environment::new(); + // 1. Test: Destructuring definition should allow inlining if not mutated + let source_inline = "(do (def [x y] [10 20]) (+ x y))"; + let res_inline = env.run_script(source_inline).unwrap(); + assert_eq!(format!("{}", res_inline), "30"); + + // 2. Test: Destructuring definition should NOT be inlined if mutated (Regression Test) + let source_mutation = r#" + (do + (def [a b] [1 2]) + (def f (fn [] (assign a (+ a b)))) + (f) + a) + "#; + let res_mutation = env.run_script(source_mutation).unwrap(); + assert_eq!(format!("{}", res_mutation), "3"); + } + + #[test] + fn test_record_basics() { + let env = Environment::new(); + let source = r#" + ((fn [user] [(.name user) (.age user)]) + {:name "Alice" :age 30}) + "#; + let res = env.run_script(source).unwrap(); + assert_eq!(format!("{}", res), "[\"Alice\" 30]"); + } + + #[test] + fn test_record_optimized_access() { + let env = Environment::new(); + let source_eval = "(.price {:id 1 :price 99.5})"; + + // 1. Check result (will be fully folded to a constant by the new optimization) + let res = env.run_script(source_eval).unwrap(); + assert_eq!(format!("{}", res), "99.5"); + + // 2. Verify optimization to GET_FIELD when the record contains non-constants + let source_ast = "(fn [id] (.price {:id id :price 99.5}))"; + let dump = env.dump_ast(source_ast).unwrap(); + assert!(dump.contains("GetField: .price"), "Should be optimized to GetField. Dump:\n{}", dump); + } + #[test] + fn test_first_class_field_accessor() { + let env = Environment::new(); + let source = r#" + (do + (def get-name .name) + ; Dynamic call to field accessor + (get-name {:name "Alice"})) + "#; + let res = env.run_script(source).unwrap(); + assert_eq!(format!("{}", res), "\"Alice\""); + } + + #[test] + fn test_record_constant_folding() { + let env = Environment::new(); + // Optimizer should fold (.x {:x 10}) into 10 + let source = "(.x {:x 10 :y 20})"; + let dump = env.dump_ast(source).unwrap(); + assert!(dump.contains("Constant: 10"), "Should evaluate GetField at compile time."); + } + + #[test] + fn test_record_literal_constant_folding() { + let env = Environment::new(); + let source = " {:a 1 :b 2} "; + let dump = env.dump_ast(source).unwrap(); + + // Ensure the record definition itself is folded into a Constant. + assert!(dump.contains("Constant: {:a 1, :b 2}"), "Should transform a pure record literal into a constant value."); + assert!(!dump.contains("Record {"), "Should not leave a runtime Record node in the AST."); + } + + #[test] + fn test_record_inlining_in_while_loop() { + let env_ast = Environment::new(); + // Ensure that constants (like the config record) are inherited into inner lambda scopes (like the body of a while loop). + let source = r#" + (do + (def loop-config {:start 0 :limit 10}) + (def loop-idx 0) + (while (< loop-idx (.limit loop-config)) + (assign loop-idx (+ loop-idx 1))) + loop-idx + ) + "#; + let dump = env_ast.dump_ast(source).unwrap(); + + // The optimizer should inline `loop-config`, resolving `(.limit loop-config)` to `10`. + // The GetField and the Get for 'loop-config' should vanish inside the condition. + assert!(!dump.contains("GetField: .limit"), "The record field should be completely inlined."); + assert!(dump.contains("Constant: 10"), "The limit should be resolved to a constant 10."); + + // The result of running it should obviously still be correct. + let env_run = Environment::new(); + let res = env_run.run_script(source).unwrap(); + assert_eq!(format!("{}", res), "10"); + } + + #[test] + fn test_record_errors() { + let env = Environment::new(); + + // Both cases are reported by the TypeChecker since it can't resolve the call + // for a missing field or a non-record argument. + + // 1. Missing field + let res_missing = env.run_script("(.missing {:a 1})"); + assert!(res_missing.is_err()); + assert!(res_missing.unwrap_err().contains("Invalid arguments")); + + // 2. Not a record + let res_not_rec = env.run_script("(.name 123)"); + assert!(res_not_rec.is_err()); + assert!(res_not_rec.unwrap_err().contains("Invalid arguments")); + } + + #[test] + fn test_record_layout_interning() { + let env = Environment::new(); + let source = r#" + (do + (def r1 {:a 1 :b 2}) + (def r2 {:a 10 :b 20}) + ; Identical layouts result in identical types + (= r1 r2)) + "#; + // This will be false because values differ, but let's just check if it compiles and runs. + // To really test interning, we'd need a way to check if layouts are the same Arc. + let res = env.run_script(source).unwrap(); + assert_eq!(format!("{}", res), "false"); + } + + #[test] + fn test_error_recovery_parser() { + let env = Environment::new(); + // Syntax error: mismatched bracket/paren + let source = "(do (def a [1 2 ) )"; + let result = env.compile(source); + + assert!(result.diagnostics.has_errors(), "Expected parser errors"); + let error_msgs: Vec<_> = result.diagnostics.items.iter().map(|d| d.message.as_str()).collect(); + + // It should complain about finding ) instead of ] + assert!(error_msgs.iter().any(|m| m.contains("RightParen")), "Expected error about RightParen"); + + // AST should still be partially built (not None) + assert!(result.ast.is_some(), "AST should be partially built despite parser errors"); + } + + #[test] + fn test_error_recovery_binder() { + let env = Environment::new(); + // Semantic error: undefined variable + let source = "(do (def a 10) (def b unknown_var) (+ a 5))"; + let result = env.compile(source); + + assert!(result.diagnostics.has_errors(), "Expected binder errors"); + let error_msgs: Vec<_> = result.diagnostics.items.iter().map(|d| d.message.as_str()).collect(); + + assert!(error_msgs.iter().any(|m| m.contains("Undefined variable 'unknown_var'")), "Expected undefined variable error"); + assert!(result.ast.is_some(), "AST should be built with Error nodes"); + } + + #[test] + fn test_error_recovery_type_checker() { + let env = Environment::new(); + // Semantic error: Type mismatch in function call + let source = "(do (def a 10) (def b (not \"text\")) (- a 2))"; + let result = env.compile(source); + + assert!(result.diagnostics.has_errors(), "Expected type checker errors"); + let error_msgs: Vec<_> = result.diagnostics.items.iter().map(|d| d.message.as_str()).collect(); + + assert!(error_msgs.iter().any(|m| m.contains("Invalid arguments for function call")), "Expected invalid arguments error"); + assert!(result.ast.is_some(), "AST should be built with Error nodes"); + } + + #[test] + fn test_error_recovery_multiple_errors() { + let env = Environment::new(); + // A script with multiple errors that should all be collected + let source = r#" + (do + (def a undefined_var) + (def b (not "text")) + ) + "#; + let result = env.compile(source); + + assert!(result.diagnostics.has_errors()); + assert!(result.diagnostics.items.len() >= 2, "Expected multiple errors to be collected"); + + let error_msgs: Vec<_> = result.diagnostics.items.iter().map(|d| d.message.as_str()).collect(); + assert!(error_msgs.iter().any(|m| m.contains("Undefined variable 'undefined_var'"))); + assert!(error_msgs.iter().any(|m| m.contains("Invalid arguments for function call"))); + } +} diff --git a/src/utils/tester.rs b/src/utils/tester.rs index 3ee950c..aff1246 100644 --- a/src/utils/tester.rs +++ b/src/utils/tester.rs @@ -109,7 +109,7 @@ pub fn run_benchmarks(update: bool, filter: Option<&str>) -> Vec c, Err(e) => { results.push(BenchmarkResult {