From 329b885c4b020b60282baf8e3d8af3d944ef3768 Mon Sep 17 00:00:00 2001 From: Michael Schimmel Date: Sun, 22 Feb 2026 02:35:06 +0100 Subject: [PATCH] Formatting --- src/ast/compiler/binder.rs | 913 ++++++----- src/ast/compiler/bound_nodes.rs | 447 +++--- src/ast/compiler/dumper.rs | 431 ++--- src/ast/compiler/lambda_collector.rs | 180 ++- src/ast/compiler/macros.rs | 1374 +++++++++------- src/ast/compiler/mod.rs | 40 +- src/ast/compiler/optimizer.rs | 2221 ++++++++++++++++---------- src/ast/compiler/specializer.rs | 545 ++++--- src/ast/compiler/tco.rs | 298 ++-- src/ast/compiler/type_checker.rs | 1141 +++++++------ src/ast/compiler/upvalues.rs | 253 +-- src/ast/environment.rs | 710 ++++---- src/ast/lexer.rs | 410 ++--- src/ast/mod.rs | 16 +- src/ast/nodes.rs | 230 +-- src/ast/parser.rs | 745 +++++---- src/ast/rtl/core.rs | 807 ++++++---- src/ast/rtl/datetime.rs | 54 +- src/ast/rtl/intrinsics.rs | 224 +-- src/ast/rtl/mod.rs | 22 +- src/ast/rtl/type_registry.rs | 525 +++--- src/ast/types.rs | 762 ++++----- src/ast/vm.rs | 1177 ++++++++------ src/bin/ast.rs | 266 +-- src/utils/tester.rs | 662 ++++---- 25 files changed, 8053 insertions(+), 6400 deletions(-) diff --git a/src/ast/compiler/binder.rs b/src/ast/compiler/binder.rs index b62badd..f3ace9d 100644 --- a/src/ast/compiler/binder.rs +++ b/src/ast/compiler/binder.rs @@ -1,412 +1,501 @@ -use std::collections::HashMap; -use std::rc::Rc; -use std::cell::RefCell; -use crate::ast::nodes::{Node, UntypedKind, Symbol}; -use crate::ast::compiler::bound_nodes::{BoundKind, Address, BoundNode}; -use crate::ast::types::{Identity, StaticType}; - -#[derive(Debug, Clone)] -struct LocalInfo { - slot: u32, - // 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) -> Result { - if self.locals.contains_key(sym) { - return Err(format!("Variable '{}' is already defined in this scope level.", sym.name)); - } - let slot = self.slot_count; - self.locals.insert(sym.clone(), LocalInfo { slot, _ty: StaticType::Any }); - self.slot_count += 1; - Ok(slot) - } - - fn resolve(&self, sym: &Symbol) -> Option { - self.locals.get(sym).cloned() - } -} - -struct FunctionCompiler { - scope: CompilerScope, - upvalues: Vec
, -} - -impl FunctionCompiler { - fn new() -> Self { - Self { - scope: CompilerScope::new(), - upvalues: Vec::new(), - } - } - - fn add_upvalue(&mut self, addr: Address) -> u32 { - if let Some(idx) = self.upvalues.iter().position(|&a| a == addr) { - return idx as u32; - } - let idx = self.upvalues.len() as u32; - self.upvalues.push(addr); - idx - } -} - -pub struct Binder { - functions: Vec, - // Globals mapping: Symbol -> Index - globals: Rc>>, - // Map of Declaration Identity -> List of Lambda Identities that capture it - capture_map: HashMap>, -} - -impl Binder { - pub fn new(globals: Rc>>) -> Self { - Self::with_boxed(globals, HashMap::new()) - } - - fn with_boxed(globals: Rc>>, captures: HashMap>) -> Self { - let mut binder = Self { - functions: Vec::new(), - globals, - capture_map: captures, - }; - binder.functions.push(FunctionCompiler::new()); - binder - } - - pub fn bind_root(globals: Rc>>, node: &Node) -> Result { - let captures = crate::ast::compiler::upvalues::UpvalueAnalyzer::analyze(node); - let mut binder = Self::with_boxed(globals, captures); - binder.bind(node) - } - - 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(sym) => { - // Parameters are ONLY allowed if we are inside a function (Binder stack > 1) - if self.functions.len() <= 1 { - return Err(format!("Parameter '{}' is not allowed in root scope.", sym.name)); - } - let current_fn = self.functions.last_mut().unwrap(); - let slot = current_fn.scope.define(sym)?; - Ok(self.make_node(node.identity.clone(), BoundKind::Parameter { - name: sym.clone(), - slot - })) - }, - - 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 { name, value } => { - // 1. Pre-declare name to support recursion - let slot_or_idx = if self.functions.len() == 1 { - let mut globals = self.globals.borrow_mut(); - if globals.contains_key(name) { - return Err(format!("Global variable '{}' is already defined.", name.name)); - } - let idx = globals.len() as u32; - globals.insert(name.clone(), idx); - idx - } else { - let current_fn = self.functions.last_mut().unwrap(); - current_fn.scope.define(name)? - }; - - // 2. Bind Value (now 'name' is visible) - let val_node = self.bind(value)?; - - // 3. Return Node - if self.functions.len() == 1 { - Ok(self.make_node(node.identity.clone(), BoundKind::DefGlobal { - name: name.clone(), - global_index: slot_or_idx, - value: Box::new(val_node) - })) - } else { - let captured_by = self.capture_map.get(&node.identity).cloned().unwrap_or_default(); - Ok(self.make_node(node.identity.clone(), BoundKind::DefLocal { - name: name.clone(), - slot: slot_or_idx, - value: Box::new(val_node) , - captured_by - })) - } - }, - - 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 { - Err("Assignment target must be an identifier".to_string()) - } - }, - - UntypedKind::Lambda { params, body } => { - let identity = node.identity.clone(); - self.functions.push(FunctionCompiler::new()); - - // 1. Bind the parameter pattern/tuple - let params_bound = self.bind(params)?; - - // 2. Bind the body - let body_bound = self.bind(body)?; - - let compiled_fn = self.functions.pop().unwrap(); - - // 3. Static optimization: check if parameters are purely positional - let positional_count = match ¶ms_bound.kind { - BoundKind::Tuple { elements } => { - let mut count = 0; - let mut all_params = true; - for e in elements { - if matches!(e.kind, BoundKind::Parameter { .. }) { - count += 1; - } else { - all_params = false; - break; - } - } - if all_params { Some(count) } else { None } - } - BoundKind::Parameter { .. } => Some(1), - _ => None, - }; - - 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::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_fields = Vec::new(); - for (k, v) in fields { - bound_fields.push((self.bind(k)?, self.bind(v)?)); - } - Ok(self.make_node(node.identity.clone(), BoundKind::Record { fields: bound_fields })) - }, - - 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); - - for k in (i + 1)..=current_fn_idx { - 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 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 = Binder::bind_root(globals, &untyped).unwrap(); - - // Structure: Lambda -> Block -> [ DefLocal(x), DefLocal(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::DefLocal { captured_by, .. } = &x_decl.kind { - assert!(!captured_by.is_empty(), "Variable 'x' should have capturers because it is used in lambda 'f'"); - } else { - panic!("First expression in block should be DefLocal, 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 = 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::DefLocal { captured_by, .. } = &x_decl.kind { - assert!(captured_by.is_empty(), "Variable 'x' should NOT have any capturers"); - } else { - panic!("First expression should be DefLocal"); - } - } 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}; +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: u32, + // 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) -> Result { + if self.locals.contains_key(sym) { + return Err(format!( + "Variable '{}' is already defined in this scope level.", + sym.name + )); + } + let slot = self.slot_count; + self.locals.insert( + sym.clone(), + LocalInfo { + slot, + _ty: StaticType::Any, + }, + ); + self.slot_count += 1; + Ok(slot) + } + + fn resolve(&self, sym: &Symbol) -> Option { + self.locals.get(sym).cloned() + } +} + +struct FunctionCompiler { + scope: CompilerScope, + upvalues: Vec
, +} + +impl FunctionCompiler { + fn new() -> Self { + Self { + scope: CompilerScope::new(), + upvalues: Vec::new(), + } + } + + fn add_upvalue(&mut self, addr: Address) -> u32 { + if let Some(idx) = self.upvalues.iter().position(|&a| a == addr) { + return idx as u32; + } + let idx = self.upvalues.len() as u32; + self.upvalues.push(addr); + idx + } +} + +pub struct Binder { + functions: Vec, + // Globals mapping: Symbol -> Index + globals: Rc>>, + // Map of Declaration Identity -> List of Lambda Identities that capture it + capture_map: HashMap>, +} + +impl Binder { + pub fn new(globals: Rc>>) -> Self { + Self::with_boxed(globals, HashMap::new()) + } + + fn with_boxed( + globals: Rc>>, + captures: HashMap>, + ) -> Self { + let mut binder = Self { + functions: Vec::new(), + globals, + capture_map: captures, + }; + binder.functions.push(FunctionCompiler::new()); + binder + } + + pub fn bind_root( + globals: Rc>>, + node: &Node, + ) -> Result { + let captures = crate::ast::compiler::upvalues::UpvalueAnalyzer::analyze(node); + let mut binder = Self::with_boxed(globals, captures); + binder.bind(node) + } + + 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(sym) => { + // Parameters are ONLY allowed if we are inside a function (Binder stack > 1) + if self.functions.len() <= 1 { + return Err(format!( + "Parameter '{}' is not allowed in root scope.", + sym.name + )); + } + let current_fn = self.functions.last_mut().unwrap(); + let slot = current_fn.scope.define(sym)?; + Ok(self.make_node( + node.identity.clone(), + BoundKind::Parameter { + name: sym.clone(), + slot, + }, + )) + } + + 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 { name, value } => { + // 1. Pre-declare name to support recursion + let slot_or_idx = if self.functions.len() == 1 { + let mut globals = self.globals.borrow_mut(); + if globals.contains_key(name) { + return Err(format!( + "Global variable '{}' is already defined.", + name.name + )); + } + let idx = globals.len() as u32; + globals.insert(name.clone(), idx); + idx + } else { + let current_fn = self.functions.last_mut().unwrap(); + current_fn.scope.define(name)? + }; + + // 2. Bind Value (now 'name' is visible) + let val_node = self.bind(value)?; + + // 3. Return Node + if self.functions.len() == 1 { + Ok(self.make_node( + node.identity.clone(), + BoundKind::DefGlobal { + name: name.clone(), + global_index: slot_or_idx, + value: Box::new(val_node), + }, + )) + } else { + let captured_by = self + .capture_map + .get(&node.identity) + .cloned() + .unwrap_or_default(); + Ok(self.make_node( + node.identity.clone(), + BoundKind::DefLocal { + name: name.clone(), + slot: slot_or_idx, + value: Box::new(val_node), + captured_by, + }, + )) + } + } + + 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 { + Err("Assignment target must be an identifier".to_string()) + } + } + + UntypedKind::Lambda { params, body } => { + let identity = node.identity.clone(); + self.functions.push(FunctionCompiler::new()); + + // 1. Bind the parameter pattern/tuple + let params_bound = self.bind(params)?; + + // 2. Bind the body + let body_bound = self.bind(body)?; + + let compiled_fn = self.functions.pop().unwrap(); + + // 3. Static optimization: check if parameters are purely positional + let positional_count = match ¶ms_bound.kind { + BoundKind::Tuple { elements } => { + let mut count = 0; + let mut all_params = true; + for e in elements { + if matches!(e.kind, BoundKind::Parameter { .. }) { + count += 1; + } else { + all_params = false; + break; + } + } + if all_params { Some(count) } else { None } + } + BoundKind::Parameter { .. } => Some(1), + _ => None, + }; + + 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::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_fields = Vec::new(); + for (k, v) in fields { + bound_fields.push((self.bind(k)?, self.bind(v)?)); + } + Ok(self.make_node( + node.identity.clone(), + BoundKind::Record { + fields: bound_fields, + }, + )) + } + + 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); + + for k in (i + 1)..=current_fn_idx { + 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 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 = Binder::bind_root(globals, &untyped).unwrap(); + + // Structure: Lambda -> Block -> [ DefLocal(x), DefLocal(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::DefLocal { captured_by, .. } = &x_decl.kind { + assert!( + !captured_by.is_empty(), + "Variable 'x' should have capturers because it is used in lambda 'f'" + ); + } else { + panic!( + "First expression in block should be DefLocal, 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 = 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::DefLocal { captured_by, .. } = &x_decl.kind { + assert!( + captured_by.is_empty(), + "Variable 'x' should NOT have any capturers" + ); + } else { + panic!("First expression should be DefLocal"); + } + } 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")); + } +} diff --git a/src/ast/compiler/bound_nodes.rs b/src/ast/compiler/bound_nodes.rs index 60f5171..4c8226f 100644 --- a/src/ast/compiler/bound_nodes.rs +++ b/src/ast/compiler/bound_nodes.rs @@ -1,194 +1,253 @@ -use std::rc::Rc; -use crate::ast::types::{Value, StaticType, Identity}; -use crate::ast::nodes::{Node, Symbol}; - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub enum Address { - Local(u32), // Stack-Slot index (relative to frame base) - Upvalue(u32), // Index in the closure's upvalue array - Global(u32), // Index in the global environment vector -} - -/// Trait for DSL-specific AST extensions (like Pipe, Series-Access, etc.) -pub trait BoundExtension: std::fmt::Debug { - fn clone_box(&self) -> Box>; - fn display_name(&self) -> String; -} - -impl Clone for Box> { - fn clone(&self) -> Self { - self.clone_box() - } -} - -/// Type alias for a node that has been bound. Defaults to untyped (T = ()). -pub type BoundNode = Node, T>; - -/// Type alias for a node that has been fully type-checked. -pub type TypedNode = BoundNode; - -#[derive(Debug, Clone)] -pub enum BoundKind { - Nop, - Constant(Value), - - /// A positional parameter in a Lambda's parameter list. - Parameter { - name: Symbol, - slot: u32, - }, - - // Variable Access (Resolved) - Get { - addr: Address, - name: Symbol, - }, - - // Variable Update (Assignment) - Set { - addr: Address, - value: Box>, - }, - - // Variable Declaration (Local) - DefLocal { - name: Symbol, - slot: u32, - value: Box>, - captured_by: Vec, - }, - - If { - cond: Box>, - then_br: Box>, - else_br: Option>>, - }, - - // Global Definition (Locals are implicit by stack position) - DefGlobal { - name: Symbol, - global_index: u32, - value: Box>, - }, - - Lambda { - params: Rc>, - // The list of variables captured from enclosing scopes - upvalues: Vec
, - body: Rc>, - /// Static optimization: number of positional parameters if the pattern is flat. - positional_count: Option, - }, - - Call { - callee: Box>, - args: Box>, - }, - - Block { - exprs: Vec>, - }, - - Tuple { - elements: Vec>, - }, - - Record { - fields: Vec>, - }, - - /// An expanded macro call, preserving the original call for debugging and UI. - Expansion { - /// The original call from the untyped AST. - original_call: Rc>, - /// The result of binding the expanded AST. - bound_expanded: Box>, - }, - - /// DSL-specific extension slot - Extension(Box>), -} - -impl PartialEq for BoundKind -where T: PartialEq { - fn eq(&self, other: &Self) -> bool { - match (self, other) { - (BoundKind::Nop, BoundKind::Nop) => true, - (BoundKind::Constant(a), BoundKind::Constant(b)) => a == b, - (BoundKind::Parameter { name: na, slot: sa }, BoundKind::Parameter { name: nb, slot: sb }) => { - na == nb && sa == sb - } - (BoundKind::Get { addr: aa, name: na }, BoundKind::Get { addr: ab, name: nb }) => { - aa == ab && na == nb - } - (BoundKind::Set { addr: aa, value: va }, BoundKind::Set { addr: ab, value: vb }) => { - aa == ab && va == vb - } - (BoundKind::DefLocal { name: na, slot: sa, value: va, captured_by: ca }, - BoundKind::DefLocal { name: nb, slot: sb, value: vb, captured_by: cb }) => { - na == nb && sa == sb && va == vb && ca == cb - } - (BoundKind::If { cond: ca, then_br: ta, else_br: ea }, - BoundKind::If { cond: cb, then_br: tb, else_br: eb }) => { - ca == cb && ta == tb && ea == eb - } - (BoundKind::DefGlobal { name: na, global_index: ga, value: va }, - BoundKind::DefGlobal { name: nb, global_index: gb, value: vb }) => { - na == nb && ga == gb && va == vb - } - (BoundKind::Lambda { params: pa, upvalues: ua, body: ba, positional_count: pca }, - BoundKind::Lambda { params: pb, upvalues: ub, body: bb, positional_count: pcb }) => { - pa == pb && ua == ub && ba == bb && pca == pcb - } - (BoundKind::Call { callee: ca, args: aa }, BoundKind::Call { callee: cb, args: ab }) => { - ca == cb && aa == ab - } - (BoundKind::Block { exprs: ea }, BoundKind::Block { exprs: eb }) => { - ea == eb - } - (BoundKind::Tuple { elements: ea }, BoundKind::Tuple { elements: eb }) => { - ea == eb - } - (BoundKind::Record { fields: fa }, BoundKind::Record { fields: fb }) => { - fa == fb - } - (BoundKind::Expansion { original_call: oa, bound_expanded: ba }, - BoundKind::Expansion { original_call: ob, bound_expanded: bb }) => { - Rc::ptr_eq(oa, ob) && ba == bb - } - (BoundKind::Extension(_), BoundKind::Extension(_)) => false, // Currently no equality for extensions - _ => false, - } - } -} - -/// A single field in a Record literal (Key-Value pair) -pub type RecordField = (BoundNode, BoundNode); - -impl BoundKind { - pub fn display_name(&self) -> String { - match self { - BoundKind::Nop => "NOP".to_string(), - BoundKind::Constant(v) => format!("CONST({})", v), - BoundKind::Parameter { name, slot } => format!("PARAM({}, Slot:{})", name.name, slot), - BoundKind::Get { addr, name } => format!("GET({}, {:?})", name.name, addr), - BoundKind::Set { addr, .. } => format!("SET({:?})", addr), - BoundKind::DefLocal { name, slot, .. } => format!("DEF_LOCAL({}, Slot:{})", name.name, slot), - BoundKind::If { .. } => "IF".to_string(), - BoundKind::DefGlobal { name, global_index, .. } => format!("DEF_GLOBAL({}, Idx:{})", name.name, global_index), - BoundKind::Lambda { params, upvalues, .. } => { - let p_str = match ¶ms.kind { - BoundKind::Tuple { elements } => format!("p:{}", elements.len()), - _ => "p:1".to_string(), - }; - format!("LAMBDA({}, Captures:{})", p_str, upvalues.len()) - }, - BoundKind::Call { .. } => "CALL".to_string(), - BoundKind::Block { .. } => "BLOCK".to_string(), - BoundKind::Tuple { elements } => format!("TUPLE({})", elements.len()), - BoundKind::Record { fields } => format!("RECORD({})", fields.len()), - BoundKind::Expansion { .. } => "EXPANSION".to_string(), - BoundKind::Extension(ext) => ext.display_name(), - } - } -} +use crate::ast::nodes::{Node, Symbol}; +use crate::ast::types::{Identity, StaticType, Value}; +use std::rc::Rc; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum Address { + Local(u32), // Stack-Slot index (relative to frame base) + Upvalue(u32), // Index in the closure's upvalue array + Global(u32), // Index in the global environment vector +} + +/// Trait for DSL-specific AST extensions (like Pipe, Series-Access, etc.) +pub trait BoundExtension: std::fmt::Debug { + fn clone_box(&self) -> Box>; + fn display_name(&self) -> String; +} + +impl Clone for Box> { + fn clone(&self) -> Self { + self.clone_box() + } +} + +/// Type alias for a node that has been bound. Defaults to untyped (T = ()). +pub type BoundNode = Node, T>; + +/// Type alias for a node that has been fully type-checked. +pub type TypedNode = BoundNode; + +#[derive(Debug, Clone)] +pub enum BoundKind { + Nop, + Constant(Value), + + /// A positional parameter in a Lambda's parameter list. + Parameter { + name: Symbol, + slot: u32, + }, + + // Variable Access (Resolved) + Get { + addr: Address, + name: Symbol, + }, + + // Variable Update (Assignment) + Set { + addr: Address, + value: Box>, + }, + + // Variable Declaration (Local) + DefLocal { + name: Symbol, + slot: u32, + value: Box>, + captured_by: Vec, + }, + + If { + cond: Box>, + then_br: Box>, + else_br: Option>>, + }, + + // Global Definition (Locals are implicit by stack position) + DefGlobal { + name: Symbol, + global_index: u32, + value: Box>, + }, + + Lambda { + params: Rc>, + // The list of variables captured from enclosing scopes + upvalues: Vec
, + body: Rc>, + /// Static optimization: number of positional parameters if the pattern is flat. + positional_count: Option, + }, + + Call { + callee: Box>, + args: Box>, + }, + + Block { + exprs: Vec>, + }, + + Tuple { + elements: Vec>, + }, + + Record { + fields: Vec>, + }, + + /// An expanded macro call, preserving the original call for debugging and UI. + Expansion { + /// The original call from the untyped AST. + original_call: Rc>, + /// The result of binding the expanded AST. + bound_expanded: Box>, + }, + + /// DSL-specific extension slot + Extension(Box>), +} + +impl PartialEq for BoundKind +where + T: PartialEq, +{ + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (BoundKind::Nop, BoundKind::Nop) => true, + (BoundKind::Constant(a), BoundKind::Constant(b)) => a == b, + ( + BoundKind::Parameter { name: na, slot: sa }, + BoundKind::Parameter { name: nb, slot: sb }, + ) => na == nb && sa == sb, + (BoundKind::Get { addr: aa, name: na }, BoundKind::Get { addr: ab, name: nb }) => { + aa == ab && na == nb + } + ( + BoundKind::Set { + addr: aa, + value: va, + }, + BoundKind::Set { + addr: ab, + value: vb, + }, + ) => aa == ab && va == vb, + ( + BoundKind::DefLocal { + name: na, + slot: sa, + value: va, + captured_by: ca, + }, + BoundKind::DefLocal { + name: nb, + slot: sb, + value: vb, + captured_by: cb, + }, + ) => na == nb && sa == sb && va == vb && ca == cb, + ( + BoundKind::If { + cond: ca, + then_br: ta, + else_br: ea, + }, + BoundKind::If { + cond: cb, + then_br: tb, + else_br: eb, + }, + ) => ca == cb && ta == tb && ea == eb, + ( + BoundKind::DefGlobal { + name: na, + global_index: ga, + value: va, + }, + BoundKind::DefGlobal { + name: nb, + global_index: gb, + value: vb, + }, + ) => na == nb && ga == gb && va == vb, + ( + BoundKind::Lambda { + params: pa, + upvalues: ua, + body: ba, + positional_count: pca, + }, + BoundKind::Lambda { + params: pb, + upvalues: ub, + body: bb, + positional_count: pcb, + }, + ) => pa == pb && ua == ub && ba == bb && pca == pcb, + ( + BoundKind::Call { + callee: ca, + args: aa, + }, + BoundKind::Call { + callee: cb, + args: ab, + }, + ) => ca == cb && aa == ab, + (BoundKind::Block { exprs: ea }, BoundKind::Block { exprs: eb }) => ea == eb, + (BoundKind::Tuple { elements: ea }, BoundKind::Tuple { elements: eb }) => ea == eb, + (BoundKind::Record { fields: fa }, BoundKind::Record { fields: fb }) => fa == fb, + ( + BoundKind::Expansion { + original_call: oa, + bound_expanded: ba, + }, + BoundKind::Expansion { + original_call: ob, + bound_expanded: bb, + }, + ) => Rc::ptr_eq(oa, ob) && ba == bb, + (BoundKind::Extension(_), BoundKind::Extension(_)) => false, // Currently no equality for extensions + _ => false, + } + } +} + +/// A single field in a Record literal (Key-Value pair) +pub type RecordField = (BoundNode, BoundNode); + +impl BoundKind { + pub fn display_name(&self) -> String { + match self { + BoundKind::Nop => "NOP".to_string(), + BoundKind::Constant(v) => format!("CONST({})", v), + BoundKind::Parameter { name, slot } => format!("PARAM({}, Slot:{})", name.name, slot), + BoundKind::Get { addr, name } => format!("GET({}, {:?})", name.name, addr), + BoundKind::Set { addr, .. } => format!("SET({:?})", addr), + BoundKind::DefLocal { name, slot, .. } => { + format!("DEF_LOCAL({}, Slot:{})", name.name, slot) + } + BoundKind::If { .. } => "IF".to_string(), + BoundKind::DefGlobal { + name, global_index, .. + } => format!("DEF_GLOBAL({}, Idx:{})", name.name, global_index), + BoundKind::Lambda { + params, upvalues, .. + } => { + let p_str = match ¶ms.kind { + BoundKind::Tuple { elements } => format!("p:{}", elements.len()), + _ => "p:1".to_string(), + }; + format!("LAMBDA({}, Captures:{})", p_str, upvalues.len()) + } + BoundKind::Call { .. } => "CALL".to_string(), + BoundKind::Block { .. } => "BLOCK".to_string(), + BoundKind::Tuple { elements } => format!("TUPLE({})", elements.len()), + BoundKind::Record { fields } => format!("RECORD({})", fields.len()), + BoundKind::Expansion { .. } => "EXPANSION".to_string(), + BoundKind::Extension(ext) => ext.display_name(), + } + } +} diff --git a/src/ast/compiler/dumper.rs b/src/ast/compiler/dumper.rs index 927ada7..e5d3154 100644 --- a/src/ast/compiler/dumper.rs +++ b/src/ast/compiler/dumper.rs @@ -1,195 +1,236 @@ -use crate::ast::nodes::Node; -use crate::ast::compiler::bound_nodes::BoundKind; -use crate::ast::types::Value; -use crate::ast::vm::Closure; -use std::fmt::Debug; - -/// Human-readable AST dumper for the bound AST. -pub struct Dumper { - output: String, - indent: usize, -} - -impl Dumper { - /// Produces a formatted string representation of the given bound AST node and its children. - pub fn dump(node: &Node, T>) -> String { - let mut dumper = Self { - output: String::new(), - indent: 0, - }; - dumper.visit(node); - dumper.output - } - - fn write_indent(&mut self) { - for _ in 0..self.indent { - self.output.push_str(" "); - } - } - - fn log(&mut self, label: &str, node: &Node, T>) { - self.write_indent(); - self.output.push_str(label); - self.output.push_str(&format!(" \n", node.ty)); - } - - fn visit(&mut self, node: &Node, T>) { - match &node.kind { - BoundKind::Nop => self.log("Nop", node), - BoundKind::Constant(v) => { - self.log(&format!("Constant: {}", v), node); - // Introspect Closure AST if possible - if let Value::Object(obj) = v - && let Some(closure) = obj.as_any().downcast_ref::() - { - self.indent += 1; - self.write_indent(); - self.output.push_str("--- Specialized Body ---\n"); - // We need to cast the inner TypedNode to the generic T required by visit. - // Since Dumper is generic over T, but Closure stores TypedNode (where T = StaticType), - // we can only fully dump if T is StaticType. - // However, we can hack it by creating a new Dumper for the inner AST string. - - // We can't call self.visit because types mismatch if T != StaticType. - // So we just recursively dump to string and append. - let inner_dump = Dumper::dump(&closure.function_node); - for line in inner_dump.lines() { - self.write_indent(); - self.output.push_str(line); - self.output.push('\n'); - } - self.indent -= 1; - } - }, - BoundKind::Get { addr, name } => self.log(&format!("Get: {} ({:?})", name.name, addr), node), - - BoundKind::Set { addr, value } => { - self.log(&format!("Set: {:?}", addr), node); - self.indent += 1; - self.visit(value); - self.indent -= 1; - } - - BoundKind::DefLocal { name, slot, value, captured_by } => { - let capture_info = if captured_by.is_empty() { - String::from("not captured") - } else { - format!("captured by {} lambdas", captured_by.len()) - }; - self.log(&format!("DefLocal (Name: '{}', Slot: {}, {})", name.name, slot, capture_info), node); - - self.indent += 1; - if !captured_by.is_empty() { - for capturer in captured_by { - self.write_indent(); - self.output.push_str(&format!("- Capturer: Lambda at line {}, col {}\n", - capturer.location.line, capturer.location.col)); - } - } - self.visit(value); - self.indent -= 1; - } - - BoundKind::DefGlobal { name, global_index, value } => { - self.log(&format!("DefGlobal (Name: '{}', Index: {})", name.name, global_index), node); - self.indent += 1; - self.visit(value); - self.indent -= 1; - } - - BoundKind::If { cond, then_br, else_br } => { - self.log("If", node); - self.indent += 1; - - self.write_indent(); - self.output.push_str("Condition:\n"); - self.visit(cond); - - self.write_indent(); - self.output.push_str("Then:\n"); - self.visit(then_br); - - if let Some(e) = else_br { - self.write_indent(); - self.output.push_str("Else:\n"); - self.visit(e); - } - self.indent -= 1; - } - - BoundKind::Lambda { params, upvalues, body, .. } => { - self.log(&format!("Lambda (Upvalues: {})", upvalues.len()), node); - self.indent += 1; - - self.write_indent(); - self.output.push_str("Parameters:\n"); - self.visit(params); - - if !upvalues.is_empty() { - self.write_indent(); - self.output.push_str(&format!("Upvalues: {:?}\n", upvalues)); - } - self.visit(body); - self.indent -= 1; - } - - BoundKind::Parameter { name, slot } => { - self.log(&format!("Parameter (Name: '{}', Slot: {})", name.name, slot), node); - } - - BoundKind::Call { callee, args } => { - self.log("Call", node); - self.indent += 1; - - self.write_indent(); - self.output.push_str("Callee:\n"); - self.visit(callee); - - self.write_indent(); - self.output.push_str("Arguments:\n"); - self.visit(args); - - self.indent -= 1; - } - - BoundKind::Block { exprs } => { - self.log("Block", node); - self.indent += 1; - for expr in exprs { - self.visit(expr); - } - self.indent -= 1; - } - - BoundKind::Tuple { elements } => { - self.log("Tuple", node); - self.indent += 1; - for el in elements { - self.visit(el); - } - self.indent -= 1; - } - - BoundKind::Record { fields } => { - self.log("Record", node); - self.indent += 1; - for (k, v) in fields { - self.visit(k); - self.visit(v); - } - self.indent -= 1; - } - - BoundKind::Expansion { original_call, bound_expanded } => { - self.log(&format!("Expansion (Original: {:?})", original_call.kind), node); - self.indent += 1; - self.visit(bound_expanded); - self.indent -= 1; - } - - BoundKind::Extension(ext) => { - self.log(&ext.display_name(), node); - } - } - } -} +use crate::ast::compiler::bound_nodes::BoundKind; +use crate::ast::nodes::Node; +use crate::ast::types::Value; +use crate::ast::vm::Closure; +use std::fmt::Debug; + +/// Human-readable AST dumper for the bound AST. +pub struct Dumper { + output: String, + indent: usize, +} + +impl Dumper { + /// Produces a formatted string representation of the given bound AST node and its children. + pub fn dump(node: &Node, T>) -> String { + let mut dumper = Self { + output: String::new(), + indent: 0, + }; + dumper.visit(node); + dumper.output + } + + fn write_indent(&mut self) { + for _ in 0..self.indent { + self.output.push_str(" "); + } + } + + fn log(&mut self, label: &str, node: &Node, T>) { + self.write_indent(); + self.output.push_str(label); + self.output + .push_str(&format!(" \n", node.ty)); + } + + fn visit(&mut self, node: &Node, T>) { + match &node.kind { + BoundKind::Nop => self.log("Nop", node), + BoundKind::Constant(v) => { + self.log(&format!("Constant: {}", v), node); + // Introspect Closure AST if possible + if let Value::Object(obj) = v + && let Some(closure) = obj.as_any().downcast_ref::() + { + self.indent += 1; + self.write_indent(); + self.output.push_str("--- Specialized Body ---\n"); + // We need to cast the inner TypedNode to the generic T required by visit. + // Since Dumper is generic over T, but Closure stores TypedNode (where T = StaticType), + // we can only fully dump if T is StaticType. + // However, we can hack it by creating a new Dumper for the inner AST string. + + // We can't call self.visit because types mismatch if T != StaticType. + // So we just recursively dump to string and append. + let inner_dump = Dumper::dump(&closure.function_node); + for line in inner_dump.lines() { + self.write_indent(); + self.output.push_str(line); + self.output.push('\n'); + } + self.indent -= 1; + } + } + BoundKind::Get { addr, name } => { + self.log(&format!("Get: {} ({:?})", name.name, addr), node) + } + + BoundKind::Set { addr, value } => { + self.log(&format!("Set: {:?}", addr), node); + self.indent += 1; + self.visit(value); + self.indent -= 1; + } + + BoundKind::DefLocal { + name, + slot, + value, + captured_by, + } => { + let capture_info = if captured_by.is_empty() { + String::from("not captured") + } else { + format!("captured by {} lambdas", captured_by.len()) + }; + self.log( + &format!( + "DefLocal (Name: '{}', Slot: {}, {})", + name.name, slot, capture_info + ), + node, + ); + + self.indent += 1; + if !captured_by.is_empty() { + for capturer in captured_by { + self.write_indent(); + self.output.push_str(&format!( + "- Capturer: Lambda at line {}, col {}\n", + capturer.location.line, capturer.location.col + )); + } + } + self.visit(value); + self.indent -= 1; + } + + BoundKind::DefGlobal { + name, + global_index, + value, + } => { + self.log( + &format!("DefGlobal (Name: '{}', Index: {})", name.name, global_index), + node, + ); + self.indent += 1; + self.visit(value); + self.indent -= 1; + } + + BoundKind::If { + cond, + then_br, + else_br, + } => { + self.log("If", node); + self.indent += 1; + + self.write_indent(); + self.output.push_str("Condition:\n"); + self.visit(cond); + + self.write_indent(); + self.output.push_str("Then:\n"); + self.visit(then_br); + + if let Some(e) = else_br { + self.write_indent(); + self.output.push_str("Else:\n"); + self.visit(e); + } + self.indent -= 1; + } + + BoundKind::Lambda { + params, + upvalues, + body, + .. + } => { + self.log(&format!("Lambda (Upvalues: {})", upvalues.len()), node); + self.indent += 1; + + self.write_indent(); + self.output.push_str("Parameters:\n"); + self.visit(params); + + if !upvalues.is_empty() { + self.write_indent(); + self.output.push_str(&format!("Upvalues: {:?}\n", upvalues)); + } + self.visit(body); + self.indent -= 1; + } + + BoundKind::Parameter { name, slot } => { + self.log( + &format!("Parameter (Name: '{}', Slot: {})", name.name, slot), + node, + ); + } + + BoundKind::Call { callee, args } => { + self.log("Call", node); + self.indent += 1; + + self.write_indent(); + self.output.push_str("Callee:\n"); + self.visit(callee); + + self.write_indent(); + self.output.push_str("Arguments:\n"); + self.visit(args); + + self.indent -= 1; + } + + BoundKind::Block { exprs } => { + self.log("Block", node); + self.indent += 1; + for expr in exprs { + self.visit(expr); + } + self.indent -= 1; + } + + BoundKind::Tuple { elements } => { + self.log("Tuple", node); + self.indent += 1; + for el in elements { + self.visit(el); + } + self.indent -= 1; + } + + BoundKind::Record { fields } => { + self.log("Record", node); + self.indent += 1; + for (k, v) in fields { + self.visit(k); + self.visit(v); + } + self.indent -= 1; + } + + BoundKind::Expansion { + original_call, + bound_expanded, + } => { + self.log( + &format!("Expansion (Original: {:?})", original_call.kind), + node, + ); + self.indent += 1; + self.visit(bound_expanded); + self.indent -= 1; + } + + BoundKind::Extension(ext) => { + self.log(&ext.display_name(), node); + } + } + } +} diff --git a/src/ast/compiler/lambda_collector.rs b/src/ast/compiler/lambda_collector.rs index be1dbd7..ea4a2a1 100644 --- a/src/ast/compiler/lambda_collector.rs +++ b/src/ast/compiler/lambda_collector.rs @@ -1,85 +1,95 @@ -use std::collections::HashMap; -use crate::ast::compiler::bound_nodes::{BoundKind, Address, BoundNode}; - -/// A pass that collects all global function definitions (lambdas) into a registry. -/// This allows the Specializer to retrieve the original AST of a function for monomorphization. -pub struct LambdaCollector<'a, T> { - registry: &'a mut HashMap>, -} - -impl<'a, T: Clone> LambdaCollector<'a, T> { - /// Performs a full traversal of the AST and populates the provided registry. - pub fn collect(node: &BoundNode, registry: &'a mut HashMap>) { - let mut collector = Self { registry }; - collector.visit(node); - } - - fn visit(&mut self, node: &BoundNode) { - match &node.kind { - BoundKind::Block { exprs } => { - for expr in exprs { - self.visit(expr); - } - } - - BoundKind::DefGlobal { global_index, value, .. } => { - // Register the full Lambda node as the template. - if let BoundKind::Lambda { .. } = &value.kind { - self.registry.insert(*global_index, (*value).as_ref().clone()); - } - self.visit(value); - } - - BoundKind::Set { addr, value } => { - // Also track assignments to globals if they hold lambdas. - if let Address::Global(global_index) = addr - && let BoundKind::Lambda { .. } = &value.kind - { - self.registry.insert(*global_index, (*value).as_ref().clone()); - } - self.visit(value); - } - - BoundKind::If { cond, then_br, else_br } => { - self.visit(cond); - self.visit(then_br); - if let Some(e) = else_br { - self.visit(e); - } - } - - BoundKind::Lambda { params, body, .. } => { - self.visit(params); - self.visit(body); - } - - BoundKind::DefLocal { value, .. } => { - self.visit(value); - } - - BoundKind::Call { callee, args } => { - self.visit(callee); - self.visit(args); - } - - BoundKind::Tuple { elements } => { - for el in elements { - self.visit(el); - } - } - - BoundKind::Record { fields } => { - for (k, v) in fields { - self.visit(k); - self.visit(v); - } - } - - BoundKind::Expansion { bound_expanded, .. } => { - self.visit(bound_expanded); - } - - _ => {} // Leaf nodes - } - } -} +use crate::ast::compiler::bound_nodes::{Address, BoundKind, BoundNode}; +use std::collections::HashMap; + +/// A pass that collects all global function definitions (lambdas) into a registry. +/// This allows the Specializer to retrieve the original AST of a function for monomorphization. +pub struct LambdaCollector<'a, T> { + registry: &'a mut HashMap>, +} + +impl<'a, T: Clone> LambdaCollector<'a, T> { + /// Performs a full traversal of the AST and populates the provided registry. + pub fn collect(node: &BoundNode, registry: &'a mut HashMap>) { + let mut collector = Self { registry }; + collector.visit(node); + } + + fn visit(&mut self, node: &BoundNode) { + match &node.kind { + BoundKind::Block { exprs } => { + for expr in exprs { + self.visit(expr); + } + } + + BoundKind::DefGlobal { + global_index, + value, + .. + } => { + // Register the full Lambda node as the template. + if let BoundKind::Lambda { .. } = &value.kind { + self.registry + .insert(*global_index, (*value).as_ref().clone()); + } + self.visit(value); + } + + BoundKind::Set { addr, value } => { + // Also track assignments to globals if they hold lambdas. + if let Address::Global(global_index) = addr + && let BoundKind::Lambda { .. } = &value.kind + { + self.registry + .insert(*global_index, (*value).as_ref().clone()); + } + self.visit(value); + } + + BoundKind::If { + cond, + then_br, + else_br, + } => { + self.visit(cond); + self.visit(then_br); + if let Some(e) = else_br { + self.visit(e); + } + } + + BoundKind::Lambda { params, body, .. } => { + self.visit(params); + self.visit(body); + } + + BoundKind::DefLocal { value, .. } => { + self.visit(value); + } + + BoundKind::Call { callee, args } => { + self.visit(callee); + self.visit(args); + } + + BoundKind::Tuple { elements } => { + for el in elements { + self.visit(el); + } + } + + BoundKind::Record { fields } => { + for (k, v) in fields { + self.visit(k); + self.visit(v); + } + } + + BoundKind::Expansion { bound_expanded, .. } => { + self.visit(bound_expanded); + } + + _ => {} // Leaf nodes + } + } +} diff --git a/src/ast/compiler/macros.rs b/src/ast/compiler/macros.rs index 96904bf..91f140d 100644 --- a/src/ast/compiler/macros.rs +++ b/src/ast/compiler/macros.rs @@ -1,609 +1,765 @@ -use std::collections::HashMap; -use std::rc::Rc; -use crate::ast::nodes::{Node, UntypedKind, Symbol}; -use crate::ast::types::{Value, Identity}; - -/// 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. -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 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::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 { name, value } => { - let value = self.expand_recursive(*value)?; - Ok(Node { - identity: node.identity, - kind: UntypedKind::Def { name, 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 { mut name, value } => { - name.context = Some(state.expansion_id.clone()); - let val = self.expand_template(*value, state)?; - Ok(Node { - identity: node.identity, - kind: UntypedKind::Def { name, value: Box::new(val) }, - ty: (), - }) - } - - UntypedKind::Assign { target, value } => { - let target = self.expand_template(*target, state)?; - let value = self.expand_template(*value, state)?; - Ok(Node { - identity: node.identity, - kind: UntypedKind::Assign { target: Box::new(target), value: Box::new(value) }, - ty: (), - }) - } - - UntypedKind::Tuple { elements } => { - let mut new_elements = Vec::new(); - for e in elements { - if let UntypedKind::Splice(ref inner) = e.kind { - let val = self.evaluator.evaluate(inner, 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::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 std::cell::RefCell; - use crate::ast::parser::Parser; - use crate::ast::types::{Value, Object}; - use crate::ast::compiler::Binder; - - struct SimpleEvaluator; - impl MacroEvaluator for SimpleEvaluator { - fn evaluate(&self, node: &Node, bindings: &HashMap, Node>) -> Result { - if let UntypedKind::Identifier(ref 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 { name, .. } = &result.kind { - assert_eq!(name.context, Some(call.identity.clone())); - assert_eq!(name.name.as_ref(), "y"); - } else { panic!("Expected Def result, got {:?}", result.kind); } - } - } - - #[test] - fn test_macro_expansion_unless() { - let source = " - (do - (macro unless [c b] `(if ~c ... ~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("*"), 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 bound = Binder::bind_root(globals, &expanded); - assert!(bound.is_ok(), "Hygiene failed: {:?}", bound.err()); - } - - #[test] - fn test_repro_macro_hygiene_clash_fixed() { - let source = " - (do - (def y 1) - (macro m1 [x] `(do (def y 10) (assign y 4))) - (m1 8) - y) - "; - let mut parser = Parser::new(source).unwrap(); - let untyped = parser.parse_expression().unwrap(); - - let mut expander = MacroExpander::new(MacroRegistry::new(), SimpleEvaluator); - let expanded = expander.expand(untyped).unwrap(); - - let globals = Rc::new(RefCell::new(HashMap::new())); - let bound = Binder::bind_root(globals, &expanded); - assert!(bound.is_ok(), "Explicit Hygiene with Backticks failed: {:?}", bound.err()); - } -} +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. +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 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::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 { name, value } => { + let value = self.expand_recursive(*value)?; + Ok(Node { + identity: node.identity, + kind: UntypedKind::Def { + name, + 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 { mut name, value } => { + name.context = Some(state.expansion_id.clone()); + let val = self.expand_template(*value, state)?; + Ok(Node { + identity: node.identity, + kind: UntypedKind::Def { + name, + value: Box::new(val), + }, + ty: (), + }) + } + + UntypedKind::Assign { target, value } => { + let target = self.expand_template(*target, state)?; + let value = self.expand_template(*value, state)?; + Ok(Node { + identity: node.identity, + kind: UntypedKind::Assign { + target: Box::new(target), + value: Box::new(value), + }, + ty: (), + }) + } + + UntypedKind::Tuple { elements } => { + let mut new_elements = Vec::new(); + for e in elements { + if let UntypedKind::Splice(ref inner) = e.kind { + let val = self.evaluator.evaluate(inner, 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::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 { name, .. } = &result.kind { + assert_eq!(name.context, Some(call.identity.clone())); + assert_eq!(name.name.as_ref(), "y"); + } else { + panic!("Expected Def result, got {:?}", result.kind); + } + } + } + + #[test] + fn test_macro_expansion_unless() { + let source = " + (do + (macro unless [c b] `(if ~c ... ~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("*"), 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 bound = Binder::bind_root(globals, &expanded); + assert!(bound.is_ok(), "Hygiene failed: {:?}", bound.err()); + } + + #[test] + fn test_repro_macro_hygiene_clash_fixed() { + let source = " + (do + (def y 1) + (macro m1 [x] `(do (def y 10) (assign y 4))) + (m1 8) + y) + "; + let mut parser = Parser::new(source).unwrap(); + let untyped = parser.parse_expression().unwrap(); + + let mut expander = MacroExpander::new(MacroRegistry::new(), SimpleEvaluator); + let expanded = expander.expand(untyped).unwrap(); + + let globals = Rc::new(RefCell::new(HashMap::new())); + let bound = Binder::bind_root(globals, &expanded); + assert!( + bound.is_ok(), + "Explicit Hygiene with Backticks failed: {:?}", + bound.err() + ); + } +} diff --git a/src/ast/compiler/mod.rs b/src/ast/compiler/mod.rs index 0c71ccb..972179e 100644 --- a/src/ast/compiler/mod.rs +++ b/src/ast/compiler/mod.rs @@ -1,20 +1,20 @@ -pub mod binder; -pub mod bound_nodes; -pub mod tco; -pub mod upvalues; -pub mod dumper; -pub mod macros; -pub mod type_checker; -pub mod specializer; -pub mod optimizer; -pub mod lambda_collector; - -pub use binder::*; -pub use bound_nodes::*; -pub use tco::*; -pub use upvalues::*; -pub use dumper::*; -pub use macros::*; -pub use type_checker::*; -pub use specializer::*; -pub use optimizer::*; +pub mod binder; +pub mod bound_nodes; +pub mod dumper; +pub mod lambda_collector; +pub mod macros; +pub mod optimizer; +pub mod specializer; +pub mod tco; +pub mod type_checker; +pub mod upvalues; + +pub use binder::*; +pub use bound_nodes::*; +pub use dumper::*; +pub use macros::*; +pub use optimizer::*; +pub use specializer::*; +pub use tco::*; +pub use type_checker::*; +pub use upvalues::*; diff --git a/src/ast/compiler/optimizer.rs b/src/ast/compiler/optimizer.rs index 738fbaa..4527bab 100644 --- a/src/ast/compiler/optimizer.rs +++ b/src/ast/compiler/optimizer.rs @@ -1,877 +1,1344 @@ -use std::rc::Rc; -use std::cell::RefCell; -use crate::ast::compiler::bound_nodes::{BoundKind, TypedNode, Address}; -use crate::ast::types::{Value, StaticType}; -use crate::ast::vm::Closure; -use crate::ast::nodes::Node; -use std::collections::{HashMap, HashSet}; - -/// The Optimizer performs Phase 2 (Cracking), Phase 2.5/2.6 (Aggressive Collapsing & DCE) -/// and Phase 4 (Global Inlining). -pub struct Optimizer { - pub enabled: bool, - max_passes: usize, - pub globals: Option>>>, - pub global_purity: Option>>>, - pub lambda_registry: Option>>>, -} - -impl Optimizer { - pub fn new(enabled: bool) -> Self { - Self { enabled, max_passes: 5, globals: None, global_purity: None, lambda_registry: None } - } - - pub fn with_globals(mut self, globals: Rc>>) -> Self { - self.globals = Some(globals); - self - } - - pub fn with_purity(mut self, purity: Rc>>) -> Self { - self.global_purity = Some(purity); - self - } - - pub fn with_registry(mut self, registry: Rc>>) -> Self { - self.lambda_registry = Some(registry); - self - } - - pub fn optimize(&self, node: TypedNode) -> TypedNode { - if !self.enabled { - return node; - } - - let mut current = node; - for _ in 0..self.max_passes { - let mut sub = SubstitutionMap::new(); - let next = self.visit_node(current.clone(), &mut sub); - if next == current { - break; - } - current = next; - } - current - } - - fn visit_node(&self, node: TypedNode, sub: &mut SubstitutionMap) -> TypedNode { - let (new_kind, new_ty) = match node.kind { - BoundKind::Get { addr, name } => { - let new_addr = match addr { - Address::Local(slot) => { - if !sub.assigned_locals.contains(&slot) { - if let Some(inlined_node) = sub.ast_locals.get(&slot) { - return inlined_node.clone(); - } - if let Some(val) = sub.locals.get(&slot) { - return Node { - identity: node.identity, - ty: val.static_type(), - kind: BoundKind::Constant(val.clone()), - }; - } - } - sub.used_slots.insert(slot); - Address::Local(sub.map_slot(slot)) - } - Address::Upvalue(idx) => { - if let Some(val) = sub.upvalues.get(&idx) { - return Node { - identity: node.identity, - ty: val.static_type(), - kind: BoundKind::Constant(val.clone()), - }; - } - Address::Upvalue(idx) - } - Address::Global(idx) => { - if !sub.assigned_globals.contains(&idx) { - if let Some(val) = sub.globals.get(&idx) { - return Node { - identity: node.identity, - ty: val.static_type(), - kind: BoundKind::Constant(val.clone()), - }; - } - if let Some(globals_rc) = &self.globals { - let globals = globals_rc.borrow(); - if let Some(val) = globals.get(idx as usize) - && self.is_inlinable_value(val) - { - return Node { - identity: node.identity, - ty: val.static_type(), - kind: BoundKind::Constant(val.clone()), - }; - } - } - } - sub.used_globals.insert(idx); - Address::Global(idx) - } - }; - (BoundKind::Get { addr: new_addr, name }, node.ty) - } - - BoundKind::Parameter { name, slot } => { - let new_slot = sub.map_slot(slot); - (BoundKind::Parameter { name, slot: new_slot }, node.ty) - } - - BoundKind::Set { addr, value } => { - let value = Box::new(self.visit_node(*value, sub)); - let new_addr = match addr { - Address::Local(slot) => { - if let BoundKind::Constant(val) = &value.kind { - sub.add_local(slot, val.clone()); - } else { - sub.locals.remove(&slot); - } - Address::Local(sub.map_slot(slot)) - } - Address::Global(idx) => { - if let BoundKind::Constant(val) = &value.kind { - sub.add_global(idx, val.clone()); - } else { - sub.globals.remove(&idx); - } - Address::Global(idx) - } - _ => addr - }; - (BoundKind::Set { addr: new_addr, value }, node.ty) - }, - - BoundKind::Call { callee, args } => { - let callee = self.visit_node(*callee, sub); - let args = self.visit_node(*args, sub); - - if self.enabled { - // Case 1: Beta-Reduction for Lambda Literals - if let BoundKind::Lambda { params, body, upvalues, positional_count } = &callee.kind - && upvalues.is_empty() - && positional_count.is_some() - && let Some(collapsed) = self.try_beta_reduce(params, &args, (**body).clone()) { - return collapsed; - } - - // Case 1.5: Beta-Reduction for Global Registry functions (from Registry) - if let BoundKind::Get { addr: Address::Global(idx), .. } = &callee.kind - && let Some(registry_rc) = &self.lambda_registry - && sub.inlining_depth < 5 - && !sub.inlining_stack.contains(idx) { - let registry = registry_rc.borrow(); - if let Some(lambda_node) = registry.get(idx) - && let BoundKind::Lambda { params, body, upvalues, positional_count } = &lambda_node.kind - && upvalues.is_empty() - && positional_count.is_some() - { - // RECURSION CHECK: Don't inline if the function calls itself - let mut used_in_body = HashSet::new(); - let mut assigned_in_body = HashSet::new(); - let mut used_globals_in_body = HashSet::new(); - let mut assigned_globals_in_body = HashSet::new(); - self.collect_usage(body, &mut used_in_body, &mut used_globals_in_body, &mut assigned_in_body, &mut assigned_globals_in_body); - - if !used_globals_in_body.contains(idx) { - let mut inner_sub = SubstitutionMap::new(); - inner_sub.inlining_stack = sub.inlining_stack.clone(); - inner_sub.inlining_stack.insert(*idx); - inner_sub.inlining_depth = sub.inlining_depth + 1; - if let Some(collapsed) = self.try_beta_reduce_with_sub(params.as_ref(), &args, body.as_ref().clone(), &mut inner_sub) { - return collapsed; - } - } - } - } - - // Case 2: Cracking and Inlining for Constant Closures - if let BoundKind::Constant(Value::Object(ref obj)) = callee.kind - && sub.inlining_depth < 5 - && let Some(closure) = obj.as_any().downcast_ref::() - && (closure.upvalues.is_empty() || self.is_pure(&closure.function_node)) - { - let mut closure_sub = SubstitutionMap::new(); - closure_sub.inlining_depth = sub.inlining_depth + 1; - for (i, cell) in closure.upvalues.iter().enumerate() { - closure_sub.add_upvalue(i as u32, cell.borrow().clone()); - } - let inlined_body = self.visit_node((*closure.function_node).clone(), &mut closure_sub); - - if let Some(collapsed) = self.try_beta_reduce_with_sub(&closure.parameter_node, &args, inlined_body, &mut closure_sub) { - return collapsed; - } - } - - if let Some(folded) = self.try_fold_pure(&callee, &args) { - return folded; - } - } - - if self.enabled - && let BoundKind::Constant(Value::Object(ref obj)) = callee.kind - && sub.inlining_depth < 5 - && let Some(closure) = obj.as_any().downcast_ref::() - && (closure.upvalues.is_empty() || self.is_pure(&closure.function_node)) - { - let mut closure_sub = SubstitutionMap::new(); - closure_sub.inlining_depth = sub.inlining_depth + 1; - for (i, cell) in closure.upvalues.iter().enumerate() { - closure_sub.add_upvalue(i as u32, cell.borrow().clone()); - } - let inlined_body = self.visit_node((*closure.function_node).clone(), &mut closure_sub); - - let cracked_lambda = Node { - identity: callee.identity.clone(), - ty: callee.ty.clone(), - kind: BoundKind::Lambda { - params: closure.parameter_node.clone(), - upvalues: vec![], - body: Rc::new(inlined_body), - positional_count: closure.positional_count, - }, - }; - return Node { - identity: node.identity, - kind: BoundKind::Call { callee: Box::new(cracked_lambda), args: Box::new(args) }, - ty: node.ty, - }; - } - - (BoundKind::Call { callee: Box::new(callee), args: Box::new(args) }, node.ty) - }, - - BoundKind::If { cond, then_br, else_br } => { - let cond = self.visit_node(*cond, sub); - if self.enabled - && let BoundKind::Constant(ref val) = cond.kind { - if val.is_truthy() { - return self.visit_node(*then_br, sub); - } else if let Some(else_node) = else_br { - return self.visit_node(*else_node, sub); - } else { - return Node { identity: node.identity, kind: BoundKind::Nop, ty: StaticType::Void }; - } - } - - let then_br = Box::new(self.visit_node(*then_br, sub)); - let else_br = else_br.map(|e| Box::new(self.visit_node(*e, sub))); - (BoundKind::If { cond: Box::new(cond), then_br, else_br }, node.ty) - }, - - BoundKind::Block { exprs } => { - let mut used_in_block = HashSet::new(); - let mut used_globals_in_block = HashSet::new(); - let mut assigned_locals_in_block = HashSet::new(); - let mut assigned_globals_in_block = HashSet::new(); - - if !exprs.is_empty() { - for e in &exprs { - self.collect_usage(e, &mut used_in_block, &mut used_globals_in_block, &mut assigned_locals_in_block, &mut assigned_globals_in_block); - } - if let Some(last) = exprs.last() { - match &last.kind { - BoundKind::Get { addr: Address::Local(slot), .. } => { used_in_block.insert(*slot); } - BoundKind::Get { addr: Address::Global(idx), .. } => { used_globals_in_block.insert(*idx); } - _ => {} - } - } - } - - // IMPORTANT: Propagate block assignments to the outer map so we don't - // incorrectly inline these variables in the rest of the script. - for slot in &assigned_locals_in_block { sub.assigned_locals.insert(*slot); } - for idx in &assigned_globals_in_block { sub.assigned_globals.insert(*idx); } - - let mut new_exprs = Vec::with_capacity(exprs.len()); - let last_idx = exprs.len().saturating_sub(1); - - for (i, e) in exprs.into_iter().enumerate() { - let is_last = i == last_idx; - - if self.enabled && !is_last { - let removable = match &e.kind { - BoundKind::DefLocal { slot, value, .. } => { - !used_in_block.contains(slot) && !sub.captured_slots.contains(slot) && self.is_pure(value) - } - BoundKind::DefGlobal { global_index, value, .. } => { - !used_globals_in_block.contains(global_index) && (self.is_pure(value) || matches!(value.kind, BoundKind::Lambda { .. })) - } - BoundKind::Set { addr: Address::Local(slot), value, .. } => { - !used_in_block.contains(slot) && !sub.captured_slots.contains(slot) && self.is_pure(value) - } - _ => self.is_pure(&e), - }; - - if removable { - continue; - } - } - - let opt = self.visit_node(e, sub); - if self.enabled && matches!(opt.kind, BoundKind::Nop) && !is_last { - continue; - } - new_exprs.push(opt); - } - - if self.enabled { - if new_exprs.is_empty() { - return Node { identity: node.identity, kind: BoundKind::Nop, ty: StaticType::Void }; - } else if new_exprs.len() == 1 { - return new_exprs.pop().unwrap(); - } - } - - let ty = if new_exprs.is_empty() { StaticType::Void } else { new_exprs.last().unwrap().ty.clone() }; - (BoundKind::Block { exprs: new_exprs }, ty) - }, - - - BoundKind::Lambda { .. } => { - let (params, original_upvalues, body, positional_count) = if let BoundKind::Lambda { params, upvalues, body, positional_count } = &node.kind { - (params, upvalues, body, positional_count) - } else { unreachable!() }; - - let mut used_in_lambda = HashSet::new(); - let mut used_globals_in_lambda = HashSet::new(); - let mut assigned_locals_in_lambda = HashSet::new(); - let mut assigned_globals_in_lambda = HashSet::new(); - - self.collect_usage(&node, &mut used_in_lambda, &mut used_globals_in_lambda, &mut assigned_locals_in_lambda, &mut assigned_globals_in_lambda); - - // Track captures from outer scope that are assigned in this lambda - for idx in &assigned_globals_in_lambda { sub.assigned_globals.insert(*idx); } - - let mut new_upvalues = Vec::new(); - let mut mapping = Vec::new(); - let mut next_inner_subs = SubstitutionMap::new(); - next_inner_subs.inlining_depth = sub.inlining_depth; - next_inner_subs.assigned_locals = assigned_locals_in_lambda; - next_inner_subs.assigned_globals = assigned_globals_in_lambda; - - for (old_idx, capture_addr) in original_upvalues.iter().enumerate() { - let mut inlined_val = None; - match capture_addr { - Address::Local(slot) => { - if let Some(val) = sub.locals.get(slot) { inlined_val = Some(val.clone()); } - sub.captured_slots.insert(*slot); - } - Address::Upvalue(idx) => { - if let Some(val) = sub.upvalues.get(idx) { inlined_val = Some(val.clone()); } - } - _ => {} - } - - if let Some(val) = inlined_val { - next_inner_subs.add_upvalue(old_idx as u32, val); - mapping.push(None); - } else { - mapping.push(Some(new_upvalues.len() as u32)); - let new_addr = if let Address::Local(parent_slot) = capture_addr { - Address::Local(sub.map_slot(*parent_slot)) - } else { - *capture_addr - }; - new_upvalues.push(new_addr); - } - } - - // 1. Visit parameters to determine their new slots - let params_node = self.visit_node(params.as_ref().clone(), &mut next_inner_subs); - - // 2. IMPORTANT: Pre-register parameter slots in the inner substitution map - // so they are mapped 1:1 and don't get reassigned to higher slots in the body. - self.collect_parameter_slots(¶ms_node, &mut next_inner_subs); - - let body_node = self.visit_node(body.as_ref().clone(), &mut next_inner_subs); - let reindexed_body = if new_upvalues.len() != original_upvalues.len() { - sub.reindex_upvalues(body_node, &mapping) - } else { - body_node - }; - - (BoundKind::Lambda { - params: Rc::new(params_node), - upvalues: new_upvalues, - body: Rc::new(reindexed_body), - positional_count: *positional_count - }, node.ty) - }, - - BoundKind::DefLocal { name, slot, value, captured_by } => { - if !captured_by.is_empty() { - sub.captured_slots.insert(slot); - } - let value = Box::new(self.visit_node(*value, sub)); - if let BoundKind::Constant(val) = &value.kind { - sub.add_local(slot, val.clone()); - } else { - sub.locals.remove(&slot); - } - - if self.is_pure(&value) { - sub.pure_slots.insert(slot); - } else { - sub.pure_slots.remove(&slot); - } - - let new_slot = sub.map_slot(slot); - (BoundKind::DefLocal { name, slot: new_slot, value, captured_by }, node.ty) - }, - - BoundKind::DefGlobal { name, global_index, value } => { - let value = Box::new(self.visit_node(*value, sub)); - if let BoundKind::Constant(val) = &value.kind { - sub.add_global(global_index, val.clone()); - } - if self.is_pure(&value) - && let Some(purity_rc) = &self.global_purity { - purity_rc.borrow_mut().insert(global_index, true); - } - (BoundKind::DefGlobal { name, global_index, value }, node.ty) - }, - - BoundKind::Tuple { elements } => { - let elements = elements.into_iter().map(|e| self.visit_node(e, sub)).collect(); - (BoundKind::Tuple { elements }, node.ty) - }, - BoundKind::Record { fields } => { - let fields = fields.into_iter().map(|(k, v)| (self.visit_node(k, sub), self.visit_node(v, sub))).collect(); - (BoundKind::Record { fields }, node.ty) - }, - BoundKind::Expansion { original_call, bound_expanded } => { - sub.inlining_depth += 1; - let bound_expanded = Box::new(self.visit_node(*bound_expanded, sub)); - sub.inlining_depth -= 1; - (BoundKind::Expansion { original_call, bound_expanded }, node.ty) - }, - k => (k, node.ty), - }; - - Node { identity: node.identity, kind: new_kind, ty: new_ty } - } - - fn collect_parameter_slots(&self, node: &TypedNode, sub: &mut SubstitutionMap) { - match &node.kind { - BoundKind::Parameter { slot, .. } => { - // Identity mapping for parameters to keep them in their reserved frame slots - sub.slot_mapping.insert(*slot, *slot); - sub.next_slot = sub.next_slot.max(*slot + 1); - } - BoundKind::Tuple { elements } => { - for el in elements { self.collect_parameter_slots(el, sub); } - } - _ => {} - } - } - - fn is_inlinable_value(&self, val: &Value) -> bool { - match val { - Value::Int(_) | Value::Float(_) | Value::Bool(_) | Value::Text(_) | Value::Keyword(_) | Value::DateTime(_) => true, - Value::Object(obj) => { - if let Some(closure) = obj.as_any().downcast_ref::() { - closure.upvalues.is_empty() - } else { - false - } - } - _ => false, - } - } - - fn is_pure(&self, node: &TypedNode) -> bool { - match &node.kind { - BoundKind::Constant(_) | BoundKind::Parameter { .. } | BoundKind::Nop => true, - BoundKind::Lambda { body, .. } => self.is_pure(body), - BoundKind::Get { addr, .. } => { - match addr { - Address::Global(idx) => { - if let Some(purity_rc) = &self.global_purity { - *purity_rc.borrow().get(idx).unwrap_or(&false) - } else { - false - } - } - _ => true, - } - } - BoundKind::Tuple { elements } => elements.iter().all(|e| self.is_pure(e)), - BoundKind::Record { fields } => fields.iter().all(|(k, v)| self.is_pure(k) && self.is_pure(v)), - BoundKind::If { cond, then_br, else_br } => { - self.is_pure(cond) && self.is_pure(then_br) && else_br.as_ref().is_none_or(|e| self.is_pure(e)) - } - BoundKind::Block { exprs } => exprs.iter().all(|e| self.is_pure(e)), - BoundKind::Expansion { bound_expanded, .. } => self.is_pure(bound_expanded), - BoundKind::DefLocal { value, .. } => self.is_pure(value), - BoundKind::Set { .. } => false, - BoundKind::Call { callee, args } => { - let callee_pure = match &callee.kind { - BoundKind::Get { addr: Address::Global(idx), .. } => { - if let Some(purity_rc) = &self.global_purity { - *purity_rc.borrow().get(idx).unwrap_or(&false) - } else { - false - } - } - BoundKind::Constant(Value::Function(_)) => true, - BoundKind::Constant(Value::Object(obj)) => { - if let Some(closure) = obj.as_any().downcast_ref::() { - self.is_pure(&closure.function_node) - } else { - false - } - } - _ => false, - }; - callee_pure && self.is_pure(args) - } - _ => false, - } - } - - fn try_fold_pure(&self, callee: &TypedNode, args: &TypedNode) -> Option { - let temp_call = Node { - identity: callee.identity.clone(), - ty: StaticType::Any, - kind: BoundKind::Call { callee: Box::new(callee.clone()), args: Box::new(args.clone()) } - }; - if !self.is_pure(&temp_call) { return None; } - let mut arg_nodes = Vec::new(); - self.flatten_typed_tuple(args, &mut arg_nodes); - let mut arg_values = Vec::with_capacity(arg_nodes.len()); - for node in arg_nodes { - if let BoundKind::Constant(val) = node.kind { arg_values.push(val); } - else { return None; } - } - let func_val = match &callee.kind { - BoundKind::Get { addr: Address::Global(idx), .. } => { - self.globals.as_ref()?.borrow().get(*idx as usize)?.clone() - } - BoundKind::Constant(val) => val.clone(), - _ => return None, - }; - let result = match func_val { - Value::Function(f) => f(arg_values), - _ => return None, - }; - Some(Node { - identity: callee.identity.clone(), - ty: result.static_type(), - kind: BoundKind::Constant(result), - }) - } - - fn try_beta_reduce(&self, params: &TypedNode, args: &TypedNode, body: TypedNode) -> Option { - let mut sub = SubstitutionMap::new(); - self.try_beta_reduce_with_sub(params, args, body, &mut sub) - } - - fn try_beta_reduce_with_sub(&self, params: &TypedNode, args: &TypedNode, body: TypedNode, sub: &mut SubstitutionMap) -> Option { - let mut arg_vals = Vec::new(); - self.flatten_typed_tuple(args, &mut arg_vals); - - let mut slot_index = 0; - self.map_params_to_args(params, &arg_vals, &mut slot_index, sub); - - // Safety: Only proceed if we mapped EXACTLY the number of arguments provided. - // And if we actually have parameters to bind (or if it's a 0-arg call). - if slot_index != arg_vals.len() { return None; } - if sub.locals.is_empty() && sub.ast_locals.is_empty() && sub.upvalues.is_empty() && !arg_vals.is_empty() { return None; } - - Some(self.visit_node(body, sub)) - } - - fn flatten_typed_tuple(&self, node: &TypedNode, into: &mut Vec) { - if let BoundKind::Tuple { elements } = &node.kind { - for el in elements { self.flatten_typed_tuple(el, into); } - } else if !matches!(node.kind, BoundKind::Nop) { - into.push(node.clone()); - } - } - - fn map_params_to_args(&self, pattern: &TypedNode, args: &[TypedNode], offset: &mut usize, sub: &mut SubstitutionMap) { - match &pattern.kind { - BoundKind::Parameter { slot, .. } => { - if let Some(arg) = args.get(*offset) { - if let BoundKind::Constant(val) = &arg.kind { - sub.add_local(*slot, val.clone()); - } else if let BoundKind::Lambda { upvalues, .. } = &arg.kind { - if upvalues.is_empty() { - sub.add_ast_local(*slot, arg.clone()); - } - } else if let BoundKind::Get { addr: Address::Global(_), .. } = &arg.kind { - sub.add_ast_local(*slot, arg.clone()); - } - } - // Parameters in beta-reduction are also identity mapped if not inlined - sub.slot_mapping.insert(*slot, *slot); - sub.next_slot = sub.next_slot.max(*slot + 1); - *offset += 1; - } - BoundKind::Tuple { elements } => { - for el in elements { self.map_params_to_args(el, args, offset, sub); } - } - _ => {} - } - } - - fn collect_usage(&self, node: &TypedNode, used_locals: &mut HashSet, used_globals: &mut HashSet, assigned_locals: &mut HashSet, assigned_globals: &mut HashSet) { - match &node.kind { - BoundKind::Constant(v) => { - if let Value::Object(obj) = v - && let Some(closure) = obj.as_any().downcast_ref::() - { - self.collect_usage(&closure.function_node, used_locals, used_globals, assigned_locals, assigned_globals); - } - } - BoundKind::Get { addr, .. } => { - match addr { - Address::Local(slot) => { used_locals.insert(*slot); } - Address::Global(idx) => { used_globals.insert(*idx); } - _ => {} - } - } - BoundKind::Set { addr, value } => { - match addr { - Address::Local(slot) => { assigned_locals.insert(*slot); } - Address::Global(idx) => { assigned_globals.insert(*idx); } - _ => {} - } - self.collect_usage(value, used_locals, used_globals, assigned_locals, assigned_globals); - } - BoundKind::Lambda { params, body, upvalues, .. } => { - self.collect_usage(params, used_locals, used_globals, assigned_locals, assigned_globals); - self.collect_usage(body, used_locals, used_globals, assigned_locals, assigned_globals); - for addr in upvalues { - match addr { - Address::Local(slot) => { used_locals.insert(*slot); } - Address::Global(idx) => { used_globals.insert(*idx); } - _ => {} - } - } - } - BoundKind::Block { exprs } => { - for e in exprs { self.collect_usage(e, used_locals, used_globals, assigned_locals, assigned_globals); } - } - BoundKind::If { cond, then_br, else_br } => { - self.collect_usage(cond, used_locals, used_globals, assigned_locals, assigned_globals); - self.collect_usage(then_br, used_locals, used_globals, assigned_locals, assigned_globals); - if let Some(e) = else_br { self.collect_usage(e, used_locals, used_globals, assigned_locals, assigned_globals); } - } - BoundKind::Call { callee, args } => { - self.collect_usage(callee, used_locals, used_globals, assigned_locals, assigned_globals); - self.collect_usage(args, used_locals, used_globals, assigned_locals, assigned_globals); - } - BoundKind::Tuple { elements } => { - for e in elements { self.collect_usage(e, used_locals, used_globals, assigned_locals, assigned_globals); } - } - BoundKind::Record { fields } => { - for (k, v) in fields { - self.collect_usage(k, used_locals, used_globals, assigned_locals, assigned_globals); - self.collect_usage(v, used_locals, used_globals, assigned_locals, assigned_globals); - } - } - BoundKind::DefLocal { value, .. } | BoundKind::DefGlobal { value, .. } => { - self.collect_usage(value, used_locals, used_globals, assigned_locals, assigned_globals); - } - BoundKind::Expansion { bound_expanded, .. } => { - self.collect_usage(bound_expanded, used_locals, used_globals, assigned_locals, assigned_globals); - } - _ => {} - } - } -} - -/// Helper for transitively inlining values and cleaning up capture lists. -struct SubstitutionMap { - locals: HashMap, - upvalues: HashMap, - globals: HashMap, - ast_locals: HashMap, - slot_mapping: HashMap, - inlining_stack: HashSet, - inlining_depth: usize, - assigned_locals: HashSet, - assigned_globals: HashSet, - next_slot: u32, - used_slots: HashSet, - pure_slots: HashSet, - used_globals: HashSet, - captured_slots: HashSet, -} - -impl SubstitutionMap { - fn new() -> Self { - Self { - locals: HashMap::new(), - upvalues: HashMap::new(), - globals: HashMap::new(), - ast_locals: HashMap::new(), - slot_mapping: HashMap::new(), - inlining_stack: HashSet::new(), - inlining_depth: 0, - assigned_locals: HashSet::new(), - assigned_globals: HashSet::new(), - next_slot: 0, - used_slots: HashSet::new(), - pure_slots: HashSet::new(), - used_globals: HashSet::new(), - captured_slots: HashSet::new(), - } - } - - fn add_ast_local(&mut self, slot: u32, node: TypedNode) { - self.ast_locals.insert(slot, node); - } - - fn map_slot(&mut self, old_slot: u32) -> u32 { - if let Some(&new_slot) = self.slot_mapping.get(&old_slot) { - return new_slot; - } - let new_slot = self.next_slot; - self.slot_mapping.insert(old_slot, new_slot); - self.next_slot += 1; - new_slot - } - - fn add_local(&mut self, slot: u32, val: Value) { - self.locals.insert(slot, val); - } - - fn add_upvalue(&mut self, idx: u32, val: Value) { - self.upvalues.insert(idx, val); - } - - fn add_global(&mut self, idx: u32, val: Value) { - self.globals.insert(idx, val); - } - - fn reindex_upvalues(&self, node: TypedNode, mapping: &[Option]) -> TypedNode { - let (new_kind, new_ty) = match node.kind { - BoundKind::Get { addr: Address::Upvalue(idx), name } => { - if let Some(res) = mapping.get(idx as usize) { - match res { - Some(new_idx) => (BoundKind::Get { addr: Address::Upvalue(*new_idx), name }, node.ty), - None => (BoundKind::Get { addr: Address::Upvalue(idx), name }, node.ty), - } - } else { - (BoundKind::Get { addr: Address::Upvalue(idx), name }, node.ty) - } - }, - BoundKind::Lambda { params, upvalues, body, positional_count } => { - let mut next_upvalues = Vec::new(); - for addr in upvalues { - if let Address::Upvalue(idx) = addr - && let Some(res) = mapping.get(idx as usize) { - if let Some(new_idx) = res { next_upvalues.push(Address::Upvalue(*new_idx)); } - continue; - } - next_upvalues.push(addr); - } - (BoundKind::Lambda { params, upvalues: next_upvalues, body, positional_count }, node.ty) - }, - BoundKind::If { cond, then_br, else_br } => { - let cond = Box::new(self.reindex_upvalues(*cond, mapping)); - let then_br = Box::new(self.reindex_upvalues(*then_br, mapping)); - let else_br = else_br.map(|e| Box::new(self.reindex_upvalues(*e, mapping))); - (BoundKind::If { cond, then_br, else_br }, node.ty) - }, - BoundKind::Block { exprs } => { - let exprs = exprs.into_iter().map(|e| self.reindex_upvalues(e, mapping)).collect(); - (BoundKind::Block { exprs }, node.ty) - }, - BoundKind::Call { callee, args } => { - let callee = Box::new(self.reindex_upvalues(*callee, mapping)); - let args = Box::new(self.reindex_upvalues(*args, mapping)); - (BoundKind::Call { callee, args }, node.ty) - }, - BoundKind::DefLocal { name, slot, value, captured_by } => { - let value = Box::new(self.reindex_upvalues(*value, mapping)); - (BoundKind::DefLocal { name, slot, value, captured_by }, node.ty) - }, - BoundKind::DefGlobal { name, global_index, value } => { - let value = Box::new(self.reindex_upvalues(*value, mapping)); - (BoundKind::DefGlobal { name, global_index, value }, node.ty) - }, - BoundKind::Set { addr, value } => { - let value = Box::new(self.reindex_upvalues(*value, mapping)); - (BoundKind::Set { addr, value }, node.ty) - }, - BoundKind::Tuple { elements } => { - let elements = elements.into_iter().map(|e| self.reindex_upvalues(e, mapping)).collect(); - (BoundKind::Tuple { elements }, node.ty) - }, - BoundKind::Record { fields } => { - let fields = fields.into_iter().map(|(k, v)| (self.reindex_upvalues(k, mapping), self.reindex_upvalues(v, mapping))).collect(); - (BoundKind::Record { fields }, node.ty) - }, - k => (k, node.ty), - }; - Node { identity: node.identity, kind: new_kind, ty: new_ty } - } -} - -#[cfg(test)] -mod tests { - use crate::ast::environment::Environment; - - fn get_optimized_dump(source: &str, enabled: bool) -> String { - let mut env = Environment::new(); - env.optimization = enabled; - env.dump_ast(source).expect("Compilation failed during test") - } - - #[test] - fn test_opt_folding() { - let dump = get_optimized_dump("(+ 10 20)", true); - insta::assert_snapshot!(dump); - } - - #[test] - fn test_opt_folding_complex() { - let dump_float = get_optimized_dump("(+ 1.5 2.5)", true); - assert!(dump_float.contains("Constant: 4")); - let dump_text = get_optimized_dump("(+ \"hello \" \"world\")", true); - assert!(dump_text.contains("Constant: \"hello world\"")); - let dump_date = get_optimized_dump("(date \"2023-01-01\")", true); - assert!(dump_date.contains("Constant: #2023-01-01 00:00:00#")); - let dump_cmp = get_optimized_dump("(> 10 5)", true); - assert!(dump_cmp.contains("Constant: true")); - } - - #[test] - fn test_opt_local_inlining() { - let source = "(fn [] (do (def x 10) (+ x 5)))"; - let dump = get_optimized_dump(source, true); - insta::assert_snapshot!(dump); - } - - #[test] - fn test_opt_dce_unused() { - let source = "(fn [] (do (def x 10) 42))"; - let dump = get_optimized_dump(source, true); - insta::assert_snapshot!(dump); - } - - #[test] - fn test_opt_assignment_safety() { - let source = "(fn [] (do (def x 10) (assign x 20) x))"; - let dump = get_optimized_dump(source, true); - insta::assert_snapshot!(dump); - } - - #[test] - fn test_opt_lambda_cracking() { - let source = "(fn [] (do (def x 10) (fn [] x)))"; - let dump = get_optimized_dump(source, true); - insta::assert_snapshot!(dump); - } - - #[test] - fn test_opt_global_unused_dce() { - let source = "(do (def f (fn [x] x)) 42)"; - let dump = get_optimized_dump(source, true); - assert!(!dump.contains("DefGlobal"), "Unused global helper 'f' should be optimized away. Dump: \n{}", dump); - assert!(dump.contains("Constant: 42")); - } -} +use crate::ast::compiler::bound_nodes::{Address, BoundKind, TypedNode}; +use crate::ast::nodes::Node; +use crate::ast::types::{StaticType, Value}; +use crate::ast::vm::Closure; +use std::cell::RefCell; +use std::collections::{HashMap, HashSet}; +use std::rc::Rc; + +/// The Optimizer performs Phase 2 (Cracking), Phase 2.5/2.6 (Aggressive Collapsing & DCE) +/// and Phase 4 (Global Inlining). +pub struct Optimizer { + pub enabled: bool, + max_passes: usize, + pub globals: Option>>>, + pub global_purity: Option>>>, + pub lambda_registry: Option>>>, +} + +impl Optimizer { + pub fn new(enabled: bool) -> Self { + Self { + enabled, + max_passes: 5, + globals: None, + global_purity: None, + lambda_registry: None, + } + } + + pub fn with_globals(mut self, globals: Rc>>) -> Self { + self.globals = Some(globals); + self + } + + pub fn with_purity(mut self, purity: Rc>>) -> Self { + self.global_purity = Some(purity); + self + } + + pub fn with_registry(mut self, registry: Rc>>) -> Self { + self.lambda_registry = Some(registry); + self + } + + pub fn optimize(&self, node: TypedNode) -> TypedNode { + if !self.enabled { + return node; + } + + let mut current = node; + for _ in 0..self.max_passes { + let mut sub = SubstitutionMap::new(); + let next = self.visit_node(current.clone(), &mut sub); + if next == current { + break; + } + current = next; + } + current + } + + fn visit_node(&self, node: TypedNode, sub: &mut SubstitutionMap) -> TypedNode { + let (new_kind, new_ty) = match node.kind { + BoundKind::Get { addr, name } => { + let new_addr = match addr { + Address::Local(slot) => { + if !sub.assigned_locals.contains(&slot) { + if let Some(inlined_node) = sub.ast_locals.get(&slot) { + return inlined_node.clone(); + } + if let Some(val) = sub.locals.get(&slot) { + return Node { + identity: node.identity, + ty: val.static_type(), + kind: BoundKind::Constant(val.clone()), + }; + } + } + sub.used_slots.insert(slot); + Address::Local(sub.map_slot(slot)) + } + Address::Upvalue(idx) => { + if let Some(val) = sub.upvalues.get(&idx) { + return Node { + identity: node.identity, + ty: val.static_type(), + kind: BoundKind::Constant(val.clone()), + }; + } + Address::Upvalue(idx) + } + Address::Global(idx) => { + if !sub.assigned_globals.contains(&idx) { + if let Some(val) = sub.globals.get(&idx) { + return Node { + identity: node.identity, + ty: val.static_type(), + kind: BoundKind::Constant(val.clone()), + }; + } + if let Some(globals_rc) = &self.globals { + let globals = globals_rc.borrow(); + if let Some(val) = globals.get(idx as usize) + && self.is_inlinable_value(val) + { + return Node { + identity: node.identity, + ty: val.static_type(), + kind: BoundKind::Constant(val.clone()), + }; + } + } + } + sub.used_globals.insert(idx); + Address::Global(idx) + } + }; + ( + BoundKind::Get { + addr: new_addr, + name, + }, + node.ty, + ) + } + + BoundKind::Parameter { name, slot } => { + let new_slot = sub.map_slot(slot); + ( + BoundKind::Parameter { + name, + slot: new_slot, + }, + node.ty, + ) + } + + BoundKind::Set { addr, value } => { + let value = Box::new(self.visit_node(*value, sub)); + let new_addr = match addr { + Address::Local(slot) => { + if let BoundKind::Constant(val) = &value.kind { + sub.add_local(slot, val.clone()); + } else { + sub.locals.remove(&slot); + } + Address::Local(sub.map_slot(slot)) + } + Address::Global(idx) => { + if let BoundKind::Constant(val) = &value.kind { + sub.add_global(idx, val.clone()); + } else { + sub.globals.remove(&idx); + } + Address::Global(idx) + } + _ => addr, + }; + ( + BoundKind::Set { + addr: new_addr, + value, + }, + node.ty, + ) + } + + BoundKind::Call { callee, args } => { + let callee = self.visit_node(*callee, sub); + let args = self.visit_node(*args, sub); + + if self.enabled { + // Case 1: Beta-Reduction for Lambda Literals + if let BoundKind::Lambda { + params, + body, + upvalues, + positional_count, + } = &callee.kind + && upvalues.is_empty() + && positional_count.is_some() + && let Some(collapsed) = + self.try_beta_reduce(params, &args, (**body).clone()) + { + return collapsed; + } + + // Case 1.5: Beta-Reduction for Global Registry functions (from Registry) + if let BoundKind::Get { + addr: Address::Global(idx), + .. + } = &callee.kind + && let Some(registry_rc) = &self.lambda_registry + && sub.inlining_depth < 5 + && !sub.inlining_stack.contains(idx) + { + let registry = registry_rc.borrow(); + if let Some(lambda_node) = registry.get(idx) + && let BoundKind::Lambda { + params, + body, + upvalues, + positional_count, + } = &lambda_node.kind + && upvalues.is_empty() + && positional_count.is_some() + { + // RECURSION CHECK: Don't inline if the function calls itself + let mut used_in_body = HashSet::new(); + let mut assigned_in_body = HashSet::new(); + let mut used_globals_in_body = HashSet::new(); + let mut assigned_globals_in_body = HashSet::new(); + self.collect_usage( + body, + &mut used_in_body, + &mut used_globals_in_body, + &mut assigned_in_body, + &mut assigned_globals_in_body, + ); + + if !used_globals_in_body.contains(idx) { + let mut inner_sub = SubstitutionMap::new(); + inner_sub.inlining_stack = sub.inlining_stack.clone(); + inner_sub.inlining_stack.insert(*idx); + inner_sub.inlining_depth = sub.inlining_depth + 1; + if let Some(collapsed) = self.try_beta_reduce_with_sub( + params.as_ref(), + &args, + body.as_ref().clone(), + &mut inner_sub, + ) { + return collapsed; + } + } + } + } + + // Case 2: Cracking and Inlining for Constant Closures + if let BoundKind::Constant(Value::Object(ref obj)) = callee.kind + && sub.inlining_depth < 5 + && let Some(closure) = obj.as_any().downcast_ref::() + && (closure.upvalues.is_empty() || self.is_pure(&closure.function_node)) + { + let mut closure_sub = SubstitutionMap::new(); + closure_sub.inlining_depth = sub.inlining_depth + 1; + for (i, cell) in closure.upvalues.iter().enumerate() { + closure_sub.add_upvalue(i as u32, cell.borrow().clone()); + } + let inlined_body = + self.visit_node((*closure.function_node).clone(), &mut closure_sub); + + if let Some(collapsed) = self.try_beta_reduce_with_sub( + &closure.parameter_node, + &args, + inlined_body, + &mut closure_sub, + ) { + return collapsed; + } + } + + if let Some(folded) = self.try_fold_pure(&callee, &args) { + return folded; + } + } + + if self.enabled + && let BoundKind::Constant(Value::Object(ref obj)) = callee.kind + && sub.inlining_depth < 5 + && let Some(closure) = obj.as_any().downcast_ref::() + && (closure.upvalues.is_empty() || self.is_pure(&closure.function_node)) + { + let mut closure_sub = SubstitutionMap::new(); + closure_sub.inlining_depth = sub.inlining_depth + 1; + for (i, cell) in closure.upvalues.iter().enumerate() { + closure_sub.add_upvalue(i as u32, cell.borrow().clone()); + } + let inlined_body = + self.visit_node((*closure.function_node).clone(), &mut closure_sub); + + let cracked_lambda = Node { + identity: callee.identity.clone(), + ty: callee.ty.clone(), + kind: BoundKind::Lambda { + params: closure.parameter_node.clone(), + upvalues: vec![], + body: Rc::new(inlined_body), + positional_count: closure.positional_count, + }, + }; + return Node { + identity: node.identity, + kind: BoundKind::Call { + callee: Box::new(cracked_lambda), + args: Box::new(args), + }, + ty: node.ty, + }; + } + + ( + BoundKind::Call { + callee: Box::new(callee), + args: Box::new(args), + }, + node.ty, + ) + } + + BoundKind::If { + cond, + then_br, + else_br, + } => { + let cond = self.visit_node(*cond, sub); + if self.enabled + && let BoundKind::Constant(ref val) = cond.kind + { + if val.is_truthy() { + return self.visit_node(*then_br, sub); + } else if let Some(else_node) = else_br { + return self.visit_node(*else_node, sub); + } else { + return Node { + identity: node.identity, + kind: BoundKind::Nop, + ty: StaticType::Void, + }; + } + } + + let then_br = Box::new(self.visit_node(*then_br, sub)); + let else_br = else_br.map(|e| Box::new(self.visit_node(*e, sub))); + ( + BoundKind::If { + cond: Box::new(cond), + then_br, + else_br, + }, + node.ty, + ) + } + + BoundKind::Block { exprs } => { + let mut used_in_block = HashSet::new(); + let mut used_globals_in_block = HashSet::new(); + let mut assigned_locals_in_block = HashSet::new(); + let mut assigned_globals_in_block = HashSet::new(); + + if !exprs.is_empty() { + for e in &exprs { + self.collect_usage( + e, + &mut used_in_block, + &mut used_globals_in_block, + &mut assigned_locals_in_block, + &mut assigned_globals_in_block, + ); + } + if let Some(last) = exprs.last() { + match &last.kind { + BoundKind::Get { + addr: Address::Local(slot), + .. + } => { + used_in_block.insert(*slot); + } + BoundKind::Get { + addr: Address::Global(idx), + .. + } => { + used_globals_in_block.insert(*idx); + } + _ => {} + } + } + } + + // IMPORTANT: Propagate block assignments to the outer map so we don't + // incorrectly inline these variables in the rest of the script. + for slot in &assigned_locals_in_block { + sub.assigned_locals.insert(*slot); + } + for idx in &assigned_globals_in_block { + sub.assigned_globals.insert(*idx); + } + + let mut new_exprs = Vec::with_capacity(exprs.len()); + let last_idx = exprs.len().saturating_sub(1); + + for (i, e) in exprs.into_iter().enumerate() { + let is_last = i == last_idx; + + if self.enabled && !is_last { + let removable = match &e.kind { + BoundKind::DefLocal { slot, value, .. } => { + !used_in_block.contains(slot) + && !sub.captured_slots.contains(slot) + && self.is_pure(value) + } + BoundKind::DefGlobal { + global_index, + value, + .. + } => { + !used_globals_in_block.contains(global_index) + && (self.is_pure(value) + || matches!(value.kind, BoundKind::Lambda { .. })) + } + BoundKind::Set { + addr: Address::Local(slot), + value, + .. + } => { + !used_in_block.contains(slot) + && !sub.captured_slots.contains(slot) + && self.is_pure(value) + } + _ => self.is_pure(&e), + }; + + if removable { + continue; + } + } + + let opt = self.visit_node(e, sub); + if self.enabled && matches!(opt.kind, BoundKind::Nop) && !is_last { + continue; + } + new_exprs.push(opt); + } + + if self.enabled { + if new_exprs.is_empty() { + return Node { + identity: node.identity, + kind: BoundKind::Nop, + ty: StaticType::Void, + }; + } else if new_exprs.len() == 1 { + return new_exprs.pop().unwrap(); + } + } + + let ty = if new_exprs.is_empty() { + StaticType::Void + } else { + new_exprs.last().unwrap().ty.clone() + }; + (BoundKind::Block { exprs: new_exprs }, ty) + } + + BoundKind::Lambda { .. } => { + let (params, original_upvalues, body, positional_count) = + if let BoundKind::Lambda { + params, + upvalues, + body, + positional_count, + } = &node.kind + { + (params, upvalues, body, positional_count) + } else { + unreachable!() + }; + + let mut used_in_lambda = HashSet::new(); + let mut used_globals_in_lambda = HashSet::new(); + let mut assigned_locals_in_lambda = HashSet::new(); + let mut assigned_globals_in_lambda = HashSet::new(); + + self.collect_usage( + &node, + &mut used_in_lambda, + &mut used_globals_in_lambda, + &mut assigned_locals_in_lambda, + &mut assigned_globals_in_lambda, + ); + + // Track captures from outer scope that are assigned in this lambda + for idx in &assigned_globals_in_lambda { + sub.assigned_globals.insert(*idx); + } + + let mut new_upvalues = Vec::new(); + let mut mapping = Vec::new(); + let mut next_inner_subs = SubstitutionMap::new(); + next_inner_subs.inlining_depth = sub.inlining_depth; + next_inner_subs.assigned_locals = assigned_locals_in_lambda; + next_inner_subs.assigned_globals = assigned_globals_in_lambda; + + for (old_idx, capture_addr) in original_upvalues.iter().enumerate() { + let mut inlined_val = None; + match capture_addr { + Address::Local(slot) => { + if let Some(val) = sub.locals.get(slot) { + inlined_val = Some(val.clone()); + } + sub.captured_slots.insert(*slot); + } + Address::Upvalue(idx) => { + if let Some(val) = sub.upvalues.get(idx) { + inlined_val = Some(val.clone()); + } + } + _ => {} + } + + if let Some(val) = inlined_val { + next_inner_subs.add_upvalue(old_idx as u32, val); + mapping.push(None); + } else { + mapping.push(Some(new_upvalues.len() as u32)); + let new_addr = if let Address::Local(parent_slot) = capture_addr { + Address::Local(sub.map_slot(*parent_slot)) + } else { + *capture_addr + }; + new_upvalues.push(new_addr); + } + } + + // 1. Visit parameters to determine their new slots + let params_node = self.visit_node(params.as_ref().clone(), &mut next_inner_subs); + + // 2. IMPORTANT: Pre-register parameter slots in the inner substitution map + // so they are mapped 1:1 and don't get reassigned to higher slots in the body. + self.collect_parameter_slots(¶ms_node, &mut next_inner_subs); + + let body_node = self.visit_node(body.as_ref().clone(), &mut next_inner_subs); + let reindexed_body = if new_upvalues.len() != original_upvalues.len() { + sub.reindex_upvalues(body_node, &mapping) + } else { + body_node + }; + + ( + BoundKind::Lambda { + params: Rc::new(params_node), + upvalues: new_upvalues, + body: Rc::new(reindexed_body), + positional_count: *positional_count, + }, + node.ty, + ) + } + + BoundKind::DefLocal { + name, + slot, + value, + captured_by, + } => { + if !captured_by.is_empty() { + sub.captured_slots.insert(slot); + } + let value = Box::new(self.visit_node(*value, sub)); + if let BoundKind::Constant(val) = &value.kind { + sub.add_local(slot, val.clone()); + } else { + sub.locals.remove(&slot); + } + + if self.is_pure(&value) { + sub.pure_slots.insert(slot); + } else { + sub.pure_slots.remove(&slot); + } + + let new_slot = sub.map_slot(slot); + ( + BoundKind::DefLocal { + name, + slot: new_slot, + value, + captured_by, + }, + node.ty, + ) + } + + BoundKind::DefGlobal { + name, + global_index, + value, + } => { + let value = Box::new(self.visit_node(*value, sub)); + if let BoundKind::Constant(val) = &value.kind { + sub.add_global(global_index, val.clone()); + } + if self.is_pure(&value) + && let Some(purity_rc) = &self.global_purity + { + purity_rc.borrow_mut().insert(global_index, true); + } + ( + BoundKind::DefGlobal { + name, + global_index, + value, + }, + node.ty, + ) + } + + BoundKind::Tuple { elements } => { + let elements = elements + .into_iter() + .map(|e| self.visit_node(e, sub)) + .collect(); + (BoundKind::Tuple { elements }, node.ty) + } + BoundKind::Record { fields } => { + let fields = fields + .into_iter() + .map(|(k, v)| (self.visit_node(k, sub), self.visit_node(v, sub))) + .collect(); + (BoundKind::Record { fields }, node.ty) + } + BoundKind::Expansion { + original_call, + bound_expanded, + } => { + sub.inlining_depth += 1; + let bound_expanded = Box::new(self.visit_node(*bound_expanded, sub)); + sub.inlining_depth -= 1; + ( + BoundKind::Expansion { + original_call, + bound_expanded, + }, + node.ty, + ) + } + k => (k, node.ty), + }; + + Node { + identity: node.identity, + kind: new_kind, + ty: new_ty, + } + } + + fn collect_parameter_slots(&self, node: &TypedNode, sub: &mut SubstitutionMap) { + match &node.kind { + BoundKind::Parameter { slot, .. } => { + // Identity mapping for parameters to keep them in their reserved frame slots + sub.slot_mapping.insert(*slot, *slot); + sub.next_slot = sub.next_slot.max(*slot + 1); + } + BoundKind::Tuple { elements } => { + for el in elements { + self.collect_parameter_slots(el, sub); + } + } + _ => {} + } + } + + fn is_inlinable_value(&self, val: &Value) -> bool { + match val { + Value::Int(_) + | Value::Float(_) + | Value::Bool(_) + | Value::Text(_) + | Value::Keyword(_) + | Value::DateTime(_) => true, + Value::Object(obj) => { + if let Some(closure) = obj.as_any().downcast_ref::() { + closure.upvalues.is_empty() + } else { + false + } + } + _ => false, + } + } + + fn is_pure(&self, node: &TypedNode) -> bool { + match &node.kind { + BoundKind::Constant(_) | BoundKind::Parameter { .. } | BoundKind::Nop => true, + BoundKind::Lambda { body, .. } => self.is_pure(body), + BoundKind::Get { addr, .. } => match addr { + Address::Global(idx) => { + if let Some(purity_rc) = &self.global_purity { + *purity_rc.borrow().get(idx).unwrap_or(&false) + } else { + false + } + } + _ => true, + }, + BoundKind::Tuple { elements } => elements.iter().all(|e| self.is_pure(e)), + BoundKind::Record { fields } => fields + .iter() + .all(|(k, v)| self.is_pure(k) && self.is_pure(v)), + BoundKind::If { + cond, + then_br, + else_br, + } => { + self.is_pure(cond) + && self.is_pure(then_br) + && else_br.as_ref().is_none_or(|e| self.is_pure(e)) + } + BoundKind::Block { exprs } => exprs.iter().all(|e| self.is_pure(e)), + BoundKind::Expansion { bound_expanded, .. } => self.is_pure(bound_expanded), + BoundKind::DefLocal { value, .. } => self.is_pure(value), + BoundKind::Set { .. } => false, + BoundKind::Call { callee, args } => { + let callee_pure = match &callee.kind { + BoundKind::Get { + addr: Address::Global(idx), + .. + } => { + if let Some(purity_rc) = &self.global_purity { + *purity_rc.borrow().get(idx).unwrap_or(&false) + } else { + false + } + } + BoundKind::Constant(Value::Function(_)) => true, + BoundKind::Constant(Value::Object(obj)) => { + if let Some(closure) = obj.as_any().downcast_ref::() { + self.is_pure(&closure.function_node) + } else { + false + } + } + _ => false, + }; + callee_pure && self.is_pure(args) + } + _ => false, + } + } + + fn try_fold_pure(&self, callee: &TypedNode, args: &TypedNode) -> Option { + let temp_call = Node { + identity: callee.identity.clone(), + ty: StaticType::Any, + kind: BoundKind::Call { + callee: Box::new(callee.clone()), + args: Box::new(args.clone()), + }, + }; + if !self.is_pure(&temp_call) { + return None; + } + let mut arg_nodes = Vec::new(); + self.flatten_typed_tuple(args, &mut arg_nodes); + let mut arg_values = Vec::with_capacity(arg_nodes.len()); + for node in arg_nodes { + if let BoundKind::Constant(val) = node.kind { + arg_values.push(val); + } else { + return None; + } + } + let func_val = match &callee.kind { + BoundKind::Get { + addr: Address::Global(idx), + .. + } => self.globals.as_ref()?.borrow().get(*idx as usize)?.clone(), + BoundKind::Constant(val) => val.clone(), + _ => return None, + }; + let result = match func_val { + Value::Function(f) => f(arg_values), + _ => return None, + }; + Some(Node { + identity: callee.identity.clone(), + ty: result.static_type(), + kind: BoundKind::Constant(result), + }) + } + + fn try_beta_reduce( + &self, + params: &TypedNode, + args: &TypedNode, + body: TypedNode, + ) -> Option { + let mut sub = SubstitutionMap::new(); + self.try_beta_reduce_with_sub(params, args, body, &mut sub) + } + + fn try_beta_reduce_with_sub( + &self, + params: &TypedNode, + args: &TypedNode, + body: TypedNode, + sub: &mut SubstitutionMap, + ) -> Option { + let mut arg_vals = Vec::new(); + self.flatten_typed_tuple(args, &mut arg_vals); + + let mut slot_index = 0; + self.map_params_to_args(params, &arg_vals, &mut slot_index, sub); + + // Safety: Only proceed if we mapped EXACTLY the number of arguments provided. + // And if we actually have parameters to bind (or if it's a 0-arg call). + if slot_index != arg_vals.len() { + return None; + } + if sub.locals.is_empty() + && sub.ast_locals.is_empty() + && sub.upvalues.is_empty() + && !arg_vals.is_empty() + { + return None; + } + + Some(self.visit_node(body, sub)) + } + + fn flatten_typed_tuple(&self, node: &TypedNode, into: &mut Vec) { + if let BoundKind::Tuple { elements } = &node.kind { + for el in elements { + self.flatten_typed_tuple(el, into); + } + } else if !matches!(node.kind, BoundKind::Nop) { + into.push(node.clone()); + } + } + + fn map_params_to_args( + &self, + pattern: &TypedNode, + args: &[TypedNode], + offset: &mut usize, + sub: &mut SubstitutionMap, + ) { + match &pattern.kind { + BoundKind::Parameter { slot, .. } => { + if let Some(arg) = args.get(*offset) { + if let BoundKind::Constant(val) = &arg.kind { + sub.add_local(*slot, val.clone()); + } else if let BoundKind::Lambda { upvalues, .. } = &arg.kind { + if upvalues.is_empty() { + sub.add_ast_local(*slot, arg.clone()); + } + } else if let BoundKind::Get { + addr: Address::Global(_), + .. + } = &arg.kind + { + sub.add_ast_local(*slot, arg.clone()); + } + } + // Parameters in beta-reduction are also identity mapped if not inlined + sub.slot_mapping.insert(*slot, *slot); + sub.next_slot = sub.next_slot.max(*slot + 1); + *offset += 1; + } + BoundKind::Tuple { elements } => { + for el in elements { + self.map_params_to_args(el, args, offset, sub); + } + } + _ => {} + } + } + + fn collect_usage( + &self, + node: &TypedNode, + used_locals: &mut HashSet, + used_globals: &mut HashSet, + assigned_locals: &mut HashSet, + assigned_globals: &mut HashSet, + ) { + match &node.kind { + BoundKind::Constant(v) => { + if let Value::Object(obj) = v + && let Some(closure) = obj.as_any().downcast_ref::() + { + self.collect_usage( + &closure.function_node, + used_locals, + used_globals, + assigned_locals, + assigned_globals, + ); + } + } + BoundKind::Get { addr, .. } => match addr { + Address::Local(slot) => { + used_locals.insert(*slot); + } + Address::Global(idx) => { + used_globals.insert(*idx); + } + _ => {} + }, + BoundKind::Set { addr, value } => { + match addr { + Address::Local(slot) => { + assigned_locals.insert(*slot); + } + Address::Global(idx) => { + assigned_globals.insert(*idx); + } + _ => {} + } + self.collect_usage( + value, + used_locals, + used_globals, + assigned_locals, + assigned_globals, + ); + } + BoundKind::Lambda { + params, + body, + upvalues, + .. + } => { + self.collect_usage( + params, + used_locals, + used_globals, + assigned_locals, + assigned_globals, + ); + self.collect_usage( + body, + used_locals, + used_globals, + assigned_locals, + assigned_globals, + ); + for addr in upvalues { + match addr { + Address::Local(slot) => { + used_locals.insert(*slot); + } + Address::Global(idx) => { + used_globals.insert(*idx); + } + _ => {} + } + } + } + BoundKind::Block { exprs } => { + for e in exprs { + self.collect_usage( + e, + used_locals, + used_globals, + assigned_locals, + assigned_globals, + ); + } + } + BoundKind::If { + cond, + then_br, + else_br, + } => { + self.collect_usage( + cond, + used_locals, + used_globals, + assigned_locals, + assigned_globals, + ); + self.collect_usage( + then_br, + used_locals, + used_globals, + assigned_locals, + assigned_globals, + ); + if let Some(e) = else_br { + self.collect_usage( + e, + used_locals, + used_globals, + assigned_locals, + assigned_globals, + ); + } + } + BoundKind::Call { callee, args } => { + self.collect_usage( + callee, + used_locals, + used_globals, + assigned_locals, + assigned_globals, + ); + self.collect_usage( + args, + used_locals, + used_globals, + assigned_locals, + assigned_globals, + ); + } + BoundKind::Tuple { elements } => { + for e in elements { + self.collect_usage( + e, + used_locals, + used_globals, + assigned_locals, + assigned_globals, + ); + } + } + BoundKind::Record { fields } => { + for (k, v) in fields { + self.collect_usage( + k, + used_locals, + used_globals, + assigned_locals, + assigned_globals, + ); + self.collect_usage( + v, + used_locals, + used_globals, + assigned_locals, + assigned_globals, + ); + } + } + BoundKind::DefLocal { value, .. } | BoundKind::DefGlobal { value, .. } => { + self.collect_usage( + value, + used_locals, + used_globals, + assigned_locals, + assigned_globals, + ); + } + BoundKind::Expansion { bound_expanded, .. } => { + self.collect_usage( + bound_expanded, + used_locals, + used_globals, + assigned_locals, + assigned_globals, + ); + } + _ => {} + } + } +} + +/// Helper for transitively inlining values and cleaning up capture lists. +struct SubstitutionMap { + locals: HashMap, + upvalues: HashMap, + globals: HashMap, + ast_locals: HashMap, + slot_mapping: HashMap, + inlining_stack: HashSet, + inlining_depth: usize, + assigned_locals: HashSet, + assigned_globals: HashSet, + next_slot: u32, + used_slots: HashSet, + pure_slots: HashSet, + used_globals: HashSet, + captured_slots: HashSet, +} + +impl SubstitutionMap { + fn new() -> Self { + Self { + locals: HashMap::new(), + upvalues: HashMap::new(), + globals: HashMap::new(), + ast_locals: HashMap::new(), + slot_mapping: HashMap::new(), + inlining_stack: HashSet::new(), + inlining_depth: 0, + assigned_locals: HashSet::new(), + assigned_globals: HashSet::new(), + next_slot: 0, + used_slots: HashSet::new(), + pure_slots: HashSet::new(), + used_globals: HashSet::new(), + captured_slots: HashSet::new(), + } + } + + fn add_ast_local(&mut self, slot: u32, node: TypedNode) { + self.ast_locals.insert(slot, node); + } + + fn map_slot(&mut self, old_slot: u32) -> u32 { + if let Some(&new_slot) = self.slot_mapping.get(&old_slot) { + return new_slot; + } + let new_slot = self.next_slot; + self.slot_mapping.insert(old_slot, new_slot); + self.next_slot += 1; + new_slot + } + + fn add_local(&mut self, slot: u32, val: Value) { + self.locals.insert(slot, val); + } + + fn add_upvalue(&mut self, idx: u32, val: Value) { + self.upvalues.insert(idx, val); + } + + fn add_global(&mut self, idx: u32, val: Value) { + self.globals.insert(idx, val); + } + + fn reindex_upvalues(&self, node: TypedNode, mapping: &[Option]) -> TypedNode { + let (new_kind, new_ty) = match node.kind { + BoundKind::Get { + addr: Address::Upvalue(idx), + name, + } => { + if let Some(res) = mapping.get(idx as usize) { + match res { + Some(new_idx) => ( + BoundKind::Get { + addr: Address::Upvalue(*new_idx), + name, + }, + node.ty, + ), + None => ( + BoundKind::Get { + addr: Address::Upvalue(idx), + name, + }, + node.ty, + ), + } + } else { + ( + BoundKind::Get { + addr: Address::Upvalue(idx), + name, + }, + node.ty, + ) + } + } + BoundKind::Lambda { + params, + upvalues, + body, + positional_count, + } => { + let mut next_upvalues = Vec::new(); + for addr in upvalues { + if let Address::Upvalue(idx) = addr + && let Some(res) = mapping.get(idx as usize) + { + if let Some(new_idx) = res { + next_upvalues.push(Address::Upvalue(*new_idx)); + } + continue; + } + next_upvalues.push(addr); + } + ( + BoundKind::Lambda { + params, + upvalues: next_upvalues, + body, + positional_count, + }, + node.ty, + ) + } + BoundKind::If { + cond, + then_br, + else_br, + } => { + let cond = Box::new(self.reindex_upvalues(*cond, mapping)); + let then_br = Box::new(self.reindex_upvalues(*then_br, mapping)); + let else_br = else_br.map(|e| Box::new(self.reindex_upvalues(*e, mapping))); + ( + BoundKind::If { + cond, + then_br, + else_br, + }, + node.ty, + ) + } + BoundKind::Block { exprs } => { + let exprs = exprs + .into_iter() + .map(|e| self.reindex_upvalues(e, mapping)) + .collect(); + (BoundKind::Block { exprs }, node.ty) + } + BoundKind::Call { callee, args } => { + let callee = Box::new(self.reindex_upvalues(*callee, mapping)); + let args = Box::new(self.reindex_upvalues(*args, mapping)); + (BoundKind::Call { callee, args }, node.ty) + } + BoundKind::DefLocal { + name, + slot, + value, + captured_by, + } => { + let value = Box::new(self.reindex_upvalues(*value, mapping)); + ( + BoundKind::DefLocal { + name, + slot, + value, + captured_by, + }, + node.ty, + ) + } + BoundKind::DefGlobal { + name, + global_index, + value, + } => { + let value = Box::new(self.reindex_upvalues(*value, mapping)); + ( + BoundKind::DefGlobal { + name, + global_index, + value, + }, + node.ty, + ) + } + BoundKind::Set { addr, value } => { + let value = Box::new(self.reindex_upvalues(*value, mapping)); + (BoundKind::Set { addr, value }, node.ty) + } + BoundKind::Tuple { elements } => { + let elements = elements + .into_iter() + .map(|e| self.reindex_upvalues(e, mapping)) + .collect(); + (BoundKind::Tuple { elements }, node.ty) + } + BoundKind::Record { fields } => { + let fields = fields + .into_iter() + .map(|(k, v)| { + ( + self.reindex_upvalues(k, mapping), + self.reindex_upvalues(v, mapping), + ) + }) + .collect(); + (BoundKind::Record { fields }, node.ty) + } + k => (k, node.ty), + }; + Node { + identity: node.identity, + kind: new_kind, + ty: new_ty, + } + } +} + +#[cfg(test)] +mod tests { + use crate::ast::environment::Environment; + + fn get_optimized_dump(source: &str, enabled: bool) -> String { + let mut env = Environment::new(); + env.optimization = enabled; + env.dump_ast(source) + .expect("Compilation failed during test") + } + + #[test] + fn test_opt_folding() { + let dump = get_optimized_dump("(+ 10 20)", true); + insta::assert_snapshot!(dump); + } + + #[test] + fn test_opt_folding_complex() { + let dump_float = get_optimized_dump("(+ 1.5 2.5)", true); + assert!(dump_float.contains("Constant: 4")); + let dump_text = get_optimized_dump("(+ \"hello \" \"world\")", true); + assert!(dump_text.contains("Constant: \"hello world\"")); + let dump_date = get_optimized_dump("(date \"2023-01-01\")", true); + assert!(dump_date.contains("Constant: #2023-01-01 00:00:00#")); + let dump_cmp = get_optimized_dump("(> 10 5)", true); + assert!(dump_cmp.contains("Constant: true")); + } + + #[test] + fn test_opt_local_inlining() { + let source = "(fn [] (do (def x 10) (+ x 5)))"; + let dump = get_optimized_dump(source, true); + insta::assert_snapshot!(dump); + } + + #[test] + fn test_opt_dce_unused() { + let source = "(fn [] (do (def x 10) 42))"; + let dump = get_optimized_dump(source, true); + insta::assert_snapshot!(dump); + } + + #[test] + fn test_opt_assignment_safety() { + let source = "(fn [] (do (def x 10) (assign x 20) x))"; + let dump = get_optimized_dump(source, true); + insta::assert_snapshot!(dump); + } + + #[test] + fn test_opt_lambda_cracking() { + let source = "(fn [] (do (def x 10) (fn [] x)))"; + let dump = get_optimized_dump(source, true); + insta::assert_snapshot!(dump); + } + + #[test] + fn test_opt_global_unused_dce() { + let source = "(do (def f (fn [x] x)) 42)"; + let dump = get_optimized_dump(source, true); + assert!( + !dump.contains("DefGlobal"), + "Unused global helper 'f' should be optimized away. Dump: \n{}", + dump + ); + assert!(dump.contains("Constant: 42")); + } +} diff --git a/src/ast/compiler/specializer.rs b/src/ast/compiler/specializer.rs index 5138f72..79e8ce5 100644 --- a/src/ast/compiler/specializer.rs +++ b/src/ast/compiler/specializer.rs @@ -1,232 +1,313 @@ -use std::collections::HashMap; -use std::rc::Rc; -use std::cell::RefCell; -use crate::ast::types::{StaticType, Value, Signature}; -use crate::ast::compiler::bound_nodes::{BoundKind, Address, TypedNode, BoundNode}; -use crate::ast::nodes::Node; - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct MonoCacheKey { - pub address: Address, - pub arg_types: Vec, -} - -pub type CompileFunc = Rc Result<(Value, StaticType), String>>; -pub type RtlLookupFunc = Rc Option<(Value, StaticType)>>; - -pub trait FunctionRegistry { - fn resolve(&self, addr: Address) -> Option; -} - -pub type MonoCache = HashMap; - -pub struct Specializer { - pub cache: Rc>, - registry: Option>, - compiler: Option, - rtl_lookup: Option, -} - -impl Specializer { - pub fn new( - registry: Option>, - compiler: Option, - rtl_lookup: Option, - cache: Option>>, - ) -> Self { - Self { - cache: cache.unwrap_or_else(|| Rc::new(RefCell::new(HashMap::new()))), - registry, - compiler, - rtl_lookup, - } - } - - pub fn specialize(&self, node: TypedNode) -> TypedNode { - self.visit_node(node) - } - - fn visit_node(&self, node: TypedNode) -> TypedNode { - let (new_kind, new_ty) = match node.kind { - BoundKind::Call { callee, args } => { - let (new_callee, new_args, ret_ty) = self.specialize_call_logic(*callee, *args, node.ty.clone()); - (BoundKind::Call { callee: Box::new(new_callee), args: Box::new(new_args) }, ret_ty) - }, - - // Recursive traversal for other nodes - BoundKind::If { cond, then_br, else_br } => { - let cond = Box::new(self.visit_node(*cond)); - let then_br = Box::new(self.visit_node(*then_br)); - let else_br = else_br.map(|e| Box::new(self.visit_node(*e))); - (BoundKind::If { cond, then_br, else_br }, node.ty) - }, - BoundKind::Block { exprs } => { - let exprs = exprs.into_iter().map(|e| self.visit_node(e)).collect(); - (BoundKind::Block { exprs }, node.ty) - }, - BoundKind::Lambda { params, upvalues, body, positional_count } => { - let params = Rc::new(self.visit_node(params.as_ref().clone())); - let body = Rc::new(self.visit_node((*body).clone())); - (BoundKind::Lambda { params, upvalues, body, positional_count }, node.ty) - }, - BoundKind::DefLocal { name, slot, value, captured_by } => { - let value = Box::new(self.visit_node(*value)); - (BoundKind::DefLocal { name, slot, value, captured_by }, node.ty) - }, - BoundKind::DefGlobal { name, global_index, value } => { - let value = Box::new(self.visit_node(*value)); - (BoundKind::DefGlobal { name, global_index, value }, node.ty) - }, - BoundKind::Set { addr, value } => { - let value = Box::new(self.visit_node(*value)); - (BoundKind::Set { addr, value }, node.ty) - }, - BoundKind::Tuple { elements } => { - let elements = elements.into_iter().map(|e| self.visit_node(e)).collect(); - (BoundKind::Tuple { elements }, node.ty) - }, - BoundKind::Record { fields } => { - let fields = fields.into_iter().map(|(k, v)| (self.visit_node(k), self.visit_node(v))).collect(); - (BoundKind::Record { fields }, node.ty) - }, - BoundKind::Expansion { original_call, bound_expanded } => { - let bound_expanded = Box::new(self.visit_node(*bound_expanded)); - (BoundKind::Expansion { original_call, bound_expanded }, node.ty) - }, - - // Leaf nodes or uninteresting nodes - k => (k, node.ty), - }; - - Node { - identity: node.identity, - kind: new_kind, - ty: new_ty, - } - } - - fn specialize_call_logic(&self, callee: TypedNode, args: TypedNode, original_ty: StaticType) -> (TypedNode, TypedNode, StaticType) { - // 1. Specialize children first - let new_callee = self.visit_node(callee); - let new_args = self.visit_node(args); - - // 2. Check if this call is a candidate (Callee is Get(Address)) - let address = if let BoundKind::Get { addr, .. } = &new_callee.kind { - *addr - } else { - // Not a direct call to a named function/variable - return (new_callee, new_args, original_ty); - }; - - // 3. Check if all argument types are statically known - let arg_types: Vec = if let StaticType::Tuple(elements) = &new_args.ty { - elements.clone() - } else { - vec![new_args.ty.clone()] - }; - - if arg_types.iter().any(|t| matches!(t, StaticType::Any)) { - // Cannot specialize with unknown types - return (new_callee, new_args, original_ty); - } - - // --- Optimization Candidate --- - let key = MonoCacheKey { address, arg_types: arg_types.clone() }; - - // 4. Check Cache - if let Some((val, ret_ty)) = self.cache.borrow().get(&key) { - // Cache Hit! Replace Callee with Constant(Function) - let specialized_callee = Node { - identity: new_callee.identity.clone(), - kind: BoundKind::Constant(val.clone()), - ty: StaticType::Function(Box::new(Signature { - params: StaticType::Tuple(arg_types), - ret: ret_ty.clone(), - })), - }; - return (specialized_callee, new_args, ret_ty.clone()); - } - - // 5. Check RTL (Host Functions) - if let Some(rtl_lookup) = &self.rtl_lookup - && let BoundKind::Get { name, .. } = &new_callee.kind - && let Some((val, ret_ty)) = rtl_lookup(&name.name, &arg_types) - { - // Cache Hit (RTL) - self.cache.borrow_mut().insert(key.clone(), (val.clone(), ret_ty.clone())); - - let specialized_callee = Node { - identity: new_callee.identity.clone(), - kind: BoundKind::Constant(val.clone()), - ty: StaticType::Function(Box::new(Signature { - params: StaticType::Tuple(arg_types), - ret: ret_ty.clone(), - })), - }; - return (specialized_callee, new_args, ret_ty); - } - - // 6. Resolve Function Definition - if let Some(func_node) = self.registry.as_ref().and_then(|r| r.resolve(address)) { - // Check constraints (no closures with state) - if let BoundKind::Lambda { upvalues, .. } = &func_node.kind { - if !upvalues.is_empty() { - return (new_callee, new_args, original_ty); - } - } else { - return (new_callee, new_args, original_ty); - } - - // 7. Compile Specialization (User Code) - if let Some(compiler) = &self.compiler { - match compiler(func_node, &arg_types) { - Ok((compiled_val, ret_ty)) => { - let res_val: Value = compiled_val; - let res_ty: StaticType = ret_ty; - - // Store in cache - self.cache.borrow_mut().insert(key, (res_val.clone(), res_ty.clone())); - - // PERFORMANCE: Flatten the argument tuple to match the specialized signature. - let flat_elements = self.flatten_tuple(new_args.clone()); - let flat_types = flat_elements.iter().map(|e| e.ty.clone()).collect(); - let flattened_args = Node { - identity: new_args.identity.clone(), - kind: BoundKind::Tuple { elements: flat_elements }, - ty: StaticType::Tuple(flat_types), - }; - - let specialized_callee = Node { - identity: new_callee.identity.clone(), - kind: BoundKind::Constant(res_val), - ty: StaticType::Function(Box::new(Signature { - params: flattened_args.ty.clone(), - ret: res_ty.clone(), - })), - }; - return (specialized_callee, flattened_args, res_ty); - }, - Err(_) => { - // Fallback on error - } - } - } - } - - // Fallback: Dynamic Call - (new_callee, new_args, original_ty) - } - - fn flatten_tuple(&self, node: TypedNode) -> Vec { - match node.kind { - BoundKind::Tuple { elements } => { - let mut flat = Vec::new(); - for el in elements { - flat.extend(self.flatten_tuple(el)); - } - flat - } - _ => vec![node], - } - } -} +use crate::ast::compiler::bound_nodes::{Address, BoundKind, BoundNode, TypedNode}; +use crate::ast::nodes::Node; +use crate::ast::types::{Signature, StaticType, Value}; +use std::cell::RefCell; +use std::collections::HashMap; +use std::rc::Rc; + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct MonoCacheKey { + pub address: Address, + pub arg_types: Vec, +} + +pub type CompileFunc = Rc Result<(Value, StaticType), String>>; +pub type RtlLookupFunc = Rc Option<(Value, StaticType)>>; + +pub trait FunctionRegistry { + fn resolve(&self, addr: Address) -> Option; +} + +pub type MonoCache = HashMap; + +pub struct Specializer { + pub cache: Rc>, + registry: Option>, + compiler: Option, + rtl_lookup: Option, +} + +impl Specializer { + pub fn new( + registry: Option>, + compiler: Option, + rtl_lookup: Option, + cache: Option>>, + ) -> Self { + Self { + cache: cache.unwrap_or_else(|| Rc::new(RefCell::new(HashMap::new()))), + registry, + compiler, + rtl_lookup, + } + } + + pub fn specialize(&self, node: TypedNode) -> TypedNode { + self.visit_node(node) + } + + fn visit_node(&self, node: TypedNode) -> TypedNode { + let (new_kind, new_ty) = match node.kind { + BoundKind::Call { callee, args } => { + let (new_callee, new_args, ret_ty) = + self.specialize_call_logic(*callee, *args, node.ty.clone()); + ( + BoundKind::Call { + callee: Box::new(new_callee), + args: Box::new(new_args), + }, + ret_ty, + ) + } + + // Recursive traversal for other nodes + BoundKind::If { + cond, + then_br, + else_br, + } => { + let cond = Box::new(self.visit_node(*cond)); + let then_br = Box::new(self.visit_node(*then_br)); + let else_br = else_br.map(|e| Box::new(self.visit_node(*e))); + ( + BoundKind::If { + cond, + then_br, + else_br, + }, + node.ty, + ) + } + BoundKind::Block { exprs } => { + let exprs = exprs.into_iter().map(|e| self.visit_node(e)).collect(); + (BoundKind::Block { exprs }, node.ty) + } + BoundKind::Lambda { + params, + upvalues, + body, + positional_count, + } => { + let params = Rc::new(self.visit_node(params.as_ref().clone())); + let body = Rc::new(self.visit_node((*body).clone())); + ( + BoundKind::Lambda { + params, + upvalues, + body, + positional_count, + }, + node.ty, + ) + } + BoundKind::DefLocal { + name, + slot, + value, + captured_by, + } => { + let value = Box::new(self.visit_node(*value)); + ( + BoundKind::DefLocal { + name, + slot, + value, + captured_by, + }, + node.ty, + ) + } + BoundKind::DefGlobal { + name, + global_index, + value, + } => { + let value = Box::new(self.visit_node(*value)); + ( + BoundKind::DefGlobal { + name, + global_index, + value, + }, + node.ty, + ) + } + BoundKind::Set { addr, value } => { + let value = Box::new(self.visit_node(*value)); + (BoundKind::Set { addr, value }, node.ty) + } + BoundKind::Tuple { elements } => { + let elements = elements.into_iter().map(|e| self.visit_node(e)).collect(); + (BoundKind::Tuple { elements }, node.ty) + } + BoundKind::Record { fields } => { + let fields = fields + .into_iter() + .map(|(k, v)| (self.visit_node(k), self.visit_node(v))) + .collect(); + (BoundKind::Record { fields }, node.ty) + } + BoundKind::Expansion { + original_call, + bound_expanded, + } => { + let bound_expanded = Box::new(self.visit_node(*bound_expanded)); + ( + BoundKind::Expansion { + original_call, + bound_expanded, + }, + node.ty, + ) + } + + // Leaf nodes or uninteresting nodes + k => (k, node.ty), + }; + + Node { + identity: node.identity, + kind: new_kind, + ty: new_ty, + } + } + + fn specialize_call_logic( + &self, + callee: TypedNode, + args: TypedNode, + original_ty: StaticType, + ) -> (TypedNode, TypedNode, StaticType) { + // 1. Specialize children first + let new_callee = self.visit_node(callee); + let new_args = self.visit_node(args); + + // 2. Check if this call is a candidate (Callee is Get(Address)) + let address = if let BoundKind::Get { addr, .. } = &new_callee.kind { + *addr + } else { + // Not a direct call to a named function/variable + return (new_callee, new_args, original_ty); + }; + + // 3. Check if all argument types are statically known + let arg_types: Vec = if let StaticType::Tuple(elements) = &new_args.ty { + elements.clone() + } else { + vec![new_args.ty.clone()] + }; + + if arg_types.iter().any(|t| matches!(t, StaticType::Any)) { + // Cannot specialize with unknown types + return (new_callee, new_args, original_ty); + } + + // --- Optimization Candidate --- + let key = MonoCacheKey { + address, + arg_types: arg_types.clone(), + }; + + // 4. Check Cache + if let Some((val, ret_ty)) = self.cache.borrow().get(&key) { + // Cache Hit! Replace Callee with Constant(Function) + let specialized_callee = Node { + identity: new_callee.identity.clone(), + kind: BoundKind::Constant(val.clone()), + ty: StaticType::Function(Box::new(Signature { + params: StaticType::Tuple(arg_types), + ret: ret_ty.clone(), + })), + }; + return (specialized_callee, new_args, ret_ty.clone()); + } + + // 5. Check RTL (Host Functions) + if let Some(rtl_lookup) = &self.rtl_lookup + && let BoundKind::Get { name, .. } = &new_callee.kind + && let Some((val, ret_ty)) = rtl_lookup(&name.name, &arg_types) + { + // Cache Hit (RTL) + self.cache + .borrow_mut() + .insert(key.clone(), (val.clone(), ret_ty.clone())); + + let specialized_callee = Node { + identity: new_callee.identity.clone(), + kind: BoundKind::Constant(val.clone()), + ty: StaticType::Function(Box::new(Signature { + params: StaticType::Tuple(arg_types), + ret: ret_ty.clone(), + })), + }; + return (specialized_callee, new_args, ret_ty); + } + + // 6. Resolve Function Definition + if let Some(func_node) = self.registry.as_ref().and_then(|r| r.resolve(address)) { + // Check constraints (no closures with state) + if let BoundKind::Lambda { upvalues, .. } = &func_node.kind { + if !upvalues.is_empty() { + return (new_callee, new_args, original_ty); + } + } else { + return (new_callee, new_args, original_ty); + } + + // 7. Compile Specialization (User Code) + if let Some(compiler) = &self.compiler { + match compiler(func_node, &arg_types) { + Ok((compiled_val, ret_ty)) => { + let res_val: Value = compiled_val; + let res_ty: StaticType = ret_ty; + + // Store in cache + self.cache + .borrow_mut() + .insert(key, (res_val.clone(), res_ty.clone())); + + // PERFORMANCE: Flatten the argument tuple to match the specialized signature. + let flat_elements = self.flatten_tuple(new_args.clone()); + let flat_types = flat_elements.iter().map(|e| e.ty.clone()).collect(); + let flattened_args = Node { + identity: new_args.identity.clone(), + kind: BoundKind::Tuple { + elements: flat_elements, + }, + ty: StaticType::Tuple(flat_types), + }; + + let specialized_callee = Node { + identity: new_callee.identity.clone(), + kind: BoundKind::Constant(res_val), + ty: StaticType::Function(Box::new(Signature { + params: flattened_args.ty.clone(), + ret: res_ty.clone(), + })), + }; + return (specialized_callee, flattened_args, res_ty); + } + Err(_) => { + // Fallback on error + } + } + } + } + + // Fallback: Dynamic Call + (new_callee, new_args, original_ty) + } + + fn flatten_tuple(&self, node: TypedNode) -> Vec { + match node.kind { + BoundKind::Tuple { elements } => { + let mut flat = Vec::new(); + for el in elements { + flat.extend(self.flatten_tuple(el)); + } + flat + } + _ => vec![node], + } + } +} diff --git a/src/ast/compiler/tco.rs b/src/ast/compiler/tco.rs index 5490b4e..a3a213b 100644 --- a/src/ast/compiler/tco.rs +++ b/src/ast/compiler/tco.rs @@ -1,131 +1,167 @@ -use std::rc::Rc; -use crate::ast::nodes::Node; -use crate::ast::compiler::bound_nodes::{BoundKind, TypedNode}; -use crate::ast::types::StaticType; - -use std::fmt::Debug; - -#[derive(Clone)] -pub struct RuntimeMetadata { - pub ty: StaticType, - pub is_tail: bool, - /// The original, high-level typed node. Perfect for Debuggers and Optimizers. - pub original: Rc, -} - -impl Debug for RuntimeMetadata { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Metadata") - .field("ty", &self.ty) - .field("is_tail", &self.is_tail) - .finish() - } -} - -/// The ExecNode is the AST used by the VM. It carries TCO flags and links to source. -pub type ExecNode = Node, RuntimeMetadata>; - -pub struct TCO; - -impl TCO { - /// Lowers a TypedNode (Compiler-AST) to an ExecNode (VM-AST) and marks tail positions. - pub fn optimize(node: TypedNode) -> ExecNode { - Self::transform(Rc::new(node), true) - } - - fn transform(node_rc: Rc, is_tail_position: bool) -> ExecNode { - let node = &*node_rc; - let new_kind = match &node.kind { - BoundKind::Call { callee, args } => { - BoundKind::Call { - callee: Box::new(Self::transform(Rc::new((**callee).clone()), false)), - args: Box::new(Self::transform(Rc::new((**args).clone()), false)), - } - }, - - BoundKind::If { cond, then_br, else_br } => { - BoundKind::If { - cond: Box::new(Self::transform(Rc::new((**cond).clone()), false)), - then_br: Box::new(Self::transform(Rc::new((**then_br).clone()), is_tail_position)), - else_br: else_br.as_ref().map(|e| Box::new(Self::transform(Rc::new((**e).clone()), is_tail_position))), - } - }, - - BoundKind::Block { exprs } => { - if exprs.is_empty() { - BoundKind::Block { exprs: vec![] } - } else { - let last_idx = exprs.len() - 1; - let mut new_exprs = Vec::with_capacity(exprs.len()); - - for (i, expr) in exprs.iter().enumerate() { - let is_last = i == last_idx; - new_exprs.push(Self::transform(Rc::new(expr.clone()), is_tail_position && is_last)); - } - BoundKind::Block { exprs: new_exprs } - } - }, - - BoundKind::Lambda { params, upvalues, body, positional_count } => { - BoundKind::Lambda { - params: Rc::new(Self::transform(params.clone(), false)), - upvalues: upvalues.clone(), - body: Rc::new(Self::transform(body.clone(), true)), - positional_count: *positional_count, - } - }, - - BoundKind::Set { addr, value } => { - BoundKind::Set { addr: *addr, value: Box::new(Self::transform(Rc::new((**value).clone()), false)) } - }, - BoundKind::DefLocal { name, slot, value, captured_by } => { - BoundKind::DefLocal { - name: name.clone(), - slot: *slot, - value: Box::new(Self::transform(Rc::new((**value).clone()), false)), - captured_by: captured_by.clone() - } - }, - BoundKind::DefGlobal { name, global_index, value } => { - BoundKind::DefGlobal { - name: name.clone(), - global_index: *global_index, - value: Box::new(Self::transform(Rc::new((**value).clone()), false)) - } - }, - BoundKind::Record { fields } => { - let new_fields = fields.iter().map(|(k, v)| { - (Self::transform(Rc::new(k.clone()), false), Self::transform(Rc::new(v.clone()), false)) - }).collect(); - BoundKind::Record { fields: new_fields } - }, - BoundKind::Tuple { elements } => { - let new_elements = elements.iter().map(|e| Self::transform(Rc::new(e.clone()), false)).collect(); - BoundKind::Tuple { elements: new_elements } - }, - BoundKind::Parameter { name, slot } => BoundKind::Parameter { name: name.clone(), slot: *slot }, - BoundKind::Constant(v) => BoundKind::Constant(v.clone()), - BoundKind::Get { addr, name } => BoundKind::Get { addr: *addr, name: name.clone() }, - BoundKind::Nop => BoundKind::Nop, - BoundKind::Expansion { original_call, bound_expanded } => { - BoundKind::Expansion { - original_call: original_call.clone(), - bound_expanded: Box::new(Self::transform(Rc::new((**bound_expanded).clone()), is_tail_position)) - } - } - BoundKind::Extension(_) => { - BoundKind::Nop - } - }; - - Node { - identity: node.identity.clone(), - kind: new_kind, - ty: RuntimeMetadata { - ty: node.ty.clone(), - is_tail: is_tail_position, - original: node_rc, - }, - } - } -} +use crate::ast::compiler::bound_nodes::{BoundKind, TypedNode}; +use crate::ast::nodes::Node; +use crate::ast::types::StaticType; +use std::rc::Rc; + +use std::fmt::Debug; + +#[derive(Clone)] +pub struct RuntimeMetadata { + pub ty: StaticType, + pub is_tail: bool, + /// The original, high-level typed node. Perfect for Debuggers and Optimizers. + pub original: Rc, +} + +impl Debug for RuntimeMetadata { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Metadata") + .field("ty", &self.ty) + .field("is_tail", &self.is_tail) + .finish() + } +} + +/// The ExecNode is the AST used by the VM. It carries TCO flags and links to source. +pub type ExecNode = Node, RuntimeMetadata>; + +pub struct TCO; + +impl TCO { + /// Lowers a TypedNode (Compiler-AST) to an ExecNode (VM-AST) and marks tail positions. + pub fn optimize(node: TypedNode) -> ExecNode { + Self::transform(Rc::new(node), true) + } + + fn transform(node_rc: Rc, is_tail_position: bool) -> ExecNode { + let node = &*node_rc; + let new_kind = match &node.kind { + BoundKind::Call { callee, args } => BoundKind::Call { + callee: Box::new(Self::transform(Rc::new((**callee).clone()), false)), + args: Box::new(Self::transform(Rc::new((**args).clone()), false)), + }, + + BoundKind::If { + cond, + then_br, + else_br, + } => BoundKind::If { + cond: Box::new(Self::transform(Rc::new((**cond).clone()), false)), + then_br: Box::new(Self::transform( + Rc::new((**then_br).clone()), + is_tail_position, + )), + else_br: else_br + .as_ref() + .map(|e| Box::new(Self::transform(Rc::new((**e).clone()), is_tail_position))), + }, + + BoundKind::Block { exprs } => { + if exprs.is_empty() { + BoundKind::Block { exprs: vec![] } + } else { + let last_idx = exprs.len() - 1; + let mut new_exprs = Vec::with_capacity(exprs.len()); + + for (i, expr) in exprs.iter().enumerate() { + let is_last = i == last_idx; + new_exprs.push(Self::transform( + Rc::new(expr.clone()), + is_tail_position && is_last, + )); + } + BoundKind::Block { exprs: new_exprs } + } + } + + BoundKind::Lambda { + params, + upvalues, + body, + positional_count, + } => BoundKind::Lambda { + params: Rc::new(Self::transform(params.clone(), false)), + upvalues: upvalues.clone(), + body: Rc::new(Self::transform(body.clone(), true)), + positional_count: *positional_count, + }, + + BoundKind::Set { addr, value } => BoundKind::Set { + addr: *addr, + value: Box::new(Self::transform(Rc::new((**value).clone()), false)), + }, + BoundKind::DefLocal { + name, + slot, + value, + captured_by, + } => BoundKind::DefLocal { + name: name.clone(), + slot: *slot, + value: Box::new(Self::transform(Rc::new((**value).clone()), false)), + captured_by: captured_by.clone(), + }, + BoundKind::DefGlobal { + name, + global_index, + value, + } => BoundKind::DefGlobal { + name: name.clone(), + global_index: *global_index, + value: Box::new(Self::transform(Rc::new((**value).clone()), false)), + }, + BoundKind::Record { fields } => { + let new_fields = fields + .iter() + .map(|(k, v)| { + ( + Self::transform(Rc::new(k.clone()), false), + Self::transform(Rc::new(v.clone()), false), + ) + }) + .collect(); + BoundKind::Record { fields: new_fields } + } + BoundKind::Tuple { elements } => { + let new_elements = elements + .iter() + .map(|e| Self::transform(Rc::new(e.clone()), false)) + .collect(); + BoundKind::Tuple { + elements: new_elements, + } + } + BoundKind::Parameter { name, slot } => BoundKind::Parameter { + name: name.clone(), + slot: *slot, + }, + BoundKind::Constant(v) => BoundKind::Constant(v.clone()), + BoundKind::Get { addr, name } => BoundKind::Get { + addr: *addr, + name: name.clone(), + }, + BoundKind::Nop => BoundKind::Nop, + BoundKind::Expansion { + original_call, + bound_expanded, + } => BoundKind::Expansion { + original_call: original_call.clone(), + bound_expanded: Box::new(Self::transform( + Rc::new((**bound_expanded).clone()), + is_tail_position, + )), + }, + BoundKind::Extension(_) => BoundKind::Nop, + }; + + Node { + identity: node.identity.clone(), + kind: new_kind, + ty: RuntimeMetadata { + ty: node.ty.clone(), + is_tail: is_tail_position, + original: node_rc, + }, + } + } +} diff --git a/src/ast/compiler/type_checker.rs b/src/ast/compiler/type_checker.rs index 134a6ea..1ec0568 100644 --- a/src/ast/compiler/type_checker.rs +++ b/src/ast/compiler/type_checker.rs @@ -1,491 +1,650 @@ -use std::collections::HashMap; -use std::rc::Rc; -use crate::ast::compiler::bound_nodes::{BoundKind, Address, BoundNode, TypedNode}; -use crate::ast::nodes::Node; -use crate::ast::types::StaticType; - -/// Manages the types of locals and upvalues during a single type-checking pass. -struct TypeContext<'a> { - _parent: Option<&'a TypeContext<'a>>, - /// Maps slot index -> Inferred Type - slots: Vec, - /// Types of captured variables (passed from outer scope) - upvalue_types: Vec, -} - -impl<'a> TypeContext<'a> { - fn new(slot_count: u32, upvalue_types: Vec, parent: Option<&'a TypeContext<'a>>) -> Self { - Self { - _parent: parent, - slots: vec![StaticType::Any; slot_count as usize], - upvalue_types, - } - } - - fn get_type(&self, addr: Address) -> StaticType { - match addr { - Address::Local(idx) => self.slots.get(idx as usize).cloned().unwrap_or(StaticType::Any), - Address::Global(_) => StaticType::Any, // Globals are dynamic for now - Address::Upvalue(idx) => self.upvalue_types.get(idx as usize).cloned().unwrap_or(StaticType::Any), - } - } - - fn set_local_type(&mut self, idx: u32, ty: StaticType) { - if let Some(slot) = self.slots.get_mut(idx as usize) { - *slot = ty; - } - } -} - -pub struct TypeChecker { - global_types: Rc>>, -} - -impl TypeChecker { - pub fn new(global_types: Rc>>) -> Self { - Self { global_types } - } - - pub fn check(&self, node: BoundNode, arg_types: &[StaticType]) -> Result { - match node.kind { - BoundKind::Lambda { params, upvalues, body, positional_count } => { - // 1. Determine types of captured variables (Root lambdas have none) - let mut upvalue_types = Vec::with_capacity(upvalues.len()); - for _ in &upvalues { - upvalue_types.push(StaticType::Any); - } - - // 2. Create the specialized context - let root_ctx = TypeContext::new(0, vec![], None); - let mut lambda_ctx = TypeContext::new(64, upvalue_types, Some(&root_ctx)); - - // 3. INJECT specialized argument types into slots - let arg_tuple_ty = if arg_types.is_empty() { - StaticType::Any // No specialized info - } else { - StaticType::Tuple(arg_types.to_vec()) - }; - - let params_typed = self.check_params(params.as_ref().clone(), &arg_tuple_ty, &mut lambda_ctx)?; - - // 4. Check body with the new types - let body_typed = self.check_node((*body).clone(), &mut lambda_ctx)?; - let ret_ty = body_typed.ty.clone(); - - // 5. Construct specialized function type - let final_params_ty = params_typed.ty.clone(); - - let fn_ty = StaticType::Function(Box::new(crate::ast::types::Signature { - params: final_params_ty, - ret: ret_ty, - })); - - Ok(Node { - identity: node.identity, - kind: BoundKind::Lambda { - params: Rc::new(params_typed), - upvalues, - body: Rc::new(body_typed), - positional_count, - }, - ty: fn_ty, - }) - } - _ => { - // Fallback: Wrap in implicit lambda - let virtual_lambda = BoundNode { - identity: node.identity.clone(), - kind: BoundKind::Lambda { - params: Rc::new(Node { - identity: node.identity.clone(), - kind: BoundKind::Tuple { elements: vec![] }, - ty: (), - }), - upvalues: vec![], - body: Rc::new(node), - positional_count: Some(0), - }, - ty: (), - }; - self.check(virtual_lambda, &[]) - } - } - } - - fn check_params(&self, node: BoundNode, specialized_ty: &StaticType, ctx: &mut TypeContext) -> Result { - let (kind, ty) = match node.kind { - BoundKind::Parameter { name, slot } => { - ctx.set_local_type(slot, specialized_ty.clone()); - (BoundKind::Parameter { name, slot }, specialized_ty.clone()) - } - BoundKind::Tuple { elements } => { - let mut typed_elements = Vec::new(); - let mut elem_types = Vec::new(); - - for (i, el) in elements.into_iter().enumerate() { - let sub_ty = match specialized_ty { - StaticType::Tuple(t) => t.get(i).cloned().unwrap_or(StaticType::Any), - StaticType::Vector(inner, _) => (**inner).clone(), - _ => StaticType::Any, - }; - let t = self.check_params(el, &sub_ty, ctx)?; - elem_types.push(t.ty.clone()); - typed_elements.push(t); - } - (BoundKind::Tuple { elements: typed_elements }, StaticType::Tuple(elem_types)) - } - _ => return Err("Invalid node in parameter list".to_string()), - }; - - Ok(Node { - identity: node.identity, - kind, - ty, - }) - } - - fn check_node(&self, node: BoundNode, ctx: &mut TypeContext) -> Result { - let (kind, ty) = match node.kind { - BoundKind::Nop => (BoundKind::Nop, StaticType::Void), - - BoundKind::Constant(v) => { - let ty = v.static_type(); - (BoundKind::Constant(v), ty) - }, - - BoundKind::Parameter { name, slot } => { - (BoundKind::Parameter { name, slot }, ctx.get_type(Address::Local(slot))) - }, - - BoundKind::Get { addr, name } => { - let ty = if let Address::Global(idx) = addr { - self.global_types.borrow().get(&idx).cloned().unwrap_or(StaticType::Any) - } else { - ctx.get_type(addr) - }; - (BoundKind::Get { addr, name }, ty) - }, - - BoundKind::Set { addr, value } => { - let val_typed = self.check_node(*value, ctx)?; - let ty = val_typed.ty.clone(); - - if let Address::Local(idx) = addr { - ctx.set_local_type(idx, ty.clone()); - } else if let Address::Global(idx) = addr { - self.global_types.borrow_mut().insert(idx, ty.clone()); - } - - (BoundKind::Set { addr, value: Box::new(val_typed) }, ty) - }, - - BoundKind::DefLocal { name, slot, value, captured_by } => { - let val_typed = self.check_node(*value, ctx)?; - let ty = val_typed.ty.clone(); - ctx.set_local_type(slot, ty.clone()); - - (BoundKind::DefLocal { name, slot, value: Box::new(val_typed), captured_by }, ty) - }, - - BoundKind::DefGlobal { name, global_index, value } => { - let val_typed = self.check_node(*value, ctx)?; - let ty = val_typed.ty.clone(); - self.global_types.borrow_mut().insert(global_index, ty.clone()); - - (BoundKind::DefGlobal { name, global_index, value: Box::new(val_typed) }, ty) - }, - - BoundKind::If { cond, then_br, else_br } => { - let cond_typed = self.check_node(*cond, ctx)?; - let then_typed = self.check_node(*then_br, ctx)?; - - 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)?; - // Basic type promotion: if types differ, fall back to Any - if et.ty != final_ty { - final_ty = StaticType::Any; - } - else_typed = Some(Box::new(et)); - } else { - // If without else returns Void or Optional(Then) - final_ty = StaticType::Any; // Delphi: MakeOptional(Then) - } - - (BoundKind::If { - cond: Box::new(cond_typed), - then_br: Box::new(then_typed), - else_br: else_typed - }, final_ty) - }, - - BoundKind::Block { exprs } => { - let mut typed_exprs = Vec::new(); - let mut last_ty = StaticType::Void; - - for e in exprs { - let t = self.check_node(e, ctx)?; - last_ty = t.ty.clone(); - typed_exprs.push(t); - } - - (BoundKind::Block { exprs: typed_exprs }, last_ty) - }, - - BoundKind::Lambda { params, upvalues, body, positional_count } => { - // 1. Determine types of captured variables - let mut upvalue_types = Vec::with_capacity(upvalues.len()); - for &addr in &upvalues { - upvalue_types.push(ctx.get_type(addr)); - } - - // 2. Create nested context for lambda body - let mut lambda_ctx = TypeContext::new(64, upvalue_types, Some(ctx)); - - // 3. Check parameters and body - let params_typed = self.check_params(params.as_ref().clone(), &StaticType::Any, &mut lambda_ctx)?; - let body_typed = self.check_node((*body).clone(), &mut lambda_ctx)?; - let ret_ty = body_typed.ty.clone(); - - // 4. Construct function type - let fn_ty = StaticType::Function(Box::new(crate::ast::types::Signature { - params: params_typed.ty.clone(), - ret: ret_ty, - })); - - (BoundKind::Lambda { - params: Rc::new(params_typed), - upvalues, - body: Rc::new(body_typed), - positional_count, - }, fn_ty) - }, - - BoundKind::Call { callee, args } => { - let callee_typed = self.check_node(*callee, ctx)?; - let args_typed = self.check_node(*args, ctx)?; - - let ret_ty = callee_typed.ty.resolve_call(&args_typed.ty).unwrap_or(StaticType::Any); - - (BoundKind::Call { - callee: Box::new(callee_typed), - args: Box::new(args_typed) - }, ret_ty) - }, - - BoundKind::Tuple { elements } => { - let mut typed_elements = Vec::new(); - for e in elements { - typed_elements.push(self.check_node(e, ctx)?); - } - - let ty = if typed_elements.is_empty() { - StaticType::Vector(Box::new(StaticType::Any), 0) - } else { - let first_ty = &typed_elements[0].ty; - let all_same = typed_elements.iter().all(|e| e.ty == *first_ty); - - if all_same { - match first_ty { - StaticType::Vector(inner, len) => { - StaticType::Matrix(inner.clone(), vec![typed_elements.len(), *len]) - }, - StaticType::Matrix(inner, shape) => { - let mut new_shape = vec![typed_elements.len()]; - new_shape.extend(shape); - StaticType::Matrix(inner.clone(), new_shape) - }, - _ => StaticType::Vector(Box::new(first_ty.clone()), typed_elements.len()) - } - } else { - StaticType::Tuple(typed_elements.iter().map(|e| e.ty.clone()).collect()) - } - }; - - (BoundKind::Tuple { elements: typed_elements }, ty) - }, - - BoundKind::Record { fields } => { - let mut typed_fields = Vec::with_capacity(fields.len()); - let mut fields_ty = Vec::with_capacity(fields.len()); - for (k, v) in fields { - let kt = self.check_node(k, ctx)?; - let vt = self.check_node(v, ctx)?; - - if let BoundKind::Constant(crate::ast::types::Value::Keyword(kw)) = &kt.kind { - fields_ty.push((*kw, vt.ty.clone())); - } - - typed_fields.push((kt, vt)); - } - (BoundKind::Record { fields: typed_fields }, StaticType::Record(Rc::new(fields_ty))) - }, - - BoundKind::Expansion { original_call, bound_expanded } => { - let expanded_typed = self.check_node(*bound_expanded, ctx)?; - let ty = expanded_typed.ty.clone(); - (BoundKind::Expansion { - original_call, - bound_expanded: Box::new(expanded_typed) - }, ty) - }, - - BoundKind::Extension(_ext) => { - return Err(format!("TypeChecking for extension '{}' not implemented", _ext.display_name())); - }, - }; - - Ok(Node { - identity: node.identity, - kind, - ty, - }) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::ast::types::StaticType; - - fn check_source(source: &str) -> TypedNode { - let env = crate::ast::environment::Environment::new(); - env.compile(source).unwrap() - } - - fn get_ret_type(node: &TypedNode) -> StaticType { - if let StaticType::Function(sig) = &node.ty { - sig.ret.clone() - } else { - node.ty.clone() - } - } - - #[test] - fn test_inference_constants() { - assert_eq!(get_ret_type(&check_source("10")), StaticType::Int); - assert_eq!(get_ret_type(&check_source("10.5")), StaticType::Float); - assert_eq!(get_ret_type(&check_source("true")), StaticType::Bool); - assert_eq!(get_ret_type(&check_source("\"hello\"")), StaticType::Text); - } - - #[test] - fn test_inference_variable_propagation() { - // (do (def x 10) x) -> The last 'x' must be Int - let typed = check_source("(do (def x 10) x)"); - // Outer is Lambda, Body is Block - if let BoundKind::Lambda { body, .. } = &typed.kind { - if let BoundKind::Block { exprs } = &body.kind { - let last_expr = exprs.last().unwrap(); - assert_eq!(last_expr.ty, StaticType::Int, "Variable 'x' should be inferred as Int"); - } else { panic!("Expected block in lambda body"); } - } else { panic!("Expected Lambda wrapper"); } - } - - #[test] - fn test_inference_block_type() { - // Block type = last expression type - assert_eq!(get_ret_type(&check_source("(do 1 2.5)")), StaticType::Float); - assert_eq!(get_ret_type(&check_source("(do 1.5 \"test\")")), StaticType::Text); - } - - #[test] - fn test_inference_lambda_return() { - // (fn [a] 10) -> fn(any) -> Int - // Since it's already a Lambda, it's NOT wrapped further. - let typed = check_source("(fn [a] 10)"); - if let StaticType::Function(sig) = &typed.ty { - assert_eq!(sig.ret, StaticType::Int); - } else { panic!("Expected function type, got {:?}", typed.ty); } - - // Nested: (fn [] (do 1 2.5)) -> fn() -> Float - let typed_nested = check_source("(fn [] (do 1 2.5))"); - if let StaticType::Function(sig) = &typed_nested.ty { - assert_eq!(sig.ret, StaticType::Float); - } else { panic!("Expected function type"); } - } - - #[test] - fn test_inference_assignment_updates_type() { - // (do (def x 10) (assign x 20.5) x) -> x becomes Float after assignment - let typed = check_source("(do (def x 10) (assign x 20.5) x)"); - if let BoundKind::Lambda { body, .. } = &typed.kind { - if let BoundKind::Block { exprs } = &body.kind { - let last_expr = exprs.last().unwrap(); - assert_eq!(last_expr.ty, StaticType::Float, "Variable 'x' should be specialized to Float after assignment"); - } else { panic!("Expected block"); } - } else { panic!("Expected Lambda"); } - } - - #[test] - fn test_operator_overloading_inference() { - assert_eq!(get_ret_type(&check_source("(+ 1 2)")), StaticType::Int); - assert_eq!(get_ret_type(&check_source("(+ 1.0 2.0)")), StaticType::Float); - assert_eq!(get_ret_type(&check_source("(+ \"a\" \"b\")")), StaticType::Text); - assert_eq!(get_ret_type(&check_source("(/ 1 2)")), StaticType::Float); - } - - #[test] - fn test_datetime_inference() { - // date("2023-01-01") -> DateTime - assert_eq!(get_ret_type(&check_source("(date \"2023-01-01\")")), StaticType::DateTime); - // DateTime + Int -> DateTime - assert_eq!(get_ret_type(&check_source("(+ (date \"2023-01-01\") 86400000)")), StaticType::DateTime); - // DateTime - DateTime -> Int (Duration) - assert_eq!(get_ret_type(&check_source("(- (date \"2023-01-02\") (date \"2023-01-01\"))")), StaticType::Int); - // DateTime comparison -> Bool - assert_eq!(get_ret_type(&check_source("(> (date \"2023-01-02\") (date \"2023-01-01\"))")), StaticType::Bool); - } - - #[test] - fn test_inference_tuple_vector_matrix() { - // Heterogeneous -> Tuple - assert_eq!( - get_ret_type(&check_source("[1 3.14 \"text\"]")), - StaticType::Tuple(vec![StaticType::Int, StaticType::Float, StaticType::Text]) - ); - - // Homogeneous -> Vector - assert_eq!( - get_ret_type(&check_source("[10 20 30]")), - StaticType::Vector(Box::new(StaticType::Int), 3) - ); - - // Nested Homogeneous -> Matrix - assert_eq!( - get_ret_type(&check_source("[[1 2] [3 4]]")), - StaticType::Matrix(Box::new(StaticType::Int), vec![2, 2]) - ); - - // Deep Matrix - assert_eq!( - get_ret_type(&check_source("[[[1 2]] [[3 4]]]")), - StaticType::Matrix(Box::new(StaticType::Int), vec![2, 1, 2]) - ); - - // Shape mismatch -> Tuple of Vectors - let mixed = get_ret_type(&check_source("[[1 2] [3 4 5]]")); - if let StaticType::Tuple(elements) = mixed { - assert_eq!(elements[0], StaticType::Vector(Box::new(StaticType::Int), 2)); - assert_eq!(elements[1], StaticType::Vector(Box::new(StaticType::Int), 3)); - } else { - panic!("Expected Tuple for shape mismatch, got {:?}", mixed); - } - } - - #[test] - fn test_inference_record() { - use crate::ast::types::Keyword; - let typed = check_source("{:x 1, :y 0.3}"); - let ty = get_ret_type(&typed); - if let StaticType::Record(fields) = ty { - assert_eq!(fields.len(), 2); - assert_eq!(fields[0], (Keyword::intern("x"), StaticType::Int)); - assert_eq!(fields[1], (Keyword::intern("y"), StaticType::Float)); - } else { - panic!("Expected Record, got {:?}", ty); - } - } -} +use crate::ast::compiler::bound_nodes::{Address, BoundKind, BoundNode, TypedNode}; +use crate::ast::nodes::Node; +use crate::ast::types::StaticType; +use std::collections::HashMap; +use std::rc::Rc; + +/// Manages the types of locals and upvalues during a single type-checking pass. +struct TypeContext<'a> { + _parent: Option<&'a TypeContext<'a>>, + /// Maps slot index -> Inferred Type + slots: Vec, + /// Types of captured variables (passed from outer scope) + upvalue_types: Vec, +} + +impl<'a> TypeContext<'a> { + fn new( + slot_count: u32, + upvalue_types: Vec, + parent: Option<&'a TypeContext<'a>>, + ) -> Self { + Self { + _parent: parent, + slots: vec![StaticType::Any; slot_count as usize], + upvalue_types, + } + } + + fn get_type(&self, addr: Address) -> StaticType { + match addr { + Address::Local(idx) => self + .slots + .get(idx as usize) + .cloned() + .unwrap_or(StaticType::Any), + Address::Global(_) => StaticType::Any, // Globals are dynamic for now + Address::Upvalue(idx) => self + .upvalue_types + .get(idx as usize) + .cloned() + .unwrap_or(StaticType::Any), + } + } + + fn set_local_type(&mut self, idx: u32, ty: StaticType) { + if let Some(slot) = self.slots.get_mut(idx as usize) { + *slot = ty; + } + } +} + +pub struct TypeChecker { + global_types: Rc>>, +} + +impl TypeChecker { + pub fn new(global_types: Rc>>) -> Self { + Self { global_types } + } + + pub fn check(&self, node: BoundNode, arg_types: &[StaticType]) -> Result { + match node.kind { + BoundKind::Lambda { + params, + upvalues, + body, + positional_count, + } => { + // 1. Determine types of captured variables (Root lambdas have none) + let mut upvalue_types = Vec::with_capacity(upvalues.len()); + for _ in &upvalues { + upvalue_types.push(StaticType::Any); + } + + // 2. Create the specialized context + let root_ctx = TypeContext::new(0, vec![], None); + let mut lambda_ctx = TypeContext::new(64, upvalue_types, Some(&root_ctx)); + + // 3. INJECT specialized argument types into slots + let arg_tuple_ty = if arg_types.is_empty() { + StaticType::Any // No specialized info + } else { + StaticType::Tuple(arg_types.to_vec()) + }; + + let params_typed = + self.check_params(params.as_ref().clone(), &arg_tuple_ty, &mut lambda_ctx)?; + + // 4. Check body with the new types + let body_typed = self.check_node((*body).clone(), &mut lambda_ctx)?; + let ret_ty = body_typed.ty.clone(); + + // 5. Construct specialized function type + let final_params_ty = params_typed.ty.clone(); + + let fn_ty = StaticType::Function(Box::new(crate::ast::types::Signature { + params: final_params_ty, + ret: ret_ty, + })); + + Ok(Node { + identity: node.identity, + kind: BoundKind::Lambda { + params: Rc::new(params_typed), + upvalues, + body: Rc::new(body_typed), + positional_count, + }, + ty: fn_ty, + }) + } + _ => { + // Fallback: Wrap in implicit lambda + let virtual_lambda = BoundNode { + identity: node.identity.clone(), + kind: BoundKind::Lambda { + params: Rc::new(Node { + identity: node.identity.clone(), + kind: BoundKind::Tuple { elements: vec![] }, + ty: (), + }), + upvalues: vec![], + body: Rc::new(node), + positional_count: Some(0), + }, + ty: (), + }; + self.check(virtual_lambda, &[]) + } + } + } + + fn check_params( + &self, + node: BoundNode, + specialized_ty: &StaticType, + ctx: &mut TypeContext, + ) -> Result { + let (kind, ty) = match node.kind { + BoundKind::Parameter { name, slot } => { + ctx.set_local_type(slot, specialized_ty.clone()); + (BoundKind::Parameter { name, slot }, specialized_ty.clone()) + } + BoundKind::Tuple { elements } => { + let mut typed_elements = Vec::new(); + let mut elem_types = Vec::new(); + + for (i, el) in elements.into_iter().enumerate() { + let sub_ty = match specialized_ty { + StaticType::Tuple(t) => t.get(i).cloned().unwrap_or(StaticType::Any), + StaticType::Vector(inner, _) => (**inner).clone(), + _ => StaticType::Any, + }; + let t = self.check_params(el, &sub_ty, ctx)?; + elem_types.push(t.ty.clone()); + typed_elements.push(t); + } + ( + BoundKind::Tuple { + elements: typed_elements, + }, + StaticType::Tuple(elem_types), + ) + } + _ => return Err("Invalid node in parameter list".to_string()), + }; + + Ok(Node { + identity: node.identity, + kind, + ty, + }) + } + + fn check_node(&self, node: BoundNode, ctx: &mut TypeContext) -> Result { + let (kind, ty) = match node.kind { + BoundKind::Nop => (BoundKind::Nop, StaticType::Void), + + BoundKind::Constant(v) => { + let ty = v.static_type(); + (BoundKind::Constant(v), ty) + } + + BoundKind::Parameter { name, slot } => ( + BoundKind::Parameter { name, slot }, + ctx.get_type(Address::Local(slot)), + ), + + BoundKind::Get { addr, name } => { + let ty = if let Address::Global(idx) = addr { + self.global_types + .borrow() + .get(&idx) + .cloned() + .unwrap_or(StaticType::Any) + } else { + ctx.get_type(addr) + }; + (BoundKind::Get { addr, name }, ty) + } + + BoundKind::Set { addr, value } => { + let val_typed = self.check_node(*value, ctx)?; + let ty = val_typed.ty.clone(); + + if let Address::Local(idx) = addr { + ctx.set_local_type(idx, ty.clone()); + } else if let Address::Global(idx) = addr { + self.global_types.borrow_mut().insert(idx, ty.clone()); + } + + ( + BoundKind::Set { + addr, + value: Box::new(val_typed), + }, + ty, + ) + } + + BoundKind::DefLocal { + name, + slot, + value, + captured_by, + } => { + let val_typed = self.check_node(*value, ctx)?; + let ty = val_typed.ty.clone(); + ctx.set_local_type(slot, ty.clone()); + + ( + BoundKind::DefLocal { + name, + slot, + value: Box::new(val_typed), + captured_by, + }, + ty, + ) + } + + BoundKind::DefGlobal { + name, + global_index, + value, + } => { + let val_typed = self.check_node(*value, ctx)?; + let ty = val_typed.ty.clone(); + self.global_types + .borrow_mut() + .insert(global_index, ty.clone()); + + ( + BoundKind::DefGlobal { + name, + global_index, + value: Box::new(val_typed), + }, + ty, + ) + } + + BoundKind::If { + cond, + then_br, + else_br, + } => { + let cond_typed = self.check_node(*cond, ctx)?; + let then_typed = self.check_node(*then_br, ctx)?; + + 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)?; + // Basic type promotion: if types differ, fall back to Any + if et.ty != final_ty { + final_ty = StaticType::Any; + } + else_typed = Some(Box::new(et)); + } else { + // If without else returns Void or Optional(Then) + final_ty = StaticType::Any; // Delphi: MakeOptional(Then) + } + + ( + BoundKind::If { + cond: Box::new(cond_typed), + then_br: Box::new(then_typed), + else_br: else_typed, + }, + final_ty, + ) + } + + BoundKind::Block { exprs } => { + let mut typed_exprs = Vec::new(); + let mut last_ty = StaticType::Void; + + for e in exprs { + let t = self.check_node(e, ctx)?; + last_ty = t.ty.clone(); + typed_exprs.push(t); + } + + (BoundKind::Block { exprs: typed_exprs }, last_ty) + } + + BoundKind::Lambda { + params, + upvalues, + body, + positional_count, + } => { + // 1. Determine types of captured variables + let mut upvalue_types = Vec::with_capacity(upvalues.len()); + for &addr in &upvalues { + upvalue_types.push(ctx.get_type(addr)); + } + + // 2. Create nested context for lambda body + let mut lambda_ctx = TypeContext::new(64, upvalue_types, Some(ctx)); + + // 3. Check parameters and body + let params_typed = + self.check_params(params.as_ref().clone(), &StaticType::Any, &mut lambda_ctx)?; + let body_typed = self.check_node((*body).clone(), &mut lambda_ctx)?; + let ret_ty = body_typed.ty.clone(); + + // 4. Construct function type + let fn_ty = StaticType::Function(Box::new(crate::ast::types::Signature { + params: params_typed.ty.clone(), + ret: ret_ty, + })); + + ( + BoundKind::Lambda { + params: Rc::new(params_typed), + upvalues, + body: Rc::new(body_typed), + positional_count, + }, + fn_ty, + ) + } + + BoundKind::Call { callee, args } => { + let callee_typed = self.check_node(*callee, ctx)?; + let args_typed = self.check_node(*args, ctx)?; + + let ret_ty = callee_typed + .ty + .resolve_call(&args_typed.ty) + .unwrap_or(StaticType::Any); + + ( + BoundKind::Call { + callee: Box::new(callee_typed), + args: Box::new(args_typed), + }, + ret_ty, + ) + } + + BoundKind::Tuple { elements } => { + let mut typed_elements = Vec::new(); + for e in elements { + typed_elements.push(self.check_node(e, ctx)?); + } + + let ty = if typed_elements.is_empty() { + StaticType::Vector(Box::new(StaticType::Any), 0) + } else { + let first_ty = &typed_elements[0].ty; + let all_same = typed_elements.iter().all(|e| e.ty == *first_ty); + + if all_same { + match first_ty { + StaticType::Vector(inner, len) => { + StaticType::Matrix(inner.clone(), vec![typed_elements.len(), *len]) + } + StaticType::Matrix(inner, shape) => { + let mut new_shape = vec![typed_elements.len()]; + new_shape.extend(shape); + StaticType::Matrix(inner.clone(), new_shape) + } + _ => { + StaticType::Vector(Box::new(first_ty.clone()), typed_elements.len()) + } + } + } else { + StaticType::Tuple(typed_elements.iter().map(|e| e.ty.clone()).collect()) + } + }; + + ( + BoundKind::Tuple { + elements: typed_elements, + }, + ty, + ) + } + + BoundKind::Record { fields } => { + let mut typed_fields = Vec::with_capacity(fields.len()); + let mut fields_ty = Vec::with_capacity(fields.len()); + for (k, v) in fields { + let kt = self.check_node(k, ctx)?; + let vt = self.check_node(v, ctx)?; + + if let BoundKind::Constant(crate::ast::types::Value::Keyword(kw)) = &kt.kind { + fields_ty.push((*kw, vt.ty.clone())); + } + + typed_fields.push((kt, vt)); + } + ( + BoundKind::Record { + fields: typed_fields, + }, + StaticType::Record(Rc::new(fields_ty)), + ) + } + + BoundKind::Expansion { + original_call, + bound_expanded, + } => { + let expanded_typed = self.check_node(*bound_expanded, ctx)?; + let ty = expanded_typed.ty.clone(); + ( + BoundKind::Expansion { + original_call, + bound_expanded: Box::new(expanded_typed), + }, + ty, + ) + } + + BoundKind::Extension(_ext) => { + return Err(format!( + "TypeChecking for extension '{}' not implemented", + _ext.display_name() + )); + } + }; + + Ok(Node { + identity: node.identity, + kind, + ty, + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::ast::types::StaticType; + + fn check_source(source: &str) -> TypedNode { + let env = crate::ast::environment::Environment::new(); + env.compile(source).unwrap() + } + + fn get_ret_type(node: &TypedNode) -> StaticType { + if let StaticType::Function(sig) = &node.ty { + sig.ret.clone() + } else { + node.ty.clone() + } + } + + #[test] + fn test_inference_constants() { + assert_eq!(get_ret_type(&check_source("10")), StaticType::Int); + assert_eq!(get_ret_type(&check_source("10.5")), StaticType::Float); + assert_eq!(get_ret_type(&check_source("true")), StaticType::Bool); + assert_eq!(get_ret_type(&check_source("\"hello\"")), StaticType::Text); + } + + #[test] + fn test_inference_variable_propagation() { + // (do (def x 10) x) -> The last 'x' must be Int + let typed = check_source("(do (def x 10) x)"); + // Outer is Lambda, Body is Block + if let BoundKind::Lambda { body, .. } = &typed.kind { + if let BoundKind::Block { exprs } = &body.kind { + let last_expr = exprs.last().unwrap(); + assert_eq!( + last_expr.ty, + StaticType::Int, + "Variable 'x' should be inferred as Int" + ); + } else { + panic!("Expected block in lambda body"); + } + } else { + panic!("Expected Lambda wrapper"); + } + } + + #[test] + fn test_inference_block_type() { + // Block type = last expression type + assert_eq!(get_ret_type(&check_source("(do 1 2.5)")), StaticType::Float); + assert_eq!( + get_ret_type(&check_source("(do 1.5 \"test\")")), + StaticType::Text + ); + } + + #[test] + fn test_inference_lambda_return() { + // (fn [a] 10) -> fn(any) -> Int + // Since it's already a Lambda, it's NOT wrapped further. + let typed = check_source("(fn [a] 10)"); + if let StaticType::Function(sig) = &typed.ty { + assert_eq!(sig.ret, StaticType::Int); + } else { + panic!("Expected function type, got {:?}", typed.ty); + } + + // Nested: (fn [] (do 1 2.5)) -> fn() -> Float + let typed_nested = check_source("(fn [] (do 1 2.5))"); + if let StaticType::Function(sig) = &typed_nested.ty { + assert_eq!(sig.ret, StaticType::Float); + } else { + panic!("Expected function type"); + } + } + + #[test] + fn test_inference_assignment_updates_type() { + // (do (def x 10) (assign x 20.5) x) -> x becomes Float after assignment + let typed = check_source("(do (def x 10) (assign x 20.5) x)"); + if let BoundKind::Lambda { body, .. } = &typed.kind { + if let BoundKind::Block { exprs } = &body.kind { + let last_expr = exprs.last().unwrap(); + assert_eq!( + last_expr.ty, + StaticType::Float, + "Variable 'x' should be specialized to Float after assignment" + ); + } else { + panic!("Expected block"); + } + } else { + panic!("Expected Lambda"); + } + } + + #[test] + fn test_operator_overloading_inference() { + assert_eq!(get_ret_type(&check_source("(+ 1 2)")), StaticType::Int); + assert_eq!( + get_ret_type(&check_source("(+ 1.0 2.0)")), + StaticType::Float + ); + assert_eq!( + get_ret_type(&check_source("(+ \"a\" \"b\")")), + StaticType::Text + ); + assert_eq!(get_ret_type(&check_source("(/ 1 2)")), StaticType::Float); + } + + #[test] + fn test_datetime_inference() { + // date("2023-01-01") -> DateTime + assert_eq!( + get_ret_type(&check_source("(date \"2023-01-01\")")), + StaticType::DateTime + ); + // DateTime + Int -> DateTime + assert_eq!( + get_ret_type(&check_source("(+ (date \"2023-01-01\") 86400000)")), + StaticType::DateTime + ); + // DateTime - DateTime -> Int (Duration) + assert_eq!( + get_ret_type(&check_source( + "(- (date \"2023-01-02\") (date \"2023-01-01\"))" + )), + StaticType::Int + ); + // DateTime comparison -> Bool + assert_eq!( + get_ret_type(&check_source( + "(> (date \"2023-01-02\") (date \"2023-01-01\"))" + )), + StaticType::Bool + ); + } + + #[test] + fn test_inference_tuple_vector_matrix() { + // Heterogeneous -> Tuple + assert_eq!( + get_ret_type(&check_source("[1 3.14 \"text\"]")), + StaticType::Tuple(vec![StaticType::Int, StaticType::Float, StaticType::Text]) + ); + + // Homogeneous -> Vector + assert_eq!( + get_ret_type(&check_source("[10 20 30]")), + StaticType::Vector(Box::new(StaticType::Int), 3) + ); + + // Nested Homogeneous -> Matrix + assert_eq!( + get_ret_type(&check_source("[[1 2] [3 4]]")), + StaticType::Matrix(Box::new(StaticType::Int), vec![2, 2]) + ); + + // Deep Matrix + assert_eq!( + get_ret_type(&check_source("[[[1 2]] [[3 4]]]")), + StaticType::Matrix(Box::new(StaticType::Int), vec![2, 1, 2]) + ); + + // Shape mismatch -> Tuple of Vectors + let mixed = get_ret_type(&check_source("[[1 2] [3 4 5]]")); + if let StaticType::Tuple(elements) = mixed { + assert_eq!( + elements[0], + StaticType::Vector(Box::new(StaticType::Int), 2) + ); + assert_eq!( + elements[1], + StaticType::Vector(Box::new(StaticType::Int), 3) + ); + } else { + panic!("Expected Tuple for shape mismatch, got {:?}", mixed); + } + } + + #[test] + fn test_inference_record() { + use crate::ast::types::Keyword; + let typed = check_source("{:x 1, :y 0.3}"); + let ty = get_ret_type(&typed); + if let StaticType::Record(fields) = ty { + assert_eq!(fields.len(), 2); + assert_eq!(fields[0], (Keyword::intern("x"), StaticType::Int)); + assert_eq!(fields[1], (Keyword::intern("y"), StaticType::Float)); + } else { + panic!("Expected Record, got {:?}", ty); + } + } +} diff --git a/src/ast/compiler/upvalues.rs b/src/ast/compiler/upvalues.rs index 2f7db14..fe665ab 100644 --- a/src/ast/compiler/upvalues.rs +++ b/src/ast/compiler/upvalues.rs @@ -1,121 +1,132 @@ -use std::collections::{HashMap, HashSet}; -use crate::ast::nodes::{Node, UntypedKind, Symbol}; -use crate::ast::types::Identity; - -/// Analyzes the AST to find which lambdas capture which variable declarations. -/// Returns a map: Declaration Identity -> List of Lambda Identities that capture it. -pub struct UpvalueAnalyzer; - -impl UpvalueAnalyzer { - pub fn analyze(root: &Node) -> HashMap> { - let mut capture_map: HashMap> = HashMap::new(); - let mut scopes = vec![HashMap::new()]; // Root scope - - Self::visit(root, &mut scopes, &mut capture_map, None); - - // Convert HashSet back to sorted Vec for the final result - capture_map.into_iter() - .map(|(decl, lambdas)| (decl, lambdas.into_iter().collect())) - .collect() - } - - fn visit_params(node: &Node, scope: &mut HashMap) { - match &node.kind { - UntypedKind::Parameter(sym) => { - scope.insert(sym.clone(), node.identity.clone()); - } - UntypedKind::Tuple { elements } => { - for el in elements { - Self::visit_params(el, scope); - } - } - _ => {} // Ignore other nodes in parameter patterns for now - } - } - - fn visit( - node: &Node, - scopes: &mut Vec>, - capture_map: &mut HashMap>, - current_lambda: Option - ) { - match &node.kind { - UntypedKind::Identifier(sym) => { - // Resolve name in scope stack (from inner to outer) - for (depth, scope) in scopes.iter().rev().enumerate() { - if let Some(decl_id) = scope.get(sym) { - if depth > 0 { - // Captured from an outer scope! - if let Some(lambda_id) = ¤t_lambda { - capture_map.entry(decl_id.clone()) - .or_default() - .insert(lambda_id.clone()); - } - } - break; - } - } - } - UntypedKind::Def { name, value } => { - Self::visit(value, scopes, capture_map, current_lambda.clone()); - if let Some(current) = scopes.last_mut() { - current.insert(name.clone(), node.identity.clone()); - } - } - UntypedKind::Lambda { params, body } => { - let mut new_scope = HashMap::new(); - Self::visit_params(params, &mut new_scope); - - scopes.push(new_scope); - // The current node is the lambda causing captures in its body - Self::visit(body, scopes, capture_map, Some(node.identity.clone())); - scopes.pop(); - } - UntypedKind::MacroDecl { params, body, .. } => { - let mut new_scope = HashMap::new(); - Self::visit_params(params, &mut new_scope); - scopes.push(new_scope); - Self::visit(body, scopes, capture_map, current_lambda); - scopes.pop(); - } - UntypedKind::If { cond, then_br, else_br } => { - Self::visit(cond, scopes, capture_map, current_lambda.clone()); - Self::visit(then_br, scopes, capture_map, current_lambda.clone()); - if let Some(e) = else_br { - Self::visit(e, scopes, capture_map, current_lambda.clone()); - } - } - UntypedKind::Assign { target, value } => { - Self::visit(target, scopes, capture_map, current_lambda.clone()); - Self::visit(value, scopes, capture_map, current_lambda.clone()); - } - UntypedKind::Call { callee, args } => { - Self::visit(callee, scopes, capture_map, current_lambda.clone()); - Self::visit(args, scopes, capture_map, current_lambda.clone()); - } - UntypedKind::Block { exprs } => { - for expr in exprs { - Self::visit(expr, scopes, capture_map, current_lambda.clone()); - } - } - UntypedKind::Tuple { elements } => { - for el in elements { - Self::visit(el, scopes, capture_map, current_lambda.clone()); - } - } - UntypedKind::Record { fields } => { - for (k, v) in fields { - Self::visit(k, scopes, capture_map, current_lambda.clone()); - Self::visit(v, scopes, capture_map, current_lambda.clone()); - } - } - UntypedKind::Expansion { call: _, expanded } => { - Self::visit(expanded, scopes, capture_map, current_lambda); - } - UntypedKind::Template(body) | UntypedKind::Placeholder(body) | UntypedKind::Splice(body) => { - Self::visit(body, scopes, capture_map, current_lambda); - } - UntypedKind::Nop | UntypedKind::Constant(_) | UntypedKind::Extension(_) | UntypedKind::Parameter(_) => {} - } - } -} +use crate::ast::nodes::{Node, Symbol, UntypedKind}; +use crate::ast::types::Identity; +use std::collections::{HashMap, HashSet}; + +/// Analyzes the AST to find which lambdas capture which variable declarations. +/// Returns a map: Declaration Identity -> List of Lambda Identities that capture it. +pub struct UpvalueAnalyzer; + +impl UpvalueAnalyzer { + pub fn analyze(root: &Node) -> HashMap> { + let mut capture_map: HashMap> = HashMap::new(); + let mut scopes = vec![HashMap::new()]; // Root scope + + Self::visit(root, &mut scopes, &mut capture_map, None); + + // Convert HashSet back to sorted Vec for the final result + capture_map + .into_iter() + .map(|(decl, lambdas)| (decl, lambdas.into_iter().collect())) + .collect() + } + + fn visit_params(node: &Node, scope: &mut HashMap) { + match &node.kind { + UntypedKind::Parameter(sym) => { + scope.insert(sym.clone(), node.identity.clone()); + } + UntypedKind::Tuple { elements } => { + for el in elements { + Self::visit_params(el, scope); + } + } + _ => {} // Ignore other nodes in parameter patterns for now + } + } + + fn visit( + node: &Node, + scopes: &mut Vec>, + capture_map: &mut HashMap>, + current_lambda: Option, + ) { + match &node.kind { + UntypedKind::Identifier(sym) => { + // Resolve name in scope stack (from inner to outer) + for (depth, scope) in scopes.iter().rev().enumerate() { + if let Some(decl_id) = scope.get(sym) { + if depth > 0 { + // Captured from an outer scope! + if let Some(lambda_id) = ¤t_lambda { + capture_map + .entry(decl_id.clone()) + .or_default() + .insert(lambda_id.clone()); + } + } + break; + } + } + } + UntypedKind::Def { name, value } => { + Self::visit(value, scopes, capture_map, current_lambda.clone()); + if let Some(current) = scopes.last_mut() { + current.insert(name.clone(), node.identity.clone()); + } + } + UntypedKind::Lambda { params, body } => { + let mut new_scope = HashMap::new(); + Self::visit_params(params, &mut new_scope); + + scopes.push(new_scope); + // The current node is the lambda causing captures in its body + Self::visit(body, scopes, capture_map, Some(node.identity.clone())); + scopes.pop(); + } + UntypedKind::MacroDecl { params, body, .. } => { + let mut new_scope = HashMap::new(); + Self::visit_params(params, &mut new_scope); + scopes.push(new_scope); + Self::visit(body, scopes, capture_map, current_lambda); + scopes.pop(); + } + UntypedKind::If { + cond, + then_br, + else_br, + } => { + Self::visit(cond, scopes, capture_map, current_lambda.clone()); + Self::visit(then_br, scopes, capture_map, current_lambda.clone()); + if let Some(e) = else_br { + Self::visit(e, scopes, capture_map, current_lambda.clone()); + } + } + UntypedKind::Assign { target, value } => { + Self::visit(target, scopes, capture_map, current_lambda.clone()); + Self::visit(value, scopes, capture_map, current_lambda.clone()); + } + UntypedKind::Call { callee, args } => { + Self::visit(callee, scopes, capture_map, current_lambda.clone()); + Self::visit(args, scopes, capture_map, current_lambda.clone()); + } + UntypedKind::Block { exprs } => { + for expr in exprs { + Self::visit(expr, scopes, capture_map, current_lambda.clone()); + } + } + UntypedKind::Tuple { elements } => { + for el in elements { + Self::visit(el, scopes, capture_map, current_lambda.clone()); + } + } + UntypedKind::Record { fields } => { + for (k, v) in fields { + Self::visit(k, scopes, capture_map, current_lambda.clone()); + Self::visit(v, scopes, capture_map, current_lambda.clone()); + } + } + UntypedKind::Expansion { call: _, expanded } => { + Self::visit(expanded, scopes, capture_map, current_lambda); + } + UntypedKind::Template(body) + | UntypedKind::Placeholder(body) + | UntypedKind::Splice(body) => { + Self::visit(body, scopes, capture_map, current_lambda); + } + UntypedKind::Nop + | UntypedKind::Constant(_) + | UntypedKind::Extension(_) + | UntypedKind::Parameter(_) => {} + } + } +} diff --git a/src/ast/environment.rs b/src/ast/environment.rs index 521a04c..da62e1a 100644 --- a/src/ast/environment.rs +++ b/src/ast/environment.rs @@ -1,355 +1,355 @@ -use crate::ast::compiler::binder::Binder; -use crate::ast::compiler::{TypeChecker, TypedNode}; -use crate::ast::nodes::{Node, Symbol, UntypedKind}; -use crate::ast::parser::Parser; -use crate::ast::types::{Object, StaticType, Value}; -use crate::ast::vm::{TracingObserver, VM}; -use std::cell::RefCell; -use std::collections::HashMap; -use std::rc::Rc; - -use crate::ast::compiler::bound_nodes::{Address, BoundNode}; -use crate::ast::compiler::dumper::Dumper; -use crate::ast::compiler::lambda_collector::LambdaCollector; -use crate::ast::compiler::macros::{MacroEvaluator, MacroExpander, MacroRegistry}; -use crate::ast::compiler::optimizer::Optimizer; -use crate::ast::compiler::specializer::{FunctionRegistry, MonoCache, Specializer}; -use crate::ast::compiler::tco::{TCO, ExecNode}; -use crate::ast::rtl; -use crate::ast::rtl::intrinsics; - -pub struct Environment { - pub global_names: Rc>>, - pub global_types: Rc>>, - pub global_purity: Rc>>, - pub global_values: Rc>>, - pub function_registry: Rc>>, - pub typed_function_registry: Rc>>, - pub monomorph_cache: Rc>, - pub debug_mode: bool, - pub optimization: bool, -} - -struct EnvFunctionRegistry { - registry: Rc>>, -} - -impl FunctionRegistry for EnvFunctionRegistry { - fn resolve(&self, addr: Address) -> Option { - if let Address::Global(idx) = addr { - self.registry.borrow().get(&idx).cloned() - } else { - None - } - } -} - -/// Evaluator used during macro expansion to allow compile-time logic. -struct RuntimeMacroEvaluator { - global_names: Rc>>, - global_types: Rc>>, - global_values: Rc>>, -} - -impl MacroEvaluator for RuntimeMacroEvaluator { - fn evaluate( - &self, - node: &Node, - bindings: &HashMap, Node>, - ) -> Result { - // 1. Check if it's a simple parameter substitution - if let UntypedKind::Identifier(sym) = &node.kind - && let Some(arg_node) = bindings.get(&sym.name) - { - return Ok(Value::Object(Rc::new(arg_node.clone()) as Rc)); - } - - // 2. Full evaluation for complex compile-time expressions - let bound_ast = Binder::bind_root(self.global_names.clone(), node)?; - - let checker = TypeChecker::new(self.global_types.clone()); - let typed_ast = checker.check(bound_ast, &[])?; - let exec_ast = TCO::optimize(typed_ast); - - let mut vm = VM::new(self.global_values.clone()); - vm.run(&exec_ast) - } -} - -impl Default for Environment { - fn default() -> Self { - Self::new() - } -} - -impl Environment { - pub fn new() -> Self { - let env = Self { - global_names: Rc::new(RefCell::new(HashMap::new())), - global_types: Rc::new(RefCell::new(HashMap::new())), - global_purity: Rc::new(RefCell::new(HashMap::new())), - global_values: Rc::new(RefCell::new(Vec::new())), - function_registry: Rc::new(RefCell::new(HashMap::new())), - typed_function_registry: Rc::new(RefCell::new(HashMap::new())), - monomorph_cache: Rc::new(RefCell::new(HashMap::new())), - debug_mode: false, - optimization: true, - }; - env.register_stdlib(); - env - } - - pub fn set_debug_mode(&mut self, enabled: bool) { - self.debug_mode = enabled; - } - - fn get_expander(&self) -> MacroExpander { - let evaluator = RuntimeMacroEvaluator { - global_names: self.global_names.clone(), - global_types: self.global_types.clone(), - global_values: self.global_values.clone(), - }; - MacroExpander::new(MacroRegistry::new(), evaluator) - } - - pub fn register_native( - &self, - name: &str, - ty: StaticType, - is_pure: bool, - func: impl Fn(Vec) -> Value + 'static, - ) { - let mut names = self.global_names.borrow_mut(); - let mut types = self.global_types.borrow_mut(); - let mut values = self.global_values.borrow_mut(); - let mut purity = self.global_purity.borrow_mut(); - - let idx = values.len() as u32; - names.insert(Symbol::from(name), idx); - types.insert(idx, ty); - purity.insert(idx, is_pure); - values.push(Value::Function(Rc::new(func))); - } - - pub fn register_constant(&self, name: &str, ty: StaticType, val: Value) { - let mut names = self.global_names.borrow_mut(); - let mut types = self.global_types.borrow_mut(); - let mut values = self.global_values.borrow_mut(); - let mut purity = self.global_purity.borrow_mut(); - - let idx = values.len() as u32; - names.insert(Symbol::from(name), idx); - types.insert(idx, ty); - purity.insert(idx, true); // Constants are always pure - values.push(val); - } - - fn register_stdlib(&self) { - // Register all standard library functions via RTL module - rtl::register(self); - } - - pub fn dump_ast(&self, source: &str) -> Result { - let compiled = self.compile(source)?; - let linked = self.link(compiled); - Ok(Dumper::dump(&linked)) - } - - /// Frontend: Parse -> Expand Macros -> Bind -> Type Check - pub fn compile(&self, source: &str) -> Result { - // 1. Parse - let mut parser = Parser::new(source)?; - let untyped_ast = parser.parse_expression()?; - - // 2. Check for trailing tokens - if !parser.at_eof() { - return Err( - "Unexpected trailing expressions in script. Use (do ...) for sequences." - .to_string(), - ); - } - - // 3. Expand Macros - let expanded_ast = self.get_expander().expand(untyped_ast)?; - - // 4. Bind - let bound_ast = Binder::bind_root(self.global_names.clone(), &expanded_ast)?; - - // 5. Collect Lambdas (Populate the registry with untyped templates) - LambdaCollector::collect(&bound_ast, &mut self.function_registry.borrow_mut()); - - // 6. Type Check - let checker = TypeChecker::new(self.global_types.clone()); - let typed_ast = checker.check(bound_ast, &[])?; - - // 7. Collect Typed Lambdas - LambdaCollector::collect(&typed_ast, &mut self.typed_function_registry.borrow_mut()); - - Ok(typed_ast) - } - - /// Backend: Optimization (TCO, etc.) - pub fn link(&self, node: TypedNode) -> ExecNode { - // 1. Specialize (Always performed for correctness) - let specialized = self.specialize_node(node); - - // 2. Optimize (Level 1: Cracking, Level 2: Collapsing) - let optimizer = Optimizer::new(self.optimization) - .with_globals(self.global_values.clone()) - .with_purity(self.global_purity.clone()) - .with_registry(self.typed_function_registry.clone()); - let optimized = optimizer.optimize(specialized); - - // 3. TCO (Always performed, converts to ExecNode) - TCO::optimize(optimized) - } - - fn specialize_node(&self, node: TypedNode) -> TypedNode { - let registry = Rc::new(EnvFunctionRegistry { - registry: self.function_registry.clone(), - }); - - let rtl_lookup = Rc::new(|name: &str, args: &[StaticType]| intrinsics::lookup(name, args)); - - let func_reg = self.function_registry.clone(); - let mono_cache = self.monomorph_cache.clone(); - let global_values = self.global_values.clone(); - let global_types = self.global_types.clone(); - let global_purity = self.global_purity.clone(); - let optimization = self.optimization; - - let compiler = Rc::new( - move |func_template: BoundNode, - arg_types: &[StaticType]| - -> Result<(Value, StaticType), String> { - // 1. Re-TypeCheck the template with concrete argument types - let checker = TypeChecker::new(global_types.clone()); - let retyped_ast = checker.check(func_template, arg_types)?; - - // 2. Specialize (Recursive) - let sub_registry = Rc::new(EnvFunctionRegistry { - registry: func_reg.clone(), - }); - let sub_rtl_lookup = - Rc::new(|name: &str, args: &[StaticType]| intrinsics::lookup(name, args)); - - let sub_specializer = Specializer::new( - Some(sub_registry), - None, - Some(sub_rtl_lookup), - Some(mono_cache.clone()), - ); - - let specialized_ast = sub_specializer.specialize(retyped_ast); - - // 3. Optimize (Phase 2: Cracking & Folding) - let optimizer = Optimizer::new(optimization) - .with_globals(global_values.clone()) - .with_purity(global_purity.clone()); - let optimized_ast = optimizer.optimize(specialized_ast); - - // 4. TCO (converts to ExecNode) - let tco_ast = TCO::optimize(optimized_ast); - - // 5. Compile to Value (VM) - let mut vm = VM::new(global_values.clone()); - let compiled_val = match vm.run(&tco_ast) { - Ok(v) => v, - Err(e) => return Err(format!("VM Error during specialization: {}", e)), - }; - - // 6. Determine correct return type from the newly inferred function signature - let ret_type = if let StaticType::Function(sig) = &tco_ast.ty.ty { - sig.ret.clone() - } else { - StaticType::Any - }; - - Ok((compiled_val, ret_type)) - }, - ); - - let specializer = Specializer::new( - Some(registry), - Some(compiler), - Some(rtl_lookup), - Some(self.monomorph_cache.clone()), - ); - - specializer.specialize(node) - } - - /// Runtime: Execute the linked AST in the VM - pub fn run(&self, node: &ExecNode) -> Result { - let mut vm = VM::new(self.global_values.clone()); - let mut result = vm.run(node)?; - - // Handle potential script body closure - if let Value::Object(obj) = &result - && let Some(closure) = obj.as_any().downcast_ref::() - { - result = vm.run(&closure.exec_node)?; - } - - // IMPORTANT: Resolve any pending tail call requests from the top-level execution - while let Value::TailCallRequest(payload) = result { - let (next_obj, next_args) = *payload; - if let Some(closure) = next_obj.as_any().downcast_ref::() { - result = vm.run_with_args(closure, next_args)?; - } else { - return Err(format!( - "Tail call target is not a closure: {}", - next_obj.type_name() - )); - } - } - Ok(result) - } - - pub fn run_script(&self, source: &str) -> Result { - if self.debug_mode { - let (res, logs) = self.run_debug(source)?; - for line in logs { - println!("{}", line); - } - res - } else { - let compiled = self.compile(source)?; - let linked = self.link(compiled); - self.run(&linked) - } - } - - pub fn run_debug(&self, source: &str) -> Result<(Result, Vec), String> { - let compiled = self.compile(source)?; - let linked = self.link(compiled); - - // Execute with TracingObserver - let mut vm = VM::new(self.global_values.clone()); - let mut observer = TracingObserver::new(); - let mut result = vm.run_with_observer(&mut observer, &linked); - - // If result is a closure (script entry), execute the body too - if let Ok(Value::Object(obj)) = &result - && let Some(closure) = obj.as_any().downcast_ref::() - { - result = vm.run_with_observer(&mut observer, &closure.exec_node); - } - - // Resolve top-level tail calls - while let Ok(Value::TailCallRequest(payload)) = result { - let (next_obj, next_args) = *payload; - if let Some(closure) = next_obj.as_any().downcast_ref::() { - result = vm.run_with_args_observed(&mut observer, closure, next_args); - } else { - result = Err(format!( - "Tail call target is not a closure: {}", - next_obj.type_name() - )); - break; - } - } - - Ok((result, observer.logs)) - } -} +use crate::ast::compiler::binder::Binder; +use crate::ast::compiler::{TypeChecker, TypedNode}; +use crate::ast::nodes::{Node, Symbol, UntypedKind}; +use crate::ast::parser::Parser; +use crate::ast::types::{Object, StaticType, Value}; +use crate::ast::vm::{TracingObserver, VM}; +use std::cell::RefCell; +use std::collections::HashMap; +use std::rc::Rc; + +use crate::ast::compiler::bound_nodes::{Address, BoundNode}; +use crate::ast::compiler::dumper::Dumper; +use crate::ast::compiler::lambda_collector::LambdaCollector; +use crate::ast::compiler::macros::{MacroEvaluator, MacroExpander, MacroRegistry}; +use crate::ast::compiler::optimizer::Optimizer; +use crate::ast::compiler::specializer::{FunctionRegistry, MonoCache, Specializer}; +use crate::ast::compiler::tco::{ExecNode, TCO}; +use crate::ast::rtl; +use crate::ast::rtl::intrinsics; + +pub struct Environment { + pub global_names: Rc>>, + pub global_types: Rc>>, + pub global_purity: Rc>>, + pub global_values: Rc>>, + pub function_registry: Rc>>, + pub typed_function_registry: Rc>>, + pub monomorph_cache: Rc>, + pub debug_mode: bool, + pub optimization: bool, +} + +struct EnvFunctionRegistry { + registry: Rc>>, +} + +impl FunctionRegistry for EnvFunctionRegistry { + fn resolve(&self, addr: Address) -> Option { + if let Address::Global(idx) = addr { + self.registry.borrow().get(&idx).cloned() + } else { + None + } + } +} + +/// Evaluator used during macro expansion to allow compile-time logic. +struct RuntimeMacroEvaluator { + global_names: Rc>>, + global_types: Rc>>, + global_values: Rc>>, +} + +impl MacroEvaluator for RuntimeMacroEvaluator { + fn evaluate( + &self, + node: &Node, + bindings: &HashMap, Node>, + ) -> Result { + // 1. Check if it's a simple parameter substitution + if let UntypedKind::Identifier(sym) = &node.kind + && let Some(arg_node) = bindings.get(&sym.name) + { + return Ok(Value::Object(Rc::new(arg_node.clone()) as Rc)); + } + + // 2. Full evaluation for complex compile-time expressions + let bound_ast = Binder::bind_root(self.global_names.clone(), node)?; + + let checker = TypeChecker::new(self.global_types.clone()); + let typed_ast = checker.check(bound_ast, &[])?; + let exec_ast = TCO::optimize(typed_ast); + + let mut vm = VM::new(self.global_values.clone()); + vm.run(&exec_ast) + } +} + +impl Default for Environment { + fn default() -> Self { + Self::new() + } +} + +impl Environment { + pub fn new() -> Self { + let env = Self { + global_names: Rc::new(RefCell::new(HashMap::new())), + global_types: Rc::new(RefCell::new(HashMap::new())), + global_purity: Rc::new(RefCell::new(HashMap::new())), + global_values: Rc::new(RefCell::new(Vec::new())), + function_registry: Rc::new(RefCell::new(HashMap::new())), + typed_function_registry: Rc::new(RefCell::new(HashMap::new())), + monomorph_cache: Rc::new(RefCell::new(HashMap::new())), + debug_mode: false, + optimization: true, + }; + env.register_stdlib(); + env + } + + pub fn set_debug_mode(&mut self, enabled: bool) { + self.debug_mode = enabled; + } + + fn get_expander(&self) -> MacroExpander { + let evaluator = RuntimeMacroEvaluator { + global_names: self.global_names.clone(), + global_types: self.global_types.clone(), + global_values: self.global_values.clone(), + }; + MacroExpander::new(MacroRegistry::new(), evaluator) + } + + pub fn register_native( + &self, + name: &str, + ty: StaticType, + is_pure: bool, + func: impl Fn(Vec) -> Value + 'static, + ) { + let mut names = self.global_names.borrow_mut(); + let mut types = self.global_types.borrow_mut(); + let mut values = self.global_values.borrow_mut(); + let mut purity = self.global_purity.borrow_mut(); + + let idx = values.len() as u32; + names.insert(Symbol::from(name), idx); + types.insert(idx, ty); + purity.insert(idx, is_pure); + values.push(Value::Function(Rc::new(func))); + } + + pub fn register_constant(&self, name: &str, ty: StaticType, val: Value) { + let mut names = self.global_names.borrow_mut(); + let mut types = self.global_types.borrow_mut(); + let mut values = self.global_values.borrow_mut(); + let mut purity = self.global_purity.borrow_mut(); + + let idx = values.len() as u32; + names.insert(Symbol::from(name), idx); + types.insert(idx, ty); + purity.insert(idx, true); // Constants are always pure + values.push(val); + } + + fn register_stdlib(&self) { + // Register all standard library functions via RTL module + rtl::register(self); + } + + pub fn dump_ast(&self, source: &str) -> Result { + let compiled = self.compile(source)?; + let linked = self.link(compiled); + Ok(Dumper::dump(&linked)) + } + + /// Frontend: Parse -> Expand Macros -> Bind -> Type Check + pub fn compile(&self, source: &str) -> Result { + // 1. Parse + let mut parser = Parser::new(source)?; + let untyped_ast = parser.parse_expression()?; + + // 2. Check for trailing tokens + if !parser.at_eof() { + return Err( + "Unexpected trailing expressions in script. Use (do ...) for sequences." + .to_string(), + ); + } + + // 3. Expand Macros + let expanded_ast = self.get_expander().expand(untyped_ast)?; + + // 4. Bind + let bound_ast = Binder::bind_root(self.global_names.clone(), &expanded_ast)?; + + // 5. Collect Lambdas (Populate the registry with untyped templates) + LambdaCollector::collect(&bound_ast, &mut self.function_registry.borrow_mut()); + + // 6. Type Check + let checker = TypeChecker::new(self.global_types.clone()); + let typed_ast = checker.check(bound_ast, &[])?; + + // 7. Collect Typed Lambdas + LambdaCollector::collect(&typed_ast, &mut self.typed_function_registry.borrow_mut()); + + Ok(typed_ast) + } + + /// Backend: Optimization (TCO, etc.) + pub fn link(&self, node: TypedNode) -> ExecNode { + // 1. Specialize (Always performed for correctness) + let specialized = self.specialize_node(node); + + // 2. Optimize (Level 1: Cracking, Level 2: Collapsing) + let optimizer = Optimizer::new(self.optimization) + .with_globals(self.global_values.clone()) + .with_purity(self.global_purity.clone()) + .with_registry(self.typed_function_registry.clone()); + let optimized = optimizer.optimize(specialized); + + // 3. TCO (Always performed, converts to ExecNode) + TCO::optimize(optimized) + } + + fn specialize_node(&self, node: TypedNode) -> TypedNode { + let registry = Rc::new(EnvFunctionRegistry { + registry: self.function_registry.clone(), + }); + + let rtl_lookup = Rc::new(|name: &str, args: &[StaticType]| intrinsics::lookup(name, args)); + + let func_reg = self.function_registry.clone(); + let mono_cache = self.monomorph_cache.clone(); + let global_values = self.global_values.clone(); + let global_types = self.global_types.clone(); + let global_purity = self.global_purity.clone(); + let optimization = self.optimization; + + let compiler = Rc::new( + move |func_template: BoundNode, + arg_types: &[StaticType]| + -> Result<(Value, StaticType), String> { + // 1. Re-TypeCheck the template with concrete argument types + let checker = TypeChecker::new(global_types.clone()); + let retyped_ast = checker.check(func_template, arg_types)?; + + // 2. Specialize (Recursive) + let sub_registry = Rc::new(EnvFunctionRegistry { + registry: func_reg.clone(), + }); + let sub_rtl_lookup = + Rc::new(|name: &str, args: &[StaticType]| intrinsics::lookup(name, args)); + + let sub_specializer = Specializer::new( + Some(sub_registry), + None, + Some(sub_rtl_lookup), + Some(mono_cache.clone()), + ); + + let specialized_ast = sub_specializer.specialize(retyped_ast); + + // 3. Optimize (Phase 2: Cracking & Folding) + let optimizer = Optimizer::new(optimization) + .with_globals(global_values.clone()) + .with_purity(global_purity.clone()); + let optimized_ast = optimizer.optimize(specialized_ast); + + // 4. TCO (converts to ExecNode) + let tco_ast = TCO::optimize(optimized_ast); + + // 5. Compile to Value (VM) + let mut vm = VM::new(global_values.clone()); + let compiled_val = match vm.run(&tco_ast) { + Ok(v) => v, + Err(e) => return Err(format!("VM Error during specialization: {}", e)), + }; + + // 6. Determine correct return type from the newly inferred function signature + let ret_type = if let StaticType::Function(sig) = &tco_ast.ty.ty { + sig.ret.clone() + } else { + StaticType::Any + }; + + Ok((compiled_val, ret_type)) + }, + ); + + let specializer = Specializer::new( + Some(registry), + Some(compiler), + Some(rtl_lookup), + Some(self.monomorph_cache.clone()), + ); + + specializer.specialize(node) + } + + /// Runtime: Execute the linked AST in the VM + pub fn run(&self, node: &ExecNode) -> Result { + let mut vm = VM::new(self.global_values.clone()); + let mut result = vm.run(node)?; + + // Handle potential script body closure + if let Value::Object(obj) = &result + && let Some(closure) = obj.as_any().downcast_ref::() + { + result = vm.run(&closure.exec_node)?; + } + + // IMPORTANT: Resolve any pending tail call requests from the top-level execution + while let Value::TailCallRequest(payload) = result { + let (next_obj, next_args) = *payload; + if let Some(closure) = next_obj.as_any().downcast_ref::() { + result = vm.run_with_args(closure, next_args)?; + } else { + return Err(format!( + "Tail call target is not a closure: {}", + next_obj.type_name() + )); + } + } + Ok(result) + } + + pub fn run_script(&self, source: &str) -> Result { + if self.debug_mode { + let (res, logs) = self.run_debug(source)?; + for line in logs { + println!("{}", line); + } + res + } else { + let compiled = self.compile(source)?; + let linked = self.link(compiled); + self.run(&linked) + } + } + + pub fn run_debug(&self, source: &str) -> Result<(Result, Vec), String> { + let compiled = self.compile(source)?; + let linked = self.link(compiled); + + // Execute with TracingObserver + let mut vm = VM::new(self.global_values.clone()); + let mut observer = TracingObserver::new(); + let mut result = vm.run_with_observer(&mut observer, &linked); + + // If result is a closure (script entry), execute the body too + if let Ok(Value::Object(obj)) = &result + && let Some(closure) = obj.as_any().downcast_ref::() + { + result = vm.run_with_observer(&mut observer, &closure.exec_node); + } + + // Resolve top-level tail calls + while let Ok(Value::TailCallRequest(payload)) = result { + let (next_obj, next_args) = *payload; + if let Some(closure) = next_obj.as_any().downcast_ref::() { + result = vm.run_with_args_observed(&mut observer, closure, next_args); + } else { + result = Err(format!( + "Tail call target is not a closure: {}", + next_obj.type_name() + )); + break; + } + } + + Ok((result, observer.logs)) + } +} diff --git a/src/ast/lexer.rs b/src/ast/lexer.rs index 23238b7..6a9c673 100644 --- a/src/ast/lexer.rs +++ b/src/ast/lexer.rs @@ -1,191 +1,219 @@ -use std::iter::Peekable; -use std::str::Chars; -use std::rc::Rc; -use crate::ast::types::SourceLocation; - -#[derive(Debug, Clone, PartialEq)] -pub enum TokenKind { - LeftParen, RightParen, - LeftBracket, RightBracket, - LeftBrace, RightBrace, - Quote, Backtick, Tilde, At, - Identifier(Rc), - Keyword(Rc), - Integer(i64), - Float(f64), - String(Rc), - EOF, -} - -#[derive(Debug, Clone)] -pub struct Token { - pub kind: TokenKind, - pub location: SourceLocation, -} - -pub struct Lexer<'a> { - input: Peekable>, - line: u32, - col: u32, -} - -impl<'a> Lexer<'a> { - pub fn new(input: &'a str) -> Self { - Self { - input: input.chars().peekable(), - line: 1, - col: 1, - } - } - - pub fn next_token(&mut self) -> Result { - self.skip_whitespace(); - - let start_location = SourceLocation { line: self.line, col: self.col }; - - let char = match self.input.next() { - Some(c) => { - let current_char = c; - self.col += 1; - current_char - }, - None => return Ok(Token { kind: TokenKind::EOF, location: start_location }), - }; - - let kind = match char { - '(' => TokenKind::LeftParen, - ')' => TokenKind::RightParen, - '[' => TokenKind::LeftBracket, - ']' => TokenKind::RightBracket, - '{' => TokenKind::LeftBrace, - '}' => TokenKind::RightBrace, - '\'' => TokenKind::Quote, - '`' => TokenKind::Backtick, - '~' => TokenKind::Tilde, - '@' => TokenKind::At, - ':' => { - let id = self.read_while(|c| !c.is_whitespace() && !"()[]{}\"'`~@;".contains(c)); - TokenKind::Keyword(Rc::from(id)) - } - '"' => TokenKind::String(Rc::from(self.read_string()?)), - c if c.is_ascii_digit() || (c == '-' && self.input.peek().is_some_and(|p| p.is_ascii_digit())) => { - let mut num_str = String::from(c); - while let Some(&next) = self.peek() { - if next.is_ascii_digit() || next == '.' { - num_str.push(self.input.next().unwrap()); - self.col += 1; - } else { - break; - } - } - - if num_str.contains('.') { - TokenKind::Float(num_str.parse().map_err(|_| "Invalid float format")?) - } else { - TokenKind::Integer(num_str.parse().map_err(|_| "Invalid integer format")?) - } - } - c if is_ident_start(c) => { - let mut id = String::from(c); - id.push_str(&self.read_while(is_ident_char)); - TokenKind::Identifier(Rc::from(id)) - } - _ => return Err(format!("Unexpected character: {} at {}:{}", char, self.line, self.col - 1)), - }; - - Ok(Token { kind, location: start_location }) - } - - fn peek(&mut self) -> Option<&char> { - self.input.peek() - } - - fn skip_whitespace(&mut self) { - while let Some(&c) = self.peek() { - if c.is_whitespace() || is_invisible(c) || c == ',' { - if c == '\n' { - self.line += 1; - self.col = 1; - } else { - self.col += 1; - } - self.input.next(); - } else if c == ';' { - for c in self.input.by_ref() { - if c == '\n' { - self.line += 1; - self.col = 1; - break; - } - } - } else { - break; - } - } - } - - fn read_while(&mut self, mut predicate: F) -> String - where F: FnMut(char) -> bool { - let mut s = String::new(); - while let Some(&c) = self.peek() { - if predicate(c) { - s.push(self.input.next().unwrap()); - self.col += 1; - } else { - break; - } - } - s - } - - fn read_string(&mut self) -> Result { - let mut s = String::new(); - while let Some(c) = self.input.next() { - self.col += 1; - match c { - '"' => return Ok(s), - '\\' => { - let next = self.input.next().ok_or("Unterminated string escape")?; - self.col += 1; - match next { - 'n' => s.push('\n'), - 'r' => s.push('\r'), - 't' => s.push('\t'), - '\\' => s.push('\\'), - '"' => s.push('"'), - _ => s.push(next), - } - } - '\n' => { - self.line += 1; - self.col = 1; - s.push(c); - } - _ => s.push(c), - } - } - Err("Unterminated string".to_string()) - } -} - -fn is_ident_start(c: char) -> bool { - c.is_alphabetic() || "+-*/<=>!$%&_?.".contains(c) -} - -fn is_ident_char(c: char) -> bool { - is_ident_start(c) || c.is_ascii_digit() -} - -/// Returns true if the character is an invisible control character -/// that should be ignored by the lexer (like BOM or Zero Width Space). -fn is_invisible(c: char) -> bool { - match c { - '\u{FEFF}' | // BOM - '\u{200B}' | // Zero Width Space - '\u{200C}' | // Zero Width Non-Joiner - '\u{200D}' | // Zero Width Joiner - '\u{2060}' // Word Joiner - => true, - _ => false, - } -} +use crate::ast::types::SourceLocation; +use std::iter::Peekable; +use std::rc::Rc; +use std::str::Chars; + +#[derive(Debug, Clone, PartialEq)] +pub enum TokenKind { + LeftParen, + RightParen, + LeftBracket, + RightBracket, + LeftBrace, + RightBrace, + Quote, + Backtick, + Tilde, + At, + Identifier(Rc), + Keyword(Rc), + Integer(i64), + Float(f64), + String(Rc), + EOF, +} + +#[derive(Debug, Clone)] +pub struct Token { + pub kind: TokenKind, + pub location: SourceLocation, +} + +pub struct Lexer<'a> { + input: Peekable>, + line: u32, + col: u32, +} + +impl<'a> Lexer<'a> { + pub fn new(input: &'a str) -> Self { + Self { + input: input.chars().peekable(), + line: 1, + col: 1, + } + } + + pub fn next_token(&mut self) -> Result { + self.skip_whitespace(); + + let start_location = SourceLocation { + line: self.line, + col: self.col, + }; + + let char = match self.input.next() { + Some(c) => { + let current_char = c; + self.col += 1; + current_char + } + None => { + return Ok(Token { + kind: TokenKind::EOF, + location: start_location, + }); + } + }; + + let kind = match char { + '(' => TokenKind::LeftParen, + ')' => TokenKind::RightParen, + '[' => TokenKind::LeftBracket, + ']' => TokenKind::RightBracket, + '{' => TokenKind::LeftBrace, + '}' => TokenKind::RightBrace, + '\'' => TokenKind::Quote, + '`' => TokenKind::Backtick, + '~' => TokenKind::Tilde, + '@' => TokenKind::At, + ':' => { + let id = self.read_while(|c| !c.is_whitespace() && !"()[]{}\"'`~@;".contains(c)); + TokenKind::Keyword(Rc::from(id)) + } + '"' => TokenKind::String(Rc::from(self.read_string()?)), + c if c.is_ascii_digit() + || (c == '-' && self.input.peek().is_some_and(|p| p.is_ascii_digit())) => + { + let mut num_str = String::from(c); + while let Some(&next) = self.peek() { + if next.is_ascii_digit() || next == '.' { + num_str.push(self.input.next().unwrap()); + self.col += 1; + } else { + break; + } + } + + if num_str.contains('.') { + TokenKind::Float(num_str.parse().map_err(|_| "Invalid float format")?) + } else { + TokenKind::Integer(num_str.parse().map_err(|_| "Invalid integer format")?) + } + } + c if is_ident_start(c) => { + let mut id = String::from(c); + id.push_str(&self.read_while(is_ident_char)); + TokenKind::Identifier(Rc::from(id)) + } + _ => { + return Err(format!( + "Unexpected character: {} at {}:{}", + char, + self.line, + self.col - 1 + )); + } + }; + + Ok(Token { + kind, + location: start_location, + }) + } + + fn peek(&mut self) -> Option<&char> { + self.input.peek() + } + + fn skip_whitespace(&mut self) { + while let Some(&c) = self.peek() { + if c.is_whitespace() || is_invisible(c) || c == ',' { + if c == '\n' { + self.line += 1; + self.col = 1; + } else { + self.col += 1; + } + self.input.next(); + } else if c == ';' { + for c in self.input.by_ref() { + if c == '\n' { + self.line += 1; + self.col = 1; + break; + } + } + } else { + break; + } + } + } + + fn read_while(&mut self, mut predicate: F) -> String + where + F: FnMut(char) -> bool, + { + let mut s = String::new(); + while let Some(&c) = self.peek() { + if predicate(c) { + s.push(self.input.next().unwrap()); + self.col += 1; + } else { + break; + } + } + s + } + + fn read_string(&mut self) -> Result { + let mut s = String::new(); + while let Some(c) = self.input.next() { + self.col += 1; + match c { + '"' => return Ok(s), + '\\' => { + let next = self.input.next().ok_or("Unterminated string escape")?; + self.col += 1; + match next { + 'n' => s.push('\n'), + 'r' => s.push('\r'), + 't' => s.push('\t'), + '\\' => s.push('\\'), + '"' => s.push('"'), + _ => s.push(next), + } + } + '\n' => { + self.line += 1; + self.col = 1; + s.push(c); + } + _ => s.push(c), + } + } + Err("Unterminated string".to_string()) + } +} + +fn is_ident_start(c: char) -> bool { + c.is_alphabetic() || "+-*/<=>!$%&_?.".contains(c) +} + +fn is_ident_char(c: char) -> bool { + is_ident_start(c) || c.is_ascii_digit() +} + +/// Returns true if the character is an invisible control character +/// that should be ignored by the lexer (like BOM or Zero Width Space). +fn is_invisible(c: char) -> bool { + match c { + '\u{FEFF}' | // BOM + '\u{200B}' | // Zero Width Space + '\u{200C}' | // Zero Width Non-Joiner + '\u{200D}' | // Zero Width Joiner + '\u{2060}' // Word Joiner + => true, + _ => false, + } +} diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 2cce653..c93cac0 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -1,8 +1,8 @@ -pub mod types; -pub mod lexer; -pub mod nodes; -pub mod parser; -pub mod compiler; -pub mod environment; -pub mod vm; -pub mod rtl; +pub mod compiler; +pub mod environment; +pub mod lexer; +pub mod nodes; +pub mod parser; +pub mod rtl; +pub mod types; +pub mod vm; diff --git a/src/ast/nodes.rs b/src/ast/nodes.rs index 8fe5680..0812573 100644 --- a/src/ast/nodes.rs +++ b/src/ast/nodes.rs @@ -1,112 +1,118 @@ -use std::rc::Rc; -use std::fmt::Debug; -use std::any::Any; -use crate::ast::types::{Identity, Value, Object}; - -/// A name with an optional context for macro hygiene. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct Symbol { - pub name: Rc, - /// Points to the identity of the Expansion node if this symbol - /// was created/referenced inside a macro expansion. - pub context: Option, -} - -impl From> for Symbol { - fn from(name: Rc) -> Self { - Self { name, context: None } - } -} - -impl From<&str> for Symbol { - fn from(name: &str) -> Self { - Self { name: Rc::from(name), context: None } - } -} - -/// A generic AST Node wrapper to preserve identity and metadata -#[derive(Debug, Clone, PartialEq)] -pub struct Node { - pub identity: Identity, - pub kind: K, - pub ty: T, -} - -impl Object for Node { - fn type_name(&self) -> &'static str { - "ast-node" - } - fn as_any(&self) -> &dyn Any { - self - } -} - -/// The base for custom node types (extensions) -pub trait CustomNode: Debug { - fn display_name(&self) -> &'static str; - fn clone_box(&self) -> Box; -} - -impl Clone for Box { - fn clone(&self) -> Self { - self.clone_box() - } -} - -#[derive(Debug, Clone)] -pub enum UntypedKind { - Nop, - Constant(Value), - Identifier(Symbol), - Parameter(Symbol), - If { - cond: Box>, - then_br: Box>, - else_br: Option>>, - }, - Def { - name: Symbol, - value: Box>, - }, - Assign { - target: Box>, - value: Box>, - }, - Lambda { - params: Box>, - body: Rc>, - }, - Call { - callee: Box>, - args: Box>, - }, - Block { - exprs: Vec>, - }, - Tuple { - elements: Vec>, - }, - Record { - fields: Vec<(Node, Node)>, - }, - /// A macro declaration that can be expanded at compile time. - MacroDecl { - name: Symbol, - params: Box>, - body: Box>, - }, - /// A template for AST nodes, allowing for substitutions. (Quasiquote) - Template(Box>), - /// A placeholder inside a template to be replaced by a node or value. (Unquote) - Placeholder(Box>), - /// A placeholder that flattens a sequence or merges a record into its surroundings. (Splice) - Splice(Box>), - /// Represents an expanded macro call, preserving the original call for debugging. - Expansion { - /// The original call from the source AST. - call: Box>, - /// The resulting AST after macro expansion. - expanded: Box>, - }, - Extension(Box), -} +use crate::ast::types::{Identity, Object, Value}; +use std::any::Any; +use std::fmt::Debug; +use std::rc::Rc; + +/// A name with an optional context for macro hygiene. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct Symbol { + pub name: Rc, + /// Points to the identity of the Expansion node if this symbol + /// was created/referenced inside a macro expansion. + pub context: Option, +} + +impl From> for Symbol { + fn from(name: Rc) -> Self { + Self { + name, + context: None, + } + } +} + +impl From<&str> for Symbol { + fn from(name: &str) -> Self { + Self { + name: Rc::from(name), + context: None, + } + } +} + +/// A generic AST Node wrapper to preserve identity and metadata +#[derive(Debug, Clone, PartialEq)] +pub struct Node { + pub identity: Identity, + pub kind: K, + pub ty: T, +} + +impl Object for Node { + fn type_name(&self) -> &'static str { + "ast-node" + } + fn as_any(&self) -> &dyn Any { + self + } +} + +/// The base for custom node types (extensions) +pub trait CustomNode: Debug { + fn display_name(&self) -> &'static str; + fn clone_box(&self) -> Box; +} + +impl Clone for Box { + fn clone(&self) -> Self { + self.clone_box() + } +} + +#[derive(Debug, Clone)] +pub enum UntypedKind { + Nop, + Constant(Value), + Identifier(Symbol), + Parameter(Symbol), + If { + cond: Box>, + then_br: Box>, + else_br: Option>>, + }, + Def { + name: Symbol, + value: Box>, + }, + Assign { + target: Box>, + value: Box>, + }, + Lambda { + params: Box>, + body: Rc>, + }, + Call { + callee: Box>, + args: Box>, + }, + Block { + exprs: Vec>, + }, + Tuple { + elements: Vec>, + }, + Record { + fields: Vec<(Node, Node)>, + }, + /// A macro declaration that can be expanded at compile time. + MacroDecl { + name: Symbol, + params: Box>, + body: Box>, + }, + /// A template for AST nodes, allowing for substitutions. (Quasiquote) + Template(Box>), + /// A placeholder inside a template to be replaced by a node or value. (Unquote) + Placeholder(Box>), + /// A placeholder that flattens a sequence or merges a record into its surroundings. (Splice) + Splice(Box>), + /// Represents an expanded macro call, preserving the original call for debugging. + Expansion { + /// The original call from the source AST. + call: Box>, + /// The resulting AST after macro expansion. + expanded: Box>, + }, + Extension(Box), +} diff --git a/src/ast/parser.rs b/src/ast/parser.rs index 1bed25b..7344cc3 100644 --- a/src/ast/parser.rs +++ b/src/ast/parser.rs @@ -1,345 +1,400 @@ -use std::rc::Rc; -use crate::ast::lexer::{Lexer, Token, TokenKind}; -use crate::ast::types::{Identity, NodeIdentity, Value, Keyword}; -use crate::ast::nodes::{Node, UntypedKind, Symbol}; - -pub struct Parser<'a> { - lexer: Lexer<'a>, - current_token: Token, -} - -impl<'a> Parser<'a> { - pub fn new(input: &'a str) -> Result { - let mut lexer = Lexer::new(input); - let current_token = lexer.next_token()?; - Ok(Self { lexer, current_token }) - } - - fn advance(&mut self) -> Result { - let prev = std::mem::replace(&mut self.current_token, self.lexer.next_token()?); - Ok(prev) - } - - fn peek(&self) -> &TokenKind { - &self.current_token.kind - } - - pub fn parse_expression(&mut self) -> Result, String> { - let token_loc = self.current_token.location; - let identity = Rc::new(NodeIdentity { location: token_loc }); - - match self.peek() { - TokenKind::LeftParen => self.parse_list(), - TokenKind::LeftBracket => self.parse_vector_literal(), - TokenKind::LeftBrace => self.parse_record_literal(), - TokenKind::Quote => { - self.advance()?; // consume ' - let expr = self.parse_expression()?; - Ok(Node { - identity: identity.clone(), - kind: UntypedKind::Call { - callee: Box::new(self.make_id_node("quote", identity.clone())), - args: Box::new(Node { - identity, - kind: UntypedKind::Tuple { elements: vec![expr] }, - ty: (), - }), - }, - ty: (), - }) - } - TokenKind::Backtick => { - self.advance()?; // consume ` - let expr = self.parse_expression()?; - Ok(Node { - identity, - kind: UntypedKind::Template(Box::new(expr)), - ty: (), - }) - } - TokenKind::Tilde => { - self.advance()?; // consume ~ - if *self.peek() == TokenKind::At { - self.advance()?; // consume @ - let expr = self.parse_expression()?; - Ok(Node { - identity, - kind: UntypedKind::Splice(Box::new(expr)), - ty: (), - }) - } else { - let expr = self.parse_expression()?; - Ok(Node { - identity, - kind: UntypedKind::Placeholder(Box::new(expr)), - ty: (), - }) - } - } - _ => self.parse_atom(), - } - } - - pub fn at_eof(&self) -> bool { - matches!(self.current_token.kind, TokenKind::EOF) - } - - fn parse_atom(&mut self) -> Result, String> { - let token = self.advance()?; - let identity = Rc::new(NodeIdentity { location: token.location }); - - let kind = match token.kind { - TokenKind::Integer(n) => UntypedKind::Constant(Value::Int(n)), - TokenKind::Float(n) => UntypedKind::Constant(Value::Float(n)), - TokenKind::String(s) => UntypedKind::Constant(Value::Text(s)), - TokenKind::Keyword(k) => UntypedKind::Constant(Value::Keyword(Keyword::intern(&k))), - TokenKind::Identifier(id) => { - match id.as_ref() { - "..." => UntypedKind::Nop, - _ => UntypedKind::Identifier(id.into()), - } - } - _ => return Err(format!("Unexpected token in atom: {:?} at {:?}", token.kind, token.location)), - }; - - Ok(Node { identity, kind, ty: () }) - } - - fn parse_list(&mut self) -> Result, String> { - let start_loc = self.advance()?.location; // consume '(' - let identity = Rc::new(NodeIdentity { location: start_loc }); - - if *self.peek() == TokenKind::RightParen { - return Err(format!("Empty list () is not a valid expression at {:?}", start_loc)); - } - - let head = self.parse_expression()?; - - let result = if let UntypedKind::Identifier(ref sym) = head.kind { - match sym.name.as_ref() { - "if" => self.parse_if(identity), - "fn" => self.parse_fn(identity), - "def" => self.parse_def(identity), - "assign" => self.parse_assign(identity), - "do" => self.parse_do(identity), - "macro" => self.parse_macro_decl(identity), - _ => self.parse_call(head, identity), - } - } else { - self.parse_call(head, identity) - }; - - self.expect(TokenKind::RightParen)?; - result - } - - fn parse_if(&mut self, identity: Identity) -> Result, String> { - 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()?)); - } - - Ok(Node { - identity, - kind: UntypedKind::If { cond, then_br, else_br }, - ty: (), - }) - } - - fn parse_def(&mut self, identity: Identity) -> Result, String> { - let name_node = self.parse_expression()?; - let name = match name_node.kind { - UntypedKind::Identifier(sym) => sym, - _ => return Err("Expected identifier for def name".to_string()), - }; - - let value = Box::new(self.parse_expression()?); - - Ok(Node { - identity, - kind: UntypedKind::Def { name, value }, - ty: (), - }) - } - - fn parse_assign(&mut self, identity: Identity) -> Result, String> { - // (assign target value) - let target = Box::new(self.parse_expression()?); - let value = Box::new(self.parse_expression()?); - - Ok(Node { - identity, - kind: UntypedKind::Assign { target, value }, - ty: (), - }) - } - - fn parse_do(&mut self, identity: Identity) -> Result, String> { - let mut exprs = Vec::new(); - while *self.peek() != TokenKind::RightParen && *self.peek() != TokenKind::EOF { - exprs.push(self.parse_expression()?); - } - Ok(Node { - identity, - kind: UntypedKind::Block { exprs }, - ty: (), - }) - } - - fn parse_fn(&mut self, identity: Identity) -> Result, String> { - let params = Box::new(self.parse_param_vector()?); - let body = self.parse_expression()?; - - Ok(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()?; - let name = match name_node.kind { - UntypedKind::Identifier(sym) => sym, - _ => return Err("Expected identifier for macro name".to_string()), - }; - let params = Box::new(self.parse_param_vector()?); - let body = self.parse_expression()?; - Ok(Node { - identity, - kind: UntypedKind::MacroDecl { name, params, body: Box::new(body) }, - ty: (), - }) - } - - fn parse_param_vector(&mut self) -> Result, String> { - if *self.peek() != TokenKind::LeftBracket { - return Err(format!("Expected parameter vector [...] for fn, found {:?}", self.peek())); - } - let token = self.advance()?; - let identity = Rc::new(NodeIdentity { location: token.location }); - - let mut elements = Vec::new(); - while *self.peek() != TokenKind::RightBracket { - let next_peek = self.peek(); - match next_peek { - TokenKind::Identifier(name) => { - let name = name.clone(); - let token = self.advance()?; - let p_identity = Rc::new(NodeIdentity { location: token.location }); - elements.push(Node { - identity: p_identity, - kind: UntypedKind::Parameter(name.into()), - ty: (), - }); - }, - TokenKind::LeftBracket => { - elements.push(self.parse_param_vector()?); - }, - _ => return Err(format!("Expected identifier or nested parameter vector, found {:?}", next_peek)), - } - } - self.expect(TokenKind::RightBracket)?; - Ok(Node { - identity, - kind: UntypedKind::Tuple { elements }, - ty: (), - }) - } - - fn parse_call(&mut self, callee: Node, identity: Identity) -> Result, String> { - let mut elements = Vec::new(); - - while *self.peek() != TokenKind::RightParen && *self.peek() != TokenKind::EOF { - elements.push(self.parse_expression()?); - } - - // The arguments are wrapped in a Tuple node, reusing the call's identity/location. - let args_node = Node { - identity: identity.clone(), - kind: UntypedKind::Tuple { elements }, - ty: (), - }; - - Ok(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()?; - let mut elements = Vec::new(); - - while *self.peek() != TokenKind::RightBracket && *self.peek() != TokenKind::EOF { - let expr = self.parse_expression()?; - elements.push(expr); - } - - self.expect(TokenKind::RightBracket)?; - - Ok(Node { - identity: Rc::new(NodeIdentity { location: token.location }), - kind: UntypedKind::Tuple { elements }, - ty: (), - }) - } - - fn parse_record_literal(&mut self) -> Result, String> { - 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()?; - // 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()), - } - - if *self.peek() == TokenKind::RightBrace { - return Err("Record literal must have even number of forms".to_string()); - } - let val_node = self.parse_expression()?; - - fields.push((key_node, val_node)); - } - self.expect(TokenKind::RightBrace)?; - - Ok(Node { - identity: Rc::new(NodeIdentity { location: token.location }), - kind: UntypedKind::Record { fields }, - ty: (), - }) - } - - fn expect(&mut self, kind: TokenKind) -> Result<(), String> { - let token = self.advance()?; - if token.kind == kind { - Ok(()) - } else { - Err(format!("Expected {:?}, but found {:?} at {:?}", kind, token.kind, token.location)) - } - } - - fn make_id_node(&self, name: &str, identity: Identity) -> Node { - Node { - identity, - kind: UntypedKind::Identifier(Symbol::from(name)), - ty: (), - } - } -} +use crate::ast::lexer::{Lexer, Token, TokenKind}; +use crate::ast::nodes::{Node, Symbol, UntypedKind}; +use crate::ast::types::{Identity, Keyword, NodeIdentity, Value}; +use std::rc::Rc; + +pub struct Parser<'a> { + lexer: Lexer<'a>, + current_token: Token, +} + +impl<'a> Parser<'a> { + pub fn new(input: &'a str) -> Result { + let mut lexer = Lexer::new(input); + let current_token = lexer.next_token()?; + Ok(Self { + lexer, + current_token, + }) + } + + fn advance(&mut self) -> Result { + let prev = std::mem::replace(&mut self.current_token, self.lexer.next_token()?); + Ok(prev) + } + + fn peek(&self) -> &TokenKind { + &self.current_token.kind + } + + pub fn parse_expression(&mut self) -> Result, String> { + let token_loc = self.current_token.location; + let identity = Rc::new(NodeIdentity { + location: token_loc, + }); + + match self.peek() { + TokenKind::LeftParen => self.parse_list(), + TokenKind::LeftBracket => self.parse_vector_literal(), + TokenKind::LeftBrace => self.parse_record_literal(), + TokenKind::Quote => { + self.advance()?; // consume ' + let expr = self.parse_expression()?; + Ok(Node { + identity: identity.clone(), + kind: UntypedKind::Call { + callee: Box::new(self.make_id_node("quote", identity.clone())), + args: Box::new(Node { + identity, + kind: UntypedKind::Tuple { + elements: vec![expr], + }, + ty: (), + }), + }, + ty: (), + }) + } + TokenKind::Backtick => { + self.advance()?; // consume ` + let expr = self.parse_expression()?; + Ok(Node { + identity, + kind: UntypedKind::Template(Box::new(expr)), + ty: (), + }) + } + TokenKind::Tilde => { + self.advance()?; // consume ~ + if *self.peek() == TokenKind::At { + self.advance()?; // consume @ + let expr = self.parse_expression()?; + Ok(Node { + identity, + kind: UntypedKind::Splice(Box::new(expr)), + ty: (), + }) + } else { + let expr = self.parse_expression()?; + Ok(Node { + identity, + kind: UntypedKind::Placeholder(Box::new(expr)), + ty: (), + }) + } + } + _ => self.parse_atom(), + } + } + + pub fn at_eof(&self) -> bool { + matches!(self.current_token.kind, TokenKind::EOF) + } + + fn parse_atom(&mut self) -> Result, String> { + let token = self.advance()?; + let identity = Rc::new(NodeIdentity { + location: token.location, + }); + + let kind = match token.kind { + TokenKind::Integer(n) => UntypedKind::Constant(Value::Int(n)), + TokenKind::Float(n) => UntypedKind::Constant(Value::Float(n)), + TokenKind::String(s) => UntypedKind::Constant(Value::Text(s)), + TokenKind::Keyword(k) => UntypedKind::Constant(Value::Keyword(Keyword::intern(&k))), + TokenKind::Identifier(id) => match id.as_ref() { + "..." => UntypedKind::Nop, + _ => UntypedKind::Identifier(id.into()), + }, + _ => { + return Err(format!( + "Unexpected token in atom: {:?} at {:?}", + token.kind, token.location + )); + } + }; + + Ok(Node { + identity, + kind, + ty: (), + }) + } + + fn parse_list(&mut self) -> Result, String> { + let start_loc = self.advance()?.location; // consume '(' + let identity = Rc::new(NodeIdentity { + location: start_loc, + }); + + if *self.peek() == TokenKind::RightParen { + return Err(format!( + "Empty list () is not a valid expression at {:?}", + start_loc + )); + } + + let head = self.parse_expression()?; + + let result = if let UntypedKind::Identifier(ref sym) = head.kind { + match sym.name.as_ref() { + "if" => self.parse_if(identity), + "fn" => self.parse_fn(identity), + "def" => self.parse_def(identity), + "assign" => self.parse_assign(identity), + "do" => self.parse_do(identity), + "macro" => self.parse_macro_decl(identity), + _ => self.parse_call(head, identity), + } + } else { + self.parse_call(head, identity) + }; + + self.expect(TokenKind::RightParen)?; + result + } + + fn parse_if(&mut self, identity: Identity) -> Result, String> { + 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()?)); + } + + Ok(Node { + identity, + kind: UntypedKind::If { + cond, + then_br, + else_br, + }, + ty: (), + }) + } + + fn parse_def(&mut self, identity: Identity) -> Result, String> { + let name_node = self.parse_expression()?; + let name = match name_node.kind { + UntypedKind::Identifier(sym) => sym, + _ => return Err("Expected identifier for def name".to_string()), + }; + + let value = Box::new(self.parse_expression()?); + + Ok(Node { + identity, + kind: UntypedKind::Def { name, value }, + ty: (), + }) + } + + fn parse_assign(&mut self, identity: Identity) -> Result, String> { + // (assign target value) + let target = Box::new(self.parse_expression()?); + let value = Box::new(self.parse_expression()?); + + Ok(Node { + identity, + kind: UntypedKind::Assign { target, value }, + ty: (), + }) + } + + fn parse_do(&mut self, identity: Identity) -> Result, String> { + let mut exprs = Vec::new(); + while *self.peek() != TokenKind::RightParen && *self.peek() != TokenKind::EOF { + exprs.push(self.parse_expression()?); + } + Ok(Node { + identity, + kind: UntypedKind::Block { exprs }, + ty: (), + }) + } + + fn parse_fn(&mut self, identity: Identity) -> Result, String> { + let params = Box::new(self.parse_param_vector()?); + let body = self.parse_expression()?; + + Ok(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()?; + let name = match name_node.kind { + UntypedKind::Identifier(sym) => sym, + _ => return Err("Expected identifier for macro name".to_string()), + }; + let params = Box::new(self.parse_param_vector()?); + let body = self.parse_expression()?; + Ok(Node { + identity, + kind: UntypedKind::MacroDecl { + name, + params, + body: Box::new(body), + }, + ty: (), + }) + } + + fn parse_param_vector(&mut self) -> Result, String> { + if *self.peek() != TokenKind::LeftBracket { + return Err(format!( + "Expected parameter vector [...] for fn, found {:?}", + self.peek() + )); + } + let token = self.advance()?; + let identity = Rc::new(NodeIdentity { + location: token.location, + }); + + let mut elements = Vec::new(); + while *self.peek() != TokenKind::RightBracket { + let next_peek = self.peek(); + match next_peek { + TokenKind::Identifier(name) => { + let name = name.clone(); + let token = self.advance()?; + let p_identity = Rc::new(NodeIdentity { + location: token.location, + }); + elements.push(Node { + identity: p_identity, + kind: UntypedKind::Parameter(name.into()), + ty: (), + }); + } + TokenKind::LeftBracket => { + elements.push(self.parse_param_vector()?); + } + _ => { + return Err(format!( + "Expected identifier or nested parameter vector, found {:?}", + next_peek + )); + } + } + } + self.expect(TokenKind::RightBracket)?; + Ok(Node { + identity, + kind: UntypedKind::Tuple { elements }, + ty: (), + }) + } + + fn parse_call( + &mut self, + callee: Node, + identity: Identity, + ) -> Result, String> { + let mut elements = Vec::new(); + + while *self.peek() != TokenKind::RightParen && *self.peek() != TokenKind::EOF { + elements.push(self.parse_expression()?); + } + + // The arguments are wrapped in a Tuple node, reusing the call's identity/location. + let args_node = Node { + identity: identity.clone(), + kind: UntypedKind::Tuple { elements }, + ty: (), + }; + + Ok(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()?; + let mut elements = Vec::new(); + + while *self.peek() != TokenKind::RightBracket && *self.peek() != TokenKind::EOF { + let expr = self.parse_expression()?; + elements.push(expr); + } + + self.expect(TokenKind::RightBracket)?; + + Ok(Node { + identity: Rc::new(NodeIdentity { + location: token.location, + }), + kind: UntypedKind::Tuple { elements }, + ty: (), + }) + } + + fn parse_record_literal(&mut self) -> Result, String> { + 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()?; + // 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()), + } + + if *self.peek() == TokenKind::RightBrace { + return Err("Record literal must have even number of forms".to_string()); + } + let val_node = self.parse_expression()?; + + fields.push((key_node, val_node)); + } + self.expect(TokenKind::RightBrace)?; + + Ok(Node { + identity: Rc::new(NodeIdentity { + location: token.location, + }), + kind: UntypedKind::Record { fields }, + ty: (), + }) + } + + fn expect(&mut self, kind: TokenKind) -> Result<(), String> { + let token = self.advance()?; + if token.kind == kind { + Ok(()) + } else { + Err(format!( + "Expected {:?}, but found {:?} at {:?}", + kind, token.kind, token.location + )) + } + } + + fn make_id_node(&self, name: &str, identity: Identity) -> Node { + Node { + identity, + kind: UntypedKind::Identifier(Symbol::from(name)), + ty: (), + } + } +} diff --git a/src/ast/rtl/core.rs b/src/ast/rtl/core.rs index 3c6fff3..59b7232 100644 --- a/src/ast/rtl/core.rs +++ b/src/ast/rtl/core.rs @@ -1,334 +1,473 @@ -use std::rc::Rc; -use crate::ast::types::{Value, StaticType, Signature}; -use crate::ast::environment::Environment; - - -pub fn register(env: &Environment) { - register_constants(env); - register_arithmetic(env); - register_comparison(env); - register_logic(env); -} - -fn register_constants(env: &Environment) { - // True/False are keywords or literals in parser, but could be exposed as constants too if needed. - // In Delphi RTL: CFalse, CTrue, CNaN - - // We register NaN as a value, not a function. - env.register_constant("NaN", StaticType::Float, Value::Float(f64::NAN)); - env.register_constant("true", StaticType::Bool, Value::Bool(true)); - env.register_constant("false", StaticType::Bool, Value::Bool(false)); -} - -fn register_arithmetic(env: &Environment) { - // --- Add (+) --- - let add_ty = StaticType::FunctionOverloads(vec![ - Signature { params: StaticType::Tuple(vec![StaticType::Int, StaticType::Int]), ret: StaticType::Int }, - Signature { params: StaticType::Tuple(vec![StaticType::Float, StaticType::Float]), ret: StaticType::Float }, - Signature { params: StaticType::Tuple(vec![StaticType::Text, StaticType::Text]), ret: StaticType::Text }, - Signature { params: StaticType::Tuple(vec![StaticType::DateTime, StaticType::Int]), ret: StaticType::DateTime }, - ]); - env.register_native("+", add_ty, true, |args| { - if args.len() == 2 { - match (&args[0], &args[1]) { - (Value::Int(a), Value::Int(b)) => Value::Int(a + b), - (Value::Float(a), Value::Float(b)) => Value::Float(a + b), - (Value::Int(a), Value::Float(b)) => Value::Float(*a as f64 + b), - (Value::Float(a), Value::Int(b)) => Value::Float(a + *b as f64), - (Value::Text(a), Value::Text(b)) => { - let mut res = a.to_string(); - res.push_str(b); - Value::Text(Rc::from(res)) - }, - (Value::DateTime(ts), Value::Int(ms)) => Value::DateTime(ts + ms), - _ => Value::Void, - } - } else { - // Variadic sum - let mut acc = 0.0; - for arg in args { - if let Value::Int(i) = arg { acc += i as f64; } - else if let Value::Float(f) = arg { acc += f; } - } - if acc.fract() == 0.0 { Value::Int(acc as i64) } else { Value::Float(acc) } - } - }); - - // --- Subtract (-) --- - let sub_ty = StaticType::FunctionOverloads(vec![ - Signature { params: StaticType::Tuple(vec![StaticType::Int, StaticType::Int]), ret: StaticType::Int }, - Signature { params: StaticType::Tuple(vec![StaticType::Float, StaticType::Float]), ret: StaticType::Float }, - Signature { params: StaticType::Tuple(vec![StaticType::Int]), ret: StaticType::Int }, // Negation - Signature { params: StaticType::Tuple(vec![StaticType::Float]), ret: StaticType::Float }, - Signature { params: StaticType::Tuple(vec![StaticType::DateTime, StaticType::DateTime]), ret: StaticType::Int }, - Signature { params: StaticType::Tuple(vec![StaticType::DateTime, StaticType::Int]), ret: StaticType::DateTime }, - ]); - env.register_native("-", sub_ty, true, |args| { - if args.is_empty() { return Value::Void; } - if args.len() == 1 { - return match args[0] { - Value::Int(i) => Value::Int(-i), - Value::Float(f) => Value::Float(-f), - _ => Value::Void, - }; - } - if args.len() == 2 { - return match (&args[0], &args[1]) { - (Value::Int(a), Value::Int(b)) => Value::Int(a - b), - (Value::Float(a), Value::Float(b)) => Value::Float(a - b), - (Value::Int(a), Value::Float(b)) => Value::Float(*a as f64 - b), - (Value::Float(a), Value::Int(b)) => Value::Float(a - *b as f64), - (Value::DateTime(a), Value::DateTime(b)) => Value::Int(a - b), - (Value::DateTime(a), Value::Int(b)) => Value::DateTime(a - b), - _ => Value::Void, - }; - } - // Variadic sub - let mut acc = match args[0] { - Value::Int(i) => i as f64, - Value::Float(f) => f, - _ => return Value::Void, - }; - for arg in &args[1..] { - if let Value::Int(i) = arg { acc -= *i as f64; } - else if let Value::Float(f) = arg { acc -= f; } - } - if acc.fract() == 0.0 { Value::Int(acc as i64) } else { Value::Float(acc) } - }); - - // --- Multiply (*) --- - let mul_ty = StaticType::FunctionOverloads(vec![ - Signature { params: StaticType::Tuple(vec![StaticType::Int, StaticType::Int]), ret: StaticType::Int }, - Signature { params: StaticType::Tuple(vec![StaticType::Float, StaticType::Float]), ret: StaticType::Float }, - ]); - env.register_native("*", mul_ty, true, |args| { - if args.len() == 2 { - match (&args[0], &args[1]) { - (Value::Int(a), Value::Int(b)) => Value::Int(a * b), - (Value::Float(a), Value::Float(b)) => Value::Float(a * b), - (Value::Int(a), Value::Float(b)) => Value::Float(*a as f64 * b), - (Value::Float(a), Value::Int(b)) => Value::Float(a * *b as f64), - _ => Value::Void, - } - } else { - let mut acc = 1.0; - for arg in args { - if let Value::Int(i) = arg { acc *= i as f64; } - else if let Value::Float(f) = arg { acc *= f; } - } - if acc.fract() == 0.0 { Value::Int(acc as i64) } else { Value::Float(acc) } - } - }); - - // --- Divide (/) --- - let div_ty = StaticType::FunctionOverloads(vec![ - Signature { params: StaticType::Tuple(vec![StaticType::Int, StaticType::Int]), ret: StaticType::Float }, - Signature { params: StaticType::Tuple(vec![StaticType::Float, StaticType::Float]), ret: StaticType::Float }, - ]); - env.register_native("/", div_ty, true, |args| { - if args.len() != 2 { return Value::Void; } - let a = match args[0] { Value::Int(i) => i as f64, Value::Float(f) => f, _ => return Value::Void }; - let b = match args[1] { Value::Int(i) => i as f64, Value::Float(f) => f, _ => return Value::Void }; - if b == 0.0 { Value::Float(f64::NAN) } else { Value::Float(a / b) } - }); - - // --- Integer Divide (//) --- - let int_div_ty = StaticType::Function(Box::new( - Signature { params: StaticType::Tuple(vec![StaticType::Int, StaticType::Int]), ret: StaticType::Int } - )); - env.register_native("//", int_div_ty, true, |args| { - if args.len() != 2 { return Value::Void; } - match (&args[0], &args[1]) { - (Value::Int(a), Value::Int(b)) => { - if *b == 0 { Value::Void } else { Value::Int(a / b) } - }, - // Also allow float truncation? Delphi RTL says IntDivide takes DataValue and returns DataValue. - // Usually div is for integers. Let's stick to Int. - _ => Value::Void, - } - }); - - // --- Modulus (%) --- - let mod_ty = StaticType::Function(Box::new( - Signature { params: StaticType::Tuple(vec![StaticType::Int, StaticType::Int]), ret: StaticType::Int } - )); - env.register_native("%", mod_ty, true, |args| { - if args.len() != 2 { return Value::Void; } - match (&args[0], &args[1]) { - (Value::Int(a), Value::Int(b)) => { - if *b == 0 { Value::Void } else { Value::Int(a % b) } - }, - _ => Value::Void, - } - }); -} - -fn register_comparison(env: &Environment) { - let cmp_ty = StaticType::FunctionOverloads(vec![ - Signature { params: StaticType::Tuple(vec![StaticType::Int, StaticType::Int]), ret: StaticType::Bool }, - Signature { params: StaticType::Tuple(vec![StaticType::Float, StaticType::Float]), ret: StaticType::Bool }, - Signature { params: StaticType::Tuple(vec![StaticType::DateTime, StaticType::DateTime]), ret: StaticType::Bool }, - ]); - - // --- Greater Than (>) --- - env.register_native(">", cmp_ty.clone(), true, |args| { - if args.len() != 2 { return Value::Void; } - match (&args[0], &args[1]) { - (Value::Int(a), Value::Int(b)) => Value::Bool(a > b), - (Value::Int(a), Value::Float(b)) => Value::Bool((*a as f64) > *b), - (Value::Float(a), Value::Int(b)) => Value::Bool(*a > (*b as f64)), - (Value::Float(a), Value::Float(b)) => Value::Bool(a > b), - (Value::DateTime(a), Value::DateTime(b)) => Value::Bool(a > b), - _ => Value::Bool(false), - } - }); - - // --- Less Than (<) --- - env.register_native("<", cmp_ty.clone(), true, |args| { - if args.len() != 2 { return Value::Void; } - match (&args[0], &args[1]) { - (Value::Int(a), Value::Int(b)) => Value::Bool(a < b), - (Value::Int(a), Value::Float(b)) => Value::Bool((*a as f64) < *b), - (Value::Float(a), Value::Int(b)) => Value::Bool(*a < (*b as f64)), - (Value::Float(a), Value::Float(b)) => Value::Bool(a < b), - (Value::DateTime(a), Value::DateTime(b)) => Value::Bool(a < b), - _ => Value::Bool(false), - } - }); - - // --- Greater Or Equal (>=) --- - env.register_native(">=", cmp_ty.clone(), true, |args| { - if args.len() != 2 { return Value::Void; } - match (&args[0], &args[1]) { - (Value::Int(a), Value::Int(b)) => Value::Bool(a >= b), - (Value::Int(a), Value::Float(b)) => Value::Bool((*a as f64) >= *b), - (Value::Float(a), Value::Int(b)) => Value::Bool(*a >= (*b as f64)), - (Value::Float(a), Value::Float(b)) => Value::Bool(a >= b), - (Value::DateTime(a), Value::DateTime(b)) => Value::Bool(a >= b), - _ => Value::Bool(false), - } - }); - - // --- Less Or Equal (<=) --- - env.register_native("<=", cmp_ty.clone(), true, |args| { - if args.len() != 2 { return Value::Void; } - match (&args[0], &args[1]) { - (Value::Int(a), Value::Int(b)) => Value::Bool(a <= b), - (Value::Int(a), Value::Float(b)) => Value::Bool((*a as f64) <= *b), - (Value::Float(a), Value::Int(b)) => Value::Bool(*a <= (*b as f64)), - (Value::Float(a), Value::Float(b)) => Value::Bool(a <= b), - (Value::DateTime(a), Value::DateTime(b)) => Value::Bool(a <= b), - _ => Value::Bool(false), - } - }); - - // --- Equal (=) --- - let eq_ty = StaticType::Function(Box::new( - Signature { params: StaticType::Tuple(vec![StaticType::Any, StaticType::Any]), ret: StaticType::Bool } - )); - env.register_native("=", eq_ty.clone(), true, |args| { - if args.len() != 2 { return Value::Void; } - // Simple equality check. - // Note: Floating point equality is tricky, but we follow standard behavior for now. - match (&args[0], &args[1]) { - (Value::Int(a), Value::Int(b)) => Value::Bool(a == b), - (Value::Float(a), Value::Float(b)) => Value::Bool((a - b).abs() < f64::EPSILON), - (Value::Int(a), Value::Float(b)) => Value::Bool((*a as f64 - b).abs() < f64::EPSILON), - (Value::Float(a), Value::Int(b)) => Value::Bool((a - *b as f64).abs() < f64::EPSILON), - (Value::Text(a), Value::Text(b)) => Value::Bool(a == b), - (Value::Bool(a), Value::Bool(b)) => Value::Bool(a == b), - (Value::Keyword(a), Value::Keyword(b)) => Value::Bool(a == b), - (Value::DateTime(a), Value::DateTime(b)) => Value::Bool(a == b), - (Value::Void, Value::Void) => Value::Bool(true), - _ => Value::Bool(false), - } - }); - - // --- Not Equal (<>) --- - env.register_native("<>", eq_ty, true, |args| { - if args.len() != 2 { return Value::Void; } - match (&args[0], &args[1]) { - (Value::Int(a), Value::Int(b)) => Value::Bool(a != b), - (Value::Float(a), Value::Float(b)) => Value::Bool((a - b).abs() >= f64::EPSILON), - (Value::Int(a), Value::Float(b)) => Value::Bool((*a as f64 - b).abs() >= f64::EPSILON), - (Value::Float(a), Value::Int(b)) => Value::Bool((a - *b as f64).abs() >= f64::EPSILON), - (Value::Text(a), Value::Text(b)) => Value::Bool(a != b), - (Value::Bool(a), Value::Bool(b)) => Value::Bool(a != b), - (Value::Keyword(a), Value::Keyword(b)) => Value::Bool(a != b), - (Value::DateTime(a), Value::DateTime(b)) => Value::Bool(a != b), - (Value::Void, Value::Void) => Value::Bool(false), - _ => Value::Bool(true), - } - }); -} - -fn register_logic(env: &Environment) { - // --- Not (not) --- - let not_ty = StaticType::FunctionOverloads(vec![ - Signature { params: StaticType::Tuple(vec![StaticType::Bool]), ret: StaticType::Bool }, - Signature { params: StaticType::Tuple(vec![StaticType::Int]), ret: StaticType::Int }, - ]); - env.register_native("not", not_ty, true, |args| { - if args.len() != 1 { return Value::Void; } - match &args[0] { - Value::Bool(b) => Value::Bool(!b), - Value::Int(i) => Value::Int(!i), // Bitwise NOT - _ => Value::Void, - } - }); - - // --- And (and) --- - let logic_op_ty = StaticType::FunctionOverloads(vec![ - Signature { params: StaticType::Tuple(vec![StaticType::Bool, StaticType::Bool]), ret: StaticType::Bool }, - Signature { params: StaticType::Tuple(vec![StaticType::Int, StaticType::Int]), ret: StaticType::Int }, - ]); - env.register_native("and", logic_op_ty.clone(), true, |args| { - if args.len() != 2 { return Value::Void; } - match (&args[0], &args[1]) { - (Value::Bool(a), Value::Bool(b)) => Value::Bool(*a && *b), - (Value::Int(a), Value::Int(b)) => Value::Int(a & b), - _ => Value::Void, - } - }); - - // --- Or (or) --- - env.register_native("or", logic_op_ty.clone(), true, |args| { - if args.len() != 2 { return Value::Void; } - match (&args[0], &args[1]) { - (Value::Bool(a), Value::Bool(b)) => Value::Bool(*a || *b), - (Value::Int(a), Value::Int(b)) => Value::Int(a | b), - _ => Value::Void, - } - }); - - // --- Xor (xor) --- - env.register_native("xor", logic_op_ty.clone(), true, |args| { - if args.len() != 2 { return Value::Void; } - match (&args[0], &args[1]) { - (Value::Bool(a), Value::Bool(b)) => Value::Bool(*a ^ *b), - (Value::Int(a), Value::Int(b)) => Value::Int(a ^ b), - _ => Value::Void, - } - }); - - // --- Shift Left (<<) --- - let shift_ty = StaticType::Function(Box::new( - Signature { params: StaticType::Tuple(vec![StaticType::Int, StaticType::Int]), ret: StaticType::Int } - )); - env.register_native("<<", shift_ty.clone(), true, |args| { - if args.len() != 2 { return Value::Void; } - match (&args[0], &args[1]) { - (Value::Int(a), Value::Int(b)) => Value::Int(a << b), - _ => Value::Void, - } - }); - - // --- Shift Right (>>) --- - env.register_native(">>", shift_ty, true, |args| { - if args.len() != 2 { return Value::Void; } - match (&args[0], &args[1]) { - (Value::Int(a), Value::Int(b)) => Value::Int(a >> b), - _ => Value::Void, - } - }); -} +use crate::ast::environment::Environment; +use crate::ast::types::{Signature, StaticType, Value}; +use std::rc::Rc; + +pub fn register(env: &Environment) { + register_constants(env); + register_arithmetic(env); + register_comparison(env); + register_logic(env); +} + +fn register_constants(env: &Environment) { + // True/False are keywords or literals in parser, but could be exposed as constants too if needed. + // In Delphi RTL: CFalse, CTrue, CNaN + + // We register NaN as a value, not a function. + env.register_constant("NaN", StaticType::Float, Value::Float(f64::NAN)); + env.register_constant("true", StaticType::Bool, Value::Bool(true)); + env.register_constant("false", StaticType::Bool, Value::Bool(false)); +} + +fn register_arithmetic(env: &Environment) { + // --- Add (+) --- + let add_ty = StaticType::FunctionOverloads(vec![ + Signature { + params: StaticType::Tuple(vec![StaticType::Int, StaticType::Int]), + ret: StaticType::Int, + }, + Signature { + params: StaticType::Tuple(vec![StaticType::Float, StaticType::Float]), + ret: StaticType::Float, + }, + Signature { + params: StaticType::Tuple(vec![StaticType::Text, StaticType::Text]), + ret: StaticType::Text, + }, + Signature { + params: StaticType::Tuple(vec![StaticType::DateTime, StaticType::Int]), + ret: StaticType::DateTime, + }, + ]); + env.register_native("+", add_ty, true, |args| { + if args.len() == 2 { + match (&args[0], &args[1]) { + (Value::Int(a), Value::Int(b)) => Value::Int(a + b), + (Value::Float(a), Value::Float(b)) => Value::Float(a + b), + (Value::Int(a), Value::Float(b)) => Value::Float(*a as f64 + b), + (Value::Float(a), Value::Int(b)) => Value::Float(a + *b as f64), + (Value::Text(a), Value::Text(b)) => { + let mut res = a.to_string(); + res.push_str(b); + Value::Text(Rc::from(res)) + } + (Value::DateTime(ts), Value::Int(ms)) => Value::DateTime(ts + ms), + _ => Value::Void, + } + } else { + // Variadic sum + let mut acc = 0.0; + for arg in args { + if let Value::Int(i) = arg { + acc += i as f64; + } else if let Value::Float(f) = arg { + acc += f; + } + } + if acc.fract() == 0.0 { + Value::Int(acc as i64) + } else { + Value::Float(acc) + } + } + }); + + // --- Subtract (-) --- + let sub_ty = StaticType::FunctionOverloads(vec![ + Signature { + params: StaticType::Tuple(vec![StaticType::Int, StaticType::Int]), + ret: StaticType::Int, + }, + Signature { + params: StaticType::Tuple(vec![StaticType::Float, StaticType::Float]), + ret: StaticType::Float, + }, + Signature { + params: StaticType::Tuple(vec![StaticType::Int]), + ret: StaticType::Int, + }, // Negation + Signature { + params: StaticType::Tuple(vec![StaticType::Float]), + ret: StaticType::Float, + }, + Signature { + params: StaticType::Tuple(vec![StaticType::DateTime, StaticType::DateTime]), + ret: StaticType::Int, + }, + Signature { + params: StaticType::Tuple(vec![StaticType::DateTime, StaticType::Int]), + ret: StaticType::DateTime, + }, + ]); + env.register_native("-", sub_ty, true, |args| { + if args.is_empty() { + return Value::Void; + } + if args.len() == 1 { + return match args[0] { + Value::Int(i) => Value::Int(-i), + Value::Float(f) => Value::Float(-f), + _ => Value::Void, + }; + } + if args.len() == 2 { + return match (&args[0], &args[1]) { + (Value::Int(a), Value::Int(b)) => Value::Int(a - b), + (Value::Float(a), Value::Float(b)) => Value::Float(a - b), + (Value::Int(a), Value::Float(b)) => Value::Float(*a as f64 - b), + (Value::Float(a), Value::Int(b)) => Value::Float(a - *b as f64), + (Value::DateTime(a), Value::DateTime(b)) => Value::Int(a - b), + (Value::DateTime(a), Value::Int(b)) => Value::DateTime(a - b), + _ => Value::Void, + }; + } + // Variadic sub + let mut acc = match args[0] { + Value::Int(i) => i as f64, + Value::Float(f) => f, + _ => return Value::Void, + }; + for arg in &args[1..] { + if let Value::Int(i) = arg { + acc -= *i as f64; + } else if let Value::Float(f) = arg { + acc -= f; + } + } + if acc.fract() == 0.0 { + Value::Int(acc as i64) + } else { + Value::Float(acc) + } + }); + + // --- Multiply (*) --- + let mul_ty = StaticType::FunctionOverloads(vec![ + Signature { + params: StaticType::Tuple(vec![StaticType::Int, StaticType::Int]), + ret: StaticType::Int, + }, + Signature { + params: StaticType::Tuple(vec![StaticType::Float, StaticType::Float]), + ret: StaticType::Float, + }, + ]); + env.register_native("*", mul_ty, true, |args| { + if args.len() == 2 { + match (&args[0], &args[1]) { + (Value::Int(a), Value::Int(b)) => Value::Int(a * b), + (Value::Float(a), Value::Float(b)) => Value::Float(a * b), + (Value::Int(a), Value::Float(b)) => Value::Float(*a as f64 * b), + (Value::Float(a), Value::Int(b)) => Value::Float(a * *b as f64), + _ => Value::Void, + } + } else { + let mut acc = 1.0; + for arg in args { + if let Value::Int(i) = arg { + acc *= i as f64; + } else if let Value::Float(f) = arg { + acc *= f; + } + } + if acc.fract() == 0.0 { + Value::Int(acc as i64) + } else { + Value::Float(acc) + } + } + }); + + // --- Divide (/) --- + let div_ty = StaticType::FunctionOverloads(vec![ + Signature { + params: StaticType::Tuple(vec![StaticType::Int, StaticType::Int]), + ret: StaticType::Float, + }, + Signature { + params: StaticType::Tuple(vec![StaticType::Float, StaticType::Float]), + ret: StaticType::Float, + }, + ]); + env.register_native("/", div_ty, true, |args| { + if args.len() != 2 { + return Value::Void; + } + let a = match args[0] { + Value::Int(i) => i as f64, + Value::Float(f) => f, + _ => return Value::Void, + }; + let b = match args[1] { + Value::Int(i) => i as f64, + Value::Float(f) => f, + _ => return Value::Void, + }; + if b == 0.0 { + Value::Float(f64::NAN) + } else { + Value::Float(a / b) + } + }); + + // --- Integer Divide (//) --- + let int_div_ty = StaticType::Function(Box::new(Signature { + params: StaticType::Tuple(vec![StaticType::Int, StaticType::Int]), + ret: StaticType::Int, + })); + env.register_native("//", int_div_ty, true, |args| { + if args.len() != 2 { + return Value::Void; + } + match (&args[0], &args[1]) { + (Value::Int(a), Value::Int(b)) => { + if *b == 0 { + Value::Void + } else { + Value::Int(a / b) + } + } + // Also allow float truncation? Delphi RTL says IntDivide takes DataValue and returns DataValue. + // Usually div is for integers. Let's stick to Int. + _ => Value::Void, + } + }); + + // --- Modulus (%) --- + let mod_ty = StaticType::Function(Box::new(Signature { + params: StaticType::Tuple(vec![StaticType::Int, StaticType::Int]), + ret: StaticType::Int, + })); + env.register_native("%", mod_ty, true, |args| { + if args.len() != 2 { + return Value::Void; + } + match (&args[0], &args[1]) { + (Value::Int(a), Value::Int(b)) => { + if *b == 0 { + Value::Void + } else { + Value::Int(a % b) + } + } + _ => Value::Void, + } + }); +} + +fn register_comparison(env: &Environment) { + let cmp_ty = StaticType::FunctionOverloads(vec![ + Signature { + params: StaticType::Tuple(vec![StaticType::Int, StaticType::Int]), + ret: StaticType::Bool, + }, + Signature { + params: StaticType::Tuple(vec![StaticType::Float, StaticType::Float]), + ret: StaticType::Bool, + }, + Signature { + params: StaticType::Tuple(vec![StaticType::DateTime, StaticType::DateTime]), + ret: StaticType::Bool, + }, + ]); + + // --- Greater Than (>) --- + env.register_native(">", cmp_ty.clone(), true, |args| { + if args.len() != 2 { + return Value::Void; + } + match (&args[0], &args[1]) { + (Value::Int(a), Value::Int(b)) => Value::Bool(a > b), + (Value::Int(a), Value::Float(b)) => Value::Bool((*a as f64) > *b), + (Value::Float(a), Value::Int(b)) => Value::Bool(*a > (*b as f64)), + (Value::Float(a), Value::Float(b)) => Value::Bool(a > b), + (Value::DateTime(a), Value::DateTime(b)) => Value::Bool(a > b), + _ => Value::Bool(false), + } + }); + + // --- Less Than (<) --- + env.register_native("<", cmp_ty.clone(), true, |args| { + if args.len() != 2 { + return Value::Void; + } + match (&args[0], &args[1]) { + (Value::Int(a), Value::Int(b)) => Value::Bool(a < b), + (Value::Int(a), Value::Float(b)) => Value::Bool((*a as f64) < *b), + (Value::Float(a), Value::Int(b)) => Value::Bool(*a < (*b as f64)), + (Value::Float(a), Value::Float(b)) => Value::Bool(a < b), + (Value::DateTime(a), Value::DateTime(b)) => Value::Bool(a < b), + _ => Value::Bool(false), + } + }); + + // --- Greater Or Equal (>=) --- + env.register_native(">=", cmp_ty.clone(), true, |args| { + if args.len() != 2 { + return Value::Void; + } + match (&args[0], &args[1]) { + (Value::Int(a), Value::Int(b)) => Value::Bool(a >= b), + (Value::Int(a), Value::Float(b)) => Value::Bool((*a as f64) >= *b), + (Value::Float(a), Value::Int(b)) => Value::Bool(*a >= (*b as f64)), + (Value::Float(a), Value::Float(b)) => Value::Bool(a >= b), + (Value::DateTime(a), Value::DateTime(b)) => Value::Bool(a >= b), + _ => Value::Bool(false), + } + }); + + // --- Less Or Equal (<=) --- + env.register_native("<=", cmp_ty.clone(), true, |args| { + if args.len() != 2 { + return Value::Void; + } + match (&args[0], &args[1]) { + (Value::Int(a), Value::Int(b)) => Value::Bool(a <= b), + (Value::Int(a), Value::Float(b)) => Value::Bool((*a as f64) <= *b), + (Value::Float(a), Value::Int(b)) => Value::Bool(*a <= (*b as f64)), + (Value::Float(a), Value::Float(b)) => Value::Bool(a <= b), + (Value::DateTime(a), Value::DateTime(b)) => Value::Bool(a <= b), + _ => Value::Bool(false), + } + }); + + // --- Equal (=) --- + let eq_ty = StaticType::Function(Box::new(Signature { + params: StaticType::Tuple(vec![StaticType::Any, StaticType::Any]), + ret: StaticType::Bool, + })); + env.register_native("=", eq_ty.clone(), true, |args| { + if args.len() != 2 { + return Value::Void; + } + // Simple equality check. + // Note: Floating point equality is tricky, but we follow standard behavior for now. + match (&args[0], &args[1]) { + (Value::Int(a), Value::Int(b)) => Value::Bool(a == b), + (Value::Float(a), Value::Float(b)) => Value::Bool((a - b).abs() < f64::EPSILON), + (Value::Int(a), Value::Float(b)) => Value::Bool((*a as f64 - b).abs() < f64::EPSILON), + (Value::Float(a), Value::Int(b)) => Value::Bool((a - *b as f64).abs() < f64::EPSILON), + (Value::Text(a), Value::Text(b)) => Value::Bool(a == b), + (Value::Bool(a), Value::Bool(b)) => Value::Bool(a == b), + (Value::Keyword(a), Value::Keyword(b)) => Value::Bool(a == b), + (Value::DateTime(a), Value::DateTime(b)) => Value::Bool(a == b), + (Value::Void, Value::Void) => Value::Bool(true), + _ => Value::Bool(false), + } + }); + + // --- Not Equal (<>) --- + env.register_native("<>", eq_ty, true, |args| { + if args.len() != 2 { + return Value::Void; + } + match (&args[0], &args[1]) { + (Value::Int(a), Value::Int(b)) => Value::Bool(a != b), + (Value::Float(a), Value::Float(b)) => Value::Bool((a - b).abs() >= f64::EPSILON), + (Value::Int(a), Value::Float(b)) => Value::Bool((*a as f64 - b).abs() >= f64::EPSILON), + (Value::Float(a), Value::Int(b)) => Value::Bool((a - *b as f64).abs() >= f64::EPSILON), + (Value::Text(a), Value::Text(b)) => Value::Bool(a != b), + (Value::Bool(a), Value::Bool(b)) => Value::Bool(a != b), + (Value::Keyword(a), Value::Keyword(b)) => Value::Bool(a != b), + (Value::DateTime(a), Value::DateTime(b)) => Value::Bool(a != b), + (Value::Void, Value::Void) => Value::Bool(false), + _ => Value::Bool(true), + } + }); +} + +fn register_logic(env: &Environment) { + // --- Not (not) --- + let not_ty = StaticType::FunctionOverloads(vec![ + Signature { + params: StaticType::Tuple(vec![StaticType::Bool]), + ret: StaticType::Bool, + }, + Signature { + params: StaticType::Tuple(vec![StaticType::Int]), + ret: StaticType::Int, + }, + ]); + env.register_native("not", not_ty, true, |args| { + if args.len() != 1 { + return Value::Void; + } + match &args[0] { + Value::Bool(b) => Value::Bool(!b), + Value::Int(i) => Value::Int(!i), // Bitwise NOT + _ => Value::Void, + } + }); + + // --- And (and) --- + let logic_op_ty = StaticType::FunctionOverloads(vec![ + Signature { + params: StaticType::Tuple(vec![StaticType::Bool, StaticType::Bool]), + ret: StaticType::Bool, + }, + Signature { + params: StaticType::Tuple(vec![StaticType::Int, StaticType::Int]), + ret: StaticType::Int, + }, + ]); + env.register_native("and", logic_op_ty.clone(), true, |args| { + if args.len() != 2 { + return Value::Void; + } + match (&args[0], &args[1]) { + (Value::Bool(a), Value::Bool(b)) => Value::Bool(*a && *b), + (Value::Int(a), Value::Int(b)) => Value::Int(a & b), + _ => Value::Void, + } + }); + + // --- Or (or) --- + env.register_native("or", logic_op_ty.clone(), true, |args| { + if args.len() != 2 { + return Value::Void; + } + match (&args[0], &args[1]) { + (Value::Bool(a), Value::Bool(b)) => Value::Bool(*a || *b), + (Value::Int(a), Value::Int(b)) => Value::Int(a | b), + _ => Value::Void, + } + }); + + // --- Xor (xor) --- + env.register_native("xor", logic_op_ty.clone(), true, |args| { + if args.len() != 2 { + return Value::Void; + } + match (&args[0], &args[1]) { + (Value::Bool(a), Value::Bool(b)) => Value::Bool(*a ^ *b), + (Value::Int(a), Value::Int(b)) => Value::Int(a ^ b), + _ => Value::Void, + } + }); + + // --- Shift Left (<<) --- + let shift_ty = StaticType::Function(Box::new(Signature { + params: StaticType::Tuple(vec![StaticType::Int, StaticType::Int]), + ret: StaticType::Int, + })); + env.register_native("<<", shift_ty.clone(), true, |args| { + if args.len() != 2 { + return Value::Void; + } + match (&args[0], &args[1]) { + (Value::Int(a), Value::Int(b)) => Value::Int(a << b), + _ => Value::Void, + } + }); + + // --- Shift Right (>>) --- + env.register_native(">>", shift_ty, true, |args| { + if args.len() != 2 { + return Value::Void; + } + match (&args[0], &args[1]) { + (Value::Int(a), Value::Int(b)) => Value::Int(a >> b), + _ => Value::Void, + } + }); +} diff --git a/src/ast/rtl/datetime.rs b/src/ast/rtl/datetime.rs index 75132e5..44e5b8e 100644 --- a/src/ast/rtl/datetime.rs +++ b/src/ast/rtl/datetime.rs @@ -1,25 +1,29 @@ -use crate::ast::types::{Value, StaticType, Signature}; -use crate::ast::environment::Environment; -use chrono::{NaiveDate, NaiveDateTime}; - -pub fn register(env: &Environment) { - let date_ty = StaticType::Function(Box::new(Signature { - params: StaticType::Tuple(vec![StaticType::Text]), - ret: StaticType::DateTime - })); - - env.register_native("date", date_ty, true, |args| { - if let Value::Text(s) = &args[0] { - // Try parse YYYY-MM-DD - if let Ok(dt) = NaiveDate::parse_from_str(s, "%Y-%m-%d") { - let ts = dt.and_hms_opt(0, 0, 0).unwrap().and_utc().timestamp_millis(); - return Value::DateTime(ts); - } - // Try parse YYYY-MM-DD HH:MM:SS - if let Ok(dt) = NaiveDateTime::parse_from_str(s, "%Y-%m-%d %H:%M:%S") { - return Value::DateTime(dt.and_utc().timestamp_millis()); - } - } - Value::Void - }); -} +use crate::ast::environment::Environment; +use crate::ast::types::{Signature, StaticType, Value}; +use chrono::{NaiveDate, NaiveDateTime}; + +pub fn register(env: &Environment) { + let date_ty = StaticType::Function(Box::new(Signature { + params: StaticType::Tuple(vec![StaticType::Text]), + ret: StaticType::DateTime, + })); + + env.register_native("date", date_ty, true, |args| { + if let Value::Text(s) = &args[0] { + // Try parse YYYY-MM-DD + if let Ok(dt) = NaiveDate::parse_from_str(s, "%Y-%m-%d") { + let ts = dt + .and_hms_opt(0, 0, 0) + .unwrap() + .and_utc() + .timestamp_millis(); + return Value::DateTime(ts); + } + // Try parse YYYY-MM-DD HH:MM:SS + if let Ok(dt) = NaiveDateTime::parse_from_str(s, "%Y-%m-%d %H:%M:%S") { + return Value::DateTime(dt.and_utc().timestamp_millis()); + } + } + Value::Void + }); +} diff --git a/src/ast/rtl/intrinsics.rs b/src/ast/rtl/intrinsics.rs index 17e0ae4..b205a93 100644 --- a/src/ast/rtl/intrinsics.rs +++ b/src/ast/rtl/intrinsics.rs @@ -1,108 +1,116 @@ -use std::rc::Rc; -use crate::ast::types::{Value, StaticType}; - -/// Looks up a specialized intrinsic function for the given operator and argument types. -/// Returns (Executable Value, Return Type) if a fast-path exists. -pub fn lookup(name: &str, args: &[StaticType]) -> Option<(Value, StaticType)> { - match (name, args) { - // --- Integer Arithmetic --- - ("+", [StaticType::Int, StaticType::Int]) => Some(( - Value::Function(Rc::new(|args| { - if let (Value::Int(a), Value::Int(b)) = (&args[0], &args[1]) { - Value::Int(a + b) - } else { - Value::Int(0) // Should not happen if type checker works - } - })), - StaticType::Int - )), - ("-", [StaticType::Int, StaticType::Int]) => Some(( - Value::Function(Rc::new(|args| { - if let (Value::Int(a), Value::Int(b)) = (&args[0], &args[1]) { - Value::Int(a - b) - } else { - Value::Int(0) - } - })), - StaticType::Int - )), - ("-", [StaticType::Int, StaticType::Int, StaticType::Int]) => Some(( - // Variadic optimization for 3 args (common in some Lisp dialects, though - usually is binary/unary) - // MyC's core.rs supports variadic subtraction. - Value::Function(Rc::new(|args| { - let a = match args[0] { Value::Int(i) => i, _ => 0 }; - let b = match args[1] { Value::Int(i) => i, _ => 0 }; - let c = match args[2] { Value::Int(i) => i, _ => 0 }; - Value::Int(a - b - c) - })), - StaticType::Int - )), - ("*", [StaticType::Int, StaticType::Int]) => Some(( - Value::Function(Rc::new(|args| { - if let (Value::Int(a), Value::Int(b)) = (&args[0], &args[1]) { - Value::Int(a * b) - } else { - Value::Int(0) - } - })), - StaticType::Int - )), - - // --- Integer Comparison --- - ("<=", [StaticType::Int, StaticType::Int]) => Some(( - Value::Function(Rc::new(|args| { - if let (Value::Int(a), Value::Int(b)) = (&args[0], &args[1]) { - Value::Bool(a <= b) - } else { - Value::Bool(false) - } - })), - StaticType::Bool - )), - ("<", [StaticType::Int, StaticType::Int]) => Some(( - Value::Function(Rc::new(|args| { - if let (Value::Int(a), Value::Int(b)) = (&args[0], &args[1]) { - Value::Bool(a < b) - } else { - Value::Bool(false) - } - })), - StaticType::Bool - )), - (">", [StaticType::Int, StaticType::Int]) => Some(( - Value::Function(Rc::new(|args| { - if let (Value::Int(a), Value::Int(b)) = (&args[0], &args[1]) { - Value::Bool(a > b) - } else { - Value::Bool(false) - } - })), - StaticType::Bool - )), - (">=", [StaticType::Int, StaticType::Int]) => Some(( - Value::Function(Rc::new(|args| { - if let (Value::Int(a), Value::Int(b)) = (&args[0], &args[1]) { - Value::Bool(a >= b) - } else { - Value::Bool(false) - } - })), - StaticType::Bool - )), - ("=", [StaticType::Int, StaticType::Int]) => Some(( - Value::Function(Rc::new(|args| { - if let (Value::Int(a), Value::Int(b)) = (&args[0], &args[1]) { - Value::Bool(a == b) - } else { - Value::Bool(false) - } - })), - StaticType::Bool - )), - - // --- Constant Unary for -1 (decrement optimization) --- - // Special case: tak uses (- x 1). The specializer sees Call("-", [Int, Int]). - - _ => None - } -} +use crate::ast::types::{StaticType, Value}; +use std::rc::Rc; + +/// Looks up a specialized intrinsic function for the given operator and argument types. +/// Returns (Executable Value, Return Type) if a fast-path exists. +pub fn lookup(name: &str, args: &[StaticType]) -> Option<(Value, StaticType)> { + match (name, args) { + // --- Integer Arithmetic --- + ("+", [StaticType::Int, StaticType::Int]) => Some(( + Value::Function(Rc::new(|args| { + if let (Value::Int(a), Value::Int(b)) = (&args[0], &args[1]) { + Value::Int(a + b) + } else { + Value::Int(0) // Should not happen if type checker works + } + })), + StaticType::Int, + )), + ("-", [StaticType::Int, StaticType::Int]) => Some(( + Value::Function(Rc::new(|args| { + if let (Value::Int(a), Value::Int(b)) = (&args[0], &args[1]) { + Value::Int(a - b) + } else { + Value::Int(0) + } + })), + StaticType::Int, + )), + ("-", [StaticType::Int, StaticType::Int, StaticType::Int]) => Some(( + // Variadic optimization for 3 args (common in some Lisp dialects, though - usually is binary/unary) + // MyC's core.rs supports variadic subtraction. + Value::Function(Rc::new(|args| { + let a = match args[0] { + Value::Int(i) => i, + _ => 0, + }; + let b = match args[1] { + Value::Int(i) => i, + _ => 0, + }; + let c = match args[2] { + Value::Int(i) => i, + _ => 0, + }; + Value::Int(a - b - c) + })), + StaticType::Int, + )), + ("*", [StaticType::Int, StaticType::Int]) => Some(( + Value::Function(Rc::new(|args| { + if let (Value::Int(a), Value::Int(b)) = (&args[0], &args[1]) { + Value::Int(a * b) + } else { + Value::Int(0) + } + })), + StaticType::Int, + )), + + // --- Integer Comparison --- + ("<=", [StaticType::Int, StaticType::Int]) => Some(( + Value::Function(Rc::new(|args| { + if let (Value::Int(a), Value::Int(b)) = (&args[0], &args[1]) { + Value::Bool(a <= b) + } else { + Value::Bool(false) + } + })), + StaticType::Bool, + )), + ("<", [StaticType::Int, StaticType::Int]) => Some(( + Value::Function(Rc::new(|args| { + if let (Value::Int(a), Value::Int(b)) = (&args[0], &args[1]) { + Value::Bool(a < b) + } else { + Value::Bool(false) + } + })), + StaticType::Bool, + )), + (">", [StaticType::Int, StaticType::Int]) => Some(( + Value::Function(Rc::new(|args| { + if let (Value::Int(a), Value::Int(b)) = (&args[0], &args[1]) { + Value::Bool(a > b) + } else { + Value::Bool(false) + } + })), + StaticType::Bool, + )), + (">=", [StaticType::Int, StaticType::Int]) => Some(( + Value::Function(Rc::new(|args| { + if let (Value::Int(a), Value::Int(b)) = (&args[0], &args[1]) { + Value::Bool(a >= b) + } else { + Value::Bool(false) + } + })), + StaticType::Bool, + )), + ("=", [StaticType::Int, StaticType::Int]) => Some(( + Value::Function(Rc::new(|args| { + if let (Value::Int(a), Value::Int(b)) = (&args[0], &args[1]) { + Value::Bool(a == b) + } else { + Value::Bool(false) + } + })), + StaticType::Bool, + )), + + // --- Constant Unary for -1 (decrement optimization) --- + // Special case: tak uses (- x 1). The specializer sees Call("-", [Int, Int]). + _ => None, + } +} diff --git a/src/ast/rtl/mod.rs b/src/ast/rtl/mod.rs index 900520a..75d0847 100644 --- a/src/ast/rtl/mod.rs +++ b/src/ast/rtl/mod.rs @@ -1,11 +1,11 @@ -pub mod core; -pub mod datetime; -pub mod type_registry; -pub mod intrinsics; - -use crate::ast::environment::Environment; - -pub fn register(env: &Environment) { - core::register(env); - datetime::register(env); -} +pub mod core; +pub mod datetime; +pub mod intrinsics; +pub mod type_registry; + +use crate::ast::environment::Environment; + +pub fn register(env: &Environment) { + core::register(env); + datetime::register(env); +} diff --git a/src/ast/rtl/type_registry.rs b/src/ast/rtl/type_registry.rs index b1ed235..4459a8d 100644 --- a/src/ast/rtl/type_registry.rs +++ b/src/ast/rtl/type_registry.rs @@ -1,249 +1,276 @@ -use std::collections::HashMap; -use std::rc::Rc; -use std::any::{TypeId}; -use crate::ast::types::{Value, StaticType, Keyword, Signature}; - -/// Represents a Rust type that can be exposed to the script environment. -pub trait Scriptable: 'static + Sized { - /// Returns the name of the type for debugging/AST. - fn type_name() -> &'static str; - - /// Returns the static type definition (methods, properties) for the AST. - fn static_type() -> StaticType; - - /// Wraps the instance into a Value (usually a Record of closures). - fn wrap(self) -> Value; -} - -/// A registry for tracking registered types and their static definitions. -pub struct TypeRegistry { - known_types: HashMap, -} - -impl Default for TypeRegistry { - fn default() -> Self { - Self::new() - } -} - -impl TypeRegistry { - pub fn new() -> Self { - Self { - known_types: HashMap::new(), - } - } - - /// Registers a type T. Equivalent to `RegisterType` in Delphi. - pub fn register(&mut self) { - let id = TypeId::of::(); - self.known_types.entry(id).or_insert_with(T::static_type); - } - - /// Resolves the static type for a Rust type. - pub fn resolve_type(&self) -> StaticType { - self.known_types.get(&TypeId::of::()).cloned().unwrap_or(StaticType::Any) - } - - /// Creates a factory function value that can be bound in the environment. - /// - /// # Arguments - /// * `factory_func` - A Rust closure that takes script arguments and returns a Result. - pub fn create_factory( - factory_func: F - ) -> Value - where - T: Scriptable, - F: Fn(Vec) -> Result + 'static - { - // The factory is a script function that calls the Rust factory, gets T, then wraps it. - let closure = move |args: Vec| -> Value { - match factory_func(args) { - Ok(instance) => instance.wrap(), - Err(msg) => { - // In a real system, we'd propagate this error. For now, panic or return Void. - // Delphi returns Void if nil, but raises exception on error. - // Since Value doesn't have Error, we panic to stop execution. - panic!("Runtime Error in Factory for {}: {}", T::type_name(), msg); - } - } - }; - Value::Function(Rc::new(closure)) - } -} - -/// Helper to build the shadow record for an instance. -/// This is used inside `Scriptable::wrap`. -pub struct RecordBuilder { - fields: Vec<(Keyword, Value)>, -} - -impl Default for RecordBuilder { - fn default() -> Self { - Self::new() - } -} - -impl RecordBuilder { - pub fn new() -> Self { - Self { - fields: Vec::new(), - } - } - - pub fn method(mut self, name: &str, func: F) -> Self - where F: Fn(Vec) -> Value + 'static - { - let key = Keyword::intern(name); - self.fields.push((key, Value::Function(Rc::new(func)))); - self - } - - // Helper for methods that return Result (propagating panics for now) - pub fn method_checked(self, name: &str, func: F) -> Self - where F: Fn(Vec) -> Result + 'static - { - let closure = move |args: Vec| { - match func(args) { - Ok(v) => v, - Err(e) => panic!("Method call error: {}", e), - } - }; - self.method(name, closure) - } - - pub fn build(self) -> Value { - let mut keys = Vec::with_capacity(self.fields.len()); - let mut values = Vec::with_capacity(self.fields.len()); - for (k, v) in self.fields { - keys.push(k); - values.push(v); - } - Value::make_record(keys, values) - } -} - -/// Helper to build the StaticType definition. -pub struct TypeBuilder { - fields: Vec<(Keyword, StaticType)>, -} - -impl Default for TypeBuilder { - fn default() -> Self { - Self::new() - } -} - -impl TypeBuilder { - pub fn new() -> Self { - Self { - fields: Vec::new(), - } - } - - pub fn method(mut self, name: &str, params: Vec, ret: StaticType) -> Self { - let key = Keyword::intern(name); - let sig = StaticType::Function(Box::new(Signature { params: StaticType::Tuple(params), ret })); - self.fields.push((key, sig)); - self - } - - pub fn build(self) -> StaticType { - StaticType::Record(Rc::new(self.fields)) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::ast::types::{Value, StaticType}; - - #[derive(Debug, Clone)] - struct Person { - name: String, - age: u32, - } - - impl Scriptable for Person { - fn type_name() -> &'static str { "Person" } - - fn static_type() -> StaticType { - TypeBuilder::new() - .method("greet", vec![], StaticType::Text) - .method("older", vec![], StaticType::Int) - .build() - } - - fn wrap(self) -> Value { - let name = self.name.clone(); - let age = self.age; - - RecordBuilder::new() - .method("greet", move |_| Value::Text(format!("Hello {}", name).into())) - .method("older", move |_| Value::Int((age + 1) as i64)) - .build() - } - } - - #[test] - fn test_register_and_wrap() { - let mut registry = TypeRegistry::new(); - registry.register::(); - - let p = Person { name: "Alice".to_string(), age: 30 }; - let wrapped = p.wrap(); - - // Check static type - let st = TypeRegistry::resolve_type::(®istry); - if let StaticType::Record(fields) = st { - assert!(fields.iter().any(|(k, _)| *k == Keyword::intern("greet"))); - } else { - panic!("Expected Record type"); - } - - // Check runtime behavior - if let Value::Record(r) = wrapped { - let idx = r.keys.iter().position(|k| *k == Keyword::intern("greet")).unwrap(); - let greet_fn = &r.values[idx]; - - if let Value::Function(f) = greet_fn { - let res = f(vec![]); - if let Value::Text(s) = res { - assert_eq!(&*s, "Hello Alice"); - } else { - panic!("Expected Text result"); - } - } else { - panic!("Expected Function value for method"); - } - } else { - panic!("Expected Record value"); - } - } - - #[test] - fn test_factory() { - let factory_val = TypeRegistry::create_factory(|args: Vec| { - if args.len() != 2 { - return Err("Expected 2 args".to_string()); - } - let name = match &args[0] { Value::Text(t) => t.to_string(), _ => return Err("Name must be text".to_string()) }; - let age = match &args[1] { Value::Int(i) => *i as u32, _ => return Err("Age must be int".to_string()) }; - Ok(Person { name, age }) - }); - - if let Value::Function(f) = factory_val { - let instance = f(vec![Value::Text("Bob".into()), Value::Int(40)]); - if let Value::Record(r) = instance { - let idx = r.keys.iter().position(|k| *k == Keyword::intern("greet")).unwrap(); - let greet_fn = &r.values[idx]; - - if let Value::Function(gf) = greet_fn { - let res = gf(vec![]); - if let Value::Text(s) = res { - assert_eq!(&*s, "Hello Bob"); - } else { panic!("Wrong return type"); } - } - } else { panic!("Factory should return Record"); } - } else { panic!("Factory is not a function"); } - } -} +use crate::ast::types::{Keyword, Signature, StaticType, Value}; +use std::any::TypeId; +use std::collections::HashMap; +use std::rc::Rc; + +/// Represents a Rust type that can be exposed to the script environment. +pub trait Scriptable: 'static + Sized { + /// Returns the name of the type for debugging/AST. + fn type_name() -> &'static str; + + /// Returns the static type definition (methods, properties) for the AST. + fn static_type() -> StaticType; + + /// Wraps the instance into a Value (usually a Record of closures). + fn wrap(self) -> Value; +} + +/// A registry for tracking registered types and their static definitions. +pub struct TypeRegistry { + known_types: HashMap, +} + +impl Default for TypeRegistry { + fn default() -> Self { + Self::new() + } +} + +impl TypeRegistry { + pub fn new() -> Self { + Self { + known_types: HashMap::new(), + } + } + + /// Registers a type T. Equivalent to `RegisterType` in Delphi. + pub fn register(&mut self) { + let id = TypeId::of::(); + self.known_types.entry(id).or_insert_with(T::static_type); + } + + /// Resolves the static type for a Rust type. + pub fn resolve_type(&self) -> StaticType { + self.known_types + .get(&TypeId::of::()) + .cloned() + .unwrap_or(StaticType::Any) + } + + /// Creates a factory function value that can be bound in the environment. + /// + /// # Arguments + /// * `factory_func` - A Rust closure that takes script arguments and returns a Result. + pub fn create_factory(factory_func: F) -> Value + where + T: Scriptable, + F: Fn(Vec) -> Result + 'static, + { + // The factory is a script function that calls the Rust factory, gets T, then wraps it. + let closure = move |args: Vec| -> Value { + match factory_func(args) { + Ok(instance) => instance.wrap(), + Err(msg) => { + // In a real system, we'd propagate this error. For now, panic or return Void. + // Delphi returns Void if nil, but raises exception on error. + // Since Value doesn't have Error, we panic to stop execution. + panic!("Runtime Error in Factory for {}: {}", T::type_name(), msg); + } + } + }; + Value::Function(Rc::new(closure)) + } +} + +/// Helper to build the shadow record for an instance. +/// This is used inside `Scriptable::wrap`. +pub struct RecordBuilder { + fields: Vec<(Keyword, Value)>, +} + +impl Default for RecordBuilder { + fn default() -> Self { + Self::new() + } +} + +impl RecordBuilder { + pub fn new() -> Self { + Self { fields: Vec::new() } + } + + pub fn method(mut self, name: &str, func: F) -> Self + where + F: Fn(Vec) -> Value + 'static, + { + let key = Keyword::intern(name); + self.fields.push((key, Value::Function(Rc::new(func)))); + self + } + + // Helper for methods that return Result (propagating panics for now) + pub fn method_checked(self, name: &str, func: F) -> Self + where + F: Fn(Vec) -> Result + 'static, + { + let closure = move |args: Vec| match func(args) { + Ok(v) => v, + Err(e) => panic!("Method call error: {}", e), + }; + self.method(name, closure) + } + + pub fn build(self) -> Value { + let mut keys = Vec::with_capacity(self.fields.len()); + let mut values = Vec::with_capacity(self.fields.len()); + for (k, v) in self.fields { + keys.push(k); + values.push(v); + } + Value::make_record(keys, values) + } +} + +/// Helper to build the StaticType definition. +pub struct TypeBuilder { + fields: Vec<(Keyword, StaticType)>, +} + +impl Default for TypeBuilder { + fn default() -> Self { + Self::new() + } +} + +impl TypeBuilder { + pub fn new() -> Self { + Self { fields: Vec::new() } + } + + pub fn method(mut self, name: &str, params: Vec, ret: StaticType) -> Self { + let key = Keyword::intern(name); + let sig = StaticType::Function(Box::new(Signature { + params: StaticType::Tuple(params), + ret, + })); + self.fields.push((key, sig)); + self + } + + pub fn build(self) -> StaticType { + StaticType::Record(Rc::new(self.fields)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::ast::types::{StaticType, Value}; + + #[derive(Debug, Clone)] + struct Person { + name: String, + age: u32, + } + + impl Scriptable for Person { + fn type_name() -> &'static str { + "Person" + } + + fn static_type() -> StaticType { + TypeBuilder::new() + .method("greet", vec![], StaticType::Text) + .method("older", vec![], StaticType::Int) + .build() + } + + fn wrap(self) -> Value { + let name = self.name.clone(); + let age = self.age; + + RecordBuilder::new() + .method("greet", move |_| { + Value::Text(format!("Hello {}", name).into()) + }) + .method("older", move |_| Value::Int((age + 1) as i64)) + .build() + } + } + + #[test] + fn test_register_and_wrap() { + let mut registry = TypeRegistry::new(); + registry.register::(); + + let p = Person { + name: "Alice".to_string(), + age: 30, + }; + let wrapped = p.wrap(); + + // Check static type + let st = TypeRegistry::resolve_type::(®istry); + if let StaticType::Record(fields) = st { + assert!(fields.iter().any(|(k, _)| *k == Keyword::intern("greet"))); + } else { + panic!("Expected Record type"); + } + + // Check runtime behavior + if let Value::Record(r) = wrapped { + let idx = r + .keys + .iter() + .position(|k| *k == Keyword::intern("greet")) + .unwrap(); + let greet_fn = &r.values[idx]; + + if let Value::Function(f) = greet_fn { + let res = f(vec![]); + if let Value::Text(s) = res { + assert_eq!(&*s, "Hello Alice"); + } else { + panic!("Expected Text result"); + } + } else { + panic!("Expected Function value for method"); + } + } else { + panic!("Expected Record value"); + } + } + + #[test] + fn test_factory() { + let factory_val = TypeRegistry::create_factory(|args: Vec| { + if args.len() != 2 { + return Err("Expected 2 args".to_string()); + } + let name = match &args[0] { + Value::Text(t) => t.to_string(), + _ => return Err("Name must be text".to_string()), + }; + let age = match &args[1] { + Value::Int(i) => *i as u32, + _ => return Err("Age must be int".to_string()), + }; + Ok(Person { name, age }) + }); + + if let Value::Function(f) = factory_val { + let instance = f(vec![Value::Text("Bob".into()), Value::Int(40)]); + if let Value::Record(r) = instance { + let idx = r + .keys + .iter() + .position(|k| *k == Keyword::intern("greet")) + .unwrap(); + let greet_fn = &r.values[idx]; + + if let Value::Function(gf) = greet_fn { + let res = gf(vec![]); + if let Value::Text(s) = res { + assert_eq!(&*s, "Hello Bob"); + } else { + panic!("Wrong return type"); + } + } + } else { + panic!("Factory should return Record"); + } + } else { + panic!("Factory is not a function"); + } + } +} diff --git a/src/ast/types.rs b/src/ast/types.rs index f41ba79..c36c0bf 100644 --- a/src/ast/types.rs +++ b/src/ast/types.rs @@ -1,370 +1,392 @@ -use std::collections::HashMap; -use std::rc::Rc; -use std::cell::RefCell; -use std::fmt; -use std::sync::OnceLock; -use std::sync::Mutex; // Still needed for global keyword registry -use std::any::Any; -use chrono::{TimeZone, Utc}; - -/// Simple source location -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct SourceLocation { - pub line: u32, - pub col: u32, -} - -/// Shared identity for nodes (Location, etc.) -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct NodeIdentity { - pub location: SourceLocation, -} - -pub type Identity = Rc; - -/// Interned string identifier -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct Keyword(pub u32); - -static KEYWORD_REGISTRY: OnceLock>> = OnceLock::new(); -static KEYWORD_REVERSE: OnceLock>> = OnceLock::new(); - -impl Keyword { - pub fn intern(name: &str) -> Self { - let mut reg = KEYWORD_REGISTRY.get_or_init(|| Mutex::new(HashMap::new())).lock().unwrap(); - if let Some(&id) = reg.get(name) { - Keyword(id) - } else { - let mut rev = KEYWORD_REVERSE.get_or_init(|| Mutex::new(Vec::new())).lock().unwrap(); - let id = rev.len() as u32; - reg.insert(name.to_string(), id); - rev.push(name.to_string()); - Keyword(id) - } - } - - pub fn name(&self) -> String { - let rev = KEYWORD_REVERSE.get_or_init(|| Mutex::new(Vec::new())).lock().unwrap(); - rev[self.0 as usize].clone() - } -} - -/// Interface for custom objects (Closures, Series, Streams) -pub trait Object: fmt::Debug { - fn type_name(&self) -> &'static str; - fn as_any(&self) -> &dyn Any; -} - -/// A shared sequence of values, used by both Tuples and Records. -pub type ValueList = Rc>; - -/// Internal storage for Records to allow sharing schema (keys) between instances. -#[derive(Debug, Clone, PartialEq)] -pub struct RecordData { - /// Names for slots. - pub keys: Rc>, - /// The actual values, potentially shared with a Tuple. - pub values: ValueList, -} - -/// Core data value in Myc Script (similar to TDataValue) -#[derive(Clone)] -pub enum Value { - Void, - Bool(bool), - Int(i64), - Float(f64), - DateTime(i64), - Text(Rc), - Keyword(Keyword), - Tuple(ValueList), - Record(Rc), - Function(Rc) -> Value>), - Object(Rc), // For compiled Closures and other opaque types - Cell(Rc>), // Boxed value for captures - TailCallRequest(Box<(Rc, Vec)>), // Internal: For TCO (Boxed to keep Value small) -} - -impl PartialEq for Value { - fn eq(&self, other: &Self) -> bool { - match (self, other) { - (Value::Void, Value::Void) => true, - (Value::Bool(a), Value::Bool(b)) => a == b, - (Value::Int(a), Value::Int(b)) => a == b, - (Value::Float(a), Value::Float(b)) => a == b, - (Value::DateTime(a), Value::DateTime(b)) => a == b, - (Value::Text(a), Value::Text(b)) => a == b, - (Value::Keyword(a), Value::Keyword(b)) => a == b, - (Value::Tuple(a), Value::Tuple(b)) => a == b, - (Value::Record(a), Value::Record(b)) => a == b, - (Value::Function(a), Value::Function(b)) => Rc::ptr_eq(a, b), - (Value::Object(a), Value::Object(b)) => Rc::ptr_eq(a, b), - (Value::Cell(a), Value::Cell(b)) => Rc::ptr_eq(a, b), - (Value::TailCallRequest(a), Value::TailCallRequest(b)) => { - Rc::ptr_eq(&a.0, &b.0) && a.1 == b.1 - } - _ => false, - } - } -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct Signature { - pub params: StaticType, - pub ret: StaticType, -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum StaticType { - Any, - Void, - Bool, - Int, - Float, - DateTime, - Text, - Keyword, - List(Box), // Legacy / Dynamic list - Tuple(Vec), // Heterogeneous fixed-size - Vector(Box, usize), // Homogeneous fixed-size - Matrix(Box, Vec), // Multi-dimensional homogeneous - Record(Rc>), - Function(Box), - FunctionOverloads(Vec), - Object(&'static str), -} - -impl fmt::Display for StaticType { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - StaticType::Any => write!(f, "any"), - StaticType::Void => write!(f, "void"), - StaticType::Bool => write!(f, "bool"), - StaticType::Int => write!(f, "int"), - StaticType::Float => write!(f, "float"), - StaticType::DateTime => write!(f, "datetime"), - StaticType::Text => write!(f, "text"), - StaticType::Keyword => write!(f, "keyword"), - StaticType::List(inner) => write!(f, "[{}]", inner), - StaticType::Tuple(elements) => { - write!(f, "[")?; - for (i, el) in elements.iter().enumerate() { - if i > 0 { write!(f, " ")?; } - write!(f, "{}", el)?; - } - write!(f, "]") - }, - StaticType::Vector(inner, len) => write!(f, "vector<{}, {}>", inner, len), - StaticType::Matrix(inner, shape) => { - write!(f, "matrix<{}, [", inner)?; - for (i, s) in shape.iter().enumerate() { - if i > 0 { write!(f, " ")?; } - write!(f, "{}", s)?; - } - write!(f, "]>") - }, - StaticType::Record(fields) => { - write!(f, "{{")?; - for (i, (k, v)) in fields.iter().enumerate() { - if i > 0 { write!(f, ", ")?; } - write!(f, ":{} {}", k.name(), v)?; - } - write!(f, "}}") - }, - StaticType::Function(sig) => { - write!(f, "fn({}) -> {}", sig.params, sig.ret) - }, - StaticType::FunctionOverloads(sigs) => { - write!(f, "overloads({} variants)", sigs.len()) - } - StaticType::Object(name) => write!(f, "{}", name), - } - } -} - -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) { - return true; - } - - match (self, other) { - // A Vector is a Tuple - (StaticType::Tuple(elements), StaticType::Vector(inner, len)) => { - if elements.len() != *len { return false; } - elements.iter().all(|e| e.is_assignable_from(inner)) - }, - // A Matrix is a Vector (of Vectors/Matrices) - (StaticType::Vector(inner, len), StaticType::Matrix(m_inner, shape)) => { - if shape.is_empty() || shape[0] != *len { return false; } - if shape.len() == 1 { - inner.is_assignable_from(m_inner) - } else { - // It's a matrix of higher dimension, so inner must be assignable from a sub-matrix - let sub_shape = shape[1..].to_vec(); - inner.is_assignable_from(&StaticType::Matrix(m_inner.clone(), sub_shape)) - } - }, - _ => false - } - } - - /// Tries to resolve a call with the given argument type (usually a Tuple) and returns the return type. - pub fn resolve_call(&self, args_ty: &StaticType) -> Option { - match self { - StaticType::Any => Some(StaticType::Any), - StaticType::Function(sig) => { - if sig.params.is_assignable_from(args_ty) { - Some(sig.ret.clone()) - } else { - None - } - } - StaticType::FunctionOverloads(sigs) => { - sigs.iter() - .find(|sig| sig.params.is_assignable_from(args_ty)) - .map(|sig| sig.ret.clone()) - } - _ => None, - } - } - - /// Returns true if this type and all its recursive elements are scalars (Int, Float, etc.) - pub fn is_scalar_pure(&self) -> bool { - match self { - StaticType::Int | StaticType::Float | StaticType::Bool | StaticType::DateTime => true, - StaticType::Tuple(elements) => elements.iter().all(|e| e.is_scalar_pure()), - StaticType::Vector(inner, _) => inner.is_scalar_pure(), - StaticType::Matrix(inner, _) => inner.is_scalar_pure(), - _ => false, - } - } -} - -impl Value { - pub fn is_truthy(&self) -> bool { - match self { - Value::Void => false, - Value::Bool(b) => *b, - Value::Cell(c) => c.borrow().is_truthy(), - _ => true, - } - } - - /// Returns the underlying values as a slice if this is a Tuple or Record. - pub fn as_slice(&self) -> Option<&[Value]> { - match self { - Value::Tuple(v) => Some(v), - Value::Record(r) => Some(&r.values), - _ => None, - } - } - - pub fn make_tuple(values: Vec) -> Self { - Value::Tuple(Rc::new(values)) - } - - pub fn make_record(keys: Vec, values: Vec) -> Self { - Value::Record(Rc::new(RecordData { - keys: Rc::new(keys), - values: Rc::new(values) - })) - } - - pub fn static_type(&self) -> StaticType { - match self { - Value::Void => StaticType::Void, - Value::Bool(_) => StaticType::Bool, - Value::Int(_) => StaticType::Int, - Value::Float(_) => StaticType::Float, - Value::DateTime(_) => StaticType::DateTime, - Value::Text(_) => StaticType::Text, - Value::Keyword(_) => StaticType::Keyword, - Value::Tuple(values) => { - if values.is_empty() { - return StaticType::Vector(Box::new(StaticType::Any), 0); - } - - let element_types: Vec<_> = values.iter().map(|v| v.static_type()).collect(); - - // Check for Homogeneity (Vector) - let first_ty = &element_types[0]; - let all_same = element_types.iter().all(|t| t == first_ty); - - if all_same { - match first_ty { - StaticType::Vector(inner, len) => { - // Possible Matrix - StaticType::Matrix(inner.clone(), vec![values.len(), *len]) - }, - StaticType::Matrix(inner, shape) => { - let mut new_shape = vec![values.len()]; - new_shape.extend(shape); - StaticType::Matrix(inner.clone(), new_shape) - }, - _ => StaticType::Vector(Box::new(first_ty.clone()), values.len()) - } - } else { - StaticType::Tuple(element_types) - } - }, - Value::Record(r) => { - let mut fields = Vec::with_capacity(r.values.len()); - for (i, v) in r.values.iter().enumerate() { - fields.push((r.keys[i], v.static_type())); - } - StaticType::Record(Rc::new(fields)) - }, - Value::Function(_) => StaticType::Any, // Dynamic function - Value::Object(o) => StaticType::Object(o.type_name()), - Value::Cell(c) => c.borrow().static_type(), - Value::TailCallRequest(_) => StaticType::Any, // Internal state, but typable as Any - } - } -} - -impl fmt::Display for Value { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Value::Void => write!(f, "void"), - Value::Bool(b) => write!(f, "{}", b), - Value::Int(i) => write!(f, "{}", i), - Value::Float(fl) => write!(f, "{}", fl), - Value::DateTime(ts) => { - match Utc.timestamp_millis_opt(*ts) { - chrono::LocalResult::Single(dt) => write!(f, "#{}#", dt.format("%Y-%m-%d %H:%M:%S")), - _ => write!(f, "#timestamp({})#", ts), - } - }, - Value::Text(t) => write!(f, "\"{}\"", t), - Value::Keyword(k) => write!(f, ":{}", k.name()), - Value::Tuple(values) => { - write!(f, "[")?; - for (i, val) in values.iter().enumerate() { - if i > 0 { write!(f, " ")?; } - write!(f, "{}", val)?; - } - write!(f, "]") - }, - Value::Record(r) => { - write!(f, "{{")?; - for i in 0..r.values.len() { - if i > 0 { write!(f, ", ")?; } - write!(f, ":{} {}", r.keys[i].name(), r.values[i])?; - } - write!(f, "}}") - }, - Value::Function(_) => write!(f, ""), - Value::Object(o) => write!(f, "<{}>", o.type_name()), - Value::Cell(c) => write!(f, "{}", c.borrow()), - Value::TailCallRequest(_) => write!(f, ""), - } - } -} - -impl fmt::Debug for Value { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Display::fmt(self, f) - } -} +use chrono::{TimeZone, Utc}; +use std::any::Any; +use std::cell::RefCell; +use std::collections::HashMap; +use std::fmt; +use std::rc::Rc; +use std::sync::Mutex; // Still needed for global keyword registry +use std::sync::OnceLock; + +/// Simple source location +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct SourceLocation { + pub line: u32, + pub col: u32, +} + +/// Shared identity for nodes (Location, etc.) +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct NodeIdentity { + pub location: SourceLocation, +} + +pub type Identity = Rc; + +/// Interned string identifier +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct Keyword(pub u32); + +static KEYWORD_REGISTRY: OnceLock>> = OnceLock::new(); +static KEYWORD_REVERSE: OnceLock>> = OnceLock::new(); + +impl Keyword { + pub fn intern(name: &str) -> Self { + let mut reg = KEYWORD_REGISTRY + .get_or_init(|| Mutex::new(HashMap::new())) + .lock() + .unwrap(); + if let Some(&id) = reg.get(name) { + Keyword(id) + } else { + let mut rev = KEYWORD_REVERSE + .get_or_init(|| Mutex::new(Vec::new())) + .lock() + .unwrap(); + let id = rev.len() as u32; + reg.insert(name.to_string(), id); + rev.push(name.to_string()); + Keyword(id) + } + } + + pub fn name(&self) -> String { + let rev = KEYWORD_REVERSE + .get_or_init(|| Mutex::new(Vec::new())) + .lock() + .unwrap(); + rev[self.0 as usize].clone() + } +} + +/// Interface for custom objects (Closures, Series, Streams) +pub trait Object: fmt::Debug { + fn type_name(&self) -> &'static str; + fn as_any(&self) -> &dyn Any; +} + +/// A shared sequence of values, used by both Tuples and Records. +pub type ValueList = Rc>; + +/// Internal storage for Records to allow sharing schema (keys) between instances. +#[derive(Debug, Clone, PartialEq)] +pub struct RecordData { + /// Names for slots. + pub keys: Rc>, + /// The actual values, potentially shared with a Tuple. + pub values: ValueList, +} + +/// Core data value in Myc Script (similar to TDataValue) +#[derive(Clone)] +pub enum Value { + Void, + Bool(bool), + Int(i64), + Float(f64), + DateTime(i64), + Text(Rc), + Keyword(Keyword), + Tuple(ValueList), + Record(Rc), + Function(Rc) -> Value>), + Object(Rc), // For compiled Closures and other opaque types + Cell(Rc>), // Boxed value for captures + TailCallRequest(Box<(Rc, Vec)>), // Internal: For TCO (Boxed to keep Value small) +} + +impl PartialEq for Value { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Value::Void, Value::Void) => true, + (Value::Bool(a), Value::Bool(b)) => a == b, + (Value::Int(a), Value::Int(b)) => a == b, + (Value::Float(a), Value::Float(b)) => a == b, + (Value::DateTime(a), Value::DateTime(b)) => a == b, + (Value::Text(a), Value::Text(b)) => a == b, + (Value::Keyword(a), Value::Keyword(b)) => a == b, + (Value::Tuple(a), Value::Tuple(b)) => a == b, + (Value::Record(a), Value::Record(b)) => a == b, + (Value::Function(a), Value::Function(b)) => Rc::ptr_eq(a, b), + (Value::Object(a), Value::Object(b)) => Rc::ptr_eq(a, b), + (Value::Cell(a), Value::Cell(b)) => Rc::ptr_eq(a, b), + (Value::TailCallRequest(a), Value::TailCallRequest(b)) => { + Rc::ptr_eq(&a.0, &b.0) && a.1 == b.1 + } + _ => false, + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct Signature { + pub params: StaticType, + pub ret: StaticType, +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum StaticType { + Any, + Void, + Bool, + Int, + Float, + DateTime, + Text, + Keyword, + List(Box), // Legacy / Dynamic list + Tuple(Vec), // Heterogeneous fixed-size + Vector(Box, usize), // Homogeneous fixed-size + Matrix(Box, Vec), // Multi-dimensional homogeneous + Record(Rc>), + Function(Box), + FunctionOverloads(Vec), + Object(&'static str), +} + +impl fmt::Display for StaticType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + StaticType::Any => write!(f, "any"), + StaticType::Void => write!(f, "void"), + StaticType::Bool => write!(f, "bool"), + StaticType::Int => write!(f, "int"), + StaticType::Float => write!(f, "float"), + StaticType::DateTime => write!(f, "datetime"), + StaticType::Text => write!(f, "text"), + StaticType::Keyword => write!(f, "keyword"), + StaticType::List(inner) => write!(f, "[{}]", inner), + StaticType::Tuple(elements) => { + write!(f, "[")?; + for (i, el) in elements.iter().enumerate() { + if i > 0 { + write!(f, " ")?; + } + write!(f, "{}", el)?; + } + write!(f, "]") + } + StaticType::Vector(inner, len) => write!(f, "vector<{}, {}>", inner, len), + StaticType::Matrix(inner, shape) => { + write!(f, "matrix<{}, [", inner)?; + for (i, s) in shape.iter().enumerate() { + if i > 0 { + write!(f, " ")?; + } + write!(f, "{}", s)?; + } + write!(f, "]>") + } + StaticType::Record(fields) => { + write!(f, "{{")?; + for (i, (k, v)) in fields.iter().enumerate() { + if i > 0 { + write!(f, ", ")?; + } + write!(f, ":{} {}", k.name(), v)?; + } + write!(f, "}}") + } + StaticType::Function(sig) => { + write!(f, "fn({}) -> {}", sig.params, sig.ret) + } + StaticType::FunctionOverloads(sigs) => { + write!(f, "overloads({} variants)", sigs.len()) + } + StaticType::Object(name) => write!(f, "{}", name), + } + } +} + +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) { + return true; + } + + match (self, other) { + // A Vector is a Tuple + (StaticType::Tuple(elements), StaticType::Vector(inner, len)) => { + if elements.len() != *len { + return false; + } + elements.iter().all(|e| e.is_assignable_from(inner)) + } + // A Matrix is a Vector (of Vectors/Matrices) + (StaticType::Vector(inner, len), StaticType::Matrix(m_inner, shape)) => { + if shape.is_empty() || shape[0] != *len { + return false; + } + if shape.len() == 1 { + inner.is_assignable_from(m_inner) + } else { + // It's a matrix of higher dimension, so inner must be assignable from a sub-matrix + let sub_shape = shape[1..].to_vec(); + inner.is_assignable_from(&StaticType::Matrix(m_inner.clone(), sub_shape)) + } + } + _ => false, + } + } + + /// Tries to resolve a call with the given argument type (usually a Tuple) and returns the return type. + pub fn resolve_call(&self, args_ty: &StaticType) -> Option { + match self { + StaticType::Any => Some(StaticType::Any), + StaticType::Function(sig) => { + if sig.params.is_assignable_from(args_ty) { + Some(sig.ret.clone()) + } else { + None + } + } + StaticType::FunctionOverloads(sigs) => sigs + .iter() + .find(|sig| sig.params.is_assignable_from(args_ty)) + .map(|sig| sig.ret.clone()), + _ => None, + } + } + + /// Returns true if this type and all its recursive elements are scalars (Int, Float, etc.) + pub fn is_scalar_pure(&self) -> bool { + match self { + StaticType::Int | StaticType::Float | StaticType::Bool | StaticType::DateTime => true, + StaticType::Tuple(elements) => elements.iter().all(|e| e.is_scalar_pure()), + StaticType::Vector(inner, _) => inner.is_scalar_pure(), + StaticType::Matrix(inner, _) => inner.is_scalar_pure(), + _ => false, + } + } +} + +impl Value { + pub fn is_truthy(&self) -> bool { + match self { + Value::Void => false, + Value::Bool(b) => *b, + Value::Cell(c) => c.borrow().is_truthy(), + _ => true, + } + } + + /// Returns the underlying values as a slice if this is a Tuple or Record. + pub fn as_slice(&self) -> Option<&[Value]> { + match self { + Value::Tuple(v) => Some(v), + Value::Record(r) => Some(&r.values), + _ => None, + } + } + + pub fn make_tuple(values: Vec) -> Self { + Value::Tuple(Rc::new(values)) + } + + pub fn make_record(keys: Vec, values: Vec) -> Self { + Value::Record(Rc::new(RecordData { + keys: Rc::new(keys), + values: Rc::new(values), + })) + } + + pub fn static_type(&self) -> StaticType { + match self { + Value::Void => StaticType::Void, + Value::Bool(_) => StaticType::Bool, + Value::Int(_) => StaticType::Int, + Value::Float(_) => StaticType::Float, + Value::DateTime(_) => StaticType::DateTime, + Value::Text(_) => StaticType::Text, + Value::Keyword(_) => StaticType::Keyword, + Value::Tuple(values) => { + if values.is_empty() { + return StaticType::Vector(Box::new(StaticType::Any), 0); + } + + let element_types: Vec<_> = values.iter().map(|v| v.static_type()).collect(); + + // Check for Homogeneity (Vector) + let first_ty = &element_types[0]; + let all_same = element_types.iter().all(|t| t == first_ty); + + if all_same { + match first_ty { + StaticType::Vector(inner, len) => { + // Possible Matrix + StaticType::Matrix(inner.clone(), vec![values.len(), *len]) + } + StaticType::Matrix(inner, shape) => { + let mut new_shape = vec![values.len()]; + new_shape.extend(shape); + StaticType::Matrix(inner.clone(), new_shape) + } + _ => StaticType::Vector(Box::new(first_ty.clone()), values.len()), + } + } else { + StaticType::Tuple(element_types) + } + } + Value::Record(r) => { + let mut fields = Vec::with_capacity(r.values.len()); + for (i, v) in r.values.iter().enumerate() { + fields.push((r.keys[i], v.static_type())); + } + StaticType::Record(Rc::new(fields)) + } + Value::Function(_) => StaticType::Any, // Dynamic function + Value::Object(o) => StaticType::Object(o.type_name()), + Value::Cell(c) => c.borrow().static_type(), + Value::TailCallRequest(_) => StaticType::Any, // Internal state, but typable as Any + } + } +} + +impl fmt::Display for Value { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Value::Void => write!(f, "void"), + Value::Bool(b) => write!(f, "{}", b), + Value::Int(i) => write!(f, "{}", i), + Value::Float(fl) => write!(f, "{}", fl), + Value::DateTime(ts) => match Utc.timestamp_millis_opt(*ts) { + chrono::LocalResult::Single(dt) => { + write!(f, "#{}#", dt.format("%Y-%m-%d %H:%M:%S")) + } + _ => write!(f, "#timestamp({})#", ts), + }, + Value::Text(t) => write!(f, "\"{}\"", t), + Value::Keyword(k) => write!(f, ":{}", k.name()), + Value::Tuple(values) => { + write!(f, "[")?; + for (i, val) in values.iter().enumerate() { + if i > 0 { + write!(f, " ")?; + } + write!(f, "{}", val)?; + } + write!(f, "]") + } + Value::Record(r) => { + write!(f, "{{")?; + for i in 0..r.values.len() { + if i > 0 { + write!(f, ", ")?; + } + write!(f, ":{} {}", r.keys[i].name(), r.values[i])?; + } + write!(f, "}}") + } + Value::Function(_) => write!(f, ""), + Value::Object(o) => write!(f, "<{}>", o.type_name()), + Value::Cell(c) => write!(f, "{}", c.borrow()), + Value::TailCallRequest(_) => write!(f, ""), + } + } +} + +impl fmt::Debug for Value { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(self, f) + } +} diff --git a/src/ast/vm.rs b/src/ast/vm.rs index bd0ee90..54c0f47 100644 --- a/src/ast/vm.rs +++ b/src/ast/vm.rs @@ -1,465 +1,712 @@ -use std::rc::Rc; -use std::cell::RefCell; -use std::any::Any; -use crate::ast::compiler::bound_nodes::{Address, BoundKind, TypedNode}; -use crate::ast::compiler::tco::ExecNode; -use crate::ast::types::{Value, Object}; -use crate::ast::nodes::Node; - -#[derive(Debug, Clone)] -pub struct Closure { - pub parameter_node: Rc, - pub function_node: Rc, - pub exec_node: Rc, - pub upvalues: Vec>>, - pub positional_count: Option, -} - -impl Closure { - #[inline] - pub fn new(params: Rc, body: Rc, exec: Rc, upvalues: Vec>>, positional_count: Option) -> Self { - Self { parameter_node: params, function_node: body, exec_node: exec, upvalues, positional_count } - } -} - -impl Object for Closure { - fn type_name(&self) -> &'static str { "closure" } - fn as_any(&self) -> &dyn Any { self } -} - -#[derive(Debug)] -struct CallFrame { - stack_base: usize, - closure: Option>, -} - -pub trait VMObserver { - const ACTIVE: bool = false; - fn before_eval(&mut self, _vm: &VM, _node: &ExecNode) {} - fn after_eval(&mut self, _vm: &VM, _node: &ExecNode, _res: &Result) {} -} - -pub struct NoOpObserver; -impl VMObserver for NoOpObserver {} - -pub struct TracingObserver { - pub logs: Vec, - indent: usize, -} - -impl TracingObserver { - pub fn new() -> Self { Self { logs: Vec::new(), indent: 0 } } - fn pad(&self) -> String { "| ".repeat(self.indent) } -} - -impl Default for TracingObserver { fn default() -> Self { Self::new() } } - -impl VMObserver for TracingObserver { - const ACTIVE: bool = true; - fn before_eval(&mut self, _vm: &VM, node: &ExecNode) { - let pad = self.pad(); - self.logs.push(format!("{}{} [{}]: {{", pad, node.kind.display_name(), node.ty.ty)); - self.indent += 1; - } - fn after_eval(&mut self, vm: &VM, node: &ExecNode, res: &Result) { - self.indent = self.indent.saturating_sub(1); - let pad = self.pad(); - match &node.kind { - BoundKind::DefLocal { .. } | BoundKind::Set { .. } | BoundKind::DefGlobal { .. } => { - let s_pad = format!("{}| ", pad); - self.logs.push(format!("{}--- Scope Status ---", s_pad)); - self.logs.push(format!("{}Stack (top 5): {:?}", s_pad, vm.stack.iter().rev().take(5).collect::>())); - }, - _ => {} - } - let res_str = match res { Ok(v) => format!("{}", v), Err(e) => format!("ERROR: {}", e) }; - self.logs.push(format!("{}}} -> {}", pad, res_str)); - } -} - -pub struct VM { - stack: Vec, - globals: Rc>>, - frames: Vec, -} - -macro_rules! dispatch_eval { - ($self:ident, $node:ident, $eval_method:ident $(, $observer:ident)?) => { - match &$node.kind { - BoundKind::Nop => Ok(Value::Void), - BoundKind::Constant(v) => Ok(v.clone()), - BoundKind::Parameter { .. } => Ok(Value::Void), - BoundKind::DefGlobal { global_index, value, .. } => { - let val = $self.$eval_method($($observer,)? value)?; - let idx = *global_index as usize; - let mut globals = $self.globals.borrow_mut(); - if idx >= globals.len() { globals.resize(idx + 1, Value::Void); } - globals[idx] = val.clone(); - Ok(val) - }, - BoundKind::Get { addr, .. } => $self.get_value(*addr), - BoundKind::Set { addr, value } => { - let val = $self.$eval_method($($observer,)? value)?; - $self.set_value(*addr, val.clone())?; - Ok(val) - }, - BoundKind::DefLocal { slot, value, captured_by, .. } => { - let val = $self.$eval_method($($observer,)? value)?; - let final_val = if !captured_by.is_empty() { Value::Cell(Rc::new(RefCell::new(val))) } else { val }; - let frame = $self.frames.last().ok_or("No call frame")?; - let abs_index = frame.stack_base + (*slot as usize); - if abs_index == $self.stack.len() { $self.stack.push(final_val.clone()); } - else if abs_index < $self.stack.len() { $self.stack[abs_index] = final_val.clone(); } - else { return Err(format!("Stack gap at local {}", slot)); } - Ok(final_val) - }, - BoundKind::If { cond, then_br, else_br } => { - let c = $self.$eval_method($($observer,)? cond)?; - if c.is_truthy() { $self.$eval_method($($observer,)? then_br) } - else if let Some(e) = else_br { $self.$eval_method($($observer,)? e) } - else { Ok(Value::Void) } - }, - BoundKind::Block { exprs } => { - let mut last = Value::Void; - for e in exprs { last = $self.$eval_method($($observer,)? e)?; } - Ok(last) - }, - BoundKind::Lambda { params, upvalues, body, positional_count } => { - let mut captured = Vec::with_capacity(upvalues.len()); - for addr in upvalues { captured.push($self.capture_upvalue(*addr)?); } - // CRITICAL FIX: body.clone() is O(1), body.as_ref().clone() was O(N)! - let closure = Closure::new(params.ty.original.clone(), body.ty.original.clone(), body.clone(), captured, *positional_count); - Ok(Value::Object(Rc::new(closure))) - }, - BoundKind::Call { callee, args } => { - let mut func_val = $self.$eval_method($($observer,)? callee)?; - - macro_rules! get_arg_vals { - ($s:ident, $a:ident) => { $s.prepare_args($a)? }; - ($s:ident, $o:ident, $a:ident) => { $s.prepare_args_observed($o, $a)? }; - } - - let mut arg_vals = match &args.kind { - BoundKind::Tuple { elements } => { - let mut vals = Vec::with_capacity(elements.len()); - let mut is_complex = false; - for e in elements { - if matches!(e.kind, BoundKind::Tuple { .. }) { is_complex = true; break; } - vals.push($self.$eval_method($($observer,)? e)?); - } - if is_complex { get_arg_vals!($self, $($observer,)? args) } else { vals } - } - _ => { get_arg_vals!($self, $($observer,)? args) } - }; - - if $node.ty.is_tail { - match func_val { - Value::Object(obj) => return Ok(Value::TailCallRequest(Box::new((obj, arg_vals)))), - Value::Function(f) => return Ok(f(arg_vals)), - _ => return Err(format!("Tail call target is not a function: {}", func_val)), - } - } - - loop { - match func_val { - Value::Function(f) => break Ok(f(arg_vals)), - Value::Object(obj) => { - if let Some(closure) = obj.as_any().downcast_ref::() { - let old_stack_top = $self.stack.len(); - let closure_rc = Rc::new(closure.clone()); - $self.frames.push(CallFrame { stack_base: old_stack_top, closure: Some(closure_rc.clone()) }); - if let Some(count) = closure.positional_count && arg_vals.len() == count as usize { - $self.stack.extend(arg_vals); - } else { - $self.unpack(&closure.parameter_node, &arg_vals, &mut 0)?; - } - let result = $self.$eval_method($($observer,)? &closure.exec_node); - $self.frames.pop(); - $self.stack.truncate(old_stack_top); - match result { - Ok(Value::TailCallRequest(payload)) => { - let (next_obj, next_args) = *payload; - func_val = Value::Object(next_obj); - arg_vals = next_args; - continue; - }, - res => break res, - } - } else { break Err(format!("Object is not a closure: {}", obj.type_name())); } - }, - _ => break Err(format!("Attempt to call non-function: {}", func_val)), - } - } - }, - BoundKind::Tuple { elements } => { - let mut vals = Vec::with_capacity(elements.len()); - for e in elements { vals.push($self.$eval_method($($observer,)? e)?); } - Ok(Value::make_tuple(vals)) - }, - BoundKind::Record { fields } => { - let mut keys = Vec::with_capacity(fields.len()); - let mut values = Vec::with_capacity(fields.len()); - for (k, v) in fields { - let key = $self.$eval_method($($observer,)? k)?; - let val = $self.$eval_method($($observer,)? v)?; - if let Value::Keyword(kw) = key { keys.push(kw); values.push(val); } - else { return Err(format!("Record key must be keyword, got {}", key)); } - } - Ok(Value::make_record(keys, values)) - }, - BoundKind::Expansion { bound_expanded, .. } => { $self.$eval_method($($observer,)? bound_expanded) } - BoundKind::Extension(ext) => { Err(format!("Execution of extension '{}' not implemented yet", ext.display_name())) } - } - }; -} - -impl VM { - pub fn new(globals: Rc>>) -> Self { Self { stack: Vec::new(), globals, frames: Vec::new() } } - - pub fn run(&mut self, root: &ExecNode) -> Result { - self.stack.clear(); self.frames.clear(); - self.frames.push(CallFrame { stack_base: 0, closure: None }); - let mut result = self.eval(root); - self.frames.pop(); - loop { - match result { - Ok(Value::TailCallRequest(payload)) => { - let (next_obj, next_args) = *payload; - if let Some(closure) = next_obj.as_any().downcast_ref::() { - self.stack.clear(); - self.frames.push(CallFrame { stack_base: 0, closure: Some(Rc::new(closure.clone())) }); - if let Some(count) = closure.positional_count && next_args.len() == count as usize { - self.stack.extend(next_args); - } else { - self.unpack(&closure.parameter_node, &next_args, &mut 0)?; - } - result = self.eval(&closure.exec_node); - self.frames.pop(); - } else { return Err(format!("Tail call target is not a closure: {}", next_obj.type_name())); } - } - _ => return result, - } - } - } - - pub fn run_with_args(&mut self, closure: &Closure, args: Vec) -> Result { - self.stack.clear(); self.frames.clear(); - self.frames.push(CallFrame { stack_base: 0, closure: Some(Rc::new(closure.clone())) }); - if let Some(count) = closure.positional_count && args.len() == count as usize { self.stack.extend(args); } - else { self.unpack(&closure.parameter_node, &args, &mut 0)?; } - self.eval(&closure.exec_node) - } - - pub fn run_with_args_observed(&mut self, observer: &mut O, closure: &Closure, args: Vec) -> Result { - self.stack.clear(); self.frames.clear(); - self.frames.push(CallFrame { stack_base: 0, closure: Some(Rc::new(closure.clone())) }); - if let Some(count) = closure.positional_count && args.len() == count as usize { self.stack.extend(args); } - else { self.unpack(&closure.parameter_node, &args, &mut 0)?; } - self.eval_observed(observer, &closure.exec_node) - } - - pub fn run_with_observer(&mut self, observer: &mut O, root: &ExecNode) -> Result { - self.stack.clear(); self.frames.clear(); - self.frames.push(CallFrame { stack_base: 0, closure: None }); - let result = self.eval_observed(observer, root); - self.frames.pop(); - result - } - - #[inline(always)] fn eval(&mut self, node: &ExecNode) -> Result { dispatch_eval!(self, node, eval) } - - fn eval_observed(&mut self, observer: &mut O, node: &ExecNode) -> Result { - observer.before_eval(self, node); - let wrapper = |vm: &mut Self, obs: &mut O| -> Result { dispatch_eval!(vm, node, eval_observed, obs) }; - let result = wrapper(self, observer); - observer.after_eval(self, node, &result); - result - } - - fn capture_upvalue(&mut self, addr: Address) -> Result>, String> { - match addr { - Address::Local(idx) => { - let frame = self.frames.last().ok_or("No call frame")?; - let abs_index = frame.stack_base + (idx as usize); - if abs_index < self.stack.len() { - if let Value::Cell(cell) = &self.stack[abs_index] { Ok(cell.clone()) } - else { - let val = self.stack[abs_index].clone(); - let cell = Rc::new(RefCell::new(val)); - self.stack[abs_index] = Value::Cell(cell.clone()); - Ok(cell) - } - } else { Err(format!("Stack underflow capture local {}", idx)) } - }, - Address::Upvalue(idx) => { - let frame = self.frames.last().ok_or("No call frame")?; - if let Some(closure) = &frame.closure { - let idx = idx as usize; - if idx < closure.upvalues.len() { Ok(closure.upvalues[idx].clone()) } - else { Err(format!("Upvalue access out of bounds capture {}", idx)) } - } else { Err("Current frame has no closure".to_string()) } - }, - Address::Global(_) => Err("Cannot capture global directly".to_string()), - } - } - - fn get_value(&self, addr: Address) -> Result { - match addr { - Address::Local(idx) => { - let frame = self.frames.last().ok_or("No call frame")?; - let abs_index = frame.stack_base + (idx as usize); - if abs_index < self.stack.len() { - match &self.stack[abs_index] { Value::Cell(cell) => Ok(cell.borrow().clone()), val => Ok(val.clone()) } - } else { Err(format!("Stack underflow access local {}", idx)) } - }, - Address::Global(idx) => { - let idx = idx as usize; - let globals = self.globals.borrow(); - if idx < globals.len() { Ok(globals[idx].clone()) } - else { Err(format!("Global access out of bounds {}", idx)) } - }, - Address::Upvalue(idx) => { - let frame = self.frames.last().ok_or("No call frame")?; - if let Some(closure) = &frame.closure { - let idx = idx as usize; - if idx < closure.upvalues.len() { Ok(closure.upvalues[idx].borrow().clone()) } - else { Err(format!("Upvalue access out of bounds {}", idx)) } - } else { Err("Current frame has no closure (cannot access upvalues)".to_string()) } - } - } - } - - fn set_value(&mut self, addr: Address, value: Value) -> Result<(), String> { - match addr { - Address::Local(idx) => { - let frame = self.frames.last().ok_or("No call frame")?; - let abs_index = frame.stack_base + (idx as usize); - if abs_index < self.stack.len() { - if let Value::Cell(cell) = &self.stack[abs_index] { *cell.borrow_mut() = value; } - else { self.stack[abs_index] = value; } - } else if abs_index == self.stack.len() { self.stack.push(value); } - else { return Err(format!("Stack gap write local {}", idx)); } - Ok(()) - }, - Address::Global(idx) => { - let idx = idx as usize; - let mut globals = self.globals.borrow_mut(); - if idx >= globals.len() { globals.resize(idx + 1, Value::Void); } - globals[idx] = value; - Ok(()) - }, - Address::Upvalue(idx) => { - let frame = self.frames.last().ok_or("No call frame")?; - if let Some(closure) = &frame.closure { - let idx = idx as usize; - if idx < closure.upvalues.len() { *closure.upvalues[idx].borrow_mut() = value; Ok(()) } - else { Err(format!("Upvalue assignment out of bounds {}", idx)) } - } else { Err("Current frame has no closure".to_string()) } - }, - } - } - - fn flatten_value(val: Value, into: &mut Vec) { - if let Some(values) = val.as_slice() { for item in values.iter() { Self::flatten_value(item.clone(), into); } } - else { into.push(val); } - } - - fn prepare_args(&mut self, args: &ExecNode) -> Result, String> { - let mut arg_vals = Vec::new(); - match &args.kind { - BoundKind::Tuple { elements } => { self.eval_and_flatten(elements, &mut arg_vals)?; } - _ => { VM::flatten_value(self.eval(args)?, &mut arg_vals); } - } - Ok(arg_vals) - } - - fn prepare_args_observed(&mut self, observer: &mut O, args: &ExecNode) -> Result, String> { - let mut arg_vals = Vec::new(); - match &args.kind { - BoundKind::Tuple { elements } => { self.eval_observed_and_flatten(observer, elements, &mut arg_vals)?; } - _ => { VM::flatten_value(self.eval_observed(observer, args)?, &mut arg_vals); } - } - Ok(arg_vals) - } - - fn eval_and_flatten(&mut self, elements: &[ExecNode], into: &mut Vec) -> Result<(), String> { - for e in elements { - match &e.kind { - BoundKind::Tuple { elements: sub } => self.eval_and_flatten(sub, into)?, - _ => into.push(self.eval(e)?), - } - } - Ok(()) - } - - fn eval_observed_and_flatten(&mut self, observer: &mut O, elements: &[ExecNode], into: &mut Vec) -> Result<(), String> { - for e in elements { - match &e.kind { - BoundKind::Tuple { elements: sub } => self.eval_observed_and_flatten(observer, sub, into)?, - _ => into.push(self.eval_observed(observer, e)?), - } - } - Ok(()) - } - - fn unpack(&mut self, pattern: &Node, T>, values: &[Value], offset: &mut usize) -> Result<(), String> { - match &pattern.kind { - BoundKind::Parameter { slot, .. } => { - let val = values.get(*offset).cloned().unwrap_or(Value::Void); - *offset += 1; - let frame = self.frames.last().ok_or("No call frame")?; - let abs_index = frame.stack_base + (*slot as usize); - if abs_index == self.stack.len() { self.stack.push(val); } - else if abs_index < self.stack.len() { self.stack[abs_index] = val; } - else { return Err(format!("Stack gap during unpack at slot {}", slot)); } - Ok(()) - } - BoundKind::Tuple { elements } => { - if let Some(sub_values) = values.get(*offset).and_then(|v| v.as_slice()) { - *offset += 1; - let mut sub_offset = 0; - for el in elements { self.unpack(el, sub_values, &mut sub_offset)?; } - return Ok(()); - } - for el in elements { self.unpack(el, values, offset)?; } - Ok(()) - } - _ => Err("Invalid node in parameter pattern".to_string()), - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::ast::nodes::{Node, Symbol}; - use crate::ast::compiler::tco::TCO; - use crate::ast::types::{SourceLocation, NodeIdentity, StaticType}; - - fn make_dummy_identity() -> Rc { Rc::new(NodeIdentity { location: SourceLocation { line: 0, col: 0 } }) } - - #[test] - fn test_capture_boxing_modification() { - let id = make_dummy_identity(); - let lambda_body = Node { - identity: id.clone(), ty: StaticType::Void, - kind: BoundKind::Set { addr: Address::Upvalue(0), value: Box::new(Node { identity: id.clone(), ty: StaticType::Int, kind: BoundKind::Constant(Value::Int(20)) }) }, - }; - let root = Node { - identity: id.clone(), ty: StaticType::Int, - kind: BoundKind::Block { - exprs: vec![ - Node { identity: id.clone(), ty: StaticType::Int, kind: BoundKind::Set { addr: Address::Local(0), value: Box::new(Node { identity: id.clone(), ty: StaticType::Int, kind: BoundKind::Constant(Value::Int(10)) }) } }, - Node { identity: id.clone(), ty: StaticType::Any, kind: BoundKind::Set { addr: Address::Local(1), value: Box::new(Node { identity: id.clone(), ty: StaticType::Any, kind: BoundKind::Lambda { params: Rc::new(Node { identity: id.clone(), ty: StaticType::Tuple(vec![]), kind: BoundKind::Tuple { elements: vec![] } }), upvalues: vec![Address::Local(0)], body: Rc::new(lambda_body), positional_count: Some(0) } }) } }, - Node { identity: id.clone(), ty: StaticType::Void, kind: BoundKind::Call { callee: Box::new(Node { identity: id.clone(), ty: StaticType::Any, kind: BoundKind::Get { addr: Address::Local(1), name: Symbol::from("f") } }), args: Box::new(Node { identity: id.clone(), ty: StaticType::Tuple(vec![]), kind: BoundKind::Tuple { elements: vec![] } }) } }, - Node { identity: id.clone(), ty: StaticType::Int, kind: BoundKind::Get { addr: Address::Local(0), name: Symbol::from("x") } }, - ], - }, - }; - let globals = Rc::new(RefCell::new(Vec::new())); - let mut vm = VM::new(globals); - let exec_root = TCO::optimize(root); - let result = vm.run(&exec_root); - match result { Ok(Value::Int(val)) => assert_eq!(val, 20), _ => panic!("Expected Int(20)") } - } -} +use crate::ast::compiler::bound_nodes::{Address, BoundKind, TypedNode}; +use crate::ast::compiler::tco::ExecNode; +use crate::ast::nodes::Node; +use crate::ast::types::{Object, Value}; +use std::any::Any; +use std::cell::RefCell; +use std::rc::Rc; + +#[derive(Debug, Clone)] +pub struct Closure { + pub parameter_node: Rc, + pub function_node: Rc, + pub exec_node: Rc, + pub upvalues: Vec>>, + pub positional_count: Option, +} + +impl Closure { + #[inline] + pub fn new( + params: Rc, + body: Rc, + exec: Rc, + upvalues: Vec>>, + positional_count: Option, + ) -> Self { + Self { + parameter_node: params, + function_node: body, + exec_node: exec, + upvalues, + positional_count, + } + } +} + +impl Object for Closure { + fn type_name(&self) -> &'static str { + "closure" + } + fn as_any(&self) -> &dyn Any { + self + } +} + +#[derive(Debug)] +struct CallFrame { + stack_base: usize, + closure: Option>, +} + +pub trait VMObserver { + const ACTIVE: bool = false; + fn before_eval(&mut self, _vm: &VM, _node: &ExecNode) {} + fn after_eval(&mut self, _vm: &VM, _node: &ExecNode, _res: &Result) {} +} + +pub struct NoOpObserver; +impl VMObserver for NoOpObserver {} + +pub struct TracingObserver { + pub logs: Vec, + indent: usize, +} + +impl TracingObserver { + pub fn new() -> Self { + Self { + logs: Vec::new(), + indent: 0, + } + } + fn pad(&self) -> String { + "| ".repeat(self.indent) + } +} + +impl Default for TracingObserver { + fn default() -> Self { + Self::new() + } +} + +impl VMObserver for TracingObserver { + const ACTIVE: bool = true; + fn before_eval(&mut self, _vm: &VM, node: &ExecNode) { + let pad = self.pad(); + self.logs.push(format!( + "{}{} [{}]: {{", + pad, + node.kind.display_name(), + node.ty.ty + )); + self.indent += 1; + } + fn after_eval(&mut self, vm: &VM, node: &ExecNode, res: &Result) { + self.indent = self.indent.saturating_sub(1); + let pad = self.pad(); + match &node.kind { + BoundKind::DefLocal { .. } | BoundKind::Set { .. } | BoundKind::DefGlobal { .. } => { + let s_pad = format!("{}| ", pad); + self.logs.push(format!("{}--- Scope Status ---", s_pad)); + self.logs.push(format!( + "{}Stack (top 5): {:?}", + s_pad, + vm.stack.iter().rev().take(5).collect::>() + )); + } + _ => {} + } + let res_str = match res { + Ok(v) => format!("{}", v), + Err(e) => format!("ERROR: {}", e), + }; + self.logs.push(format!("{}}} -> {}", pad, res_str)); + } +} + +pub struct VM { + stack: Vec, + globals: Rc>>, + frames: Vec, +} + +macro_rules! dispatch_eval { + ($self:ident, $node:ident, $eval_method:ident $(, $observer:ident)?) => { + match &$node.kind { + BoundKind::Nop => Ok(Value::Void), + BoundKind::Constant(v) => Ok(v.clone()), + BoundKind::Parameter { .. } => Ok(Value::Void), + BoundKind::DefGlobal { global_index, value, .. } => { + let val = $self.$eval_method($($observer,)? value)?; + let idx = *global_index as usize; + let mut globals = $self.globals.borrow_mut(); + if idx >= globals.len() { globals.resize(idx + 1, Value::Void); } + globals[idx] = val.clone(); + Ok(val) + }, + BoundKind::Get { addr, .. } => $self.get_value(*addr), + BoundKind::Set { addr, value } => { + let val = $self.$eval_method($($observer,)? value)?; + $self.set_value(*addr, val.clone())?; + Ok(val) + }, + BoundKind::DefLocal { slot, value, captured_by, .. } => { + let val = $self.$eval_method($($observer,)? value)?; + let final_val = if !captured_by.is_empty() { Value::Cell(Rc::new(RefCell::new(val))) } else { val }; + let frame = $self.frames.last().ok_or("No call frame")?; + let abs_index = frame.stack_base + (*slot as usize); + if abs_index == $self.stack.len() { $self.stack.push(final_val.clone()); } + else if abs_index < $self.stack.len() { $self.stack[abs_index] = final_val.clone(); } + else { return Err(format!("Stack gap at local {}", slot)); } + Ok(final_val) + }, + BoundKind::If { cond, then_br, else_br } => { + let c = $self.$eval_method($($observer,)? cond)?; + if c.is_truthy() { $self.$eval_method($($observer,)? then_br) } + else if let Some(e) = else_br { $self.$eval_method($($observer,)? e) } + else { Ok(Value::Void) } + }, + BoundKind::Block { exprs } => { + let mut last = Value::Void; + for e in exprs { last = $self.$eval_method($($observer,)? e)?; } + Ok(last) + }, + BoundKind::Lambda { params, upvalues, body, positional_count } => { + let mut captured = Vec::with_capacity(upvalues.len()); + for addr in upvalues { captured.push($self.capture_upvalue(*addr)?); } + // CRITICAL FIX: body.clone() is O(1), body.as_ref().clone() was O(N)! + let closure = Closure::new(params.ty.original.clone(), body.ty.original.clone(), body.clone(), captured, *positional_count); + Ok(Value::Object(Rc::new(closure))) + }, + BoundKind::Call { callee, args } => { + let mut func_val = $self.$eval_method($($observer,)? callee)?; + + macro_rules! get_arg_vals { + ($s:ident, $a:ident) => { $s.prepare_args($a)? }; + ($s:ident, $o:ident, $a:ident) => { $s.prepare_args_observed($o, $a)? }; + } + + let mut arg_vals = match &args.kind { + BoundKind::Tuple { elements } => { + let mut vals = Vec::with_capacity(elements.len()); + let mut is_complex = false; + for e in elements { + if matches!(e.kind, BoundKind::Tuple { .. }) { is_complex = true; break; } + vals.push($self.$eval_method($($observer,)? e)?); + } + if is_complex { get_arg_vals!($self, $($observer,)? args) } else { vals } + } + _ => { get_arg_vals!($self, $($observer,)? args) } + }; + + if $node.ty.is_tail { + match func_val { + Value::Object(obj) => return Ok(Value::TailCallRequest(Box::new((obj, arg_vals)))), + Value::Function(f) => return Ok(f(arg_vals)), + _ => return Err(format!("Tail call target is not a function: {}", func_val)), + } + } + + loop { + match func_val { + Value::Function(f) => break Ok(f(arg_vals)), + Value::Object(obj) => { + if let Some(closure) = obj.as_any().downcast_ref::() { + let old_stack_top = $self.stack.len(); + let closure_rc = Rc::new(closure.clone()); + $self.frames.push(CallFrame { stack_base: old_stack_top, closure: Some(closure_rc.clone()) }); + if let Some(count) = closure.positional_count && arg_vals.len() == count as usize { + $self.stack.extend(arg_vals); + } else { + $self.unpack(&closure.parameter_node, &arg_vals, &mut 0)?; + } + let result = $self.$eval_method($($observer,)? &closure.exec_node); + $self.frames.pop(); + $self.stack.truncate(old_stack_top); + match result { + Ok(Value::TailCallRequest(payload)) => { + let (next_obj, next_args) = *payload; + func_val = Value::Object(next_obj); + arg_vals = next_args; + continue; + }, + res => break res, + } + } else { break Err(format!("Object is not a closure: {}", obj.type_name())); } + }, + _ => break Err(format!("Attempt to call non-function: {}", func_val)), + } + } + }, + BoundKind::Tuple { elements } => { + let mut vals = Vec::with_capacity(elements.len()); + for e in elements { vals.push($self.$eval_method($($observer,)? e)?); } + Ok(Value::make_tuple(vals)) + }, + BoundKind::Record { fields } => { + let mut keys = Vec::with_capacity(fields.len()); + let mut values = Vec::with_capacity(fields.len()); + for (k, v) in fields { + let key = $self.$eval_method($($observer,)? k)?; + let val = $self.$eval_method($($observer,)? v)?; + if let Value::Keyword(kw) = key { keys.push(kw); values.push(val); } + else { return Err(format!("Record key must be keyword, got {}", key)); } + } + Ok(Value::make_record(keys, values)) + }, + BoundKind::Expansion { bound_expanded, .. } => { $self.$eval_method($($observer,)? bound_expanded) } + BoundKind::Extension(ext) => { Err(format!("Execution of extension '{}' not implemented yet", ext.display_name())) } + } + }; +} + +impl VM { + pub fn new(globals: Rc>>) -> Self { + Self { + stack: Vec::new(), + globals, + frames: Vec::new(), + } + } + + pub fn run(&mut self, root: &ExecNode) -> Result { + self.stack.clear(); + self.frames.clear(); + self.frames.push(CallFrame { + stack_base: 0, + closure: None, + }); + let mut result = self.eval(root); + self.frames.pop(); + loop { + match result { + Ok(Value::TailCallRequest(payload)) => { + let (next_obj, next_args) = *payload; + if let Some(closure) = next_obj.as_any().downcast_ref::() { + self.stack.clear(); + self.frames.push(CallFrame { + stack_base: 0, + closure: Some(Rc::new(closure.clone())), + }); + if let Some(count) = closure.positional_count + && next_args.len() == count as usize + { + self.stack.extend(next_args); + } else { + self.unpack(&closure.parameter_node, &next_args, &mut 0)?; + } + result = self.eval(&closure.exec_node); + self.frames.pop(); + } else { + return Err(format!( + "Tail call target is not a closure: {}", + next_obj.type_name() + )); + } + } + _ => return result, + } + } + } + + pub fn run_with_args(&mut self, closure: &Closure, args: Vec) -> Result { + self.stack.clear(); + self.frames.clear(); + self.frames.push(CallFrame { + stack_base: 0, + closure: Some(Rc::new(closure.clone())), + }); + if let Some(count) = closure.positional_count + && args.len() == count as usize + { + self.stack.extend(args); + } else { + self.unpack(&closure.parameter_node, &args, &mut 0)?; + } + self.eval(&closure.exec_node) + } + + pub fn run_with_args_observed( + &mut self, + observer: &mut O, + closure: &Closure, + args: Vec, + ) -> Result { + self.stack.clear(); + self.frames.clear(); + self.frames.push(CallFrame { + stack_base: 0, + closure: Some(Rc::new(closure.clone())), + }); + if let Some(count) = closure.positional_count + && args.len() == count as usize + { + self.stack.extend(args); + } else { + self.unpack(&closure.parameter_node, &args, &mut 0)?; + } + self.eval_observed(observer, &closure.exec_node) + } + + pub fn run_with_observer( + &mut self, + observer: &mut O, + root: &ExecNode, + ) -> Result { + self.stack.clear(); + self.frames.clear(); + self.frames.push(CallFrame { + stack_base: 0, + closure: None, + }); + let result = self.eval_observed(observer, root); + self.frames.pop(); + result + } + + #[inline(always)] + fn eval(&mut self, node: &ExecNode) -> Result { + dispatch_eval!(self, node, eval) + } + + fn eval_observed( + &mut self, + observer: &mut O, + node: &ExecNode, + ) -> Result { + observer.before_eval(self, node); + let wrapper = |vm: &mut Self, obs: &mut O| -> Result { + dispatch_eval!(vm, node, eval_observed, obs) + }; + let result = wrapper(self, observer); + observer.after_eval(self, node, &result); + result + } + + fn capture_upvalue(&mut self, addr: Address) -> Result>, String> { + match addr { + Address::Local(idx) => { + let frame = self.frames.last().ok_or("No call frame")?; + let abs_index = frame.stack_base + (idx as usize); + if abs_index < self.stack.len() { + if let Value::Cell(cell) = &self.stack[abs_index] { + Ok(cell.clone()) + } else { + let val = self.stack[abs_index].clone(); + let cell = Rc::new(RefCell::new(val)); + self.stack[abs_index] = Value::Cell(cell.clone()); + Ok(cell) + } + } else { + Err(format!("Stack underflow capture local {}", idx)) + } + } + Address::Upvalue(idx) => { + let frame = self.frames.last().ok_or("No call frame")?; + if let Some(closure) = &frame.closure { + let idx = idx as usize; + if idx < closure.upvalues.len() { + Ok(closure.upvalues[idx].clone()) + } else { + Err(format!("Upvalue access out of bounds capture {}", idx)) + } + } else { + Err("Current frame has no closure".to_string()) + } + } + Address::Global(_) => Err("Cannot capture global directly".to_string()), + } + } + + fn get_value(&self, addr: Address) -> Result { + match addr { + Address::Local(idx) => { + let frame = self.frames.last().ok_or("No call frame")?; + let abs_index = frame.stack_base + (idx as usize); + if abs_index < self.stack.len() { + match &self.stack[abs_index] { + Value::Cell(cell) => Ok(cell.borrow().clone()), + val => Ok(val.clone()), + } + } else { + Err(format!("Stack underflow access local {}", idx)) + } + } + Address::Global(idx) => { + let idx = idx as usize; + let globals = self.globals.borrow(); + if idx < globals.len() { + Ok(globals[idx].clone()) + } else { + Err(format!("Global access out of bounds {}", idx)) + } + } + Address::Upvalue(idx) => { + let frame = self.frames.last().ok_or("No call frame")?; + if let Some(closure) = &frame.closure { + let idx = idx as usize; + if idx < closure.upvalues.len() { + Ok(closure.upvalues[idx].borrow().clone()) + } else { + Err(format!("Upvalue access out of bounds {}", idx)) + } + } else { + Err("Current frame has no closure (cannot access upvalues)".to_string()) + } + } + } + } + + fn set_value(&mut self, addr: Address, value: Value) -> Result<(), String> { + match addr { + Address::Local(idx) => { + let frame = self.frames.last().ok_or("No call frame")?; + let abs_index = frame.stack_base + (idx as usize); + if abs_index < self.stack.len() { + if let Value::Cell(cell) = &self.stack[abs_index] { + *cell.borrow_mut() = value; + } else { + self.stack[abs_index] = value; + } + } else if abs_index == self.stack.len() { + self.stack.push(value); + } else { + return Err(format!("Stack gap write local {}", idx)); + } + Ok(()) + } + Address::Global(idx) => { + let idx = idx as usize; + let mut globals = self.globals.borrow_mut(); + if idx >= globals.len() { + globals.resize(idx + 1, Value::Void); + } + globals[idx] = value; + Ok(()) + } + Address::Upvalue(idx) => { + let frame = self.frames.last().ok_or("No call frame")?; + if let Some(closure) = &frame.closure { + let idx = idx as usize; + if idx < closure.upvalues.len() { + *closure.upvalues[idx].borrow_mut() = value; + Ok(()) + } else { + Err(format!("Upvalue assignment out of bounds {}", idx)) + } + } else { + Err("Current frame has no closure".to_string()) + } + } + } + } + + fn flatten_value(val: Value, into: &mut Vec) { + if let Some(values) = val.as_slice() { + for item in values.iter() { + Self::flatten_value(item.clone(), into); + } + } else { + into.push(val); + } + } + + fn prepare_args(&mut self, args: &ExecNode) -> Result, String> { + let mut arg_vals = Vec::new(); + match &args.kind { + BoundKind::Tuple { elements } => { + self.eval_and_flatten(elements, &mut arg_vals)?; + } + _ => { + VM::flatten_value(self.eval(args)?, &mut arg_vals); + } + } + Ok(arg_vals) + } + + fn prepare_args_observed( + &mut self, + observer: &mut O, + args: &ExecNode, + ) -> Result, String> { + let mut arg_vals = Vec::new(); + match &args.kind { + BoundKind::Tuple { elements } => { + self.eval_observed_and_flatten(observer, elements, &mut arg_vals)?; + } + _ => { + VM::flatten_value(self.eval_observed(observer, args)?, &mut arg_vals); + } + } + Ok(arg_vals) + } + + fn eval_and_flatten( + &mut self, + elements: &[ExecNode], + into: &mut Vec, + ) -> Result<(), String> { + for e in elements { + match &e.kind { + BoundKind::Tuple { elements: sub } => self.eval_and_flatten(sub, into)?, + _ => into.push(self.eval(e)?), + } + } + Ok(()) + } + + fn eval_observed_and_flatten( + &mut self, + observer: &mut O, + elements: &[ExecNode], + into: &mut Vec, + ) -> Result<(), String> { + for e in elements { + match &e.kind { + BoundKind::Tuple { elements: sub } => { + self.eval_observed_and_flatten(observer, sub, into)? + } + _ => into.push(self.eval_observed(observer, e)?), + } + } + Ok(()) + } + + fn unpack( + &mut self, + pattern: &Node, T>, + values: &[Value], + offset: &mut usize, + ) -> Result<(), String> { + match &pattern.kind { + BoundKind::Parameter { slot, .. } => { + let val = values.get(*offset).cloned().unwrap_or(Value::Void); + *offset += 1; + let frame = self.frames.last().ok_or("No call frame")?; + let abs_index = frame.stack_base + (*slot as usize); + if abs_index == self.stack.len() { + self.stack.push(val); + } else if abs_index < self.stack.len() { + self.stack[abs_index] = val; + } else { + return Err(format!("Stack gap during unpack at slot {}", slot)); + } + Ok(()) + } + BoundKind::Tuple { elements } => { + if let Some(sub_values) = values.get(*offset).and_then(|v| v.as_slice()) { + *offset += 1; + let mut sub_offset = 0; + for el in elements { + self.unpack(el, sub_values, &mut sub_offset)?; + } + return Ok(()); + } + for el in elements { + self.unpack(el, values, offset)?; + } + Ok(()) + } + _ => Err("Invalid node in parameter pattern".to_string()), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::ast::compiler::tco::TCO; + use crate::ast::nodes::{Node, Symbol}; + use crate::ast::types::{NodeIdentity, SourceLocation, StaticType}; + + fn make_dummy_identity() -> Rc { + Rc::new(NodeIdentity { + location: SourceLocation { line: 0, col: 0 }, + }) + } + + #[test] + fn test_capture_boxing_modification() { + let id = make_dummy_identity(); + let lambda_body = Node { + identity: id.clone(), + ty: StaticType::Void, + kind: BoundKind::Set { + addr: Address::Upvalue(0), + value: Box::new(Node { + identity: id.clone(), + ty: StaticType::Int, + kind: BoundKind::Constant(Value::Int(20)), + }), + }, + }; + let root = Node { + identity: id.clone(), + ty: StaticType::Int, + kind: BoundKind::Block { + exprs: vec![ + Node { + identity: id.clone(), + ty: StaticType::Int, + kind: BoundKind::Set { + addr: Address::Local(0), + value: Box::new(Node { + identity: id.clone(), + ty: StaticType::Int, + kind: BoundKind::Constant(Value::Int(10)), + }), + }, + }, + Node { + identity: id.clone(), + ty: StaticType::Any, + kind: BoundKind::Set { + addr: Address::Local(1), + value: Box::new(Node { + identity: id.clone(), + ty: StaticType::Any, + kind: BoundKind::Lambda { + params: Rc::new(Node { + identity: id.clone(), + ty: StaticType::Tuple(vec![]), + kind: BoundKind::Tuple { elements: vec![] }, + }), + upvalues: vec![Address::Local(0)], + body: Rc::new(lambda_body), + positional_count: Some(0), + }, + }), + }, + }, + Node { + identity: id.clone(), + ty: StaticType::Void, + kind: BoundKind::Call { + callee: Box::new(Node { + identity: id.clone(), + ty: StaticType::Any, + kind: BoundKind::Get { + addr: Address::Local(1), + name: Symbol::from("f"), + }, + }), + args: Box::new(Node { + identity: id.clone(), + ty: StaticType::Tuple(vec![]), + kind: BoundKind::Tuple { elements: vec![] }, + }), + }, + }, + Node { + identity: id.clone(), + ty: StaticType::Int, + kind: BoundKind::Get { + addr: Address::Local(0), + name: Symbol::from("x"), + }, + }, + ], + }, + }; + let globals = Rc::new(RefCell::new(Vec::new())); + let mut vm = VM::new(globals); + let exec_root = TCO::optimize(root); + let result = vm.run(&exec_root); + match result { + Ok(Value::Int(val)) => assert_eq!(val, 20), + _ => panic!("Expected Int(20)"), + } + } +} diff --git a/src/bin/ast.rs b/src/bin/ast.rs index 14839ed..5aa9be9 100644 --- a/src/bin/ast.rs +++ b/src/bin/ast.rs @@ -1,132 +1,134 @@ -use myc::ast::environment::Environment; -use myc::utils::tester; -use clap::Parser; -use std::fs; -use std::path::PathBuf; - -#[derive(Parser)] -#[command(author, version, about = "MYC AST Compiler & Benchmarker", long_about = None)] -struct Cli { - /// The script file to run or benchmark - #[arg(value_name = "FILE")] - file: Option, - - /// Run a script string directly - #[arg(short, long)] - eval: Option, - - /// Run benchmarks (Only allowed in Release mode) - #[arg(short, long)] - bench: bool, - - /// Update the benchmark baseline (Only allowed in Release mode) - #[arg(short, long)] - update_bench: bool, - - /// Dump the compiled AST - #[arg(short, long)] - dump: bool, - - /// Run with TracingObserver enabled - #[arg(short, long)] - trace: bool, - - /// Disable optimization - #[arg(long)] - no_opt: bool, -} - -fn main() { - let cli = Cli::parse(); - let mut env = Environment::new(); - env.optimization = !cli.no_opt; - - if cli.bench || cli.update_bench { - if cfg!(debug_assertions) { - eprintln!("❌ ERROR: Benchmarks must be run in Release mode!"); - eprintln!(" Use: cargo run --release --bin ast -- --bench"); - std::process::exit(1); - } - - println!("🚀 Running benchmarks in RELEASE mode...\n"); - let results = tester::run_benchmarks(cli.update_bench); - for res in results { - let diff = res.diff_pct.map_or(String::new(), |d| format!(" ({:+.1}%)", d)); - println!("{}: {} - {:?}{}", res.status, res.name, res.median, diff); - } - return; - } - - if let Some(script_str) = cli.eval { - if cli.dump { - match env.dump_ast(&script_str) { - Ok(dump) => println!("{}", dump), - Err(e) => eprintln!("Error dumping AST: {}", e), - } - } else if cli.trace { - execute_trace(&mut env, &script_str); - } else { - execute(&env, &script_str); - } - } else if let Some(file_path) = cli.file { - match fs::read_to_string(&file_path) { - Ok(content) => { - if cli.dump { - match env.dump_ast(&content) { - Ok(dump) => println!("{}", dump), - Err(e) => eprintln!("Error dumping AST: {}", e), - } - } else if cli.trace { - execute_trace(&mut env, &content); - } else { - execute(&env, &content); - } - }, - Err(e) => { - eprintln!("Error reading file {:?}: {}", file_path, e); - std::process::exit(1); - } - } - } else { - println!("MYC AST Compiler CLI. Use --help for usage."); - } -} - -fn execute(env: &Environment, source: &str) { - match env.run_script(source) { - Ok(result) => println!("{}", result), - Err(e) => { - eprintln!("Error: {}", e); - std::process::exit(1); - } - } -} - -fn execute_trace(env: &mut Environment, source: &str) { - match env.compile(source) { - Ok(compiled) => { - let linked = env.link(compiled); - let mut vm = myc::ast::vm::VM::new(env.global_values.clone()); - let mut observer = myc::ast::vm::TracingObserver::new(); - match vm.run_with_observer(&mut observer, &linked) { - Ok(result) => { - for line in observer.logs { - println!("{}", line); - } - println!("Result: {}", result); - } - Err(e) => { - for line in observer.logs { - println!("{}", line); - } - eprintln!("Runtime Error: {}", e); - std::process::exit(1); - } - } - } - Err(e) => { - eprintln!("Compilation Error: {}", e); - std::process::exit(1); - } - } -} +use clap::Parser; +use myc::ast::environment::Environment; +use myc::utils::tester; +use std::fs; +use std::path::PathBuf; + +#[derive(Parser)] +#[command(author, version, about = "MYC AST Compiler & Benchmarker", long_about = None)] +struct Cli { + /// The script file to run or benchmark + #[arg(value_name = "FILE")] + file: Option, + + /// Run a script string directly + #[arg(short, long)] + eval: Option, + + /// Run benchmarks (Only allowed in Release mode) + #[arg(short, long)] + bench: bool, + + /// Update the benchmark baseline (Only allowed in Release mode) + #[arg(short, long)] + update_bench: bool, + + /// Dump the compiled AST + #[arg(short, long)] + dump: bool, + + /// Run with TracingObserver enabled + #[arg(short, long)] + trace: bool, + + /// Disable optimization + #[arg(long)] + no_opt: bool, +} + +fn main() { + let cli = Cli::parse(); + let mut env = Environment::new(); + env.optimization = !cli.no_opt; + + if cli.bench || cli.update_bench { + if cfg!(debug_assertions) { + eprintln!("❌ ERROR: Benchmarks must be run in Release mode!"); + eprintln!(" Use: cargo run --release --bin ast -- --bench"); + std::process::exit(1); + } + + println!("🚀 Running benchmarks in RELEASE mode...\n"); + let results = tester::run_benchmarks(cli.update_bench); + for res in results { + let diff = res + .diff_pct + .map_or(String::new(), |d| format!(" ({:+.1}%)", d)); + println!("{}: {} - {:?}{}", res.status, res.name, res.median, diff); + } + return; + } + + if let Some(script_str) = cli.eval { + if cli.dump { + match env.dump_ast(&script_str) { + Ok(dump) => println!("{}", dump), + Err(e) => eprintln!("Error dumping AST: {}", e), + } + } else if cli.trace { + execute_trace(&mut env, &script_str); + } else { + execute(&env, &script_str); + } + } else if let Some(file_path) = cli.file { + match fs::read_to_string(&file_path) { + Ok(content) => { + if cli.dump { + match env.dump_ast(&content) { + Ok(dump) => println!("{}", dump), + Err(e) => eprintln!("Error dumping AST: {}", e), + } + } else if cli.trace { + execute_trace(&mut env, &content); + } else { + execute(&env, &content); + } + } + Err(e) => { + eprintln!("Error reading file {:?}: {}", file_path, e); + std::process::exit(1); + } + } + } else { + println!("MYC AST Compiler CLI. Use --help for usage."); + } +} + +fn execute(env: &Environment, source: &str) { + match env.run_script(source) { + Ok(result) => println!("{}", result), + Err(e) => { + eprintln!("Error: {}", e); + std::process::exit(1); + } + } +} + +fn execute_trace(env: &mut Environment, source: &str) { + match env.compile(source) { + Ok(compiled) => { + let linked = env.link(compiled); + let mut vm = myc::ast::vm::VM::new(env.global_values.clone()); + let mut observer = myc::ast::vm::TracingObserver::new(); + match vm.run_with_observer(&mut observer, &linked) { + Ok(result) => { + for line in observer.logs { + println!("{}", line); + } + println!("Result: {}", result); + } + Err(e) => { + for line in observer.logs { + println!("{}", line); + } + eprintln!("Runtime Error: {}", e); + std::process::exit(1); + } + } + } + Err(e) => { + eprintln!("Compilation Error: {}", e); + std::process::exit(1); + } + } +} diff --git a/src/utils/tester.rs b/src/utils/tester.rs index 0f3aaae..28c0b28 100644 --- a/src/utils/tester.rs +++ b/src/utils/tester.rs @@ -1,328 +1,334 @@ -use crate::ast::environment::Environment; -use regex::Regex; -use std::fs; -use std::time::{Duration, Instant}; - -pub struct TestResult { - pub name: String, - pub success: bool, - pub message: String, -} - -pub struct BenchmarkResult { - pub name: String, - pub median: Duration, - pub baseline: Option, - pub diff_pct: Option, - pub status: String, -} - -pub fn run_functional_tests() -> Vec { - run_functional_tests_with_optimization(false) -} - -pub fn run_functional_tests_with_optimization(enabled: bool) -> Vec { - let mut results = Vec::new(); - let entries = fs::read_dir("examples").unwrap(); - let output_re = Regex::new(r";; Output: (.*)").unwrap(); - - for entry in entries.filter_map(|e| e.ok()) { - let mut env = Environment::new(); // Fresh environment per test file - env.optimization = enabled; - let path = entry.path(); - if path.extension().is_some_and(|ext| ext == "myc") { - let content = fs::read_to_string(&path).unwrap(); - let name = path.file_name().unwrap().to_string_lossy().to_string(); - - let expected_output = output_re - .captures(&content) - .map(|m| m.get(1).unwrap().as_str().trim().to_string()); - - if let Some(expected) = expected_output { - match env.run_script(&content) { - Ok(val) => { - let val_str = format!("{}", val); - if val_str == expected { - results.push(TestResult { - name, - success: true, - message: format!("OK: {}", val_str), - }); - } else { - results.push(TestResult { - name, - success: false, - message: format!( - "Opt {}: Expected {}, got {}", - if enabled { "ON" } else { "OFF" }, expected, val_str - ), - }); - } - } - Err(e) => results.push(TestResult { - name, - success: false, - message: format!("Opt {}: Error: {}", if enabled { "ON" } else { "OFF" }, e), - }), - } - } - } - } - results -} - -pub fn run_benchmarks(update: bool) -> Vec { - let mut results = Vec::new(); - let entries = fs::read_dir("examples").unwrap(); - let is_release = !cfg!(debug_assertions); - let baseline_re = Regex::new(r";; Benchmark: ([\d\.]+\w+)").unwrap(); - let repeat_re = Regex::new(r";; Benchmark-Repeat: (\d+)").unwrap(); - - for entry in entries.filter_map(|e| e.ok()) { - let path = entry.path(); - if path.extension().is_none_or(|ext| ext != "myc") { - continue; - } - - let content = fs::read_to_string(&path).unwrap(); - let name = path.file_name().unwrap().to_string_lossy().to_string(); - - let baseline_match = baseline_re.captures(&content); - let repeat_match = repeat_re.captures(&content); - - let mut repeats = repeat_match - .and_then(|m| m.get(1)) - .and_then(|m| m.as_str().parse::().ok()) - .unwrap_or(1); - - // Compile once for this file (symbols/types are compatible with all fresh environments) - let initial_env = Environment::new(); - let compiled_once = match initial_env.compile(&content) { - Ok(c) => c, - Err(e) => { - results.push(BenchmarkResult { - name, - median: Duration::ZERO, - baseline: None, - diff_pct: None, - status: format!("COMPILE ERROR: {}", e), - }); - continue; - } - }; - - // Helper to measure sum of VM execution times over N executions in one environment - let measure_sum = - |n: u32, node: &crate::ast::compiler::TypedNode| -> Result { - let env = Environment::new(); - // Link once per sample - let linked = env.link(node.clone()); - let mut total = Duration::ZERO; - for _ in 0..n { - let start = Instant::now(); - let _ = env.run(&linked)?; - total += start.elapsed(); - } - Ok(total) - }; - - if update { - repeats = 1; - loop { - match measure_sum(repeats, &compiled_once) { - Ok(total) => { - if total >= Duration::from_millis(2) || repeats >= 100_000 { - break; - } - let nanos = total.as_nanos().max(1) as f64; - let factor = 2_000_000.0 / nanos; - repeats = (repeats as f64 * factor).ceil() as u32; - repeats = repeats.max(repeats + 1); - } - Err(e) => { - results.push(BenchmarkResult { - name: name.clone(), - median: Duration::ZERO, - baseline: None, - diff_pct: None, - status: format!("ERROR: {}", e), - }); - break; - } - } - } - if results.last().is_some_and(|r| r.name == name) { - continue; - } - } - - let mut runs = Vec::new(); - let mut error = None; - // Adaptive samples: High repeats need fewer samples for stable median - let num_samples = if repeats > 1000 { - 10 - } else if repeats > 100 { - 30 - } else { - 100 - }; - - for _ in 0..num_samples { - match measure_sum(repeats, &compiled_once) { - Ok(d) => runs.push(d), - Err(e) => { - error = Some(e); - break; - } - } - } - - if let Some(e) = error { - results.push(BenchmarkResult { - name, - median: Duration::ZERO, - baseline: None, - diff_pct: None, - status: format!("ERROR: {}", e), - }); - continue; - } - - runs.sort(); - let median_total = runs[runs.len() / 2]; - let median_single = median_total / repeats; - - if update { - let new_val = format_duration(median_single); - let mut updated_content = content.clone(); - - // 1. Update/Insert Benchmark - let bench_line = format!(";; Benchmark: {}", new_val); - if let Some(m) = baseline_match { - updated_content = updated_content.replace(m.get(0).unwrap().as_str(), &bench_line); - } else { - updated_content = format!("{}\n{}", bench_line, updated_content); - } - - // 2. Update/Insert/Remove Benchmark-Repeat - let repeat_line = if repeats > 1 { - Some(format!(";; Benchmark-Repeat: {}", repeats)) - } else { - None - }; - let current_repeat_str = repeat_re - .captures(&updated_content) - .map(|m| m.get(0).unwrap().as_str().to_string()); - - if let Some(line) = repeat_line { - if let Some(old_line) = current_repeat_str { - updated_content = updated_content.replace(&old_line, &line); - } else if let Some(m) = baseline_re.captures(&updated_content) { - let b_str = m.get(0).unwrap().as_str().to_string(); - if let Some(pos) = updated_content.find(&b_str) { - updated_content.insert_str(pos + b_str.len(), &format!("\n{}", line)); - } - } - } else if let Some(old_line) = current_repeat_str { - updated_content = updated_content.replace(&format!("{}\n", old_line), ""); - updated_content = updated_content.replace(&old_line, ""); - } - - fs::write(&path, updated_content).unwrap(); - results.push(BenchmarkResult { - name, - median: median_single, - baseline: None, - diff_pct: None, - status: format!("UPDATED: {}", new_val), - }); - } else if let Some(m) = baseline_match { - let baseline_str = m.get(1).unwrap().as_str(); - let baseline = parse_duration(baseline_str).unwrap(); - - let diff = (median_single.as_nanos() as f64 / baseline.as_nanos() as f64) - 1.0; - - let threshold = if is_release { 0.15 } else { 0.30 }; - let status = if median_single > baseline && diff > threshold { - "FAILED" - } else { - "OK" - }; - - results.push(BenchmarkResult { - name, - median: median_single, - baseline: Some(baseline), - diff_pct: Some(diff * 100.0), - status: status.to_string(), - }); - } else { - results.push(BenchmarkResult { - name, - median: median_single, - baseline: None, - diff_pct: None, - status: "MISSING BASELINE".to_string(), - }); - } - } - results -} - -fn format_duration(d: Duration) -> String { - if d.as_nanos() < 1000 { - format!("{}ns", d.as_nanos()) - } else if d.as_micros() < 1000 { - format!("{:.1}us", d.as_nanos() as f64 / 1000.0) - } else if d.as_millis() < 1000 { - format!("{:.1}ms", d.as_micros() as f64 / 1000.0) - } else { - format!("{:.1}s", d.as_millis() as f64 / 1000.0) - } -} - -fn parse_duration(s: &str) -> Option { - use std::sync::OnceLock; - static RE: OnceLock = OnceLock::new(); - let re = RE.get_or_init(|| Regex::new(r"([\d\.]+)(\w+)").unwrap()); - - let caps = re.captures(s)?; - let val: f64 = caps[1].parse().ok()?; - let unit = &caps[2]; - match unit { - "ns" => Some(Duration::from_nanos(val as u64)), - "us" => Some(Duration::from_nanos((val * 1000.0) as u64)), - "ms" => Some(Duration::from_nanos((val * 1_000_000.0) as u64)), - "s" => Some(Duration::from_nanos((val * 1_000_000_000.0) as u64)), - _ => None, - } -} - -#[cfg(test)] -mod tests { - #[test] - #[cfg(not(debug_assertions))] - fn benchmark_regression_test() { - use super::*; - let results = run_benchmarks(false); - let failures: Vec<_> = results.iter().filter(|r| r.status == "FAILED").collect(); - - if !failures.is_empty() { - let error_msg = failures - .iter() - .map(|r| { - format!( - "{}: Median {:?}, Baseline {:?}, Diff {:.2}%", - r.name, - r.median, - r.baseline.unwrap_or_default(), - r.diff_pct.unwrap_or(0.0) - ) - }) - .collect::>() - .join("\n"); - - panic!("Performance regression detected:\n{}", error_msg); - } - } -} +use crate::ast::environment::Environment; +use regex::Regex; +use std::fs; +use std::time::{Duration, Instant}; + +pub struct TestResult { + pub name: String, + pub success: bool, + pub message: String, +} + +pub struct BenchmarkResult { + pub name: String, + pub median: Duration, + pub baseline: Option, + pub diff_pct: Option, + pub status: String, +} + +pub fn run_functional_tests() -> Vec { + run_functional_tests_with_optimization(false) +} + +pub fn run_functional_tests_with_optimization(enabled: bool) -> Vec { + let mut results = Vec::new(); + let entries = fs::read_dir("examples").unwrap(); + let output_re = Regex::new(r";; Output: (.*)").unwrap(); + + for entry in entries.filter_map(|e| e.ok()) { + let mut env = Environment::new(); // Fresh environment per test file + env.optimization = enabled; + let path = entry.path(); + if path.extension().is_some_and(|ext| ext == "myc") { + let content = fs::read_to_string(&path).unwrap(); + let name = path.file_name().unwrap().to_string_lossy().to_string(); + + let expected_output = output_re + .captures(&content) + .map(|m| m.get(1).unwrap().as_str().trim().to_string()); + + if let Some(expected) = expected_output { + match env.run_script(&content) { + Ok(val) => { + let val_str = format!("{}", val); + if val_str == expected { + results.push(TestResult { + name, + success: true, + message: format!("OK: {}", val_str), + }); + } else { + results.push(TestResult { + name, + success: false, + message: format!( + "Opt {}: Expected {}, got {}", + if enabled { "ON" } else { "OFF" }, + expected, + val_str + ), + }); + } + } + Err(e) => results.push(TestResult { + name, + success: false, + message: format!( + "Opt {}: Error: {}", + if enabled { "ON" } else { "OFF" }, + e + ), + }), + } + } + } + } + results +} + +pub fn run_benchmarks(update: bool) -> Vec { + let mut results = Vec::new(); + let entries = fs::read_dir("examples").unwrap(); + let is_release = !cfg!(debug_assertions); + let baseline_re = Regex::new(r";; Benchmark: ([\d\.]+\w+)").unwrap(); + let repeat_re = Regex::new(r";; Benchmark-Repeat: (\d+)").unwrap(); + + for entry in entries.filter_map(|e| e.ok()) { + let path = entry.path(); + if path.extension().is_none_or(|ext| ext != "myc") { + continue; + } + + let content = fs::read_to_string(&path).unwrap(); + let name = path.file_name().unwrap().to_string_lossy().to_string(); + + let baseline_match = baseline_re.captures(&content); + let repeat_match = repeat_re.captures(&content); + + let mut repeats = repeat_match + .and_then(|m| m.get(1)) + .and_then(|m| m.as_str().parse::().ok()) + .unwrap_or(1); + + // Compile once for this file (symbols/types are compatible with all fresh environments) + let initial_env = Environment::new(); + let compiled_once = match initial_env.compile(&content) { + Ok(c) => c, + Err(e) => { + results.push(BenchmarkResult { + name, + median: Duration::ZERO, + baseline: None, + diff_pct: None, + status: format!("COMPILE ERROR: {}", e), + }); + continue; + } + }; + + // Helper to measure sum of VM execution times over N executions in one environment + let measure_sum = + |n: u32, node: &crate::ast::compiler::TypedNode| -> Result { + let env = Environment::new(); + // Link once per sample + let linked = env.link(node.clone()); + let mut total = Duration::ZERO; + for _ in 0..n { + let start = Instant::now(); + let _ = env.run(&linked)?; + total += start.elapsed(); + } + Ok(total) + }; + + if update { + repeats = 1; + loop { + match measure_sum(repeats, &compiled_once) { + Ok(total) => { + if total >= Duration::from_millis(2) || repeats >= 100_000 { + break; + } + let nanos = total.as_nanos().max(1) as f64; + let factor = 2_000_000.0 / nanos; + repeats = (repeats as f64 * factor).ceil() as u32; + repeats = repeats.max(repeats + 1); + } + Err(e) => { + results.push(BenchmarkResult { + name: name.clone(), + median: Duration::ZERO, + baseline: None, + diff_pct: None, + status: format!("ERROR: {}", e), + }); + break; + } + } + } + if results.last().is_some_and(|r| r.name == name) { + continue; + } + } + + let mut runs = Vec::new(); + let mut error = None; + // Adaptive samples: High repeats need fewer samples for stable median + let num_samples = if repeats > 1000 { + 10 + } else if repeats > 100 { + 30 + } else { + 100 + }; + + for _ in 0..num_samples { + match measure_sum(repeats, &compiled_once) { + Ok(d) => runs.push(d), + Err(e) => { + error = Some(e); + break; + } + } + } + + if let Some(e) = error { + results.push(BenchmarkResult { + name, + median: Duration::ZERO, + baseline: None, + diff_pct: None, + status: format!("ERROR: {}", e), + }); + continue; + } + + runs.sort(); + let median_total = runs[runs.len() / 2]; + let median_single = median_total / repeats; + + if update { + let new_val = format_duration(median_single); + let mut updated_content = content.clone(); + + // 1. Update/Insert Benchmark + let bench_line = format!(";; Benchmark: {}", new_val); + if let Some(m) = baseline_match { + updated_content = updated_content.replace(m.get(0).unwrap().as_str(), &bench_line); + } else { + updated_content = format!("{}\n{}", bench_line, updated_content); + } + + // 2. Update/Insert/Remove Benchmark-Repeat + let repeat_line = if repeats > 1 { + Some(format!(";; Benchmark-Repeat: {}", repeats)) + } else { + None + }; + let current_repeat_str = repeat_re + .captures(&updated_content) + .map(|m| m.get(0).unwrap().as_str().to_string()); + + if let Some(line) = repeat_line { + if let Some(old_line) = current_repeat_str { + updated_content = updated_content.replace(&old_line, &line); + } else if let Some(m) = baseline_re.captures(&updated_content) { + let b_str = m.get(0).unwrap().as_str().to_string(); + if let Some(pos) = updated_content.find(&b_str) { + updated_content.insert_str(pos + b_str.len(), &format!("\n{}", line)); + } + } + } else if let Some(old_line) = current_repeat_str { + updated_content = updated_content.replace(&format!("{}\n", old_line), ""); + updated_content = updated_content.replace(&old_line, ""); + } + + fs::write(&path, updated_content).unwrap(); + results.push(BenchmarkResult { + name, + median: median_single, + baseline: None, + diff_pct: None, + status: format!("UPDATED: {}", new_val), + }); + } else if let Some(m) = baseline_match { + let baseline_str = m.get(1).unwrap().as_str(); + let baseline = parse_duration(baseline_str).unwrap(); + + let diff = (median_single.as_nanos() as f64 / baseline.as_nanos() as f64) - 1.0; + + let threshold = if is_release { 0.15 } else { 0.30 }; + let status = if median_single > baseline && diff > threshold { + "FAILED" + } else { + "OK" + }; + + results.push(BenchmarkResult { + name, + median: median_single, + baseline: Some(baseline), + diff_pct: Some(diff * 100.0), + status: status.to_string(), + }); + } else { + results.push(BenchmarkResult { + name, + median: median_single, + baseline: None, + diff_pct: None, + status: "MISSING BASELINE".to_string(), + }); + } + } + results +} + +fn format_duration(d: Duration) -> String { + if d.as_nanos() < 1000 { + format!("{}ns", d.as_nanos()) + } else if d.as_micros() < 1000 { + format!("{:.1}us", d.as_nanos() as f64 / 1000.0) + } else if d.as_millis() < 1000 { + format!("{:.1}ms", d.as_micros() as f64 / 1000.0) + } else { + format!("{:.1}s", d.as_millis() as f64 / 1000.0) + } +} + +fn parse_duration(s: &str) -> Option { + use std::sync::OnceLock; + static RE: OnceLock = OnceLock::new(); + let re = RE.get_or_init(|| Regex::new(r"([\d\.]+)(\w+)").unwrap()); + + let caps = re.captures(s)?; + let val: f64 = caps[1].parse().ok()?; + let unit = &caps[2]; + match unit { + "ns" => Some(Duration::from_nanos(val as u64)), + "us" => Some(Duration::from_nanos((val * 1000.0) as u64)), + "ms" => Some(Duration::from_nanos((val * 1_000_000.0) as u64)), + "s" => Some(Duration::from_nanos((val * 1_000_000_000.0) as u64)), + _ => None, + } +} + +#[cfg(test)] +mod tests { + #[test] + #[cfg(not(debug_assertions))] + fn benchmark_regression_test() { + use super::*; + let results = run_benchmarks(false); + let failures: Vec<_> = results.iter().filter(|r| r.status == "FAILED").collect(); + + if !failures.is_empty() { + let error_msg = failures + .iter() + .map(|r| { + format!( + "{}: Median {:?}, Baseline {:?}, Diff {:.2}%", + r.name, + r.median, + r.baseline.unwrap_or_default(), + r.diff_pct.unwrap_or(0.0) + ) + }) + .collect::>() + .join("\n"); + + panic!("Performance regression detected:\n{}", error_msg); + } + } +}