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 {