From 84226f6a1634de0f8f094280d680d1dd153a078f Mon Sep 17 00:00:00 2001 From: Michael Schimmel Date: Fri, 13 Mar 2026 14:21:28 +0100 Subject: [PATCH] Refactor: Replace UntypedNode with SyntaxNode This commit replaces the `UntypedNode` enum with the more accurately named `SyntaxNode`. This change is primarily for clarity and better reflects the role of these nodes as representing the structure of the source code prior to semantic analysis. The corresponding enum `UntypedKind` has also been renamed to `SyntaxKind` to maintain consistency. No functional changes are introduced by this refactoring; it is purely a renaming and organizational update. --- src/ast/compiler/binder.rs | 84 +- src/ast/compiler/bound_nodes.rs | 704 +++---- src/ast/compiler/captures.rs | 308 +-- src/ast/compiler/dumper.rs | 528 ++--- src/ast/compiler/lambda_collector.rs | 198 +- src/ast/compiler/macros.rs | 288 +-- src/ast/compiler/mod.rs | 42 +- src/ast/compiler/optimizer/engine.rs | 1432 ++++++------- src/ast/compiler/optimizer/folder.rs | 202 +- .../compiler/optimizer/substitution_map.rs | 420 ++-- src/ast/compiler/optimizer/utils.rs | 366 ++-- src/ast/compiler/specializer.rs | 552 ++--- src/ast/compiler/type_checker.rs | 1862 ++++++++--------- src/ast/environment.rs | 60 +- src/ast/lexer.rs | 438 ++-- src/ast/mod.rs | 18 +- src/ast/nodes.rs | 268 +-- src/ast/parser.rs | 1044 ++++----- src/ast/rtl/core.rs | 1018 ++++----- src/ast/rtl/datetime.rs | 78 +- src/ast/rtl/intrinsics.rs | 230 +- src/ast/rtl/math.rs | 352 ++-- src/ast/rtl/mod.rs | 34 +- src/ast/rtl/series.rs | 1296 ++++++------ src/ast/rtl/streams.rs | 1072 +++++----- src/ast/rtl/type_registry.rs | 546 ++--- src/ast/types.rs | 1364 ++++++------ src/bin/ast.rs | 342 +-- src/integration_test.rs | 10 +- src/main.rs | 1392 ++++++------ src/utils/tester.rs | 674 +++--- 31 files changed, 8611 insertions(+), 8611 deletions(-) diff --git a/src/ast/compiler/binder.rs b/src/ast/compiler/binder.rs index 5b0cc88..69976c0 100644 --- a/src/ast/compiler/binder.rs +++ b/src/ast/compiler/binder.rs @@ -2,7 +2,7 @@ use crate::ast::compiler::bound_nodes::{ Address, BoundKind, DeclarationKind, GlobalIdx, LocalSlot, Node, UpvalueIdx, }; use crate::ast::diagnostics::Diagnostics; -use crate::ast::nodes::{Symbol, UntypedKind, UntypedNode}; +use crate::ast::nodes::{Symbol, SyntaxKind, SyntaxNode}; use crate::ast::types::{Identity, StaticType, Purity}; use std::collections::HashMap; use std::rc::Rc; @@ -152,7 +152,7 @@ impl Binder { initial_scopes: Vec, initial_slot_count: u32, fixed_scope_idx: i32, - node: &UntypedNode, + node: &SyntaxNode, diagnostics: &mut Diagnostics, ) -> Result { let mut binder = Self::new(initial_scopes, initial_slot_count, fixed_scope_idx); @@ -208,17 +208,17 @@ impl Binder { pub fn bind( &mut self, - node: &UntypedNode, + node: &SyntaxNode, ctx: ExprContext, diag: &mut Diagnostics, ) -> Node { match &node.kind { - UntypedKind::Nop => self.make_node(node.identity.clone(), BoundKind::Nop), - UntypedKind::Constant(v) => { + SyntaxKind::Nop => self.make_node(node.identity.clone(), BoundKind::Nop), + SyntaxKind::Constant(v) => { self.make_node(node.identity.clone(), BoundKind::Constant(v.clone())) } - UntypedKind::Identifier(sym) => { + SyntaxKind::Identifier(sym) => { if let Some(addr) = self.resolve_variable(sym, diag, &node.identity) { self.make_node( node.identity.clone(), @@ -232,11 +232,11 @@ impl Binder { } } - UntypedKind::FieldAccessor(k) => { + SyntaxKind::FieldAccessor(k) => { self.make_node(node.identity.clone(), BoundKind::FieldAccessor(*k)) } - UntypedKind::If { + SyntaxKind::If { cond, then_br, else_br, @@ -264,14 +264,14 @@ impl Binder { ) } - UntypedKind::Def { target, value } => { + SyntaxKind::Def { target, value } => { if ctx == ExprContext::Expression { diag.push_error( "Statement 'def' cannot be used as an expression.", Some(node.identity.clone()), ); } - if let UntypedKind::Identifier(ref name) = target.kind { + if let SyntaxKind::Identifier(ref name) = target.kind { let addr_opt = self.declare_variable( name, node.identity.clone(), @@ -308,10 +308,10 @@ impl Binder { } } - UntypedKind::Assign { target, value } => { + SyntaxKind::Assign { target, value } => { let val_node = self.bind(value, ExprContext::Expression, diag); - if let UntypedKind::Identifier(sym) = &target.kind { + if let SyntaxKind::Identifier(sym) = &target.kind { if let Some(addr) = self.resolve_variable(sym, diag, &target.identity) { self.make_node( node.identity.clone(), @@ -335,7 +335,7 @@ impl Binder { } } - UntypedKind::Pipe { inputs, lambda } => { + SyntaxKind::Pipe { inputs, lambda } => { let mut bound_inputs = Vec::with_capacity(inputs.len()); for input in inputs { bound_inputs.push(Rc::new(self.bind(input, ExprContext::Expression, diag))); @@ -352,7 +352,7 @@ impl Binder { ) } - UntypedKind::Lambda { params, body } => { + SyntaxKind::Lambda { params, body } => { let identity = node.identity.clone(); self.functions .push(FunctionCompiler::new(identity.clone(), vec![], 0)); @@ -392,7 +392,7 @@ impl Binder { ) } - UntypedKind::Call { callee, args } => { + SyntaxKind::Call { callee, args } => { let callee = self.bind(callee, ExprContext::Expression, diag); let args = self.bind(args.as_ref(), ExprContext::Expression, diag); @@ -405,7 +405,7 @@ impl Binder { ) } - UntypedKind::Again { args } => { + SyntaxKind::Again { args } => { if self.functions.len() <= 1 { diag.push_error( "'again' is only allowed inside a function or lambda.", @@ -422,7 +422,7 @@ impl Binder { ) } - UntypedKind::Block { exprs } => { + SyntaxKind::Block { exprs } => { self.functions.last_mut().unwrap().push_scope(); let mut bound_exprs = Vec::new(); for (i, expr) in exprs.iter().enumerate() { @@ -440,7 +440,7 @@ impl Binder { ) } - UntypedKind::Tuple { elements } => { + SyntaxKind::Tuple { elements } => { let mut bound_elems = Vec::new(); for e in elements { bound_elems.push(Rc::new(self.bind(e, ExprContext::Expression, diag))); @@ -453,7 +453,7 @@ impl Binder { ) } - UntypedKind::Record { fields } => { + SyntaxKind::Record { fields } => { let mut bound_values = Vec::new(); let mut layout_fields = Vec::new(); @@ -488,7 +488,7 @@ impl Binder { ) } - UntypedKind::Expansion { call, expanded } => { + SyntaxKind::Expansion { call, expanded } => { let bound_expanded = self.bind(expanded.as_ref(), ctx, diag); self.make_node( node.identity.clone(), @@ -499,10 +499,10 @@ impl Binder { ) } - UntypedKind::Template(_) - | UntypedKind::Placeholder(_) - | UntypedKind::Splice(_) - | UntypedKind::MacroDecl { .. } => { + SyntaxKind::Template(_) + | SyntaxKind::Placeholder(_) + | SyntaxKind::Splice(_) + | SyntaxKind::MacroDecl { .. } => { diag.push_error( format!( "Macro construct {:?} found in Binder. Macros must be expanded before binding.", @@ -513,14 +513,14 @@ impl Binder { self.make_node(node.identity.clone(), BoundKind::Error) } - UntypedKind::Extension(_) => { + SyntaxKind::Extension(_) => { diag.push_error( "Custom extensions not supported in Binder yet", Some(node.identity.clone()), ); self.make_node(node.identity.clone(), BoundKind::Error) } - UntypedKind::Error => crate::ast::compiler::bound_nodes::Node { + SyntaxKind::Error => crate::ast::compiler::bound_nodes::Node { identity: node.identity.clone(), kind: crate::ast::compiler::bound_nodes::BoundKind::Error, ty: (), @@ -600,12 +600,12 @@ impl Binder { fn bind_pattern( &mut self, - node: &UntypedNode, + node: &SyntaxNode, kind: DeclarationKind, diag: &mut Diagnostics, ) -> Node { match &node.kind { - UntypedKind::Identifier(sym) => { + SyntaxKind::Identifier(sym) => { if let Some(addr) = self.declare_variable(sym, node.identity.clone(), kind, diag) { self.make_node( node.identity.clone(), @@ -621,7 +621,7 @@ impl Binder { self.make_node(node.identity.clone(), BoundKind::Error) } } - UntypedKind::Tuple { elements } => { + SyntaxKind::Tuple { elements } => { let mut bound_elems = Vec::new(); for e in elements { bound_elems.push(Rc::new(self.bind_pattern(e, kind, diag))); @@ -645,11 +645,11 @@ impl Binder { fn bind_assign_pattern( &mut self, - node: &UntypedNode, + node: &SyntaxNode, diag: &mut Diagnostics, ) -> Node { match &node.kind { - UntypedKind::Identifier(sym) => { + SyntaxKind::Identifier(sym) => { if let Some(addr) = self.resolve_variable(sym, diag, &node.identity) { self.make_node( node.identity.clone(), @@ -662,7 +662,7 @@ impl Binder { self.make_node(node.identity.clone(), BoundKind::Error) } } - UntypedKind::Tuple { elements } => { + SyntaxKind::Tuple { elements } => { let mut bound_elems = Vec::new(); for e in elements { bound_elems.push(Rc::new(self.bind_assign_pattern(e, diag))); @@ -703,10 +703,10 @@ mod tests { fn test_upvalue_capture_sets_is_boxed() { let source = "(fn [] (do (def x 10) (def f (fn [] x)) x))"; let mut parser = Parser::new(source); - let untyped = parser.parse_expression(); + let syntax = parser.parse_expression(); let mut diagnostics = Diagnostics::new(); - let (bound, captures, _scopes, _slots) = Binder::bind_root(vec![], 0, 0, &untyped, &mut diagnostics).unwrap(); + let (bound, captures, _scopes, _slots) = Binder::bind_root(vec![], 0, 0, &syntax, &mut diagnostics).unwrap(); if let BoundKind::Lambda { body, .. } = &bound.kind { if let BoundKind::Block { exprs } = &body.kind { @@ -732,10 +732,10 @@ mod tests { fn test_no_capture_not_boxed() { let source = "(fn [] (do (def x 10) x))"; let mut parser = Parser::new(source); - let untyped = parser.parse_expression(); + let syntax = parser.parse_expression(); let mut diagnostics = Diagnostics::new(); - let (bound, captures, _scopes, _slots) = Binder::bind_root(vec![], 0, 0, &untyped, &mut diagnostics).unwrap(); + let (bound, captures, _scopes, _slots) = Binder::bind_root(vec![], 0, 0, &syntax, &mut diagnostics).unwrap(); if let BoundKind::Lambda { body, .. } = &bound.kind { if let BoundKind::Block { exprs } = &body.kind { @@ -761,10 +761,10 @@ mod tests { fn test_redefinition_error() { let source = "(do (def x 1) (def x 2) x)"; let mut parser = Parser::new(source); - let untyped = parser.parse_expression(); + let syntax = parser.parse_expression(); let mut diagnostics = Diagnostics::new(); - let _ = Binder::bind_root(vec![], 0, 0, &untyped, &mut diagnostics); + let _ = Binder::bind_root(vec![], 0, 0, &syntax, &mut diagnostics); assert!(diagnostics.has_errors()); assert!(diagnostics.items.iter().any(|i| i.message.contains("already defined"))); @@ -773,15 +773,15 @@ mod tests { #[test] fn test_repro_global_redefinition() { let source1 = "(def x 1) 1"; - let untyped1 = Parser::new(source1).parse_expression(); + let syntax1 = Parser::new(source1).parse_expression(); let mut diagnostics = Diagnostics::new(); - let (_, _, scopes1, slots1) = Binder::bind_root(vec![], 0, -1, &untyped1, &mut diagnostics).unwrap(); + let (_, _, scopes1, slots1) = Binder::bind_root(vec![], 0, -1, &syntax1, &mut diagnostics).unwrap(); let source2 = "(def x 2) 2"; - let untyped2 = Parser::new(source2).parse_expression(); + let syntax2 = Parser::new(source2).parse_expression(); let mut diagnostics2 = Diagnostics::new(); // Here we simulate frozen scope by passing fixed_scope_idx = 0 - let _ = Binder::bind_root(scopes1, slots1, 0, &untyped2, &mut diagnostics2); + let _ = Binder::bind_root(scopes1, slots1, 0, &syntax2, &mut diagnostics2); assert!(diagnostics2.has_errors()); assert!(diagnostics2.items.iter().any(|i| i.message.contains("frozen/immutable"))); diff --git a/src/ast/compiler/bound_nodes.rs b/src/ast/compiler/bound_nodes.rs index 4489d52..96edba9 100644 --- a/src/ast/compiler/bound_nodes.rs +++ b/src/ast/compiler/bound_nodes.rs @@ -1,352 +1,352 @@ -use crate::ast::nodes::Symbol; -use crate::ast::types::{Identity, StaticType, Value}; -use std::rc::Rc; - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct LocalSlot(pub u32); - -impl std::fmt::Display for LocalSlot { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "L{}", self.0) - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct UpvalueIdx(pub u32); - -impl std::fmt::Display for UpvalueIdx { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "U{}", self.0) - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct GlobalIdx(pub u32); - -impl std::fmt::Display for GlobalIdx { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "G{}", self.0) - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub enum Address { - Local(LocalSlot), // Stack-Slot index (relative to frame base) - Upvalue(UpvalueIdx), // Index in the closure's upvalue array - Global(GlobalIdx), // Index in the global environment vector -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub enum DeclarationKind { - Variable, - Parameter, -} - -/// 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() - } -} - -/// A bound AST node, decorated with type or metric information T. -#[derive(Debug, Clone, PartialEq)] -pub struct Node { - pub identity: Identity, - pub kind: BoundKind, - pub ty: T, -} - -/// Type alias for a node that has been fully type-checked. -pub type TypedNode = Node; - -/// Type alias for the global function registry. -pub type GlobalFunctionRegistry = std::collections::HashMap>; - -/// Metrics collected during the analysis phase. -#[derive(Debug, Clone, PartialEq)] -pub struct NodeMetrics { - pub original: Rc, - pub purity: crate::ast::types::Purity, - pub is_recursive: bool, -} - -/// Type alias for a node that has been analyzed. -pub type AnalyzedNode = Node; - -/// Type alias for the global analyzed function registry. -pub type GlobalAnalyzedRegistry = std::collections::HashMap>; - -#[derive(Debug, Clone)] -pub enum BoundKind { - Nop, - Constant(Value), - - // Variable Access (Resolved) - Get { - addr: Address, - name: Symbol, - }, - - // Variable Update (Assignment) - Set { - addr: Address, - value: Rc>, - }, - - // Variable Declaration (Unified for Local/Global/Parameter) - Define { - name: Symbol, - addr: Address, - kind: DeclarationKind, - value: Rc>, - captured_by: Vec, - }, - - /// A first-class field accessor (e.g. .name) - FieldAccessor(crate::ast::types::Keyword), - - /// Specialized field access (O(1) via RecordLayout) - GetField { - rec: Rc>, - field: crate::ast::types::Keyword, - }, - - If { - cond: Rc>, - then_br: Rc>, - else_br: Option>>, - }, - - /// A destructuring operation (can be a definition or an assignment depending on the pattern) - Destructure { - pattern: Rc>, - value: Rc>, - }, - - 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: Rc>, - args: Rc>, - }, - - Again { - args: Rc>, - }, - - Pipe { - inputs: Vec>>, - lambda: Rc>, - out_type: crate::ast::types::StaticType, - }, - Block { - exprs: Vec>>, - }, - - Tuple { - elements: Vec>>, - }, - - Record { - layout: std::sync::Arc, - values: 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: Rc>, - }, - - /// A diagnostic poison node, allowing compilation to continue after an error. - Error, - - /// 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::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 && Rc::ptr_eq(va, vb), - ( - BoundKind::Define { - name: na, - addr: aa, - kind: ka, - value: va, - captured_by: ca, - }, - BoundKind::Define { - name: nb, - addr: ab, - kind: kb, - value: vb, - captured_by: cb, - }, - ) => na == nb && aa == ab && ka == kb && Rc::ptr_eq(va, vb) && ca == cb, - (BoundKind::FieldAccessor(a), BoundKind::FieldAccessor(b)) => a == b, - ( - BoundKind::GetField { rec: ra, field: fa }, - BoundKind::GetField { rec: rb, field: fb }, - ) => Rc::ptr_eq(ra, rb) && fa == fb, - ( - BoundKind::If { - cond: ca, - then_br: ta, - else_br: ea, - }, - BoundKind::If { - cond: cb, - then_br: tb, - else_br: eb, - }, - ) => Rc::ptr_eq(ca, cb) && Rc::ptr_eq(ta, tb) && match (ea, eb) { - (Some(a), Some(b)) => Rc::ptr_eq(a, b), - (None, None) => true, - _ => false, - }, - ( - BoundKind::Destructure { - pattern: pa, - value: va, - }, - BoundKind::Destructure { - pattern: pb, - value: vb, - }, - ) => Rc::ptr_eq(pa, pb) && Rc::ptr_eq(va, vb), - ( - BoundKind::Lambda { - params: pa, - upvalues: ua, - body: ba, - positional_count: pca, - }, - BoundKind::Lambda { - params: pb, - upvalues: ub, - body: bb, - positional_count: pcb, - }, - ) => Rc::ptr_eq(pa, pb) && ua == ub && Rc::ptr_eq(ba, bb) && pca == pcb, - ( - BoundKind::Call { - callee: ca, - args: aa, - }, - BoundKind::Call { - callee: cb, - args: ab, - }, - ) => Rc::ptr_eq(ca, cb) && Rc::ptr_eq(aa, ab), - (BoundKind::Again { args: aa }, BoundKind::Again { args: ab }) => Rc::ptr_eq(aa, ab), - (BoundKind::Block { exprs: ea }, BoundKind::Block { exprs: eb }) => { - ea.len() == eb.len() && ea.iter().zip(eb.iter()).all(|(a, b)| Rc::ptr_eq(a, b)) - } - (BoundKind::Tuple { elements: ea }, BoundKind::Tuple { elements: eb }) => { - ea.len() == eb.len() && ea.iter().zip(eb.iter()).all(|(a, b)| Rc::ptr_eq(a, b)) - } - ( - BoundKind::Record { - layout: la, - values: va, - }, - BoundKind::Record { - layout: lb, - values: vb, - }, - ) => std::sync::Arc::ptr_eq(la, lb) && va.len() == vb.len() && va.iter().zip(vb.iter()).all(|(a, b)| Rc::ptr_eq(a, b)), - ( - BoundKind::Expansion { - original_call: ca, - bound_expanded: ea, - }, - BoundKind::Expansion { - original_call: cb, - bound_expanded: eb, - }, - ) => Rc::ptr_eq(ca, cb) && Rc::ptr_eq(ea, eb), - (BoundKind::Error, BoundKind::Error) => true, - (BoundKind::Extension(_), BoundKind::Extension(_)) => false, - _ => false, - } - } -} - -/// A single field in a Record literal (Key-Value pair) -pub type RecordField = (Node, Node); - -impl BoundKind { - pub fn display_name(&self) -> String { - match self { - BoundKind::Nop => "NOP".to_string(), - BoundKind::Constant(v) => format!("CONST({})", v), - BoundKind::Get { addr, name } => format!("GET({}, {:?})", name.name, addr), - BoundKind::Set { addr, .. } => format!("SET({:?})", addr), - BoundKind::Define { - name, addr, kind, .. - } => { - let k_str = match kind { - DeclarationKind::Variable => "VAR", - DeclarationKind::Parameter => "PARAM", - }; - format!("DEF_{}({}, {:?})", k_str, name.name, addr) - } - BoundKind::FieldAccessor(k) => format!("FIELD_ACCESSOR(.{})", k.name()), - BoundKind::GetField { field, .. } => format!("GET_FIELD(.{})", field.name()), - BoundKind::If { .. } => "IF".to_string(), - BoundKind::Destructure { .. } => "DESTRUCTURE".to_string(), - 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::Again { .. } => "AGAIN".to_string(), - BoundKind::Pipe { .. } => "PIPE".to_string(), - BoundKind::Block { .. } => "BLOCK".to_string(), - BoundKind::Tuple { elements } => format!("TUPLE({})", elements.len()), - BoundKind::Record { values, .. } => format!("RECORD({})", values.len()), - BoundKind::Expansion { .. } => "EXPANSION".to_string(), - BoundKind::Extension(ext) => ext.display_name(), - BoundKind::Error => "ERROR".to_string(), - } - } -} +use crate::ast::nodes::Symbol; +use crate::ast::types::{Identity, StaticType, Value}; +use std::rc::Rc; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct LocalSlot(pub u32); + +impl std::fmt::Display for LocalSlot { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "L{}", self.0) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct UpvalueIdx(pub u32); + +impl std::fmt::Display for UpvalueIdx { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "U{}", self.0) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct GlobalIdx(pub u32); + +impl std::fmt::Display for GlobalIdx { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "G{}", self.0) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum Address { + Local(LocalSlot), // Stack-Slot index (relative to frame base) + Upvalue(UpvalueIdx), // Index in the closure's upvalue array + Global(GlobalIdx), // Index in the global environment vector +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum DeclarationKind { + Variable, + Parameter, +} + +/// 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() + } +} + +/// A bound AST node, decorated with type or metric information T. +#[derive(Debug, Clone, PartialEq)] +pub struct Node { + pub identity: Identity, + pub kind: BoundKind, + pub ty: T, +} + +/// Type alias for a node that has been fully type-checked. +pub type TypedNode = Node; + +/// Type alias for the global function registry. +pub type GlobalFunctionRegistry = std::collections::HashMap>; + +/// Metrics collected during the analysis phase. +#[derive(Debug, Clone, PartialEq)] +pub struct NodeMetrics { + pub original: Rc, + pub purity: crate::ast::types::Purity, + pub is_recursive: bool, +} + +/// Type alias for a node that has been analyzed. +pub type AnalyzedNode = Node; + +/// Type alias for the global analyzed function registry. +pub type GlobalAnalyzedRegistry = std::collections::HashMap>; + +#[derive(Debug, Clone)] +pub enum BoundKind { + Nop, + Constant(Value), + + // Variable Access (Resolved) + Get { + addr: Address, + name: Symbol, + }, + + // Variable Update (Assignment) + Set { + addr: Address, + value: Rc>, + }, + + // Variable Declaration (Unified for Local/Global/Parameter) + Define { + name: Symbol, + addr: Address, + kind: DeclarationKind, + value: Rc>, + captured_by: Vec, + }, + + /// A first-class field accessor (e.g. .name) + FieldAccessor(crate::ast::types::Keyword), + + /// Specialized field access (O(1) via RecordLayout) + GetField { + rec: Rc>, + field: crate::ast::types::Keyword, + }, + + If { + cond: Rc>, + then_br: Rc>, + else_br: Option>>, + }, + + /// A destructuring operation (can be a definition or an assignment depending on the pattern) + Destructure { + pattern: Rc>, + value: Rc>, + }, + + 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: Rc>, + args: Rc>, + }, + + Again { + args: Rc>, + }, + + Pipe { + inputs: Vec>>, + lambda: Rc>, + out_type: crate::ast::types::StaticType, + }, + Block { + exprs: Vec>>, + }, + + Tuple { + elements: Vec>>, + }, + + Record { + layout: std::sync::Arc, + values: Vec>>, + }, + + /// An expanded macro call, preserving the original call for debugging and UI. + Expansion { + /// The original call from the syntax AST. + original_call: Rc, + /// The result of binding the expanded AST. + bound_expanded: Rc>, + }, + + /// A diagnostic poison node, allowing compilation to continue after an error. + Error, + + /// 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::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 && Rc::ptr_eq(va, vb), + ( + BoundKind::Define { + name: na, + addr: aa, + kind: ka, + value: va, + captured_by: ca, + }, + BoundKind::Define { + name: nb, + addr: ab, + kind: kb, + value: vb, + captured_by: cb, + }, + ) => na == nb && aa == ab && ka == kb && Rc::ptr_eq(va, vb) && ca == cb, + (BoundKind::FieldAccessor(a), BoundKind::FieldAccessor(b)) => a == b, + ( + BoundKind::GetField { rec: ra, field: fa }, + BoundKind::GetField { rec: rb, field: fb }, + ) => Rc::ptr_eq(ra, rb) && fa == fb, + ( + BoundKind::If { + cond: ca, + then_br: ta, + else_br: ea, + }, + BoundKind::If { + cond: cb, + then_br: tb, + else_br: eb, + }, + ) => Rc::ptr_eq(ca, cb) && Rc::ptr_eq(ta, tb) && match (ea, eb) { + (Some(a), Some(b)) => Rc::ptr_eq(a, b), + (None, None) => true, + _ => false, + }, + ( + BoundKind::Destructure { + pattern: pa, + value: va, + }, + BoundKind::Destructure { + pattern: pb, + value: vb, + }, + ) => Rc::ptr_eq(pa, pb) && Rc::ptr_eq(va, vb), + ( + BoundKind::Lambda { + params: pa, + upvalues: ua, + body: ba, + positional_count: pca, + }, + BoundKind::Lambda { + params: pb, + upvalues: ub, + body: bb, + positional_count: pcb, + }, + ) => Rc::ptr_eq(pa, pb) && ua == ub && Rc::ptr_eq(ba, bb) && pca == pcb, + ( + BoundKind::Call { + callee: ca, + args: aa, + }, + BoundKind::Call { + callee: cb, + args: ab, + }, + ) => Rc::ptr_eq(ca, cb) && Rc::ptr_eq(aa, ab), + (BoundKind::Again { args: aa }, BoundKind::Again { args: ab }) => Rc::ptr_eq(aa, ab), + (BoundKind::Block { exprs: ea }, BoundKind::Block { exprs: eb }) => { + ea.len() == eb.len() && ea.iter().zip(eb.iter()).all(|(a, b)| Rc::ptr_eq(a, b)) + } + (BoundKind::Tuple { elements: ea }, BoundKind::Tuple { elements: eb }) => { + ea.len() == eb.len() && ea.iter().zip(eb.iter()).all(|(a, b)| Rc::ptr_eq(a, b)) + } + ( + BoundKind::Record { + layout: la, + values: va, + }, + BoundKind::Record { + layout: lb, + values: vb, + }, + ) => std::sync::Arc::ptr_eq(la, lb) && va.len() == vb.len() && va.iter().zip(vb.iter()).all(|(a, b)| Rc::ptr_eq(a, b)), + ( + BoundKind::Expansion { + original_call: ca, + bound_expanded: ea, + }, + BoundKind::Expansion { + original_call: cb, + bound_expanded: eb, + }, + ) => Rc::ptr_eq(ca, cb) && Rc::ptr_eq(ea, eb), + (BoundKind::Error, BoundKind::Error) => true, + (BoundKind::Extension(_), BoundKind::Extension(_)) => false, + _ => false, + } + } +} + +/// A single field in a Record literal (Key-Value pair) +pub type RecordField = (Node, Node); + +impl BoundKind { + pub fn display_name(&self) -> String { + match self { + BoundKind::Nop => "NOP".to_string(), + BoundKind::Constant(v) => format!("CONST({})", v), + BoundKind::Get { addr, name } => format!("GET({}, {:?})", name.name, addr), + BoundKind::Set { addr, .. } => format!("SET({:?})", addr), + BoundKind::Define { + name, addr, kind, .. + } => { + let k_str = match kind { + DeclarationKind::Variable => "VAR", + DeclarationKind::Parameter => "PARAM", + }; + format!("DEF_{}({}, {:?})", k_str, name.name, addr) + } + BoundKind::FieldAccessor(k) => format!("FIELD_ACCESSOR(.{})", k.name()), + BoundKind::GetField { field, .. } => format!("GET_FIELD(.{})", field.name()), + BoundKind::If { .. } => "IF".to_string(), + BoundKind::Destructure { .. } => "DESTRUCTURE".to_string(), + 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::Again { .. } => "AGAIN".to_string(), + BoundKind::Pipe { .. } => "PIPE".to_string(), + BoundKind::Block { .. } => "BLOCK".to_string(), + BoundKind::Tuple { elements } => format!("TUPLE({})", elements.len()), + BoundKind::Record { values, .. } => format!("RECORD({})", values.len()), + BoundKind::Expansion { .. } => "EXPANSION".to_string(), + BoundKind::Extension(ext) => ext.display_name(), + BoundKind::Error => "ERROR".to_string(), + } + } +} diff --git a/src/ast/compiler/captures.rs b/src/ast/compiler/captures.rs index 42c0b28..57e8997 100644 --- a/src/ast/compiler/captures.rs +++ b/src/ast/compiler/captures.rs @@ -1,154 +1,154 @@ -use crate::ast::compiler::bound_nodes::{BoundKind, Node}; -use crate::ast::types::Identity; -use std::collections::HashMap; - -pub struct CapturePass; - -impl CapturePass { - pub fn apply(node: Node, capture_map: &HashMap>) -> Node { - Self::transform(node, capture_map) - } - - fn transform(mut node: Node, capture_map: &HashMap>) -> Node { - use std::rc::Rc; - match node.kind { - BoundKind::Define { - name, - addr, - kind, - value, - .. - } => { - let captured_by = capture_map.get(&node.identity).cloned().unwrap_or_default(); - node.kind = BoundKind::Define { - name, - addr, - kind, - value: Rc::new(Self::transform(value.as_ref().clone(), capture_map)), - captured_by, - }; - } - - BoundKind::If { - cond, - then_br, - else_br, - } => { - node.kind = BoundKind::If { - cond: Rc::new(Self::transform(cond.as_ref().clone(), capture_map)), - then_br: Rc::new(Self::transform(then_br.as_ref().clone(), capture_map)), - else_br: else_br.map(|e| Rc::new(Self::transform(e.as_ref().clone(), capture_map))), - }; - } - - BoundKind::Set { addr, value } => { - node.kind = BoundKind::Set { - addr, - value: Rc::new(Self::transform(value.as_ref().clone(), capture_map)), - }; - } - - BoundKind::FieldAccessor(_) => {} - - BoundKind::GetField { rec, field } => { - node.kind = BoundKind::GetField { - rec: Rc::new(Self::transform(rec.as_ref().clone(), capture_map)), - field, - }; - } - - BoundKind::Destructure { pattern, value } => { - node.kind = BoundKind::Destructure { - pattern: Rc::new(Self::transform(pattern.as_ref().clone(), capture_map)), - value: Rc::new(Self::transform(value.as_ref().clone(), capture_map)), - }; - } - - BoundKind::Lambda { - params, - upvalues, - body, - positional_count, - } => { - node.kind = BoundKind::Lambda { - params: Rc::new(Self::transform(params.as_ref().clone(), capture_map)), - upvalues, - body: Rc::new(Self::transform(body.as_ref().clone(), capture_map)), - positional_count, - }; - } - - BoundKind::Call { callee, args } => { - node.kind = BoundKind::Call { - callee: Rc::new(Self::transform(callee.as_ref().clone(), capture_map)), - args: Rc::new(Self::transform(args.as_ref().clone(), capture_map)), - }; - } - - BoundKind::Again { args } => { - node.kind = BoundKind::Again { - args: Rc::new(Self::transform(args.as_ref().clone(), capture_map)), - }; - } - BoundKind::Pipe { - inputs, - lambda, - out_type, - } => { - let mut t_inputs = Vec::with_capacity(inputs.len()); - for input in inputs { - t_inputs.push(Rc::new(Self::transform(input.as_ref().clone(), capture_map))); - } - node.kind = BoundKind::Pipe { - inputs: t_inputs, - lambda: Rc::new(Self::transform(lambda.as_ref().clone(), capture_map)), - out_type: out_type.clone(), - }; - } - BoundKind::Block { exprs } => { - node.kind = BoundKind::Block { - exprs: exprs - .into_iter() - .map(|e| Rc::new(Self::transform(e.as_ref().clone(), capture_map))) - .collect(), - }; - } - - BoundKind::Tuple { elements } => { - node.kind = BoundKind::Tuple { - elements: elements - .into_iter() - .map(|e| Rc::new(Self::transform(e.as_ref().clone(), capture_map))) - .collect(), - }; - } - - BoundKind::Record { layout, values } => { - node.kind = BoundKind::Record { - layout, - values: values - .into_iter() - .map(|v| Rc::new(Self::transform(v.as_ref().clone(), capture_map))) - .collect(), - }; - } - - BoundKind::Expansion { - original_call, - bound_expanded, - } => { - node.kind = BoundKind::Expansion { - original_call, - bound_expanded: Rc::new(Self::transform(bound_expanded.as_ref().clone(), capture_map)), - }; - } - - BoundKind::Nop - | BoundKind::Constant(_) - | BoundKind::Get { .. } - | BoundKind::Extension(_) - | BoundKind::Error => {} - } - node - } -} +use crate::ast::compiler::bound_nodes::{BoundKind, Node}; +use crate::ast::types::Identity; +use std::collections::HashMap; + +pub struct CapturePass; + +impl CapturePass { + pub fn apply(node: Node, capture_map: &HashMap>) -> Node { + Self::transform(node, capture_map) + } + + fn transform(mut node: Node, capture_map: &HashMap>) -> Node { + use std::rc::Rc; + match node.kind { + BoundKind::Define { + name, + addr, + kind, + value, + .. + } => { + let captured_by = capture_map.get(&node.identity).cloned().unwrap_or_default(); + node.kind = BoundKind::Define { + name, + addr, + kind, + value: Rc::new(Self::transform(value.as_ref().clone(), capture_map)), + captured_by, + }; + } + + BoundKind::If { + cond, + then_br, + else_br, + } => { + node.kind = BoundKind::If { + cond: Rc::new(Self::transform(cond.as_ref().clone(), capture_map)), + then_br: Rc::new(Self::transform(then_br.as_ref().clone(), capture_map)), + else_br: else_br.map(|e| Rc::new(Self::transform(e.as_ref().clone(), capture_map))), + }; + } + + BoundKind::Set { addr, value } => { + node.kind = BoundKind::Set { + addr, + value: Rc::new(Self::transform(value.as_ref().clone(), capture_map)), + }; + } + + BoundKind::FieldAccessor(_) => {} + + BoundKind::GetField { rec, field } => { + node.kind = BoundKind::GetField { + rec: Rc::new(Self::transform(rec.as_ref().clone(), capture_map)), + field, + }; + } + + BoundKind::Destructure { pattern, value } => { + node.kind = BoundKind::Destructure { + pattern: Rc::new(Self::transform(pattern.as_ref().clone(), capture_map)), + value: Rc::new(Self::transform(value.as_ref().clone(), capture_map)), + }; + } + + BoundKind::Lambda { + params, + upvalues, + body, + positional_count, + } => { + node.kind = BoundKind::Lambda { + params: Rc::new(Self::transform(params.as_ref().clone(), capture_map)), + upvalues, + body: Rc::new(Self::transform(body.as_ref().clone(), capture_map)), + positional_count, + }; + } + + BoundKind::Call { callee, args } => { + node.kind = BoundKind::Call { + callee: Rc::new(Self::transform(callee.as_ref().clone(), capture_map)), + args: Rc::new(Self::transform(args.as_ref().clone(), capture_map)), + }; + } + + BoundKind::Again { args } => { + node.kind = BoundKind::Again { + args: Rc::new(Self::transform(args.as_ref().clone(), capture_map)), + }; + } + BoundKind::Pipe { + inputs, + lambda, + out_type, + } => { + let mut t_inputs = Vec::with_capacity(inputs.len()); + for input in inputs { + t_inputs.push(Rc::new(Self::transform(input.as_ref().clone(), capture_map))); + } + node.kind = BoundKind::Pipe { + inputs: t_inputs, + lambda: Rc::new(Self::transform(lambda.as_ref().clone(), capture_map)), + out_type: out_type.clone(), + }; + } + BoundKind::Block { exprs } => { + node.kind = BoundKind::Block { + exprs: exprs + .into_iter() + .map(|e| Rc::new(Self::transform(e.as_ref().clone(), capture_map))) + .collect(), + }; + } + + BoundKind::Tuple { elements } => { + node.kind = BoundKind::Tuple { + elements: elements + .into_iter() + .map(|e| Rc::new(Self::transform(e.as_ref().clone(), capture_map))) + .collect(), + }; + } + + BoundKind::Record { layout, values } => { + node.kind = BoundKind::Record { + layout, + values: values + .into_iter() + .map(|v| Rc::new(Self::transform(v.as_ref().clone(), capture_map))) + .collect(), + }; + } + + BoundKind::Expansion { + original_call, + bound_expanded, + } => { + node.kind = BoundKind::Expansion { + original_call, + bound_expanded: Rc::new(Self::transform(bound_expanded.as_ref().clone(), capture_map)), + }; + } + + BoundKind::Nop + | BoundKind::Constant(_) + | BoundKind::Get { .. } + | BoundKind::Extension(_) + | BoundKind::Error => {} + } + node + } +} diff --git a/src/ast/compiler/dumper.rs b/src/ast/compiler/dumper.rs index e490b76..5bb0286 100644 --- a/src/ast/compiler/dumper.rs +++ b/src/ast/compiler/dumper.rs @@ -1,264 +1,264 @@ -use crate::ast::compiler::bound_nodes::{BoundKind, 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) -> 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) { - self.write_indent(); - self.output.push_str(label); - self.output - .push_str(&format!(" \n", node.ty)); - } - - fn visit(&mut self, node: &Node) { - 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::FieldAccessor(k) => self.log(&format!("FieldAccessor: .{}", k.name()), node), - - BoundKind::GetField { rec, field } => { - self.log(&format!("GetField: .{}", field.name()), node); - self.indent += 1; - self.visit(rec); - self.indent -= 1; - } - - BoundKind::Set { addr, value } => { - self.log(&format!("Set: {:?}", addr), node); - self.indent += 1; - self.visit(value); - self.indent -= 1; - } - - BoundKind::Define { - name, - addr, - kind, - value, - captured_by, - } => { - let k_str = match kind { - crate::ast::compiler::bound_nodes::DeclarationKind::Variable => "Variable", - crate::ast::compiler::bound_nodes::DeclarationKind::Parameter => "Parameter", - }; - let capture_info = if captured_by.is_empty() { - String::from("not captured") - } else { - format!("captured by {} lambdas", captured_by.len()) - }; - self.log( - &format!( - "Define {} (Name: '{}', Address: {:?}, {})", - k_str, name.name, addr, capture_info - ), - node, - ); - - self.indent += 1; - if !captured_by.is_empty() { - for capturer in captured_by { - self.write_indent(); - let loc = capturer - .location - .unwrap_or(crate::ast::types::SourceLocation { line: 0, col: 0 }); - self.output.push_str(&format!( - "- Capturer: Lambda at line {}, col {}\n", - loc.line, loc.col - )); - } - } - self.visit(value); - self.indent -= 1; - } - - BoundKind::Destructure { pattern, value } => { - self.log("Destructure", node); - self.indent += 1; - self.write_indent(); - self.output.push_str("Pattern:\n"); - self.visit(pattern); - self.write_indent(); - self.output.push_str("Value:\n"); - 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::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::Again { args } => { - self.log("Again", node); - self.indent += 1; - self.visit(args); - self.indent -= 1; - } - BoundKind::Pipe { inputs, lambda, .. } => { - self.log("Pipe", node); - self.indent += 1; - for input in inputs { - self.visit(input); - } - self.visit(lambda); - 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 { layout, values } => { - self.log( - &format!("Record (Layout: {} fields)", layout.fields.len()), - node, - ); - self.indent += 1; - for v in values { - 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); - } - BoundKind::Error => { - self.log("ERROR_NODE", node); - } - } - } -} +use crate::ast::compiler::bound_nodes::{BoundKind, 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) -> 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) { + self.write_indent(); + self.output.push_str(label); + self.output + .push_str(&format!(" \n", node.ty)); + } + + fn visit(&mut self, node: &Node) { + 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::FieldAccessor(k) => self.log(&format!("FieldAccessor: .{}", k.name()), node), + + BoundKind::GetField { rec, field } => { + self.log(&format!("GetField: .{}", field.name()), node); + self.indent += 1; + self.visit(rec); + self.indent -= 1; + } + + BoundKind::Set { addr, value } => { + self.log(&format!("Set: {:?}", addr), node); + self.indent += 1; + self.visit(value); + self.indent -= 1; + } + + BoundKind::Define { + name, + addr, + kind, + value, + captured_by, + } => { + let k_str = match kind { + crate::ast::compiler::bound_nodes::DeclarationKind::Variable => "Variable", + crate::ast::compiler::bound_nodes::DeclarationKind::Parameter => "Parameter", + }; + let capture_info = if captured_by.is_empty() { + String::from("not captured") + } else { + format!("captured by {} lambdas", captured_by.len()) + }; + self.log( + &format!( + "Define {} (Name: '{}', Address: {:?}, {})", + k_str, name.name, addr, capture_info + ), + node, + ); + + self.indent += 1; + if !captured_by.is_empty() { + for capturer in captured_by { + self.write_indent(); + let loc = capturer + .location + .unwrap_or(crate::ast::types::SourceLocation { line: 0, col: 0 }); + self.output.push_str(&format!( + "- Capturer: Lambda at line {}, col {}\n", + loc.line, loc.col + )); + } + } + self.visit(value); + self.indent -= 1; + } + + BoundKind::Destructure { pattern, value } => { + self.log("Destructure", node); + self.indent += 1; + self.write_indent(); + self.output.push_str("Pattern:\n"); + self.visit(pattern); + self.write_indent(); + self.output.push_str("Value:\n"); + 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::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::Again { args } => { + self.log("Again", node); + self.indent += 1; + self.visit(args); + self.indent -= 1; + } + BoundKind::Pipe { inputs, lambda, .. } => { + self.log("Pipe", node); + self.indent += 1; + for input in inputs { + self.visit(input); + } + self.visit(lambda); + 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 { layout, values } => { + self.log( + &format!("Record (Layout: {} fields)", layout.fields.len()), + node, + ); + self.indent += 1; + for v in values { + 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); + } + BoundKind::Error => { + self.log("ERROR_NODE", node); + } + } + } +} diff --git a/src/ast/compiler/lambda_collector.rs b/src/ast/compiler/lambda_collector.rs index 1429120..ea8c065 100644 --- a/src/ast/compiler/lambda_collector.rs +++ b/src/ast/compiler/lambda_collector.rs @@ -1,99 +1,99 @@ -use crate::ast::compiler::bound_nodes::{Address, BoundKind, GlobalIdx, Node}; -use std::collections::HashMap; -use std::rc::Rc; - -/// 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: &Node, registry: &'a mut HashMap>>) { - let mut collector = Self { registry }; - collector.visit(node); - } - - fn visit(&mut self, node: &Node) { - match &node.kind { - BoundKind::Block { exprs } => { - for expr in exprs { - self.visit(expr); - } - } - - BoundKind::Define { addr, value, .. } => { - // Register global function definitions (lambdas) - if let Address::Global(global_index) = addr { - let mut current = value; - while let BoundKind::Expansion { bound_expanded, .. } = ¤t.kind { - current = bound_expanded; - } - - if let BoundKind::Lambda { .. } = ¤t.kind { - self.registry - .insert(*global_index, (*current).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 mut current = value; - while let BoundKind::Expansion { bound_expanded, .. } = ¤t.kind { - current = bound_expanded; - } - - if let BoundKind::Lambda { .. } = ¤t.kind { - self.registry - .insert(*global_index, (*current).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::Call { callee, args } => { - self.visit(callee); - self.visit(args); - } - - BoundKind::Tuple { elements } => { - for el in elements { - self.visit(el); - } - } - - BoundKind::Record { values, .. } => { - for v in values { - self.visit(v); - } - } - - BoundKind::Expansion { bound_expanded, .. } => { - self.visit(bound_expanded); - } - - _ => {} // Leaf nodes - } - } -} +use crate::ast::compiler::bound_nodes::{Address, BoundKind, GlobalIdx, Node}; +use std::collections::HashMap; +use std::rc::Rc; + +/// 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: &Node, registry: &'a mut HashMap>>) { + let mut collector = Self { registry }; + collector.visit(node); + } + + fn visit(&mut self, node: &Node) { + match &node.kind { + BoundKind::Block { exprs } => { + for expr in exprs { + self.visit(expr); + } + } + + BoundKind::Define { addr, value, .. } => { + // Register global function definitions (lambdas) + if let Address::Global(global_index) = addr { + let mut current = value; + while let BoundKind::Expansion { bound_expanded, .. } = ¤t.kind { + current = bound_expanded; + } + + if let BoundKind::Lambda { .. } = ¤t.kind { + self.registry + .insert(*global_index, (*current).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 mut current = value; + while let BoundKind::Expansion { bound_expanded, .. } = ¤t.kind { + current = bound_expanded; + } + + if let BoundKind::Lambda { .. } = ¤t.kind { + self.registry + .insert(*global_index, (*current).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::Call { callee, args } => { + self.visit(callee); + self.visit(args); + } + + BoundKind::Tuple { elements } => { + for el in elements { + self.visit(el); + } + } + + BoundKind::Record { values, .. } => { + for v in values { + 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 52d2eeb..8c69ed4 100644 --- a/src/ast/compiler/macros.rs +++ b/src/ast/compiler/macros.rs @@ -1,4 +1,4 @@ -๏ปฟuse crate::ast::nodes::{Symbol, UntypedKind, UntypedNode}; +use crate::ast::nodes::{Symbol, SyntaxKind, SyntaxNode}; use crate::ast::types::{Identity, Value}; use std::collections::HashMap; use std::rc::Rc; @@ -7,19 +7,19 @@ use std::rc::Rc; pub trait MacroEvaluator { fn evaluate( &self, - node: &UntypedNode, - bindings: &HashMap, UntypedNode>, + node: &SyntaxNode, + bindings: &HashMap, SyntaxNode>, ) -> Result; } /// Internal state for template expansion (Hygiene context + Parameter binding) struct ExpansionState<'a> { - bindings: &'a HashMap, UntypedNode>, + bindings: &'a HashMap, SyntaxNode>, /// The identity of the current expansion instance, used to "color" internal symbols. expansion_id: Identity, } -type MacroMap = HashMap, (Vec>, UntypedNode)>; +type MacroMap = HashMap, (Vec>, SyntaxNode)>; /// A registry for macro declarations. #[derive(Clone)] @@ -50,7 +50,7 @@ impl MacroRegistry { } } - pub fn define(&mut self, name: Rc, params: Vec>, body: UntypedNode) { + pub fn define(&mut self, name: Rc, params: Vec>, body: SyntaxNode) { if let Some(current_rc) = self.scopes.last_mut() { // Copy-on-Write: If the Rc is shared, clone the map before modifying. let current = Rc::make_mut(current_rc); @@ -58,7 +58,7 @@ impl MacroRegistry { } } - pub fn lookup(&self, name: &str) -> Option<(Vec>, UntypedNode)> { + pub fn lookup(&self, name: &str) -> Option<(Vec>, SyntaxNode)> { for scope in self.scopes.iter().rev() { if let Some(m) = scope.get(name) { return Some(m.clone()); @@ -85,30 +85,30 @@ impl MacroExpander { self.registry } - pub fn expand(&mut self, node: UntypedNode) -> Result { + pub fn expand(&mut self, node: SyntaxNode) -> Result { self.expand_recursive(node) } - fn expand_recursive(&mut self, node: UntypedNode) -> Result { + fn expand_recursive(&mut self, node: SyntaxNode) -> Result { match node.kind { - UntypedKind::MacroDecl { name, params, body } => { + SyntaxKind::MacroDecl { name, params, body } => { let p_names = self.extract_param_names(¶ms)?; self.registry.define(name.name, p_names, *body); - Ok(UntypedNode { + Ok(SyntaxNode { identity: node.identity, - kind: UntypedKind::Nop, + kind: SyntaxKind::Nop, }) } - UntypedKind::Call { callee, args } => { - if let UntypedKind::Identifier(ref sym) = callee.kind + SyntaxKind::Call { callee, args } => { + if let SyntaxKind::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 { + let arg_elements = if let SyntaxKind::Tuple { elements } = expanded_args.kind { elements } else { vec![expanded_args] @@ -127,9 +127,9 @@ impl MacroExpander { let expanded_callee = self.expand_recursive(*callee)?; let expanded_args = self.expand_recursive(*args)?; - Ok(UntypedNode { + Ok(SyntaxNode { identity: node.identity, - kind: UntypedKind::Call { + kind: SyntaxKind::Call { callee: Box::new(expanded_callee), args: Box::new(expanded_args), }, @@ -137,7 +137,7 @@ impl MacroExpander { }) } - UntypedKind::Block { exprs } => { + SyntaxKind::Block { exprs } => { self.registry.push(); let mut expanded_exprs = Vec::new(); for expr in exprs { @@ -145,24 +145,24 @@ impl MacroExpander { } self.registry.pop(); - Ok(UntypedNode { + Ok(SyntaxNode { identity: node.identity, - kind: UntypedKind::Block { + kind: SyntaxKind::Block { exprs: expanded_exprs, }, }) } - UntypedKind::Pipe { inputs, lambda } => { + SyntaxKind::Pipe { inputs, lambda } => { let mut expanded_inputs = Vec::with_capacity(inputs.len()); for input in inputs { expanded_inputs.push(self.expand_recursive(input)?); } let expanded_lambda = Box::new(self.expand_recursive(*lambda)?); - Ok(UntypedNode { + Ok(SyntaxNode { identity: node.identity, - kind: UntypedKind::Pipe { + kind: SyntaxKind::Pipe { inputs: expanded_inputs, lambda: expanded_lambda, }, @@ -170,15 +170,15 @@ impl MacroExpander { }) } - UntypedKind::Lambda { params, body } => { + SyntaxKind::Lambda { params, body } => { self.registry.push(); let expanded_params = self.expand_recursive(*params)?; let expanded_body = self.expand_recursive((*body).clone())?; self.registry.pop(); - Ok(UntypedNode { + Ok(SyntaxNode { identity: node.identity, - kind: UntypedKind::Lambda { + kind: SyntaxKind::Lambda { params: Box::new(expanded_params), body: Rc::new(expanded_body), }, @@ -186,7 +186,7 @@ impl MacroExpander { }) } - UntypedKind::If { + SyntaxKind::If { cond, then_br, else_br, @@ -198,9 +198,9 @@ impl MacroExpander { } else { None }; - Ok(UntypedNode { + Ok(SyntaxNode { identity: node.identity, - kind: UntypedKind::If { + kind: SyntaxKind::If { cond: Box::new(cond), then_br: Box::new(then_br), else_br, @@ -209,12 +209,12 @@ impl MacroExpander { }) } - UntypedKind::Def { target, value } => { + SyntaxKind::Def { target, value } => { let target = self.expand_recursive(*target)?; let value = self.expand_recursive(*value)?; - Ok(UntypedNode { + Ok(SyntaxNode { identity: node.identity, - kind: UntypedKind::Def { + kind: SyntaxKind::Def { target: Box::new(target), value: Box::new(value), }, @@ -222,12 +222,12 @@ impl MacroExpander { }) } - UntypedKind::Assign { target, value } => { + SyntaxKind::Assign { target, value } => { let target = self.expand_recursive(*target)?; let value = self.expand_recursive(*value)?; - Ok(UntypedNode { + Ok(SyntaxNode { identity: node.identity, - kind: UntypedKind::Assign { + kind: SyntaxKind::Assign { target: Box::new(target), value: Box::new(value), }, @@ -235,39 +235,39 @@ impl MacroExpander { }) } - UntypedKind::Tuple { elements } => { + SyntaxKind::Tuple { elements } => { let mut expanded_elements = Vec::new(); for e in elements { expanded_elements.push(self.expand_recursive(e)?); } - Ok(UntypedNode { + Ok(SyntaxNode { identity: node.identity, - kind: UntypedKind::Tuple { + kind: SyntaxKind::Tuple { elements: expanded_elements, }, }) } - UntypedKind::Record { fields } => { + SyntaxKind::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(UntypedNode { + Ok(SyntaxNode { identity: node.identity, - kind: UntypedKind::Record { + kind: SyntaxKind::Record { fields: expanded_fields, }, }) } - UntypedKind::Expansion { call, expanded } => { + SyntaxKind::Expansion { call, expanded } => { let expanded = self.expand_recursive(*expanded)?; - Ok(UntypedNode { + Ok(SyntaxNode { identity: node.identity, - kind: UntypedKind::Expansion { + kind: SyntaxKind::Expansion { call, expanded: Box::new(expanded), }, @@ -275,11 +275,11 @@ impl MacroExpander { }) } - UntypedKind::Again { args } => { + SyntaxKind::Again { args } => { let expanded_args = self.expand_recursive(*args)?; - Ok(UntypedNode { + Ok(SyntaxNode { identity: node.identity, - kind: UntypedKind::Again { + kind: SyntaxKind::Again { args: Box::new(expanded_args), }, @@ -295,9 +295,9 @@ impl MacroExpander { identity: Identity, name: &str, params: Vec>, - args: Vec, - body: UntypedNode, - ) -> Result { + args: Vec, + body: SyntaxNode, + ) -> Result { if params.len() != args.len() { return Err(format!( "Macro {} expects {} arguments, but got {}", @@ -313,7 +313,7 @@ impl MacroExpander { } // AST-Authority: Substitution and Hygiene ONLY happen if there is a Template node. - let expanded_body = if let UntypedKind::Template(inner) = body.kind { + let expanded_body = if let SyntaxKind::Template(inner) = body.kind { let mut state = ExpansionState { bindings: &bindings, expansion_id: identity.clone(), @@ -324,17 +324,17 @@ impl MacroExpander { body }; - let original_call = UntypedNode { + let original_call = SyntaxNode { identity: identity.clone(), - kind: UntypedKind::Call { - callee: Box::new(UntypedNode { + kind: SyntaxKind::Call { + callee: Box::new(SyntaxNode { identity: identity.clone(), - kind: UntypedKind::Identifier(Symbol::from(name)), + kind: SyntaxKind::Identifier(Symbol::from(name)), }), - args: Box::new(UntypedNode { + args: Box::new(SyntaxNode { identity: identity.clone(), - kind: UntypedKind::Tuple { + kind: SyntaxKind::Tuple { elements: bindings.into_values().collect(), }, @@ -343,9 +343,9 @@ impl MacroExpander { }; - Ok(UntypedNode { + Ok(SyntaxNode { identity, - kind: UntypedKind::Expansion { + kind: SyntaxKind::Expansion { call: Box::new(original_call), expanded: Box::new(expanded_body), }, @@ -353,10 +353,10 @@ impl MacroExpander { }) } - fn extract_param_names(&self, node: &UntypedNode) -> Result>, String> { + fn extract_param_names(&self, node: &SyntaxNode) -> Result>, String> { match &node.kind { - UntypedKind::Identifier(sym) => Ok(vec![sym.name.clone()]), - UntypedKind::Tuple { elements } => { + SyntaxKind::Identifier(sym) => Ok(vec![sym.name.clone()]), + SyntaxKind::Tuple { elements } => { let mut names = Vec::new(); for el in elements { names.extend(self.extract_param_names(el)?); @@ -369,23 +369,23 @@ impl MacroExpander { fn expand_template( &self, - node: UntypedNode, + node: SyntaxNode, state: &mut ExpansionState, - ) -> Result { + ) -> Result { match node.kind { - UntypedKind::Identifier(mut sym) => { + SyntaxKind::Identifier(mut sym) => { // Inside a template, all internal identifiers are colored. sym.context = Some(state.expansion_id.clone()); - Ok(UntypedNode { + Ok(SyntaxNode { identity: node.identity, - kind: UntypedKind::Identifier(sym), + kind: SyntaxKind::Identifier(sym), }) } - UntypedKind::Placeholder(inner) => { + SyntaxKind::Placeholder(inner) => { // Break out of template for substitution/evaluation - if let UntypedKind::Identifier(ref sym) = inner.kind + if let SyntaxKind::Identifier(ref sym) = inner.kind && let Some(arg) = state.bindings.get(&sym.name) { return Ok(arg.clone()); @@ -394,9 +394,9 @@ impl MacroExpander { Ok(self.value_to_node(val, node.identity)) } - UntypedKind::Splice(inner) => { + SyntaxKind::Splice(inner) => { // Splicing is also a form of substitution - if let UntypedKind::Identifier(ref sym) = inner.kind + if let SyntaxKind::Identifier(ref sym) = inner.kind && let Some(arg) = state.bindings.get(&sym.name) { return Ok(arg.clone()); @@ -405,12 +405,12 @@ impl MacroExpander { Ok(self.value_to_node(val, node.identity)) } - UntypedKind::Def { target, value } => { + SyntaxKind::Def { target, value } => { let target = self.expand_template(*target, state)?; let val = self.expand_template(*value, state)?; - Ok(UntypedNode { + Ok(SyntaxNode { identity: node.identity, - kind: UntypedKind::Def { + kind: SyntaxKind::Def { target: Box::new(target), value: Box::new(val), }, @@ -418,12 +418,12 @@ impl MacroExpander { }) } - UntypedKind::Assign { target, value } => { + SyntaxKind::Assign { target, value } => { let target = self.expand_template(*target, state)?; let value = self.expand_template(*value, state)?; - Ok(UntypedNode { + Ok(SyntaxNode { identity: node.identity, - kind: UntypedKind::Assign { + kind: SyntaxKind::Assign { target: Box::new(target), value: Box::new(value), }, @@ -431,43 +431,43 @@ impl MacroExpander { }) } - UntypedKind::Tuple { elements } => { + SyntaxKind::Tuple { elements } => { let mut new_elements = Vec::new(); for e in elements { - if let UntypedKind::Splice(ref inner) = e.kind { + if let SyntaxKind::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(UntypedNode { + Ok(SyntaxNode { identity: node.identity, - kind: UntypedKind::Tuple { + kind: SyntaxKind::Tuple { elements: new_elements, }, }) } - UntypedKind::Block { exprs } => { + SyntaxKind::Block { exprs } => { let mut new_exprs = Vec::new(); for e in exprs { - if let UntypedKind::Splice(ref inner) = e.kind { + if let SyntaxKind::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(UntypedNode { + Ok(SyntaxNode { identity: node.identity, - kind: UntypedKind::Block { exprs: new_exprs }, + kind: SyntaxKind::Block { exprs: new_exprs }, }) } - UntypedKind::Record { fields } => { + SyntaxKind::Record { fields } => { let mut new_fields = Vec::new(); for (k, v) in fields { new_fields.push(( @@ -475,19 +475,19 @@ impl MacroExpander { self.expand_template(v, state)?, )); } - Ok(UntypedNode { + Ok(SyntaxNode { identity: node.identity, - kind: UntypedKind::Record { fields: new_fields }, + kind: SyntaxKind::Record { fields: new_fields }, }) } - UntypedKind::Call { callee, args } => { + SyntaxKind::Call { callee, args } => { let expanded_callee = self.expand_template(*callee, state)?; let expanded_args = self.expand_template(*args, state)?; - Ok(UntypedNode { + Ok(SyntaxNode { identity: node.identity, - kind: UntypedKind::Call { + kind: SyntaxKind::Call { callee: Box::new(expanded_callee), args: Box::new(expanded_args), }, @@ -495,7 +495,7 @@ impl MacroExpander { }) } - UntypedKind::If { + SyntaxKind::If { cond, then_br, else_br, @@ -507,9 +507,9 @@ impl MacroExpander { } else { None }; - Ok(UntypedNode { + Ok(SyntaxNode { identity: node.identity, - kind: UntypedKind::If { + kind: SyntaxKind::If { cond: Box::new(cond), then_br: Box::new(then_br), else_br, @@ -518,15 +518,15 @@ impl MacroExpander { }) } - UntypedKind::Pipe { inputs, lambda } => { + SyntaxKind::Pipe { inputs, lambda } => { let mut expanded_inputs = Vec::with_capacity(inputs.len()); for input in inputs { expanded_inputs.push(self.expand_template(input, state)?); } let expanded_lambda = Box::new(self.expand_template(*lambda, state)?); - Ok(UntypedNode { + Ok(SyntaxNode { identity: node.identity, - kind: UntypedKind::Pipe { + kind: SyntaxKind::Pipe { inputs: expanded_inputs, lambda: expanded_lambda, }, @@ -534,12 +534,12 @@ impl MacroExpander { }) } - UntypedKind::Lambda { params, body } => { + SyntaxKind::Lambda { params, body } => { let expanded_params = self.expand_template(*params, state)?; let body = self.expand_template((*body).clone(), state)?; - Ok(UntypedNode { + Ok(SyntaxNode { identity: node.identity, - kind: UntypedKind::Lambda { + kind: SyntaxKind::Lambda { params: Box::new(expanded_params), body: Rc::new(body), }, @@ -547,11 +547,11 @@ impl MacroExpander { }) } - UntypedKind::Again { args } => { + SyntaxKind::Again { args } => { let expanded_args = self.expand_template(*args, state)?; - Ok(UntypedNode { + Ok(SyntaxNode { identity: node.identity, - kind: UntypedKind::Again { + kind: SyntaxKind::Again { args: Box::new(expanded_args), }, @@ -566,19 +566,19 @@ impl MacroExpander { &self, val: Value, _identity: Identity, - target: &mut Vec, + target: &mut Vec, ) -> Result<(), String> { match val { Value::Object(obj) => { - if let Some(node) = obj.as_any().downcast_ref::() { + if let Some(node) = obj.as_any().downcast_ref::() { match &node.kind { - UntypedKind::Tuple { elements } => { + SyntaxKind::Tuple { elements } => { for e in elements { target.push(e.clone()); } return Ok(()); } - UntypedKind::Block { exprs } => { + SyntaxKind::Block { exprs } => { for e in exprs { target.push(e.clone()); } @@ -599,21 +599,21 @@ impl MacroExpander { } } - fn value_to_node(&self, val: Value, identity: Identity) -> UntypedNode { + fn value_to_node(&self, val: Value, identity: Identity) -> SyntaxNode { match val { Value::Object(obj) => { - if let Some(node) = obj.as_any().downcast_ref::() { + if let Some(node) = obj.as_any().downcast_ref::() { return node.clone(); } - UntypedNode { + SyntaxNode { identity, - kind: UntypedKind::Constant(Value::Object(obj)), + kind: SyntaxKind::Constant(Value::Object(obj)), } } - _ => UntypedNode { + _ => SyntaxNode { identity, - kind: UntypedKind::Constant(val), + kind: SyntaxKind::Constant(val), }, } @@ -632,10 +632,10 @@ mod tests { impl MacroEvaluator for SimpleEvaluator { fn evaluate( &self, - node: &UntypedNode, - bindings: &HashMap, UntypedNode>, + node: &SyntaxNode, + bindings: &HashMap, SyntaxNode>, ) -> Result { - if let UntypedKind::Identifier(ref sym) = node.kind + if let SyntaxKind::Identifier(ref sym) = node.kind && let Some(arg) = bindings.get(&sym.name) { return Ok(Value::Object(Rc::new(arg.clone()) as Rc)); @@ -655,19 +655,19 @@ mod tests { (m)) "; let mut parser = Parser::new(source); - let untyped = parser.parse_expression(); + let syntax = parser.parse_expression(); let mut expander = MacroExpander::new(MacroRegistry::new(), SimpleEvaluator); - let expanded = expander.expand(untyped).unwrap(); + let expanded = expander.expand(syntax).unwrap(); - if let UntypedKind::Block { exprs } = &expanded.kind - && let UntypedKind::Expansion { + if let SyntaxKind::Block { exprs } = &expanded.kind + && let SyntaxKind::Expansion { expanded: result, call, } = &exprs[1].kind { - if let UntypedKind::Def { target, .. } = &result.kind { - if let UntypedKind::Identifier(sym) = &target.kind { + if let SyntaxKind::Def { target, .. } = &result.kind { + if let SyntaxKind::Identifier(sym) = &target.kind { assert_eq!(sym.context, Some(call.identity.clone())); assert_eq!(sym.name.as_ref(), "y"); } else { @@ -687,30 +687,30 @@ mod tests { (unless false 42)) "; let mut parser = Parser::new(source); - let untyped = parser.parse_expression(); + let syntax = parser.parse_expression(); let mut expander = MacroExpander::new(MacroRegistry::new(), SimpleEvaluator); - let expanded = expander.expand(untyped).unwrap(); + let expanded = expander.expand(syntax).unwrap(); - if let UntypedKind::Block { exprs } = &expanded.kind - && let UntypedKind::Expansion { + if let SyntaxKind::Block { exprs } = &expanded.kind + && let SyntaxKind::Expansion { call: _, expanded: result, } = &exprs[1].kind - && let UntypedKind::If { + && let SyntaxKind::If { cond, then_br, else_br, } = &result.kind { - if let UntypedKind::Identifier(sym) = &cond.kind { + if let SyntaxKind::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)); + assert!(matches!(then_br.kind, SyntaxKind::Nop)); if let Some(eb) = else_br { - if let UntypedKind::Constant(Value::Int(n)) = &eb.kind { + if let SyntaxKind::Constant(Value::Int(n)) = &eb.kind { assert_eq!(*n, 42); } else { panic!("Expected 42, got {:?}", eb.kind); @@ -729,23 +729,23 @@ mod tests { (def t (wrap [1 2 3]))) "; let mut parser = Parser::new(source); - let untyped = parser.parse_expression(); + let syntax = parser.parse_expression(); let mut expander = MacroExpander::new(MacroRegistry::new(), SimpleEvaluator); - let expanded = expander.expand(untyped).unwrap(); + let expanded = expander.expand(syntax).unwrap(); - if let UntypedKind::Block { exprs } = &expanded.kind - && let UntypedKind::Def { value, .. } = &exprs[1].kind - && let UntypedKind::Expansion { + if let SyntaxKind::Block { exprs } = &expanded.kind + && let SyntaxKind::Def { value, .. } = &exprs[1].kind + && let SyntaxKind::Expansion { expanded: result, .. } = &value.kind - && let UntypedKind::Tuple { elements } = &result.kind + && let SyntaxKind::Tuple { elements } = &result.kind { assert_eq!(elements.len(), 5); - if let UntypedKind::Constant(Value::Int(n)) = &elements[0].kind { + if let SyntaxKind::Constant(Value::Int(n)) = &elements[0].kind { assert_eq!(*n, 0); } - if let UntypedKind::Constant(Value::Int(n)) = &elements[4].kind { + if let SyntaxKind::Constant(Value::Int(n)) = &elements[4].kind { assert_eq!(*n, 4); } } @@ -759,10 +759,10 @@ mod tests { (square 3)) "; let mut parser = Parser::new(source); - let untyped = parser.parse_expression(); + let syntax = parser.parse_expression(); let mut expander = MacroExpander::new(MacroRegistry::new(), SimpleEvaluator); - let expanded = expander.expand(untyped).unwrap(); + let expanded = expander.expand(syntax).unwrap(); // Convert test globals into a CompilerScope let mut locals = HashMap::new(); @@ -800,10 +800,10 @@ mod tests { y) "; let mut parser = Parser::new(source); - let untyped = parser.parse_expression(); + let syntax = parser.parse_expression(); let mut expander = MacroExpander::new(MacroRegistry::new(), SimpleEvaluator); - let expanded = expander.expand(untyped).unwrap(); + let expanded = expander.expand(syntax).unwrap(); let mut diag = crate::ast::diagnostics::Diagnostics::new(); let result = Binder::bind_root(vec![], 0, 0, &expanded, &mut diag); @@ -820,10 +820,10 @@ mod tests { y) "; let mut parser = Parser::new(source); - let untyped = parser.parse_expression(); + let syntax = parser.parse_expression(); let mut expander = MacroExpander::new(MacroRegistry::new(), SimpleEvaluator); - let expanded = expander.expand(untyped).unwrap(); + let expanded = expander.expand(syntax).unwrap(); let mut diag = crate::ast::diagnostics::Diagnostics::new(); let result = Binder::bind_root(vec![], 0, 0, &expanded, &mut diag); diff --git a/src/ast/compiler/mod.rs b/src/ast/compiler/mod.rs index 5c604c7..a1f4931 100644 --- a/src/ast/compiler/mod.rs +++ b/src/ast/compiler/mod.rs @@ -1,21 +1,21 @@ -pub mod analyzer; -pub mod binder; -pub mod bound_nodes; -pub mod captures; -pub mod dumper; -pub mod lambda_collector; -pub mod macros; -pub mod optimizer; -pub mod specializer; -pub mod lowering; -pub mod type_checker; - -pub use binder::*; -pub use bound_nodes::*; -pub use captures::*; -pub use dumper::*; -pub use macros::*; -pub use optimizer::*; -pub use specializer::*; -pub use lowering::*; -pub use type_checker::*; +pub mod analyzer; +pub mod binder; +pub mod bound_nodes; +pub mod captures; +pub mod dumper; +pub mod lambda_collector; +pub mod macros; +pub mod optimizer; +pub mod specializer; +pub mod lowering; +pub mod type_checker; + +pub use binder::*; +pub use bound_nodes::*; +pub use captures::*; +pub use dumper::*; +pub use macros::*; +pub use optimizer::*; +pub use specializer::*; +pub use lowering::*; +pub use type_checker::*; diff --git a/src/ast/compiler/optimizer/engine.rs b/src/ast/compiler/optimizer/engine.rs index a5cd683..ddbb906 100644 --- a/src/ast/compiler/optimizer/engine.rs +++ b/src/ast/compiler/optimizer/engine.rs @@ -1,716 +1,716 @@ -use crate::ast::compiler::bound_nodes::{ - Address, AnalyzedNode, BoundKind, GlobalAnalyzedRegistry, Node, UpvalueIdx, -}; -use crate::ast::types::{Purity, Value}; -use crate::ast::vm::Closure; -use std::cell::RefCell; -use std::rc::Rc; - -use super::folder::Folder; -use super::inliner::Inliner; -use super::substitution_map::SubstitutionMap; -use super::utils::{PathTracker, UsageInfo}; - -pub struct Optimizer { - pub enabled: bool, - max_passes: usize, - pub globals: Option>>>, - pub root_purity: Option>>>, - pub lambda_registry: Option>>, -} - -impl Optimizer { - pub fn new(enabled: bool) -> Self { - Self { - enabled, - max_passes: 5, - globals: None, - root_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.root_purity = Some(purity); - self - } - - pub fn with_registry( - mut self, - registry: Rc>, - ) -> Self { - self.lambda_registry = Some(registry); - self - } - - pub fn optimize(&self, node: AnalyzedNode) -> AnalyzedNode { - if !self.enabled { - return node; - } - - let mut current = Rc::new(node); - for _ in 0..self.max_passes { - let mut sub = SubstitutionMap::new(); - let mut path = PathTracker::new(); - let next = self.visit_node(current.clone(), &mut sub, &mut path); - if Rc::ptr_eq(&next, ¤t) { - break; - } - current = next; - } - // Unwrap the final Rc if we are at the end, or return a clone of the inner node. - // Since AnalyzedNode is small now (header + Rcs), cloning is cheap. - (*current).clone() - } - - fn try_inline( - &self, - params: &AnalyzedNode, - arg_nodes: &[Rc], - body: &AnalyzedNode, - sub: &mut SubstitutionMap, - path: &mut PathTracker, - base_sub: Option, - ) -> Option> { - let inliner = Inliner::new(&self.globals, &self.root_purity); - let mut inner_sub = base_sub.unwrap_or_else(|| sub.new_for_inlining()); - - if inliner.prepare_beta_reduction(params, arg_nodes, body, &mut inner_sub).is_some() { - let res = self.visit_node(Rc::new(body.clone()), &mut inner_sub, path); - - // Sync back state to parent substitution map - sub.next_slot = inner_sub.next_slot; - sub.used.extend(inner_sub.used.iter().cloned()); - sub.assigned.extend(inner_sub.assigned.iter().cloned()); - sub.captured_slots.extend(inner_sub.captured_slots.iter().cloned()); - - Some(res) - } else { - None - } - } - - fn visit_node( - &self, - node_rc: Rc, - sub: &mut SubstitutionMap, - path: &mut PathTracker, - ) -> Rc { - let node = &*node_rc; - let folder = Folder::new(&self.globals); - let inliner = Inliner::new(&self.globals, &self.root_purity); - - let (new_kind, metrics) = match &node.kind { - BoundKind::Get { addr, name } => { - let addr = *addr; - if !sub.assigned.contains(&addr) { - // 1. Try inlining from current value substitution map (locals/globals/upvalues) - if let Some(val) = sub.get_value(&addr) - && inliner.is_inlinable_value(val, addr) - { - return Rc::new(folder.make_constant_node(val.clone(), node)); - } - - // 2. Try inlining from AST substitution map (pure expressions) - if let Some(inlined_node) = sub.ast_substitutions.get(&addr) { - return inlined_node.clone(); - } - - // 3. Fallback for Globals: check the actual VM environment - if let Address::Global(idx) = addr - && let Some(globals_rc) = &self.globals - { - let globals = globals_rc.borrow(); - if let Some(val) = globals.get(idx.0 as usize) - && inliner.is_inlinable_value(val, addr) - { - return Rc::new(folder.make_constant_node(val.clone(), node)); - } - } - } - - sub.used.insert(addr); - ( - BoundKind::Get { - addr: sub.map_address(addr), - name: name.clone(), - }, - node.ty.clone(), - ) - } - - BoundKind::FieldAccessor(k) => (BoundKind::FieldAccessor(*k), node.ty.clone()), - - BoundKind::GetField { rec, field } => { - let rec_opt = self.visit_node(rec.clone(), sub, path); - - // Constant folding for Field Access - if let BoundKind::Constant(Value::Record(layout, values)) = &rec_opt.kind - && let Some(idx) = layout.index_of(*field) - { - return Rc::new(folder.make_constant_node(values[idx].clone(), node)); - } - - if Rc::ptr_eq(&rec_opt, rec) { - return node_rc; - } - - ( - BoundKind::GetField { - rec: rec_opt, - field: *field, - }, - node.ty.clone(), - ) - } - - BoundKind::Set { addr, value } => { - let value_opt = self.visit_node(value.clone(), sub, path); - if let BoundKind::Constant(val) = &value_opt.kind { - sub.add_value(*addr, val.clone()); - } else { - sub.remove_value(addr); - } - - if Rc::ptr_eq(&value_opt, value) { - return node_rc; - } - - ( - BoundKind::Set { - addr: sub.map_address(*addr), - value: value_opt, - }, - node.ty.clone(), - ) - } - - BoundKind::Define { - name, - addr, - kind, - value, - captured_by, - } => { - let value_opt = self.visit_node(value.clone(), sub, path); - - if let Address::Local(slot) = addr - && !captured_by.is_empty() - { - sub.captured_slots.insert(*slot); - } - - if let BoundKind::Constant(val) = &value_opt.kind { - sub.add_value(*addr, val.clone()); - } else { - sub.remove_value(addr); - } - - if let Address::Global(global_index) = addr - && value_opt.ty.purity > Purity::Impure - && let Some(purity_rc) = &self.root_purity - { - let mut pr = purity_rc.borrow_mut(); - let idx = global_index.0 as usize; - if idx < pr.len() { - pr[idx] = value_opt.ty.purity; - } - } - - if Rc::ptr_eq(&value_opt, value) { - return node_rc; - } - - ( - BoundKind::Define { - name: name.clone(), - addr: sub.map_address(*addr), - kind: *kind, - value: value_opt, - captured_by: captured_by.clone(), - }, - node.ty.clone(), - ) - } - - BoundKind::Call { callee, args } => { - let callee_opt = self.visit_node(callee.clone(), sub, path); - let args_opt = self.visit_node(args.clone(), sub, path); - - if self.enabled { - // Optimized Field Access Transformation - if let BoundKind::FieldAccessor(k) = &callee_opt.kind - && let BoundKind::Tuple { elements } = &args_opt.kind - && elements.len() == 1 - { - let rec = elements[0].clone(); - let metrics = node.ty.clone(); - return Rc::new(Node { - identity: node.identity.clone(), - kind: BoundKind::GetField { - rec, - field: *k, - }, - ty: metrics, - }); - } - - let mut arg_nodes = Vec::new(); - self.flatten_tuple(args_opt.clone(), &mut arg_nodes); - - if let BoundKind::Lambda { - params, - body, - upvalues, - positional_count, - } = &callee_opt.kind - && upvalues.is_empty() - && positional_count.is_some() - && path.inlining_depth < 5 - && !callee_opt.ty.is_recursive - && path.enter_lambda(&callee_opt.identity) - { - path.inlining_depth += 1; - let collapsed = self.try_inline(params, &arg_nodes, body, sub, path, None); - path.inlining_depth -= 1; - path.exit_lambda(&callee_opt.identity); - - if let Some(res) = collapsed { - return res; - } - } - - if let BoundKind::Get { - addr: Address::Global(idx), - .. - } = &callee_opt.kind - && let Some(registry_rc) = &self.lambda_registry - && path.inlining_depth < 5 - && !path.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() - && !lambda_node.ty.is_recursive - { - path.inlining_stack.insert(*idx); - path.inlining_depth += 1; - let collapsed = self.try_inline(params, &arg_nodes, body, sub, path, None); - path.inlining_depth -= 1; - path.inlining_stack.remove(idx); - - if let Some(res) = collapsed { - return res; - } - } - } - - if let BoundKind::Constant(Value::Object(ref obj)) = callee_opt.kind - && path.inlining_depth < 5 - && let Some(closure) = obj.as_any().downcast_ref::() - && (closure.upvalues.is_empty() - || closure.function_node.ty.purity >= Purity::SideEffectFree) - && !closure.function_node.ty.is_recursive - && path.enter_lambda(&closure.function_node.identity) - { - let mut closure_sub = sub.new_for_inlining(); - for (i, cell) in closure.upvalues.iter().enumerate() { - closure_sub.add_value( - Address::Upvalue(UpvalueIdx(i as u32)), - cell.borrow().clone(), - ); - } - - path.inlining_depth += 1; - let collapsed = self.try_inline( - &closure.parameter_node, - &arg_nodes, - &closure.function_node, - sub, - path, - Some(closure_sub), - ); - path.inlining_depth -= 1; - path.exit_lambda(&closure.function_node.identity); - - if let Some(res) = collapsed { - return res; - } - } - - if let Some(folded) = folder.try_fold_pure(&callee_opt, &arg_nodes) { - return Rc::new(folded); - } - } - - if Rc::ptr_eq(&callee_opt, callee) && Rc::ptr_eq(&args_opt, args) { - return node_rc; - } - - ( - BoundKind::Call { - callee: callee_opt, - args: args_opt, - }, - node.ty.clone(), - ) - } - - BoundKind::Again { args } => { - let args_opt = self.visit_node(args.clone(), sub, path); - if Rc::ptr_eq(&args_opt, args) { - return node_rc; - } - ( - BoundKind::Again { - args: args_opt, - }, - node.ty.clone(), - ) - } - - BoundKind::If { - cond, - then_br, - else_br, - } => { - let cond_opt = self.visit_node(cond.clone(), sub, path); - if self.enabled - && let BoundKind::Constant(ref val) = cond_opt.kind - { - if val.is_truthy() { - return self.visit_node(then_br.clone(), sub, path); - } else if let Some(else_node) = else_br { - return self.visit_node(else_node.clone(), sub, path); - } else { - return Rc::new(folder.make_nop_node(node)); - } - } - - let then_br_opt = self.visit_node(then_br.clone(), sub, path); - let else_br_opt = else_br - .as_ref() - .map(|e| self.visit_node(e.clone(), sub, path)); - - let unchanged = Rc::ptr_eq(&cond_opt, cond) && Rc::ptr_eq(&then_br_opt, then_br) && match (&else_br_opt, else_br) { - (Some(a), Some(b)) => Rc::ptr_eq(a, b), - (None, None) => true, - _ => false, - }; - - if unchanged { - return node_rc; - } - - ( - BoundKind::If { - cond: cond_opt, - then_br: then_br_opt, - else_br: else_br_opt, - }, - node.ty.clone(), - ) - } - - BoundKind::Pipe { - inputs, - lambda, - out_type, - } => { - let mut o_inputs = Vec::with_capacity(inputs.len()); - let mut inputs_changed = false; - for input in inputs { - let opt = self.visit_node(input.clone(), sub, path); - if !Rc::ptr_eq(&opt, input) { - inputs_changed = true; - } - o_inputs.push(opt); - } - let o_lambda = self.visit_node(lambda.clone(), sub, path); - - if !inputs_changed && Rc::ptr_eq(&o_lambda, lambda) { - return node_rc; - } - - ( - BoundKind::Pipe { - inputs: o_inputs, - lambda: o_lambda, - out_type: out_type.clone(), - }, - node.ty.clone(), - ) - } - BoundKind::Block { exprs } => { - let mut info = UsageInfo::default(); - if !exprs.is_empty() { - for e in exprs { - info.collect(e); - } - } - - sub.assigned.extend(info.assigned.iter().cloned()); - - let mut new_exprs = Vec::with_capacity(exprs.len()); - let last_idx = exprs.len().saturating_sub(1); - let mut block_changed = false; - - for (i, e) in exprs.iter().enumerate() { - let is_last = i == last_idx; - if self.enabled && !is_last { - let removable = match &e.kind { - BoundKind::Define { addr, value, .. } => { - !info.is_used(addr) - && (if let Address::Local(slot) = addr { - !sub.captured_slots.contains(slot) - } else { - true - }) - && (value.ty.purity >= Purity::SideEffectFree - || matches!(value.kind, BoundKind::Lambda { .. })) - } - BoundKind::Set { addr, value, .. } => { - !info.is_used(addr) - && (if let Address::Local(slot) = addr { - !sub.captured_slots.contains(slot) - } else { - true - }) - && value.ty.purity >= Purity::SideEffectFree - } - _ => e.ty.purity >= Purity::SideEffectFree, - }; - - if removable { - block_changed = true; - continue; - } - } - - let opt = self.visit_node(e.clone(), sub, path); - - if let BoundKind::Define { addr, value, .. } = &opt.kind - && !info.assigned.contains(addr) - { - if let BoundKind::Constant(val) = &value.kind { - sub.add_value(*addr, val.clone()); - } else if let BoundKind::Lambda { upvalues, .. } = &value.kind - && upvalues.is_empty() - { - sub.add_ast_substitution(*addr, (**value).clone()); - } - } - - if self.enabled && matches!(opt.kind, BoundKind::Nop) && !is_last { - block_changed = true; - continue; - } - if !Rc::ptr_eq(&opt, e) { - block_changed = true; - } - new_exprs.push(opt); - } - - if !block_changed { - return node_rc; - } - - if self.enabled { - if new_exprs.is_empty() { - return Rc::new(folder.make_nop_node(node)); - } else if new_exprs.len() == 1 { - return new_exprs.pop().unwrap(); - } - } - - (BoundKind::Block { exprs: new_exprs }, node.ty.clone()) - } - - BoundKind::Lambda { - params, - upvalues, - body, - positional_count, - } => { - let mut info = UsageInfo::default(); - info.collect(node); - - let mut new_upvalues = Vec::new(); - let mut mapping = Vec::new(); - let mut next_inner_subs = sub.new_inner(); - next_inner_subs.assigned = info.assigned; - let mut upvalues_changed = false; - - for (old_idx, capture_addr) in upvalues.iter().enumerate() { - let mut inlined_val = None; - if !sub.assigned.contains(capture_addr) - && let Some(val) = sub.get_value(capture_addr) - { - inlined_val = Some(val.clone()); - } - - if let Address::Local(slot) = capture_addr { - sub.captured_slots.insert(*slot); - } - - if let Some(val) = inlined_val { - next_inner_subs - .add_value(Address::Upvalue(UpvalueIdx(old_idx as u32)), val); - mapping.push(None); - upvalues_changed = true; - } else { - mapping.push(Some(new_upvalues.len() as u32)); - let mapped = sub.map_address(*capture_addr); - if mapped != *capture_addr { - upvalues_changed = true; - } - new_upvalues.push(mapped); - } - } - - let params_opt = self.visit_node(params.clone(), &mut next_inner_subs, path); - inliner.collect_parameter_slots(¶ms_opt, &mut next_inner_subs); - - let body_opt = self.visit_node(body.clone(), &mut next_inner_subs, path); - let reindexed_body = if new_upvalues.len() != upvalues.len() { - sub.reindex_upvalues(body_opt, &mapping) - } else { - body_opt - }; - - if !upvalues_changed && Rc::ptr_eq(¶ms_opt, params) && Rc::ptr_eq(&reindexed_body, body) { - return node_rc; - } - - ( - BoundKind::Lambda { - params: params_opt, - upvalues: new_upvalues, - body: reindexed_body, - positional_count: *positional_count, - }, - node.ty.clone(), - ) - } - - BoundKind::Destructure { pattern, value } => { - let val_opt = self.visit_node(value.clone(), sub, path); - let pat_opt = self.visit_node(pattern.clone(), sub, path); - - let mut info = UsageInfo::default(); - info.collect_pattern(&pat_opt); - for addr in info.assigned { - sub.remove_value(&addr); - } - - if Rc::ptr_eq(&val_opt, value) && Rc::ptr_eq(&pat_opt, pattern) { - return node_rc; - } - - ( - BoundKind::Destructure { - pattern: pat_opt, - value: val_opt, - }, - node.ty.clone(), - ) - } - - BoundKind::Tuple { elements } => { - let mut new_elements = Vec::with_capacity(elements.len()); - let mut changed = false; - for e in elements { - let opt = self.visit_node(e.clone(), sub, path); - if !Rc::ptr_eq(&opt, e) { - changed = true; - } - new_elements.push(opt); - } - if !changed { - return node_rc; - } - (BoundKind::Tuple { elements: new_elements }, node.ty.clone()) - } - BoundKind::Record { layout, values } => { - let mut mapped_values = Vec::with_capacity(values.len()); - let mut changed = false; - for v in values { - let opt = self.visit_node(v.clone(), sub, path); - if !Rc::ptr_eq(&opt, v) { - changed = true; - } - mapped_values.push(opt); - } - - if self.enabled - && let Some(folded) = folder.try_fold_record(layout, &mapped_values, node) - { - return Rc::new(folded); - } - - if !changed { - return node_rc; - } - - ( - BoundKind::Record { - layout: layout.clone(), - values: mapped_values, - }, - node.ty.clone(), - ) - } - BoundKind::Expansion { - original_call, - bound_expanded, - } => { - path.inlining_depth += 1; - let expanded_opt = self.visit_node(bound_expanded.clone(), sub, path); - path.inlining_depth -= 1; - - if Rc::ptr_eq(&expanded_opt, bound_expanded) { - return node_rc; - } - - ( - BoundKind::Expansion { - original_call: original_call.clone(), - bound_expanded: expanded_opt, - }, - node.ty.clone(), - ) - } - k => (k.clone(), node.ty.clone()), - }; - - Rc::new(Node { - identity: node.identity.clone(), - kind: new_kind, - ty: metrics, - }) - } - - fn flatten_tuple(&self, node: Rc, into: &mut Vec>) { - match &node.kind { - BoundKind::Tuple { elements } => { - for el in elements { - self.flatten_tuple(el.clone(), into); - } - } - BoundKind::Nop => {} - _ => into.push(node), - } - } -} +use crate::ast::compiler::bound_nodes::{ + Address, AnalyzedNode, BoundKind, GlobalAnalyzedRegistry, Node, UpvalueIdx, +}; +use crate::ast::types::{Purity, Value}; +use crate::ast::vm::Closure; +use std::cell::RefCell; +use std::rc::Rc; + +use super::folder::Folder; +use super::inliner::Inliner; +use super::substitution_map::SubstitutionMap; +use super::utils::{PathTracker, UsageInfo}; + +pub struct Optimizer { + pub enabled: bool, + max_passes: usize, + pub globals: Option>>>, + pub root_purity: Option>>>, + pub lambda_registry: Option>>, +} + +impl Optimizer { + pub fn new(enabled: bool) -> Self { + Self { + enabled, + max_passes: 5, + globals: None, + root_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.root_purity = Some(purity); + self + } + + pub fn with_registry( + mut self, + registry: Rc>, + ) -> Self { + self.lambda_registry = Some(registry); + self + } + + pub fn optimize(&self, node: AnalyzedNode) -> AnalyzedNode { + if !self.enabled { + return node; + } + + let mut current = Rc::new(node); + for _ in 0..self.max_passes { + let mut sub = SubstitutionMap::new(); + let mut path = PathTracker::new(); + let next = self.visit_node(current.clone(), &mut sub, &mut path); + if Rc::ptr_eq(&next, ¤t) { + break; + } + current = next; + } + // Unwrap the final Rc if we are at the end, or return a clone of the inner node. + // Since AnalyzedNode is small now (header + Rcs), cloning is cheap. + (*current).clone() + } + + fn try_inline( + &self, + params: &AnalyzedNode, + arg_nodes: &[Rc], + body: &AnalyzedNode, + sub: &mut SubstitutionMap, + path: &mut PathTracker, + base_sub: Option, + ) -> Option> { + let inliner = Inliner::new(&self.globals, &self.root_purity); + let mut inner_sub = base_sub.unwrap_or_else(|| sub.new_for_inlining()); + + if inliner.prepare_beta_reduction(params, arg_nodes, body, &mut inner_sub).is_some() { + let res = self.visit_node(Rc::new(body.clone()), &mut inner_sub, path); + + // Sync back state to parent substitution map + sub.next_slot = inner_sub.next_slot; + sub.used.extend(inner_sub.used.iter().cloned()); + sub.assigned.extend(inner_sub.assigned.iter().cloned()); + sub.captured_slots.extend(inner_sub.captured_slots.iter().cloned()); + + Some(res) + } else { + None + } + } + + fn visit_node( + &self, + node_rc: Rc, + sub: &mut SubstitutionMap, + path: &mut PathTracker, + ) -> Rc { + let node = &*node_rc; + let folder = Folder::new(&self.globals); + let inliner = Inliner::new(&self.globals, &self.root_purity); + + let (new_kind, metrics) = match &node.kind { + BoundKind::Get { addr, name } => { + let addr = *addr; + if !sub.assigned.contains(&addr) { + // 1. Try inlining from current value substitution map (locals/globals/upvalues) + if let Some(val) = sub.get_value(&addr) + && inliner.is_inlinable_value(val, addr) + { + return Rc::new(folder.make_constant_node(val.clone(), node)); + } + + // 2. Try inlining from AST substitution map (pure expressions) + if let Some(inlined_node) = sub.ast_substitutions.get(&addr) { + return inlined_node.clone(); + } + + // 3. Fallback for Globals: check the actual VM environment + if let Address::Global(idx) = addr + && let Some(globals_rc) = &self.globals + { + let globals = globals_rc.borrow(); + if let Some(val) = globals.get(idx.0 as usize) + && inliner.is_inlinable_value(val, addr) + { + return Rc::new(folder.make_constant_node(val.clone(), node)); + } + } + } + + sub.used.insert(addr); + ( + BoundKind::Get { + addr: sub.map_address(addr), + name: name.clone(), + }, + node.ty.clone(), + ) + } + + BoundKind::FieldAccessor(k) => (BoundKind::FieldAccessor(*k), node.ty.clone()), + + BoundKind::GetField { rec, field } => { + let rec_opt = self.visit_node(rec.clone(), sub, path); + + // Constant folding for Field Access + if let BoundKind::Constant(Value::Record(layout, values)) = &rec_opt.kind + && let Some(idx) = layout.index_of(*field) + { + return Rc::new(folder.make_constant_node(values[idx].clone(), node)); + } + + if Rc::ptr_eq(&rec_opt, rec) { + return node_rc; + } + + ( + BoundKind::GetField { + rec: rec_opt, + field: *field, + }, + node.ty.clone(), + ) + } + + BoundKind::Set { addr, value } => { + let value_opt = self.visit_node(value.clone(), sub, path); + if let BoundKind::Constant(val) = &value_opt.kind { + sub.add_value(*addr, val.clone()); + } else { + sub.remove_value(addr); + } + + if Rc::ptr_eq(&value_opt, value) { + return node_rc; + } + + ( + BoundKind::Set { + addr: sub.map_address(*addr), + value: value_opt, + }, + node.ty.clone(), + ) + } + + BoundKind::Define { + name, + addr, + kind, + value, + captured_by, + } => { + let value_opt = self.visit_node(value.clone(), sub, path); + + if let Address::Local(slot) = addr + && !captured_by.is_empty() + { + sub.captured_slots.insert(*slot); + } + + if let BoundKind::Constant(val) = &value_opt.kind { + sub.add_value(*addr, val.clone()); + } else { + sub.remove_value(addr); + } + + if let Address::Global(global_index) = addr + && value_opt.ty.purity > Purity::Impure + && let Some(purity_rc) = &self.root_purity + { + let mut pr = purity_rc.borrow_mut(); + let idx = global_index.0 as usize; + if idx < pr.len() { + pr[idx] = value_opt.ty.purity; + } + } + + if Rc::ptr_eq(&value_opt, value) { + return node_rc; + } + + ( + BoundKind::Define { + name: name.clone(), + addr: sub.map_address(*addr), + kind: *kind, + value: value_opt, + captured_by: captured_by.clone(), + }, + node.ty.clone(), + ) + } + + BoundKind::Call { callee, args } => { + let callee_opt = self.visit_node(callee.clone(), sub, path); + let args_opt = self.visit_node(args.clone(), sub, path); + + if self.enabled { + // Optimized Field Access Transformation + if let BoundKind::FieldAccessor(k) = &callee_opt.kind + && let BoundKind::Tuple { elements } = &args_opt.kind + && elements.len() == 1 + { + let rec = elements[0].clone(); + let metrics = node.ty.clone(); + return Rc::new(Node { + identity: node.identity.clone(), + kind: BoundKind::GetField { + rec, + field: *k, + }, + ty: metrics, + }); + } + + let mut arg_nodes = Vec::new(); + self.flatten_tuple(args_opt.clone(), &mut arg_nodes); + + if let BoundKind::Lambda { + params, + body, + upvalues, + positional_count, + } = &callee_opt.kind + && upvalues.is_empty() + && positional_count.is_some() + && path.inlining_depth < 5 + && !callee_opt.ty.is_recursive + && path.enter_lambda(&callee_opt.identity) + { + path.inlining_depth += 1; + let collapsed = self.try_inline(params, &arg_nodes, body, sub, path, None); + path.inlining_depth -= 1; + path.exit_lambda(&callee_opt.identity); + + if let Some(res) = collapsed { + return res; + } + } + + if let BoundKind::Get { + addr: Address::Global(idx), + .. + } = &callee_opt.kind + && let Some(registry_rc) = &self.lambda_registry + && path.inlining_depth < 5 + && !path.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() + && !lambda_node.ty.is_recursive + { + path.inlining_stack.insert(*idx); + path.inlining_depth += 1; + let collapsed = self.try_inline(params, &arg_nodes, body, sub, path, None); + path.inlining_depth -= 1; + path.inlining_stack.remove(idx); + + if let Some(res) = collapsed { + return res; + } + } + } + + if let BoundKind::Constant(Value::Object(ref obj)) = callee_opt.kind + && path.inlining_depth < 5 + && let Some(closure) = obj.as_any().downcast_ref::() + && (closure.upvalues.is_empty() + || closure.function_node.ty.purity >= Purity::SideEffectFree) + && !closure.function_node.ty.is_recursive + && path.enter_lambda(&closure.function_node.identity) + { + let mut closure_sub = sub.new_for_inlining(); + for (i, cell) in closure.upvalues.iter().enumerate() { + closure_sub.add_value( + Address::Upvalue(UpvalueIdx(i as u32)), + cell.borrow().clone(), + ); + } + + path.inlining_depth += 1; + let collapsed = self.try_inline( + &closure.parameter_node, + &arg_nodes, + &closure.function_node, + sub, + path, + Some(closure_sub), + ); + path.inlining_depth -= 1; + path.exit_lambda(&closure.function_node.identity); + + if let Some(res) = collapsed { + return res; + } + } + + if let Some(folded) = folder.try_fold_pure(&callee_opt, &arg_nodes) { + return Rc::new(folded); + } + } + + if Rc::ptr_eq(&callee_opt, callee) && Rc::ptr_eq(&args_opt, args) { + return node_rc; + } + + ( + BoundKind::Call { + callee: callee_opt, + args: args_opt, + }, + node.ty.clone(), + ) + } + + BoundKind::Again { args } => { + let args_opt = self.visit_node(args.clone(), sub, path); + if Rc::ptr_eq(&args_opt, args) { + return node_rc; + } + ( + BoundKind::Again { + args: args_opt, + }, + node.ty.clone(), + ) + } + + BoundKind::If { + cond, + then_br, + else_br, + } => { + let cond_opt = self.visit_node(cond.clone(), sub, path); + if self.enabled + && let BoundKind::Constant(ref val) = cond_opt.kind + { + if val.is_truthy() { + return self.visit_node(then_br.clone(), sub, path); + } else if let Some(else_node) = else_br { + return self.visit_node(else_node.clone(), sub, path); + } else { + return Rc::new(folder.make_nop_node(node)); + } + } + + let then_br_opt = self.visit_node(then_br.clone(), sub, path); + let else_br_opt = else_br + .as_ref() + .map(|e| self.visit_node(e.clone(), sub, path)); + + let unchanged = Rc::ptr_eq(&cond_opt, cond) && Rc::ptr_eq(&then_br_opt, then_br) && match (&else_br_opt, else_br) { + (Some(a), Some(b)) => Rc::ptr_eq(a, b), + (None, None) => true, + _ => false, + }; + + if unchanged { + return node_rc; + } + + ( + BoundKind::If { + cond: cond_opt, + then_br: then_br_opt, + else_br: else_br_opt, + }, + node.ty.clone(), + ) + } + + BoundKind::Pipe { + inputs, + lambda, + out_type, + } => { + let mut o_inputs = Vec::with_capacity(inputs.len()); + let mut inputs_changed = false; + for input in inputs { + let opt = self.visit_node(input.clone(), sub, path); + if !Rc::ptr_eq(&opt, input) { + inputs_changed = true; + } + o_inputs.push(opt); + } + let o_lambda = self.visit_node(lambda.clone(), sub, path); + + if !inputs_changed && Rc::ptr_eq(&o_lambda, lambda) { + return node_rc; + } + + ( + BoundKind::Pipe { + inputs: o_inputs, + lambda: o_lambda, + out_type: out_type.clone(), + }, + node.ty.clone(), + ) + } + BoundKind::Block { exprs } => { + let mut info = UsageInfo::default(); + if !exprs.is_empty() { + for e in exprs { + info.collect(e); + } + } + + sub.assigned.extend(info.assigned.iter().cloned()); + + let mut new_exprs = Vec::with_capacity(exprs.len()); + let last_idx = exprs.len().saturating_sub(1); + let mut block_changed = false; + + for (i, e) in exprs.iter().enumerate() { + let is_last = i == last_idx; + if self.enabled && !is_last { + let removable = match &e.kind { + BoundKind::Define { addr, value, .. } => { + !info.is_used(addr) + && (if let Address::Local(slot) = addr { + !sub.captured_slots.contains(slot) + } else { + true + }) + && (value.ty.purity >= Purity::SideEffectFree + || matches!(value.kind, BoundKind::Lambda { .. })) + } + BoundKind::Set { addr, value, .. } => { + !info.is_used(addr) + && (if let Address::Local(slot) = addr { + !sub.captured_slots.contains(slot) + } else { + true + }) + && value.ty.purity >= Purity::SideEffectFree + } + _ => e.ty.purity >= Purity::SideEffectFree, + }; + + if removable { + block_changed = true; + continue; + } + } + + let opt = self.visit_node(e.clone(), sub, path); + + if let BoundKind::Define { addr, value, .. } = &opt.kind + && !info.assigned.contains(addr) + { + if let BoundKind::Constant(val) = &value.kind { + sub.add_value(*addr, val.clone()); + } else if let BoundKind::Lambda { upvalues, .. } = &value.kind + && upvalues.is_empty() + { + sub.add_ast_substitution(*addr, (**value).clone()); + } + } + + if self.enabled && matches!(opt.kind, BoundKind::Nop) && !is_last { + block_changed = true; + continue; + } + if !Rc::ptr_eq(&opt, e) { + block_changed = true; + } + new_exprs.push(opt); + } + + if !block_changed { + return node_rc; + } + + if self.enabled { + if new_exprs.is_empty() { + return Rc::new(folder.make_nop_node(node)); + } else if new_exprs.len() == 1 { + return new_exprs.pop().unwrap(); + } + } + + (BoundKind::Block { exprs: new_exprs }, node.ty.clone()) + } + + BoundKind::Lambda { + params, + upvalues, + body, + positional_count, + } => { + let mut info = UsageInfo::default(); + info.collect(node); + + let mut new_upvalues = Vec::new(); + let mut mapping = Vec::new(); + let mut next_inner_subs = sub.new_inner(); + next_inner_subs.assigned = info.assigned; + let mut upvalues_changed = false; + + for (old_idx, capture_addr) in upvalues.iter().enumerate() { + let mut inlined_val = None; + if !sub.assigned.contains(capture_addr) + && let Some(val) = sub.get_value(capture_addr) + { + inlined_val = Some(val.clone()); + } + + if let Address::Local(slot) = capture_addr { + sub.captured_slots.insert(*slot); + } + + if let Some(val) = inlined_val { + next_inner_subs + .add_value(Address::Upvalue(UpvalueIdx(old_idx as u32)), val); + mapping.push(None); + upvalues_changed = true; + } else { + mapping.push(Some(new_upvalues.len() as u32)); + let mapped = sub.map_address(*capture_addr); + if mapped != *capture_addr { + upvalues_changed = true; + } + new_upvalues.push(mapped); + } + } + + let params_opt = self.visit_node(params.clone(), &mut next_inner_subs, path); + inliner.collect_parameter_slots(¶ms_opt, &mut next_inner_subs); + + let body_opt = self.visit_node(body.clone(), &mut next_inner_subs, path); + let reindexed_body = if new_upvalues.len() != upvalues.len() { + sub.reindex_upvalues(body_opt, &mapping) + } else { + body_opt + }; + + if !upvalues_changed && Rc::ptr_eq(¶ms_opt, params) && Rc::ptr_eq(&reindexed_body, body) { + return node_rc; + } + + ( + BoundKind::Lambda { + params: params_opt, + upvalues: new_upvalues, + body: reindexed_body, + positional_count: *positional_count, + }, + node.ty.clone(), + ) + } + + BoundKind::Destructure { pattern, value } => { + let val_opt = self.visit_node(value.clone(), sub, path); + let pat_opt = self.visit_node(pattern.clone(), sub, path); + + let mut info = UsageInfo::default(); + info.collect_pattern(&pat_opt); + for addr in info.assigned { + sub.remove_value(&addr); + } + + if Rc::ptr_eq(&val_opt, value) && Rc::ptr_eq(&pat_opt, pattern) { + return node_rc; + } + + ( + BoundKind::Destructure { + pattern: pat_opt, + value: val_opt, + }, + node.ty.clone(), + ) + } + + BoundKind::Tuple { elements } => { + let mut new_elements = Vec::with_capacity(elements.len()); + let mut changed = false; + for e in elements { + let opt = self.visit_node(e.clone(), sub, path); + if !Rc::ptr_eq(&opt, e) { + changed = true; + } + new_elements.push(opt); + } + if !changed { + return node_rc; + } + (BoundKind::Tuple { elements: new_elements }, node.ty.clone()) + } + BoundKind::Record { layout, values } => { + let mut mapped_values = Vec::with_capacity(values.len()); + let mut changed = false; + for v in values { + let opt = self.visit_node(v.clone(), sub, path); + if !Rc::ptr_eq(&opt, v) { + changed = true; + } + mapped_values.push(opt); + } + + if self.enabled + && let Some(folded) = folder.try_fold_record(layout, &mapped_values, node) + { + return Rc::new(folded); + } + + if !changed { + return node_rc; + } + + ( + BoundKind::Record { + layout: layout.clone(), + values: mapped_values, + }, + node.ty.clone(), + ) + } + BoundKind::Expansion { + original_call, + bound_expanded, + } => { + path.inlining_depth += 1; + let expanded_opt = self.visit_node(bound_expanded.clone(), sub, path); + path.inlining_depth -= 1; + + if Rc::ptr_eq(&expanded_opt, bound_expanded) { + return node_rc; + } + + ( + BoundKind::Expansion { + original_call: original_call.clone(), + bound_expanded: expanded_opt, + }, + node.ty.clone(), + ) + } + k => (k.clone(), node.ty.clone()), + }; + + Rc::new(Node { + identity: node.identity.clone(), + kind: new_kind, + ty: metrics, + }) + } + + fn flatten_tuple(&self, node: Rc, into: &mut Vec>) { + match &node.kind { + BoundKind::Tuple { elements } => { + for el in elements { + self.flatten_tuple(el.clone(), into); + } + } + BoundKind::Nop => {} + _ => into.push(node), + } + } +} diff --git a/src/ast/compiler/optimizer/folder.rs b/src/ast/compiler/optimizer/folder.rs index 52d7f65..1aef610 100644 --- a/src/ast/compiler/optimizer/folder.rs +++ b/src/ast/compiler/optimizer/folder.rs @@ -1,101 +1,101 @@ -use crate::ast::compiler::bound_nodes::{Address, AnalyzedNode, BoundKind, Node, NodeMetrics}; -use crate::ast::types::{Purity, RecordLayout, StaticType, Value}; -use std::cell::RefCell; -use std::rc::Rc; - -pub struct Folder<'a> { - pub globals: &'a Option>>>, -} - -impl<'a> Folder<'a> { - pub fn new(globals: &'a Option>>>) -> Self { - Self { globals } - } - - pub fn make_constant_node(&self, val: Value, template: &AnalyzedNode) -> AnalyzedNode { - let ty = val.static_type(); - let typed_original = Rc::new(Node { - identity: template.identity.clone(), - kind: BoundKind::Constant(val.clone()), - ty: ty.clone(), - }); - Node { - identity: template.identity.clone(), - kind: BoundKind::Constant(val), - ty: NodeMetrics { - original: typed_original, - purity: Purity::Pure, - is_recursive: false, - }, - } - } - - pub fn make_nop_node(&self, template: &AnalyzedNode) -> AnalyzedNode { - let typed_original = Rc::new(Node { - identity: template.identity.clone(), - kind: BoundKind::Nop, - ty: StaticType::Void, - }); - Node { - identity: template.identity.clone(), - kind: BoundKind::Nop, - ty: NodeMetrics { - original: typed_original, - purity: Purity::Pure, - is_recursive: false, - }, - } - } - - pub fn try_fold_record( - &self, - layout: &std::sync::Arc, - values: &[Rc], - template: &AnalyzedNode, - ) -> Option { - let mut constant_values = Vec::with_capacity(values.len()); - - for v_node in values { - if let BoundKind::Constant(val) = &v_node.kind { - constant_values.push(val.clone()); - } else { - return None; - } - } - - let record_val = Value::Record(layout.clone(), Rc::new(constant_values)); - Some(self.make_constant_node(record_val, template)) - } - - pub fn try_fold_pure( - &self, - callee: &AnalyzedNode, - arg_nodes: &[Rc], - ) -> Option { - if callee.ty.purity < Purity::Pure { - return None; - } - - 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.clone()); - } else { - return None; - } - } - let func_val = match &callee.kind { - BoundKind::Get { - addr: Address::Global(idx), - .. - } => self.globals.as_ref()?.borrow().get(idx.0 as usize)?.clone(), - BoundKind::Constant(val) => val.clone(), - _ => return None, - }; - let result = match func_val { - Value::Function(f) => (f.func)(&arg_values), - _ => return None, - }; - Some(self.make_constant_node(result, callee)) - } -} +use crate::ast::compiler::bound_nodes::{Address, AnalyzedNode, BoundKind, Node, NodeMetrics}; +use crate::ast::types::{Purity, RecordLayout, StaticType, Value}; +use std::cell::RefCell; +use std::rc::Rc; + +pub struct Folder<'a> { + pub globals: &'a Option>>>, +} + +impl<'a> Folder<'a> { + pub fn new(globals: &'a Option>>>) -> Self { + Self { globals } + } + + pub fn make_constant_node(&self, val: Value, template: &AnalyzedNode) -> AnalyzedNode { + let ty = val.static_type(); + let typed_original = Rc::new(Node { + identity: template.identity.clone(), + kind: BoundKind::Constant(val.clone()), + ty: ty.clone(), + }); + Node { + identity: template.identity.clone(), + kind: BoundKind::Constant(val), + ty: NodeMetrics { + original: typed_original, + purity: Purity::Pure, + is_recursive: false, + }, + } + } + + pub fn make_nop_node(&self, template: &AnalyzedNode) -> AnalyzedNode { + let typed_original = Rc::new(Node { + identity: template.identity.clone(), + kind: BoundKind::Nop, + ty: StaticType::Void, + }); + Node { + identity: template.identity.clone(), + kind: BoundKind::Nop, + ty: NodeMetrics { + original: typed_original, + purity: Purity::Pure, + is_recursive: false, + }, + } + } + + pub fn try_fold_record( + &self, + layout: &std::sync::Arc, + values: &[Rc], + template: &AnalyzedNode, + ) -> Option { + let mut constant_values = Vec::with_capacity(values.len()); + + for v_node in values { + if let BoundKind::Constant(val) = &v_node.kind { + constant_values.push(val.clone()); + } else { + return None; + } + } + + let record_val = Value::Record(layout.clone(), Rc::new(constant_values)); + Some(self.make_constant_node(record_val, template)) + } + + pub fn try_fold_pure( + &self, + callee: &AnalyzedNode, + arg_nodes: &[Rc], + ) -> Option { + if callee.ty.purity < Purity::Pure { + return None; + } + + 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.clone()); + } else { + return None; + } + } + let func_val = match &callee.kind { + BoundKind::Get { + addr: Address::Global(idx), + .. + } => self.globals.as_ref()?.borrow().get(idx.0 as usize)?.clone(), + BoundKind::Constant(val) => val.clone(), + _ => return None, + }; + let result = match func_val { + Value::Function(f) => (f.func)(&arg_values), + _ => return None, + }; + Some(self.make_constant_node(result, callee)) + } +} diff --git a/src/ast/compiler/optimizer/substitution_map.rs b/src/ast/compiler/optimizer/substitution_map.rs index 8ee9a90..15c7aa7 100644 --- a/src/ast/compiler/optimizer/substitution_map.rs +++ b/src/ast/compiler/optimizer/substitution_map.rs @@ -1,210 +1,210 @@ -use crate::ast::compiler::bound_nodes::{Address, AnalyzedNode, BoundKind, LocalSlot, Node, UpvalueIdx}; -use crate::ast::types::Value; -use std::collections::{HashMap, HashSet}; -use std::rc::Rc; - -#[derive(Default)] -pub struct SubstitutionMap { - pub values: HashMap, - pub ast_substitutions: HashMap>, - pub slot_mapping: HashMap, - pub assigned: HashSet
, - pub next_slot: u32, - pub used: HashSet
, - pub captured_slots: HashSet, -} - -impl SubstitutionMap { - pub fn new() -> Self { - Self::default() - } - - /// Creates a new SubstitutionMap for an inner scope (like an inlined Lambda). - /// Safely inherits only Global substitutions, because Local and Upvalue - /// addresses are relative to the specific function frame and would overlap. - pub fn new_inner(&self) -> Self { - let mut inner = Self::new(); - for (k, v) in &self.values { - if matches!(k, Address::Global(_)) { - inner.values.insert(*k, v.clone()); - } - } - for (k, v) in &self.ast_substitutions { - if matches!(k, Address::Global(_)) { - inner.ast_substitutions.insert(*k, v.clone()); - } - } - inner - } - - pub fn new_for_inlining(&self) -> Self { - let mut inner = self.new_inner(); - inner.next_slot = self.next_slot; - inner - } - - pub fn add_ast_substitution(&mut self, addr: Address, node: AnalyzedNode) { - self.ast_substitutions.insert(addr, Rc::new(node)); - } - - pub fn map_slot(&mut self, old_slot: LocalSlot) -> LocalSlot { - if let Some(&new_slot) = self.slot_mapping.get(&old_slot) { - return new_slot; - } - let new_slot = LocalSlot(self.next_slot); - self.slot_mapping.insert(old_slot, new_slot); - self.next_slot += 1; - new_slot - } - - pub fn map_address(&mut self, addr: Address) -> Address { - match addr { - Address::Local(slot) => Address::Local(self.map_slot(slot)), - other => other, - } - } - - pub fn add_value(&mut self, addr: Address, val: Value) { - self.values.insert(addr, val); - } - - pub fn get_value(&self, addr: &Address) -> Option<&Value> { - self.values.get(addr) - } - - pub fn remove_value(&mut self, addr: &Address) { - self.values.remove(addr); - } - - fn reindex_addr(&self, addr: Address, mapping: &[Option]) -> Address { - if let Address::Upvalue(idx) = addr - && let Some(res) = mapping.get(idx.0 as usize) - && let Some(new_idx) = res - { - Address::Upvalue(UpvalueIdx(*new_idx)) - } else { - addr - } - } - - pub fn reindex_upvalues(&self, node_rc: Rc, mapping: &[Option]) -> Rc { - let node = &*node_rc; - let (new_kind, metrics) = match &node.kind { - BoundKind::Get { addr, name } => ( - BoundKind::Get { - addr: self.reindex_addr(*addr, mapping), - name: name.clone(), - }, - node.ty.clone(), - ), - BoundKind::Lambda { - params, - upvalues, - body, - positional_count, - } => { - let mut next_upvalues = Vec::new(); - for addr in upvalues { - next_upvalues.push(self.reindex_addr(*addr, mapping)); - } - ( - BoundKind::Lambda { - params: params.clone(), - upvalues: next_upvalues, - body: body.clone(), - positional_count: *positional_count, - }, - node.ty.clone(), - ) - } - BoundKind::If { - cond, - then_br, - else_br, - } => { - let cond = self.reindex_upvalues(cond.clone(), mapping); - let then_br = self.reindex_upvalues(then_br.clone(), mapping); - let else_br = else_br.as_ref().map(|e| self.reindex_upvalues(e.clone(), mapping)); - ( - BoundKind::If { - cond, - then_br, - else_br, - }, - node.ty.clone(), - ) - } - BoundKind::Block { exprs } => { - let exprs = exprs - .iter() - .map(|e| self.reindex_upvalues(e.clone(), mapping)) - .collect(); - (BoundKind::Block { exprs }, node.ty.clone()) - } - BoundKind::Call { callee, args } => { - let callee = self.reindex_upvalues(callee.clone(), mapping); - let args = self.reindex_upvalues(args.clone(), mapping); - (BoundKind::Call { callee, args }, node.ty.clone()) - } - BoundKind::Define { - name, - addr, - kind, - value, - captured_by, - } => { - let value = self.reindex_upvalues(value.clone(), mapping); - ( - BoundKind::Define { - name: name.clone(), - addr: *addr, - kind: *kind, - value, - captured_by: captured_by.clone(), - }, - node.ty.clone(), - ) - } - BoundKind::Set { addr, value } => { - let value = self.reindex_upvalues(value.clone(), mapping); - ( - BoundKind::Set { - addr: self.reindex_addr(*addr, mapping), - value, - }, - node.ty.clone(), - ) - } - BoundKind::Tuple { elements } => { - let elements = elements - .iter() - .map(|e| self.reindex_upvalues(e.clone(), mapping)) - .collect(); - (BoundKind::Tuple { elements }, node.ty.clone()) - } - BoundKind::Record { layout, values } => { - let values = values - .iter() - .map(|v| self.reindex_upvalues(v.clone(), mapping)) - .collect(); - (BoundKind::Record { layout: layout.clone(), values }, node.ty.clone()) - } - BoundKind::Expansion { original_call, bound_expanded } => { - let bound_expanded = self.reindex_upvalues(bound_expanded.clone(), mapping); - ( - BoundKind::Expansion { - original_call: original_call.clone(), - bound_expanded, - }, - node.ty.clone(), - ) - } - k => (k.clone(), node.ty.clone()), - }; - Rc::new(Node { - identity: node.identity.clone(), - kind: new_kind, - ty: metrics, - }) - } -} +use crate::ast::compiler::bound_nodes::{Address, AnalyzedNode, BoundKind, LocalSlot, Node, UpvalueIdx}; +use crate::ast::types::Value; +use std::collections::{HashMap, HashSet}; +use std::rc::Rc; + +#[derive(Default)] +pub struct SubstitutionMap { + pub values: HashMap, + pub ast_substitutions: HashMap>, + pub slot_mapping: HashMap, + pub assigned: HashSet
, + pub next_slot: u32, + pub used: HashSet
, + pub captured_slots: HashSet, +} + +impl SubstitutionMap { + pub fn new() -> Self { + Self::default() + } + + /// Creates a new SubstitutionMap for an inner scope (like an inlined Lambda). + /// Safely inherits only Global substitutions, because Local and Upvalue + /// addresses are relative to the specific function frame and would overlap. + pub fn new_inner(&self) -> Self { + let mut inner = Self::new(); + for (k, v) in &self.values { + if matches!(k, Address::Global(_)) { + inner.values.insert(*k, v.clone()); + } + } + for (k, v) in &self.ast_substitutions { + if matches!(k, Address::Global(_)) { + inner.ast_substitutions.insert(*k, v.clone()); + } + } + inner + } + + pub fn new_for_inlining(&self) -> Self { + let mut inner = self.new_inner(); + inner.next_slot = self.next_slot; + inner + } + + pub fn add_ast_substitution(&mut self, addr: Address, node: AnalyzedNode) { + self.ast_substitutions.insert(addr, Rc::new(node)); + } + + pub fn map_slot(&mut self, old_slot: LocalSlot) -> LocalSlot { + if let Some(&new_slot) = self.slot_mapping.get(&old_slot) { + return new_slot; + } + let new_slot = LocalSlot(self.next_slot); + self.slot_mapping.insert(old_slot, new_slot); + self.next_slot += 1; + new_slot + } + + pub fn map_address(&mut self, addr: Address) -> Address { + match addr { + Address::Local(slot) => Address::Local(self.map_slot(slot)), + other => other, + } + } + + pub fn add_value(&mut self, addr: Address, val: Value) { + self.values.insert(addr, val); + } + + pub fn get_value(&self, addr: &Address) -> Option<&Value> { + self.values.get(addr) + } + + pub fn remove_value(&mut self, addr: &Address) { + self.values.remove(addr); + } + + fn reindex_addr(&self, addr: Address, mapping: &[Option]) -> Address { + if let Address::Upvalue(idx) = addr + && let Some(res) = mapping.get(idx.0 as usize) + && let Some(new_idx) = res + { + Address::Upvalue(UpvalueIdx(*new_idx)) + } else { + addr + } + } + + pub fn reindex_upvalues(&self, node_rc: Rc, mapping: &[Option]) -> Rc { + let node = &*node_rc; + let (new_kind, metrics) = match &node.kind { + BoundKind::Get { addr, name } => ( + BoundKind::Get { + addr: self.reindex_addr(*addr, mapping), + name: name.clone(), + }, + node.ty.clone(), + ), + BoundKind::Lambda { + params, + upvalues, + body, + positional_count, + } => { + let mut next_upvalues = Vec::new(); + for addr in upvalues { + next_upvalues.push(self.reindex_addr(*addr, mapping)); + } + ( + BoundKind::Lambda { + params: params.clone(), + upvalues: next_upvalues, + body: body.clone(), + positional_count: *positional_count, + }, + node.ty.clone(), + ) + } + BoundKind::If { + cond, + then_br, + else_br, + } => { + let cond = self.reindex_upvalues(cond.clone(), mapping); + let then_br = self.reindex_upvalues(then_br.clone(), mapping); + let else_br = else_br.as_ref().map(|e| self.reindex_upvalues(e.clone(), mapping)); + ( + BoundKind::If { + cond, + then_br, + else_br, + }, + node.ty.clone(), + ) + } + BoundKind::Block { exprs } => { + let exprs = exprs + .iter() + .map(|e| self.reindex_upvalues(e.clone(), mapping)) + .collect(); + (BoundKind::Block { exprs }, node.ty.clone()) + } + BoundKind::Call { callee, args } => { + let callee = self.reindex_upvalues(callee.clone(), mapping); + let args = self.reindex_upvalues(args.clone(), mapping); + (BoundKind::Call { callee, args }, node.ty.clone()) + } + BoundKind::Define { + name, + addr, + kind, + value, + captured_by, + } => { + let value = self.reindex_upvalues(value.clone(), mapping); + ( + BoundKind::Define { + name: name.clone(), + addr: *addr, + kind: *kind, + value, + captured_by: captured_by.clone(), + }, + node.ty.clone(), + ) + } + BoundKind::Set { addr, value } => { + let value = self.reindex_upvalues(value.clone(), mapping); + ( + BoundKind::Set { + addr: self.reindex_addr(*addr, mapping), + value, + }, + node.ty.clone(), + ) + } + BoundKind::Tuple { elements } => { + let elements = elements + .iter() + .map(|e| self.reindex_upvalues(e.clone(), mapping)) + .collect(); + (BoundKind::Tuple { elements }, node.ty.clone()) + } + BoundKind::Record { layout, values } => { + let values = values + .iter() + .map(|v| self.reindex_upvalues(v.clone(), mapping)) + .collect(); + (BoundKind::Record { layout: layout.clone(), values }, node.ty.clone()) + } + BoundKind::Expansion { original_call, bound_expanded } => { + let bound_expanded = self.reindex_upvalues(bound_expanded.clone(), mapping); + ( + BoundKind::Expansion { + original_call: original_call.clone(), + bound_expanded, + }, + node.ty.clone(), + ) + } + k => (k.clone(), node.ty.clone()), + }; + Rc::new(Node { + identity: node.identity.clone(), + kind: new_kind, + ty: metrics, + }) + } +} diff --git a/src/ast/compiler/optimizer/utils.rs b/src/ast/compiler/optimizer/utils.rs index d5db130..2790a67 100644 --- a/src/ast/compiler/optimizer/utils.rs +++ b/src/ast/compiler/optimizer/utils.rs @@ -1,183 +1,183 @@ -use crate::ast::compiler::bound_nodes::{Address, AnalyzedNode, BoundKind, GlobalIdx}; -use crate::ast::types::{Identity, Value}; -use crate::ast::vm::Closure; -use std::collections::HashSet; - -// --- PathTracker --- -#[derive(Default)] -pub struct PathTracker { - pub inlining_depth: usize, - pub inlining_stack: HashSet, - pub identity_stack: HashSet, -} - -impl PathTracker { - pub fn new() -> Self { - Self::default() - } - - pub fn enter_lambda(&mut self, identity: &Identity) -> bool { - if self.identity_stack.contains(identity) { - return false; - } - self.identity_stack.insert(identity.clone()); - true - } - - pub fn exit_lambda(&mut self, identity: &Identity) { - self.identity_stack.remove(identity); - } -} - -// --- UsageInfo --- -#[derive(Default)] -pub struct UsageInfo { - pub used: HashSet
, - pub assigned: HashSet
, - pub used_identities: HashSet, -} - -impl UsageInfo { - pub fn is_assigned(&self, addr: &Address) -> bool { - self.assigned.contains(addr) - } - - pub fn is_used(&self, addr: &Address) -> bool { - self.used.contains(addr) - } - - pub fn collect(&mut self, node: &AnalyzedNode) { - match &node.kind { - BoundKind::Destructure { pattern, value } => { - self.collect_pattern(pattern); - self.collect(value); - } - BoundKind::Constant(v) => { - if let Value::Object(obj) = v - && let Some(closure) = obj.as_any().downcast_ref::() - { - self.used_identities - .insert(closure.function_node.identity.clone()); - self.collect(&closure.function_node); - } - } - BoundKind::Get { addr, .. } => { - self.used.insert(*addr); - } - BoundKind::Set { addr, value } => { - self.assigned.insert(*addr); - self.collect(value); - } - BoundKind::GetField { rec, .. } => { - self.collect(rec); - } - BoundKind::Lambda { - params, - body, - upvalues, - .. - } => { - self.used_identities.insert(node.identity.clone()); - - let mut inner_info = UsageInfo::default(); - inner_info.collect(params); - inner_info.collect(body); - - // Propagate globals and identities - for addr in &inner_info.used { - if let Address::Global(_) = addr { - self.used.insert(*addr); - } - } - for addr in &inner_info.assigned { - if let Address::Global(_) = addr { - self.assigned.insert(*addr); - } - } - self.used_identities.extend(inner_info.used_identities); - - // Map used upvalues to parent scope - for addr in &inner_info.used { - if let Address::Upvalue(idx) = addr - && let Some(parent_addr) = upvalues.get(idx.0 as usize) - { - self.used.insert(*parent_addr); - } - } - // Map assigned upvalues to parent scope - for addr in &inner_info.assigned { - if let Address::Upvalue(idx) = addr - && let Some(parent_addr) = upvalues.get(idx.0 as usize) - { - self.assigned.insert(*parent_addr); - } - } - } - BoundKind::Block { exprs } => { - for e in exprs { - self.collect(e); - } - } - BoundKind::If { - cond, - then_br, - else_br, - } => { - self.collect(cond); - self.collect(then_br); - if let Some(e) = else_br { - self.collect(e); - } - } - BoundKind::Call { callee, args } => { - self.collect(callee); - self.collect(args); - } - BoundKind::Tuple { elements } => { - for e in elements { - self.collect(e); - } - } - BoundKind::Record { values, .. } => { - for v in values { - self.collect(v); - } - } - BoundKind::Define { addr, value, .. } => { - self.assigned.insert(*addr); - self.collect(value); - } - BoundKind::Expansion { bound_expanded, .. } => { - self.collect(bound_expanded); - } - BoundKind::Again { args } => { - self.collect(args); - } - BoundKind::Pipe { inputs, lambda, .. } => { - for input in inputs { - self.collect(input); - } - self.collect(lambda); - } - BoundKind::Nop - | BoundKind::FieldAccessor(_) - | BoundKind::Extension(_) - | BoundKind::Error => {} - } - } - - pub fn collect_pattern(&mut self, node: &AnalyzedNode) { - match &node.kind { - BoundKind::Define { .. } => {} - BoundKind::Set { addr, .. } => { - self.assigned.insert(*addr); - } - BoundKind::Tuple { elements } => { - for el in elements { - self.collect_pattern(el); - } - } - _ => {} - } - } -} +use crate::ast::compiler::bound_nodes::{Address, AnalyzedNode, BoundKind, GlobalIdx}; +use crate::ast::types::{Identity, Value}; +use crate::ast::vm::Closure; +use std::collections::HashSet; + +// --- PathTracker --- +#[derive(Default)] +pub struct PathTracker { + pub inlining_depth: usize, + pub inlining_stack: HashSet, + pub identity_stack: HashSet, +} + +impl PathTracker { + pub fn new() -> Self { + Self::default() + } + + pub fn enter_lambda(&mut self, identity: &Identity) -> bool { + if self.identity_stack.contains(identity) { + return false; + } + self.identity_stack.insert(identity.clone()); + true + } + + pub fn exit_lambda(&mut self, identity: &Identity) { + self.identity_stack.remove(identity); + } +} + +// --- UsageInfo --- +#[derive(Default)] +pub struct UsageInfo { + pub used: HashSet
, + pub assigned: HashSet
, + pub used_identities: HashSet, +} + +impl UsageInfo { + pub fn is_assigned(&self, addr: &Address) -> bool { + self.assigned.contains(addr) + } + + pub fn is_used(&self, addr: &Address) -> bool { + self.used.contains(addr) + } + + pub fn collect(&mut self, node: &AnalyzedNode) { + match &node.kind { + BoundKind::Destructure { pattern, value } => { + self.collect_pattern(pattern); + self.collect(value); + } + BoundKind::Constant(v) => { + if let Value::Object(obj) = v + && let Some(closure) = obj.as_any().downcast_ref::() + { + self.used_identities + .insert(closure.function_node.identity.clone()); + self.collect(&closure.function_node); + } + } + BoundKind::Get { addr, .. } => { + self.used.insert(*addr); + } + BoundKind::Set { addr, value } => { + self.assigned.insert(*addr); + self.collect(value); + } + BoundKind::GetField { rec, .. } => { + self.collect(rec); + } + BoundKind::Lambda { + params, + body, + upvalues, + .. + } => { + self.used_identities.insert(node.identity.clone()); + + let mut inner_info = UsageInfo::default(); + inner_info.collect(params); + inner_info.collect(body); + + // Propagate globals and identities + for addr in &inner_info.used { + if let Address::Global(_) = addr { + self.used.insert(*addr); + } + } + for addr in &inner_info.assigned { + if let Address::Global(_) = addr { + self.assigned.insert(*addr); + } + } + self.used_identities.extend(inner_info.used_identities); + + // Map used upvalues to parent scope + for addr in &inner_info.used { + if let Address::Upvalue(idx) = addr + && let Some(parent_addr) = upvalues.get(idx.0 as usize) + { + self.used.insert(*parent_addr); + } + } + // Map assigned upvalues to parent scope + for addr in &inner_info.assigned { + if let Address::Upvalue(idx) = addr + && let Some(parent_addr) = upvalues.get(idx.0 as usize) + { + self.assigned.insert(*parent_addr); + } + } + } + BoundKind::Block { exprs } => { + for e in exprs { + self.collect(e); + } + } + BoundKind::If { + cond, + then_br, + else_br, + } => { + self.collect(cond); + self.collect(then_br); + if let Some(e) = else_br { + self.collect(e); + } + } + BoundKind::Call { callee, args } => { + self.collect(callee); + self.collect(args); + } + BoundKind::Tuple { elements } => { + for e in elements { + self.collect(e); + } + } + BoundKind::Record { values, .. } => { + for v in values { + self.collect(v); + } + } + BoundKind::Define { addr, value, .. } => { + self.assigned.insert(*addr); + self.collect(value); + } + BoundKind::Expansion { bound_expanded, .. } => { + self.collect(bound_expanded); + } + BoundKind::Again { args } => { + self.collect(args); + } + BoundKind::Pipe { inputs, lambda, .. } => { + for input in inputs { + self.collect(input); + } + self.collect(lambda); + } + BoundKind::Nop + | BoundKind::FieldAccessor(_) + | BoundKind::Extension(_) + | BoundKind::Error => {} + } + } + + pub fn collect_pattern(&mut self, node: &AnalyzedNode) { + match &node.kind { + BoundKind::Define { .. } => {} + BoundKind::Set { addr, .. } => { + self.assigned.insert(*addr); + } + BoundKind::Tuple { elements } => { + for el in elements { + self.collect_pattern(el); + } + } + _ => {} + } + } +} diff --git a/src/ast/compiler/specializer.rs b/src/ast/compiler/specializer.rs index 84e2410..20f2a2a 100644 --- a/src/ast/compiler/specializer.rs +++ b/src/ast/compiler/specializer.rs @@ -1,276 +1,276 @@ -use crate::ast::compiler::bound_nodes::{Address, AnalyzedNode, BoundKind, Node, NodeMetrics}; -use crate::ast::types::{Purity, 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, &[StaticType]) -> Result<(Value, StaticType), String>>; -pub type RtlLookupFunc = Rc Option<(Value, StaticType)>>; - -pub trait FunctionRegistry { - fn resolve(&self, addr: Address) -> Option>; - fn resolve_analyzed(&self, _addr: Address) -> Option> { - None - } -} - -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: AnalyzedNode) -> AnalyzedNode { - self.visit_node(node) - } - - fn visit_node(&self, node: AnalyzedNode) -> AnalyzedNode { - let (new_kind, metrics) = match node.kind { - BoundKind::Call { callee, args } => { - let (new_callee, new_args, _ret_ty) = - self.specialize_call_logic(callee, args, node.ty.original.ty.clone()); - - let new_metrics = node.ty.clone(); - ( - BoundKind::Call { - callee: Rc::new(new_callee), - args: Rc::new(new_args), - }, - new_metrics, - ) - } - - BoundKind::If { - cond, - then_br, - else_br, - } => { - let cond = Rc::new(self.visit_node(cond.as_ref().clone())); - let then_br = Rc::new(self.visit_node(then_br.as_ref().clone())); - let else_br = else_br.map(|e| Rc::new(self.visit_node(e.as_ref().clone()))); - ( - BoundKind::If { - cond, - then_br, - else_br, - }, - node.ty.clone(), - ) - } - BoundKind::Block { exprs } => { - let exprs = exprs.into_iter().map(|e| Rc::new(self.visit_node(e.as_ref().clone()))).collect(); - (BoundKind::Block { exprs }, node.ty.clone()) - } - 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.as_ref().clone())); - ( - BoundKind::Lambda { - params, - upvalues, - body, - positional_count, - }, - node.ty.clone(), - ) - } - BoundKind::Define { - name, - addr, - kind, - value, - captured_by, - } => { - let value = Rc::new(self.visit_node(value.as_ref().clone())); - ( - BoundKind::Define { - name: name.clone(), - addr, - kind, - value, - captured_by: captured_by.clone(), - }, - node.ty.clone(), - ) - } - BoundKind::Set { addr, value } => { - let value = Rc::new(self.visit_node(value.as_ref().clone())); - (BoundKind::Set { addr, value }, node.ty.clone()) - } - BoundKind::Tuple { elements } => { - let elements = elements.into_iter().map(|e| Rc::new(self.visit_node(e.as_ref().clone()))).collect(); - (BoundKind::Tuple { elements }, node.ty.clone()) - } - BoundKind::Record { layout, values } => { - let values = values.into_iter().map(|v| Rc::new(self.visit_node(v.as_ref().clone()))).collect(); - (BoundKind::Record { layout, values }, node.ty.clone()) - } - BoundKind::Expansion { - original_call, - bound_expanded, - } => { - let bound_expanded = Rc::new(self.visit_node(bound_expanded.as_ref().clone())); - ( - BoundKind::Expansion { - original_call, - bound_expanded, - }, - node.ty.clone(), - ) - } - k => (k, node.ty.clone()), - }; - - Node { - identity: node.identity, - kind: new_kind, - ty: metrics, - } - } - - fn specialize_call_logic( - &self, - callee: Rc, - args: Rc, - original_ty: StaticType, - ) -> (AnalyzedNode, AnalyzedNode, StaticType) { - let new_callee = self.visit_node(callee.as_ref().clone()); - let new_args = self.visit_node(args.as_ref().clone()); - - let address = if let BoundKind::Get { addr, .. } = &new_callee.kind { - *addr - } else { - return (new_callee, new_args, original_ty); - }; - - let arg_types: Vec = - if let StaticType::Tuple(elements) = &new_args.ty.original.ty { - elements.clone() - } else { - vec![new_args.ty.original.ty.clone()] - }; - - if arg_types.iter().any(|t| matches!(t, StaticType::Any)) { - return (new_callee, new_args, original_ty); - } - - let key = MonoCacheKey { - address, - arg_types: arg_types.clone(), - }; - - if let Some((val, ret_ty)) = self.cache.borrow().get(&key) { - let specialized_callee = self.make_constant_node( - val.clone(), - StaticType::Function(Box::new(Signature { - params: StaticType::Tuple(arg_types), - ret: ret_ty.clone(), - })), - &new_callee, - ); - return (specialized_callee, new_args, ret_ty.clone()); - } - - 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) - { - self.cache - .borrow_mut() - .insert(key.clone(), (val.clone(), ret_ty.clone())); - let specialized_callee = self.make_constant_node( - val.clone(), - StaticType::Function(Box::new(Signature { - params: StaticType::Tuple(arg_types), - ret: ret_ty.clone(), - })), - &new_callee, - ); - return (specialized_callee, new_args, ret_ty); - } - - if let Some(registry) = &self.registry - && let Some(func_node) = registry.resolve_analyzed(address) - && func_node.ty.is_recursive - { - return (new_callee, new_args, original_ty); - } - - if let Some(compiler) = &self.compiler - && let Some(func_node) = self.registry.as_ref().and_then(|r| r.resolve(address)) - && let Ok((compiled_val, ret_ty)) = compiler(func_node, &arg_types) - { - self.cache - .borrow_mut() - .insert(key, (compiled_val.clone(), ret_ty.clone())); - - // Only replace the callee if the compiled value is actually a function/object. - // If it's a scalar (like 30 from folding), we DON'T fold here. - // We keep the Call but update the callee to the specialized version if it's an object. - if let Value::Object(_) | Value::Function(_) = &compiled_val { - let specialized_callee = self.make_constant_node( - compiled_val, - StaticType::Function(Box::new(Signature { - params: StaticType::Tuple(arg_types), - ret: ret_ty.clone(), - })), - &new_callee, - ); - return (specialized_callee, new_args, ret_ty); - } - } - - (new_callee, new_args, original_ty) - } - - fn make_constant_node( - &self, - val: Value, - ty: StaticType, - template: &AnalyzedNode, - ) -> AnalyzedNode { - let typed_original = Rc::new(Node { - identity: template.identity.clone(), - kind: BoundKind::Constant(val.clone()), - ty: ty.clone(), - }); - Node { - identity: template.identity.clone(), - kind: BoundKind::Constant(val), - ty: NodeMetrics { - original: typed_original, - purity: Purity::Pure, - is_recursive: false, - }, - } - } -} +use crate::ast::compiler::bound_nodes::{Address, AnalyzedNode, BoundKind, Node, NodeMetrics}; +use crate::ast::types::{Purity, 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, &[StaticType]) -> Result<(Value, StaticType), String>>; +pub type RtlLookupFunc = Rc Option<(Value, StaticType)>>; + +pub trait FunctionRegistry { + fn resolve(&self, addr: Address) -> Option>; + fn resolve_analyzed(&self, _addr: Address) -> Option> { + None + } +} + +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: AnalyzedNode) -> AnalyzedNode { + self.visit_node(node) + } + + fn visit_node(&self, node: AnalyzedNode) -> AnalyzedNode { + let (new_kind, metrics) = match node.kind { + BoundKind::Call { callee, args } => { + let (new_callee, new_args, _ret_ty) = + self.specialize_call_logic(callee, args, node.ty.original.ty.clone()); + + let new_metrics = node.ty.clone(); + ( + BoundKind::Call { + callee: Rc::new(new_callee), + args: Rc::new(new_args), + }, + new_metrics, + ) + } + + BoundKind::If { + cond, + then_br, + else_br, + } => { + let cond = Rc::new(self.visit_node(cond.as_ref().clone())); + let then_br = Rc::new(self.visit_node(then_br.as_ref().clone())); + let else_br = else_br.map(|e| Rc::new(self.visit_node(e.as_ref().clone()))); + ( + BoundKind::If { + cond, + then_br, + else_br, + }, + node.ty.clone(), + ) + } + BoundKind::Block { exprs } => { + let exprs = exprs.into_iter().map(|e| Rc::new(self.visit_node(e.as_ref().clone()))).collect(); + (BoundKind::Block { exprs }, node.ty.clone()) + } + 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.as_ref().clone())); + ( + BoundKind::Lambda { + params, + upvalues, + body, + positional_count, + }, + node.ty.clone(), + ) + } + BoundKind::Define { + name, + addr, + kind, + value, + captured_by, + } => { + let value = Rc::new(self.visit_node(value.as_ref().clone())); + ( + BoundKind::Define { + name: name.clone(), + addr, + kind, + value, + captured_by: captured_by.clone(), + }, + node.ty.clone(), + ) + } + BoundKind::Set { addr, value } => { + let value = Rc::new(self.visit_node(value.as_ref().clone())); + (BoundKind::Set { addr, value }, node.ty.clone()) + } + BoundKind::Tuple { elements } => { + let elements = elements.into_iter().map(|e| Rc::new(self.visit_node(e.as_ref().clone()))).collect(); + (BoundKind::Tuple { elements }, node.ty.clone()) + } + BoundKind::Record { layout, values } => { + let values = values.into_iter().map(|v| Rc::new(self.visit_node(v.as_ref().clone()))).collect(); + (BoundKind::Record { layout, values }, node.ty.clone()) + } + BoundKind::Expansion { + original_call, + bound_expanded, + } => { + let bound_expanded = Rc::new(self.visit_node(bound_expanded.as_ref().clone())); + ( + BoundKind::Expansion { + original_call, + bound_expanded, + }, + node.ty.clone(), + ) + } + k => (k, node.ty.clone()), + }; + + Node { + identity: node.identity, + kind: new_kind, + ty: metrics, + } + } + + fn specialize_call_logic( + &self, + callee: Rc, + args: Rc, + original_ty: StaticType, + ) -> (AnalyzedNode, AnalyzedNode, StaticType) { + let new_callee = self.visit_node(callee.as_ref().clone()); + let new_args = self.visit_node(args.as_ref().clone()); + + let address = if let BoundKind::Get { addr, .. } = &new_callee.kind { + *addr + } else { + return (new_callee, new_args, original_ty); + }; + + let arg_types: Vec = + if let StaticType::Tuple(elements) = &new_args.ty.original.ty { + elements.clone() + } else { + vec![new_args.ty.original.ty.clone()] + }; + + if arg_types.iter().any(|t| matches!(t, StaticType::Any)) { + return (new_callee, new_args, original_ty); + } + + let key = MonoCacheKey { + address, + arg_types: arg_types.clone(), + }; + + if let Some((val, ret_ty)) = self.cache.borrow().get(&key) { + let specialized_callee = self.make_constant_node( + val.clone(), + StaticType::Function(Box::new(Signature { + params: StaticType::Tuple(arg_types), + ret: ret_ty.clone(), + })), + &new_callee, + ); + return (specialized_callee, new_args, ret_ty.clone()); + } + + 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) + { + self.cache + .borrow_mut() + .insert(key.clone(), (val.clone(), ret_ty.clone())); + let specialized_callee = self.make_constant_node( + val.clone(), + StaticType::Function(Box::new(Signature { + params: StaticType::Tuple(arg_types), + ret: ret_ty.clone(), + })), + &new_callee, + ); + return (specialized_callee, new_args, ret_ty); + } + + if let Some(registry) = &self.registry + && let Some(func_node) = registry.resolve_analyzed(address) + && func_node.ty.is_recursive + { + return (new_callee, new_args, original_ty); + } + + if let Some(compiler) = &self.compiler + && let Some(func_node) = self.registry.as_ref().and_then(|r| r.resolve(address)) + && let Ok((compiled_val, ret_ty)) = compiler(func_node, &arg_types) + { + self.cache + .borrow_mut() + .insert(key, (compiled_val.clone(), ret_ty.clone())); + + // Only replace the callee if the compiled value is actually a function/object. + // If it's a scalar (like 30 from folding), we DON'T fold here. + // We keep the Call but update the callee to the specialized version if it's an object. + if let Value::Object(_) | Value::Function(_) = &compiled_val { + let specialized_callee = self.make_constant_node( + compiled_val, + StaticType::Function(Box::new(Signature { + params: StaticType::Tuple(arg_types), + ret: ret_ty.clone(), + })), + &new_callee, + ); + return (specialized_callee, new_args, ret_ty); + } + } + + (new_callee, new_args, original_ty) + } + + fn make_constant_node( + &self, + val: Value, + ty: StaticType, + template: &AnalyzedNode, + ) -> AnalyzedNode { + let typed_original = Rc::new(Node { + identity: template.identity.clone(), + kind: BoundKind::Constant(val.clone()), + ty: ty.clone(), + }); + Node { + identity: template.identity.clone(), + kind: BoundKind::Constant(val), + ty: NodeMetrics { + original: typed_original, + purity: Purity::Pure, + is_recursive: false, + }, + } + } +} diff --git a/src/ast/compiler/type_checker.rs b/src/ast/compiler/type_checker.rs index 71692c3..106cfee 100644 --- a/src/ast/compiler/type_checker.rs +++ b/src/ast/compiler/type_checker.rs @@ -1,931 +1,931 @@ -use crate::ast::compiler::bound_nodes::{Address, BoundKind, Node, TypedNode}; -use crate::ast::diagnostics::Diagnostics; -use crate::ast::types::StaticType; -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, - /// Access to root types for unified resolution - root_types: &'a std::cell::RefCell>, - /// The expected parameters of the current function (for 'again' validation) - current_params_ty: Option, -} - -impl<'a> TypeContext<'a> { - fn new( - slot_count: u32, - upvalue_types: Vec, - root_types: &'a std::cell::RefCell>, - parent: Option<&'a TypeContext<'a>>, - ) -> Self { - Self { - _parent: parent, - slots: vec![StaticType::Any; slot_count as usize], - upvalue_types, - root_types, - current_params_ty: None, - } - } - - fn get_type(&self, addr: Address) -> StaticType { - match addr { - Address::Local(slot) => self - .slots - .get(slot.0 as usize) - .cloned() - .unwrap_or(StaticType::Any), - Address::Global(idx) => self - .root_types - .borrow() - .get(idx.0 as usize) - .cloned() - .unwrap_or(StaticType::Any), - Address::Upvalue(idx) => self - .upvalue_types - .get(idx.0 as usize) - .cloned() - .unwrap_or(StaticType::Any), - } - } - - fn set_type(&mut self, addr: Address, ty: StaticType) { - match addr { - Address::Local(slot) => { - if let Some(entry) = self.slots.get_mut(slot.0 as usize) { - *entry = ty; - } - } - Address::Global(idx) => { - let mut rt = self.root_types.borrow_mut(); - if (idx.0 as usize) < rt.len() { - rt[idx.0 as usize] = ty; - } - } - _ => {} - } - } -} - -pub struct TypeChecker { - root_types: Rc>>, -} - -impl TypeChecker { - pub fn new(root_types: Rc>>) -> Self { - Self { root_types } - } - - pub fn check( - &self, - node: &Node, - arg_types: &[StaticType], - diag: &mut Diagnostics, - ) -> TypedNode { - 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![], &self.root_types, None); - let mut lambda_ctx = - TypeContext::new(64, upvalue_types, &self.root_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(), - &arg_tuple_ty, - &mut lambda_ctx, - diag, - ); - - // 4. Check body with the new types - let body_typed = self.check_node(body, &mut lambda_ctx, diag); - 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, - })); - - Node { - identity: node.identity.clone(), - kind: BoundKind::Lambda { - params: Rc::new(params_typed), - upvalues: upvalues.clone(), - body: Rc::new(body_typed), - positional_count: *positional_count, - }, - ty: fn_ty, - } - } - _ => { - let mut root_ctx = TypeContext::new(0, vec![], &self.root_types, None); - self.check_node(node, &mut root_ctx, diag) - } - } - } - - fn check_params( - &self, - node: &Node, - specialized_ty: &StaticType, - ctx: &mut TypeContext, - diag: &mut Diagnostics, - ) -> TypedNode { - let (kind, ty) = match &node.kind { - BoundKind::Define { - name, - addr, - kind: decl_kind, - captured_by, - .. - } => { - ctx.set_type(*addr, specialized_ty.clone()); - ( - BoundKind::Define { - name: name.clone(), - addr: *addr, - kind: *decl_kind, - value: Rc::new(Node { - identity: node.identity.clone(), - kind: BoundKind::Nop, - ty: specialized_ty.clone(), - }), - captured_by: captured_by.clone(), - }, - specialized_ty.clone(), - ) - } - BoundKind::Set { - addr, - value: _value, - } => { - // In an assignment pattern, 'value' is just a Nop placeholder from the Binder. - // We update the type of the address (if it's a local or global) to match the specialized type. - // Note: For now, we assume assignments are compatible if types were already inferred, - // or we could add a check here against existing type. - ( - BoundKind::Set { - addr: *addr, - value: Rc::new(Node { - identity: node.identity.clone(), - kind: BoundKind::Nop, - ty: specialized_ty.clone(), - }), - }, - specialized_ty.clone(), - ) - } - BoundKind::Tuple { elements } => { - match specialized_ty { - StaticType::Any - | StaticType::Tuple(_) - | StaticType::Vector(_, _) - | StaticType::Matrix(_, _) - | StaticType::List(_) - | StaticType::Record(_) - | StaticType::Error => {} - _ => { - diag.push_error( - format!( - "Cannot destructure type {} as a tuple/vector", - specialized_ty - ), - Some(node.identity.clone()), - ); - return Node { - identity: node.identity.clone(), - kind: BoundKind::Error, - ty: StaticType::Error, - }; - } - } - - let mut typed_elements = Vec::new(); - let mut elem_types = Vec::new(); - - for (i, el) in elements.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::Matrix(inner, _) => (**inner).clone(), - StaticType::List(inner) => (**inner).clone(), - StaticType::Record(layout) => layout - .fields - .get(i) - .map(|(_, ty): &(crate::ast::types::Keyword, StaticType)| ty.clone()) - .unwrap_or(StaticType::Any), - StaticType::Error => StaticType::Error, - _ => StaticType::Any, - }; - let t = self.check_params(el, &sub_ty, ctx, diag); - elem_types.push(t.ty.clone()); - typed_elements.push(Rc::new(t)); - } - ( - BoundKind::Tuple { - elements: typed_elements, - }, - StaticType::Tuple(elem_types), - ) - } - BoundKind::Error => (BoundKind::Error, StaticType::Error), - _ => { - diag.push_error( - "Invalid node in parameter list", - Some(node.identity.clone()), - ); - (BoundKind::Error, StaticType::Error) - } - }; - - Node { - identity: node.identity.clone(), - kind, - ty, - } - } - - fn check_node( - &self, - node: &Node, - ctx: &mut TypeContext, - diag: &mut Diagnostics, - ) -> TypedNode { - let (kind, ty) = match &node.kind { - BoundKind::Nop => (BoundKind::Nop, StaticType::Void), - - BoundKind::Constant(v) => { - let ty = v.static_type(); - (BoundKind::Constant(v.clone()), ty) - } - - BoundKind::Define { - name, - addr, - kind: decl_kind, - value, - captured_by, - } => { - let val_typed = self.check_node(value, ctx, diag); - let ty = val_typed.ty.clone(); - ctx.set_type(*addr, ty.clone()); - - ( - BoundKind::Define { - name: name.clone(), - addr: *addr, - kind: *decl_kind, - value: Rc::new(val_typed), - captured_by: captured_by.clone(), - }, - ty, - ) - } - - BoundKind::Get { addr, name } => { - let ty = ctx.get_type(*addr); - ( - BoundKind::Get { - addr: *addr, - name: name.clone(), - }, - ty, - ) - } - - BoundKind::FieldAccessor(k) => { - (BoundKind::FieldAccessor(*k), StaticType::FieldAccessor(*k)) - } - - BoundKind::GetField { rec, field } => { - let rec_typed = self.check_node(rec, ctx, diag); - let field_ty = match &rec_typed.ty { - StaticType::Record(layout) => { - if let Some(idx) = layout.index_of(*field) { - layout.fields[idx].1.clone() - } else { - diag.push_error( - format!("Record does not have field :{}", field.name()), - Some(rec_typed.identity.clone()), - ); - StaticType::Error - } - } - StaticType::Any => StaticType::Any, - StaticType::Error => StaticType::Error, - _ => { - diag.push_error( - format!( - "Cannot access field :{} on non-record type {}", - field.name(), - rec_typed.ty - ), - Some(rec_typed.identity.clone()), - ); - StaticType::Error - } - }; - ( - BoundKind::GetField { - rec: Rc::new(rec_typed), - field: *field, - }, - field_ty, - ) - } - - BoundKind::Set { addr, value } => { - let val_typed = self.check_node(value, ctx, diag); - let ty = val_typed.ty.clone(); - ctx.set_type(*addr, ty.clone()); - - ( - BoundKind::Set { - addr: *addr, - value: Rc::new(val_typed), - }, - ty, - ) - } - - BoundKind::Destructure { pattern, value } => { - let val_typed = self.check_node(value, ctx, diag); - let pat_typed = self.check_params(pattern, &val_typed.ty, ctx, diag); - let ty = val_typed.ty.clone(); - ( - BoundKind::Destructure { - pattern: Rc::new(pat_typed), - value: Rc::new(val_typed), - }, - ty, - ) - } - - BoundKind::If { - cond, - then_br, - else_br, - } => { - let cond_typed = self.check_node(cond, ctx, diag); - let then_typed = self.check_node(then_br, ctx, diag); - - let mut else_typed = None; - let mut final_ty = then_typed.ty.clone(); - - if let Some(e) = else_br { - let et = self.check_node(e, ctx, diag); - // Basic type promotion: if types differ, fall back to Any - if et.ty != final_ty { - final_ty = StaticType::Any; - } - else_typed = Some(Rc::new(et)); - } else { - // If without else returns Optional(Then) (T | Void) - final_ty = StaticType::Optional(Box::new(then_typed.ty.clone())); - } - - ( - BoundKind::If { - cond: Rc::new(cond_typed), - then_br: Rc::new(then_typed), - else_br: else_typed, - }, - final_ty, - ) - } - - BoundKind::Pipe { inputs, lambda, .. } => { - let mut typed_inputs = Vec::with_capacity(inputs.len()); - let mut arg_types = Vec::with_capacity(inputs.len()); - for input in inputs { - let typed_input = self.check_node(input, ctx, diag); - // Unwrap Series(T) or Stream(T) to T for the lambda argument - let arg_ty = if let StaticType::Series(inner) = &typed_input.ty { - *inner.clone() - } else if let StaticType::Stream(inner) = &typed_input.ty { - *inner.clone() - } else { - StaticType::Any - }; - arg_types.push(arg_ty); - typed_inputs.push(Rc::new(typed_input)); - } - - // Specialized check for the lambda using the input types! - let typed_lambda = self.check(lambda, &arg_types, diag); - - let ret_ty = if let StaticType::Function(sig) = &typed_lambda.ty { - // If the lambda returns an Optional(T), the pipeline filters Void and stores T! - if let StaticType::Optional(inner) = &sig.ret { - *inner.clone() - } else { - sig.ret.clone() - } - } else { - StaticType::Any - }; - ( - BoundKind::Pipe { - inputs: typed_inputs, - lambda: Rc::new(typed_lambda), - out_type: ret_ty.clone(), - }, - StaticType::Stream(Box::new(ret_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, diag); - last_ty = t.ty.clone(); - typed_exprs.push(Rc::new(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, ctx.root_types, Some(ctx)); - - // 3. Check parameters and body - let params_typed = self.check_params( - params.as_ref(), - &StaticType::Any, - &mut lambda_ctx, - diag, - ); - - // Set current params type for 'again' validation - lambda_ctx.current_params_ty = Some(params_typed.ty.clone()); - - let body_typed = self.check_node(body, &mut lambda_ctx, diag); - 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: upvalues.clone(), - body: Rc::new(body_typed), - positional_count: *positional_count, - }, - fn_ty, - ) - } - - BoundKind::Call { callee, args } => { - let callee_typed = self.check_node(callee, ctx, diag); - - // Manually check args (Tuple) to prevent Vector/Matrix promotion - let args_typed = if let BoundKind::Tuple { elements } = &args.kind { - let mut typed_elements = Vec::new(); - let mut elem_types = Vec::new(); - for e in elements { - let t = self.check_node(e, ctx, diag); - elem_types.push(t.ty.clone()); - typed_elements.push(Rc::new(t)); - } - Node { - identity: args.identity.clone(), - kind: BoundKind::Tuple { - elements: typed_elements, - }, - ty: StaticType::Tuple(elem_types), - } - } else { - // Should not happen as parser wraps args in Tuple, but fallback safely - self.check_node(args, ctx, diag) - }; - - let ret_ty = match callee_typed.ty.resolve_call(&args_typed.ty) { - Some(ty) => ty, - None => { - diag.push_error( - format!( - "Invalid arguments for function call. Expected {}, got {}", - callee_typed.ty, args_typed.ty - ), - Some(node.identity.clone()), - ); - StaticType::Error - } - }; - - ( - BoundKind::Call { - callee: Rc::new(callee_typed), - args: Rc::new(args_typed), - }, - ret_ty, - ) - } - - BoundKind::Again { args } => { - // Manually check args (Tuple) to prevent Vector/Matrix promotion - let args_typed = if let BoundKind::Tuple { elements } = &args.kind { - let mut typed_elements = Vec::new(); - let mut elem_types = Vec::new(); - for e in elements { - let t = self.check_node(e, ctx, diag); - elem_types.push(t.ty.clone()); - typed_elements.push(Rc::new(t)); - } - Node { - identity: args.identity.clone(), - kind: BoundKind::Tuple { - elements: typed_elements, - }, - ty: StaticType::Tuple(elem_types), - } - } else { - self.check_node(args, ctx, diag) - }; - - if let Some(expected_ty) = &ctx.current_params_ty - && !expected_ty.is_assignable_from(&args_typed.ty) - { - diag.push_error( - format!( - "Type mismatch in 'again' call: expected {}, but got {}", - expected_ty, args_typed.ty - ), - Some(node.identity.clone()), - ); - } - - ( - BoundKind::Again { - args: Rc::new(args_typed), - }, - StaticType::Any, - ) - } - - BoundKind::Tuple { elements } => { - let mut typed_elements = Vec::new(); - for e in elements { - typed_elements.push(Rc::new(self.check_node(e, ctx, diag))); - } - - 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 { layout, values } => { - let mut typed_values = Vec::with_capacity(values.len()); - let mut fields_ty = Vec::with_capacity(values.len()); - - for (i, v) in values.iter().enumerate() { - let vt = self.check_node(v, ctx, diag); - fields_ty.push((layout.fields[i].0, vt.ty.clone())); - typed_values.push(Rc::new(vt)); - } - - let new_layout = crate::ast::types::RecordLayout::get_or_create(fields_ty); - ( - BoundKind::Record { - layout: new_layout.clone(), - values: typed_values, - }, - StaticType::Record(new_layout), - ) - } - - BoundKind::Expansion { - original_call, - bound_expanded, - } => { - let expanded_typed = self.check_node(bound_expanded, ctx, diag); - let ty = expanded_typed.ty.clone(); - ( - BoundKind::Expansion { - original_call: original_call.clone(), - bound_expanded: Rc::new(expanded_typed), - }, - ty, - ) - } - - BoundKind::Extension(_ext) => { - diag.push_error( - format!( - "TypeChecking for extension '{}' not implemented", - _ext.display_name() - ), - Some(node.identity.clone()), - ); - (BoundKind::Error, StaticType::Error) - } - BoundKind::Error => (BoundKind::Error, StaticType::Error), - }; - - Node { - identity: node.identity.clone(), - 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).into_result().unwrap() - } - - #[test] - fn test_destructuring_scalar_error() { - let env = crate::ast::environment::Environment::new(); - let result = env.compile("(def [x] 5)").into_result(); - assert!(result.is_err()); - assert_eq!( - result.unwrap_err(), - "Statement 'def' cannot be used as an expression.\nCannot destructure type int as a tuple/vector" - ); - } - - #[test] - fn test_call_argument_mismatch() { - let env = crate::ast::environment::Environment::new(); - // fn takes 1 arg, called with 0 - let result = env.compile("(do (def f (fn [x] x)) (f))").into_result(); - assert!(result.is_err()); - assert!( - result - .unwrap_err() - .contains("Invalid arguments for function call") - ); - } - - #[test] - fn test_destructuring_vector_args() { - let env = crate::ast::environment::Environment::new(); - // ((fn [[x y]] (+ x y)) [10 20]) -> 30 - let result = env.compile("((fn [[x y]] (+ x y)) [10 20])").into_result(); - assert!(result.is_ok()); - assert_eq!(get_ret_type(&result.unwrap()), StaticType::Int); - } - - 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(layout) = ty { - assert_eq!(layout.fields.len(), 2); - assert_eq!(layout.fields[0], (Keyword::intern("x"), StaticType::Int)); - assert_eq!(layout.fields[1], (Keyword::intern("y"), StaticType::Float)); - } else { - panic!("Expected Record, got {:?}", ty); - } - } -} +use crate::ast::compiler::bound_nodes::{Address, BoundKind, Node, TypedNode}; +use crate::ast::diagnostics::Diagnostics; +use crate::ast::types::StaticType; +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, + /// Access to root types for unified resolution + root_types: &'a std::cell::RefCell>, + /// The expected parameters of the current function (for 'again' validation) + current_params_ty: Option, +} + +impl<'a> TypeContext<'a> { + fn new( + slot_count: u32, + upvalue_types: Vec, + root_types: &'a std::cell::RefCell>, + parent: Option<&'a TypeContext<'a>>, + ) -> Self { + Self { + _parent: parent, + slots: vec![StaticType::Any; slot_count as usize], + upvalue_types, + root_types, + current_params_ty: None, + } + } + + fn get_type(&self, addr: Address) -> StaticType { + match addr { + Address::Local(slot) => self + .slots + .get(slot.0 as usize) + .cloned() + .unwrap_or(StaticType::Any), + Address::Global(idx) => self + .root_types + .borrow() + .get(idx.0 as usize) + .cloned() + .unwrap_or(StaticType::Any), + Address::Upvalue(idx) => self + .upvalue_types + .get(idx.0 as usize) + .cloned() + .unwrap_or(StaticType::Any), + } + } + + fn set_type(&mut self, addr: Address, ty: StaticType) { + match addr { + Address::Local(slot) => { + if let Some(entry) = self.slots.get_mut(slot.0 as usize) { + *entry = ty; + } + } + Address::Global(idx) => { + let mut rt = self.root_types.borrow_mut(); + if (idx.0 as usize) < rt.len() { + rt[idx.0 as usize] = ty; + } + } + _ => {} + } + } +} + +pub struct TypeChecker { + root_types: Rc>>, +} + +impl TypeChecker { + pub fn new(root_types: Rc>>) -> Self { + Self { root_types } + } + + pub fn check( + &self, + node: &Node, + arg_types: &[StaticType], + diag: &mut Diagnostics, + ) -> TypedNode { + 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![], &self.root_types, None); + let mut lambda_ctx = + TypeContext::new(64, upvalue_types, &self.root_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(), + &arg_tuple_ty, + &mut lambda_ctx, + diag, + ); + + // 4. Check body with the new types + let body_typed = self.check_node(body, &mut lambda_ctx, diag); + 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, + })); + + Node { + identity: node.identity.clone(), + kind: BoundKind::Lambda { + params: Rc::new(params_typed), + upvalues: upvalues.clone(), + body: Rc::new(body_typed), + positional_count: *positional_count, + }, + ty: fn_ty, + } + } + _ => { + let mut root_ctx = TypeContext::new(0, vec![], &self.root_types, None); + self.check_node(node, &mut root_ctx, diag) + } + } + } + + fn check_params( + &self, + node: &Node, + specialized_ty: &StaticType, + ctx: &mut TypeContext, + diag: &mut Diagnostics, + ) -> TypedNode { + let (kind, ty) = match &node.kind { + BoundKind::Define { + name, + addr, + kind: decl_kind, + captured_by, + .. + } => { + ctx.set_type(*addr, specialized_ty.clone()); + ( + BoundKind::Define { + name: name.clone(), + addr: *addr, + kind: *decl_kind, + value: Rc::new(Node { + identity: node.identity.clone(), + kind: BoundKind::Nop, + ty: specialized_ty.clone(), + }), + captured_by: captured_by.clone(), + }, + specialized_ty.clone(), + ) + } + BoundKind::Set { + addr, + value: _value, + } => { + // In an assignment pattern, 'value' is just a Nop placeholder from the Binder. + // We update the type of the address (if it's a local or global) to match the specialized type. + // Note: For now, we assume assignments are compatible if types were already inferred, + // or we could add a check here against existing type. + ( + BoundKind::Set { + addr: *addr, + value: Rc::new(Node { + identity: node.identity.clone(), + kind: BoundKind::Nop, + ty: specialized_ty.clone(), + }), + }, + specialized_ty.clone(), + ) + } + BoundKind::Tuple { elements } => { + match specialized_ty { + StaticType::Any + | StaticType::Tuple(_) + | StaticType::Vector(_, _) + | StaticType::Matrix(_, _) + | StaticType::List(_) + | StaticType::Record(_) + | StaticType::Error => {} + _ => { + diag.push_error( + format!( + "Cannot destructure type {} as a tuple/vector", + specialized_ty + ), + Some(node.identity.clone()), + ); + return Node { + identity: node.identity.clone(), + kind: BoundKind::Error, + ty: StaticType::Error, + }; + } + } + + let mut typed_elements = Vec::new(); + let mut elem_types = Vec::new(); + + for (i, el) in elements.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::Matrix(inner, _) => (**inner).clone(), + StaticType::List(inner) => (**inner).clone(), + StaticType::Record(layout) => layout + .fields + .get(i) + .map(|(_, ty): &(crate::ast::types::Keyword, StaticType)| ty.clone()) + .unwrap_or(StaticType::Any), + StaticType::Error => StaticType::Error, + _ => StaticType::Any, + }; + let t = self.check_params(el, &sub_ty, ctx, diag); + elem_types.push(t.ty.clone()); + typed_elements.push(Rc::new(t)); + } + ( + BoundKind::Tuple { + elements: typed_elements, + }, + StaticType::Tuple(elem_types), + ) + } + BoundKind::Error => (BoundKind::Error, StaticType::Error), + _ => { + diag.push_error( + "Invalid node in parameter list", + Some(node.identity.clone()), + ); + (BoundKind::Error, StaticType::Error) + } + }; + + Node { + identity: node.identity.clone(), + kind, + ty, + } + } + + fn check_node( + &self, + node: &Node, + ctx: &mut TypeContext, + diag: &mut Diagnostics, + ) -> TypedNode { + let (kind, ty) = match &node.kind { + BoundKind::Nop => (BoundKind::Nop, StaticType::Void), + + BoundKind::Constant(v) => { + let ty = v.static_type(); + (BoundKind::Constant(v.clone()), ty) + } + + BoundKind::Define { + name, + addr, + kind: decl_kind, + value, + captured_by, + } => { + let val_typed = self.check_node(value, ctx, diag); + let ty = val_typed.ty.clone(); + ctx.set_type(*addr, ty.clone()); + + ( + BoundKind::Define { + name: name.clone(), + addr: *addr, + kind: *decl_kind, + value: Rc::new(val_typed), + captured_by: captured_by.clone(), + }, + ty, + ) + } + + BoundKind::Get { addr, name } => { + let ty = ctx.get_type(*addr); + ( + BoundKind::Get { + addr: *addr, + name: name.clone(), + }, + ty, + ) + } + + BoundKind::FieldAccessor(k) => { + (BoundKind::FieldAccessor(*k), StaticType::FieldAccessor(*k)) + } + + BoundKind::GetField { rec, field } => { + let rec_typed = self.check_node(rec, ctx, diag); + let field_ty = match &rec_typed.ty { + StaticType::Record(layout) => { + if let Some(idx) = layout.index_of(*field) { + layout.fields[idx].1.clone() + } else { + diag.push_error( + format!("Record does not have field :{}", field.name()), + Some(rec_typed.identity.clone()), + ); + StaticType::Error + } + } + StaticType::Any => StaticType::Any, + StaticType::Error => StaticType::Error, + _ => { + diag.push_error( + format!( + "Cannot access field :{} on non-record type {}", + field.name(), + rec_typed.ty + ), + Some(rec_typed.identity.clone()), + ); + StaticType::Error + } + }; + ( + BoundKind::GetField { + rec: Rc::new(rec_typed), + field: *field, + }, + field_ty, + ) + } + + BoundKind::Set { addr, value } => { + let val_typed = self.check_node(value, ctx, diag); + let ty = val_typed.ty.clone(); + ctx.set_type(*addr, ty.clone()); + + ( + BoundKind::Set { + addr: *addr, + value: Rc::new(val_typed), + }, + ty, + ) + } + + BoundKind::Destructure { pattern, value } => { + let val_typed = self.check_node(value, ctx, diag); + let pat_typed = self.check_params(pattern, &val_typed.ty, ctx, diag); + let ty = val_typed.ty.clone(); + ( + BoundKind::Destructure { + pattern: Rc::new(pat_typed), + value: Rc::new(val_typed), + }, + ty, + ) + } + + BoundKind::If { + cond, + then_br, + else_br, + } => { + let cond_typed = self.check_node(cond, ctx, diag); + let then_typed = self.check_node(then_br, ctx, diag); + + let mut else_typed = None; + let mut final_ty = then_typed.ty.clone(); + + if let Some(e) = else_br { + let et = self.check_node(e, ctx, diag); + // Basic type promotion: if types differ, fall back to Any + if et.ty != final_ty { + final_ty = StaticType::Any; + } + else_typed = Some(Rc::new(et)); + } else { + // If without else returns Optional(Then) (T | Void) + final_ty = StaticType::Optional(Box::new(then_typed.ty.clone())); + } + + ( + BoundKind::If { + cond: Rc::new(cond_typed), + then_br: Rc::new(then_typed), + else_br: else_typed, + }, + final_ty, + ) + } + + BoundKind::Pipe { inputs, lambda, .. } => { + let mut typed_inputs = Vec::with_capacity(inputs.len()); + let mut arg_types = Vec::with_capacity(inputs.len()); + for input in inputs { + let typed_input = self.check_node(input, ctx, diag); + // Unwrap Series(T) or Stream(T) to T for the lambda argument + let arg_ty = if let StaticType::Series(inner) = &typed_input.ty { + *inner.clone() + } else if let StaticType::Stream(inner) = &typed_input.ty { + *inner.clone() + } else { + StaticType::Any + }; + arg_types.push(arg_ty); + typed_inputs.push(Rc::new(typed_input)); + } + + // Specialized check for the lambda using the input types! + let typed_lambda = self.check(lambda, &arg_types, diag); + + let ret_ty = if let StaticType::Function(sig) = &typed_lambda.ty { + // If the lambda returns an Optional(T), the pipeline filters Void and stores T! + if let StaticType::Optional(inner) = &sig.ret { + *inner.clone() + } else { + sig.ret.clone() + } + } else { + StaticType::Any + }; + ( + BoundKind::Pipe { + inputs: typed_inputs, + lambda: Rc::new(typed_lambda), + out_type: ret_ty.clone(), + }, + StaticType::Stream(Box::new(ret_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, diag); + last_ty = t.ty.clone(); + typed_exprs.push(Rc::new(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, ctx.root_types, Some(ctx)); + + // 3. Check parameters and body + let params_typed = self.check_params( + params.as_ref(), + &StaticType::Any, + &mut lambda_ctx, + diag, + ); + + // Set current params type for 'again' validation + lambda_ctx.current_params_ty = Some(params_typed.ty.clone()); + + let body_typed = self.check_node(body, &mut lambda_ctx, diag); + 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: upvalues.clone(), + body: Rc::new(body_typed), + positional_count: *positional_count, + }, + fn_ty, + ) + } + + BoundKind::Call { callee, args } => { + let callee_typed = self.check_node(callee, ctx, diag); + + // Manually check args (Tuple) to prevent Vector/Matrix promotion + let args_typed = if let BoundKind::Tuple { elements } = &args.kind { + let mut typed_elements = Vec::new(); + let mut elem_types = Vec::new(); + for e in elements { + let t = self.check_node(e, ctx, diag); + elem_types.push(t.ty.clone()); + typed_elements.push(Rc::new(t)); + } + Node { + identity: args.identity.clone(), + kind: BoundKind::Tuple { + elements: typed_elements, + }, + ty: StaticType::Tuple(elem_types), + } + } else { + // Should not happen as parser wraps args in Tuple, but fallback safely + self.check_node(args, ctx, diag) + }; + + let ret_ty = match callee_typed.ty.resolve_call(&args_typed.ty) { + Some(ty) => ty, + None => { + diag.push_error( + format!( + "Invalid arguments for function call. Expected {}, got {}", + callee_typed.ty, args_typed.ty + ), + Some(node.identity.clone()), + ); + StaticType::Error + } + }; + + ( + BoundKind::Call { + callee: Rc::new(callee_typed), + args: Rc::new(args_typed), + }, + ret_ty, + ) + } + + BoundKind::Again { args } => { + // Manually check args (Tuple) to prevent Vector/Matrix promotion + let args_typed = if let BoundKind::Tuple { elements } = &args.kind { + let mut typed_elements = Vec::new(); + let mut elem_types = Vec::new(); + for e in elements { + let t = self.check_node(e, ctx, diag); + elem_types.push(t.ty.clone()); + typed_elements.push(Rc::new(t)); + } + Node { + identity: args.identity.clone(), + kind: BoundKind::Tuple { + elements: typed_elements, + }, + ty: StaticType::Tuple(elem_types), + } + } else { + self.check_node(args, ctx, diag) + }; + + if let Some(expected_ty) = &ctx.current_params_ty + && !expected_ty.is_assignable_from(&args_typed.ty) + { + diag.push_error( + format!( + "Type mismatch in 'again' call: expected {}, but got {}", + expected_ty, args_typed.ty + ), + Some(node.identity.clone()), + ); + } + + ( + BoundKind::Again { + args: Rc::new(args_typed), + }, + StaticType::Any, + ) + } + + BoundKind::Tuple { elements } => { + let mut typed_elements = Vec::new(); + for e in elements { + typed_elements.push(Rc::new(self.check_node(e, ctx, diag))); + } + + 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 { layout, values } => { + let mut typed_values = Vec::with_capacity(values.len()); + let mut fields_ty = Vec::with_capacity(values.len()); + + for (i, v) in values.iter().enumerate() { + let vt = self.check_node(v, ctx, diag); + fields_ty.push((layout.fields[i].0, vt.ty.clone())); + typed_values.push(Rc::new(vt)); + } + + let new_layout = crate::ast::types::RecordLayout::get_or_create(fields_ty); + ( + BoundKind::Record { + layout: new_layout.clone(), + values: typed_values, + }, + StaticType::Record(new_layout), + ) + } + + BoundKind::Expansion { + original_call, + bound_expanded, + } => { + let expanded_typed = self.check_node(bound_expanded, ctx, diag); + let ty = expanded_typed.ty.clone(); + ( + BoundKind::Expansion { + original_call: original_call.clone(), + bound_expanded: Rc::new(expanded_typed), + }, + ty, + ) + } + + BoundKind::Extension(_ext) => { + diag.push_error( + format!( + "TypeChecking for extension '{}' not implemented", + _ext.display_name() + ), + Some(node.identity.clone()), + ); + (BoundKind::Error, StaticType::Error) + } + BoundKind::Error => (BoundKind::Error, StaticType::Error), + }; + + Node { + identity: node.identity.clone(), + 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).into_result().unwrap() + } + + #[test] + fn test_destructuring_scalar_error() { + let env = crate::ast::environment::Environment::new(); + let result = env.compile("(def [x] 5)").into_result(); + assert!(result.is_err()); + assert_eq!( + result.unwrap_err(), + "Statement 'def' cannot be used as an expression.\nCannot destructure type int as a tuple/vector" + ); + } + + #[test] + fn test_call_argument_mismatch() { + let env = crate::ast::environment::Environment::new(); + // fn takes 1 arg, called with 0 + let result = env.compile("(do (def f (fn [x] x)) (f))").into_result(); + assert!(result.is_err()); + assert!( + result + .unwrap_err() + .contains("Invalid arguments for function call") + ); + } + + #[test] + fn test_destructuring_vector_args() { + let env = crate::ast::environment::Environment::new(); + // ((fn [[x y]] (+ x y)) [10 20]) -> 30 + let result = env.compile("((fn [[x y]] (+ x y)) [10 20])").into_result(); + assert!(result.is_ok()); + assert_eq!(get_ret_type(&result.unwrap()), StaticType::Int); + } + + 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(layout) = ty { + assert_eq!(layout.fields.len(), 2); + assert_eq!(layout.fields[0], (Keyword::intern("x"), StaticType::Int)); + assert_eq!(layout.fields[1], (Keyword::intern("y"), StaticType::Float)); + } else { + panic!("Expected Record, got {:?}", ty); + } + } +} diff --git a/src/ast/environment.rs b/src/ast/environment.rs index 37f367c..4fb1d1a 100644 --- a/src/ast/environment.rs +++ b/src/ast/environment.rs @@ -1,7 +1,7 @@ use crate::ast::compiler::analyzer::Analyzer; use crate::ast::compiler::binder::Binder; use crate::ast::compiler::{TypeChecker, TypedNode}; -use crate::ast::nodes::{Symbol, UntypedKind, UntypedNode}; +use crate::ast::nodes::{Symbol, SyntaxKind, SyntaxNode}; use crate::ast::parser::Parser; use crate::ast::vm::{TracingObserver, VM}; use std::cell::RefCell; @@ -122,10 +122,10 @@ struct RuntimeMacroEvaluator { impl MacroEvaluator for RuntimeMacroEvaluator { fn evaluate( &self, - node: &UntypedNode, - bindings: &HashMap, UntypedNode>, + node: &SyntaxNode, + bindings: &HashMap, SyntaxNode>, ) -> Result { - if let UntypedKind::Identifier(sym) = &node.kind + if let SyntaxKind::Identifier(sym) = &node.kind && let Some(arg_node) = bindings.get(&sym.name) { return Ok(Value::Object(Rc::new(arg_node.clone()) as Rc)); @@ -290,7 +290,7 @@ impl Environment { &self, source: &str, base_path: &Path, - all_files: &mut Vec<(PathBuf, UntypedNode)>, + all_files: &mut Vec<(PathBuf, SyntaxNode)>, ) -> Result<(), String> { let directives = self.extract_use_directives(source); @@ -312,7 +312,7 @@ impl Environment { self.collect_dependencies(&lib_source, lib_base, all_files)?; let mut parser = Parser::new(&lib_source); - let untyped_ast = parser.parse_expression(); + let syntax_ast = parser.parse_expression(); if parser.diagnostics.has_errors() { return Err(format!( @@ -322,7 +322,7 @@ impl Environment { )); } - all_files.push((abs_path, untyped_ast)); + all_files.push((abs_path, syntax_ast)); } } Ok(()) @@ -358,34 +358,34 @@ impl Environment { /// It returns the base path which can be used to resolve further things if necessary. pub fn preload_dependencies(&self, source: &str, file_path: Option<&Path>) -> Result<(), String> { let base_path = file_path.and_then(|p| p.parent()).unwrap_or_else(|| Path::new(".")); - let mut files: Vec<(PathBuf, UntypedNode)> = Vec::new(); + let mut files: Vec<(PathBuf, SyntaxNode)> = Vec::new(); // 1. Always load the embedded system library first as a virtual module let system_path = PathBuf::from(SYSTEM_LIB_PATH); if !self.loaded_modules.borrow().contains(&system_path) { self.loaded_modules.borrow_mut().insert(system_path.clone()); let mut parser = Parser::new(SYSTEM_LIB_SOURCE); - let untyped_ast = parser.parse_expression(); + let syntax_ast = parser.parse_expression(); if parser.diagnostics.has_errors() { return Err(format!( "Failed to parse embedded system library:\n{}", parser.diagnostics.items[0].message )); } - files.push((system_path, untyped_ast)); + files.push((system_path, syntax_ast)); } // 2. Collect dependencies from the main source self.collect_dependencies(source, base_path, &mut files)?; // Pass 1: Discovery (Globals and Macros) - for (_, untyped_ast) in &files { - self.discover_globals(untyped_ast); + for (_, syntax_ast) in &files { + self.discover_globals(syntax_ast); } // Pass 2: Compilation and Initialization - for (path, untyped_ast) in files { - let typed_ast = self.compile_untyped(untyped_ast).map_err(|e: String| { + for (path, syntax_ast) in files { + let typed_ast = self.compile_syntax(syntax_ast).map_err(|e: String| { format!("Compilation error in {}:\n{}", path.display(), e) })?; self.run_script_compiled(typed_ast).map_err(|e: String| { @@ -396,10 +396,10 @@ impl Environment { Ok(()) } - fn discover_globals(&self, node: &UntypedNode) { + fn discover_globals(&self, node: &SyntaxNode) { match &node.kind { - UntypedKind::Def { target, .. } => { - if let UntypedKind::Identifier(sym) = &target.kind { + SyntaxKind::Def { target, .. } => { + if let SyntaxKind::Identifier(sym) = &target.kind { let mut root_scopes = self.root_scopes.borrow_mut(); let last_idx = root_scopes.len() - 1; let current_scope = &mut root_scopes[last_idx]; @@ -420,13 +420,13 @@ impl Environment { } } } - UntypedKind::MacroDecl { name, params, body } => { + SyntaxKind::MacroDecl { name, params, body } => { let mut registry = self.macro_registry.borrow_mut(); - fn extract_names(node: &UntypedNode) -> Vec> { + fn extract_names(node: &SyntaxNode) -> Vec> { match &node.kind { - UntypedKind::Identifier(sym) => vec![sym.name.clone()], - UntypedKind::Tuple { elements } => { + SyntaxKind::Identifier(sym) => vec![sym.name.clone()], + SyntaxKind::Tuple { elements } => { elements.iter().flat_map(extract_names).collect() } _ => vec![], @@ -436,7 +436,7 @@ impl Environment { let p_names = extract_names(params); registry.define(name.name.clone(), p_names, body.as_ref().clone()); } - UntypedKind::Block { exprs } => { + SyntaxKind::Block { exprs } => { for expr in exprs { self.discover_globals(expr); } @@ -445,8 +445,8 @@ impl Environment { } } - fn compile_pipeline(&self, untyped_ast: UntypedNode, diagnostics: &mut Diagnostics) -> Option { - let expanded_ast = match self.get_expander().expand(untyped_ast) { + fn compile_pipeline(&self, syntax_ast: SyntaxNode, diagnostics: &mut Diagnostics) -> Option { + let expanded_ast = match self.get_expander().expand(syntax_ast) { Ok(ast) => ast, Err(e) => { diagnostics.push_error(e, None); @@ -505,10 +505,10 @@ impl Environment { Some(checker.check(&wrapped_ast, &[], diagnostics)) } - fn compile_untyped(&self, untyped_ast: UntypedNode) -> Result { + fn compile_syntax(&self, syntax_ast: SyntaxNode) -> Result { let mut diagnostics = Diagnostics::new(); - let typed_ast_opt = self.compile_pipeline(untyped_ast, &mut diagnostics); + let typed_ast_opt = self.compile_pipeline(syntax_ast, &mut diagnostics); if diagnostics.has_errors() || typed_ast_opt.is_none() { return Err(diagnostics @@ -627,7 +627,7 @@ impl Environment { } let mut parser = Parser::new(source); - let untyped_ast = parser.parse_expression(); + let syntax_ast = parser.parse_expression(); if !parser.at_eof() { parser @@ -640,7 +640,7 @@ impl Environment { } let mut diagnostics = parser.diagnostics; - let typed_ast = self.compile_pipeline(untyped_ast, &mut diagnostics); + let typed_ast = self.compile_pipeline(syntax_ast, &mut diagnostics); CompilationResult { ast: typed_ast, @@ -737,7 +737,7 @@ impl Environment { let rtl_lookup = Rc::new(|name: &str, args: &[StaticType]| intrinsics::lookup(name, args)); let typed_reg = self.typed_function_registry.clone(); - let untyped_reg = self.function_registry.clone(); + let syntax_reg = self.function_registry.clone(); let mono_cache = self.monomorph_cache.clone(); let root_values = self.root_values.clone(); let root_types = self.root_types.clone(); @@ -764,7 +764,7 @@ impl Environment { let analyzed = Analyzer::analyze(&retyped_ast, &root_purity.borrow()); let sub_registry = Rc::new(EnvFunctionRegistry { - registry: untyped_reg.clone(), + registry: syntax_reg.clone(), analyzed_registry: typed_reg.clone(), }); let sub_rtl_lookup = diff --git a/src/ast/lexer.rs b/src/ast/lexer.rs index 129aa5d..3d7d176 100644 --- a/src/ast/lexer.rs +++ b/src/ast/lexer.rs @@ -1,219 +1,219 @@ -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 == ';' || 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 == ';' || 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 e76936a..2bfd8ac 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -1,9 +1,9 @@ -pub mod compiler; -pub mod diagnostics; -pub mod environment; -pub mod lexer; -pub mod nodes; -pub mod parser; -pub mod rtl; -pub mod types; -pub mod vm; +pub mod compiler; +pub mod diagnostics; +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 3e5b1a1..f82921f 100644 --- a/src/ast/nodes.rs +++ b/src/ast/nodes.rs @@ -1,134 +1,134 @@ -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 parser AST Node wrapper to preserve identity and metadata -#[derive(Debug, Clone, PartialEq)] -pub struct UntypedNode { - pub identity: Identity, - pub kind: UntypedKind, -} - -impl Object for UntypedNode { - 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, PartialEq)] -pub enum UntypedKind { - Nop, - Constant(Value), - /// A general identifier (used for both references and declarations in the untyped AST). - Identifier(Symbol), - /// A first-class field accessor (e.g. .name) - FieldAccessor(crate::ast::types::Keyword), - If { - cond: Box, - then_br: Box, - else_br: Option>, - }, - Def { - target: Box, - value: Box, - }, - Assign { - target: Box, - value: Box, - }, - Lambda { - params: Box, - body: Rc, - }, - Call { - callee: Box, - args: Box, - }, - Again { - args: Box, - }, - Pipe { - inputs: Vec, - lambda: Box, - }, - Block { - exprs: Vec, - }, - Tuple { - elements: Vec, - }, - Record { - fields: Vec<(UntypedNode, UntypedNode)>, - }, - /// 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, - }, - /// A diagnostic poison node, allowing compilation to continue after an error. - Error, - Extension(Box), -} - -impl PartialEq for Box { - fn eq(&self, _other: &Self) -> bool { - false - } -} +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 parser AST Node wrapper to preserve identity and metadata +#[derive(Debug, Clone, PartialEq)] +pub struct SyntaxNode { + pub identity: Identity, + pub kind: SyntaxKind, +} + +impl Object for SyntaxNode { + 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, PartialEq)] +pub enum SyntaxKind { + Nop, + Constant(Value), + /// A general identifier (used for both references and declarations in the syntax AST). + Identifier(Symbol), + /// A first-class field accessor (e.g. .name) + FieldAccessor(crate::ast::types::Keyword), + If { + cond: Box, + then_br: Box, + else_br: Option>, + }, + Def { + target: Box, + value: Box, + }, + Assign { + target: Box, + value: Box, + }, + Lambda { + params: Box, + body: Rc, + }, + Call { + callee: Box, + args: Box, + }, + Again { + args: Box, + }, + Pipe { + inputs: Vec, + lambda: Box, + }, + Block { + exprs: Vec, + }, + Tuple { + elements: Vec, + }, + Record { + fields: Vec<(SyntaxNode, SyntaxNode)>, + }, + /// 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, + }, + /// A diagnostic poison node, allowing compilation to continue after an error. + Error, + Extension(Box), +} + +impl PartialEq for Box { + fn eq(&self, _other: &Self) -> bool { + false + } +} diff --git a/src/ast/parser.rs b/src/ast/parser.rs index 1017f0b..8e776eb 100644 --- a/src/ast/parser.rs +++ b/src/ast/parser.rs @@ -1,522 +1,522 @@ -use crate::ast::diagnostics::Diagnostics; -use crate::ast::lexer::{Lexer, Token, TokenKind}; -use crate::ast::nodes::{Symbol, UntypedKind, UntypedNode}; -use crate::ast::types::{Identity, Keyword, NodeIdentity, Value}; -use std::rc::Rc; - -pub struct Parser<'a> { - lexer: Lexer<'a>, - current_token: Token, - pub diagnostics: Diagnostics, -} - -impl<'a> Parser<'a> { - pub fn new(input: &'a str) -> Self { - let mut lexer = Lexer::new(input); - let mut diagnostics = Diagnostics::new(); - let current_token = match lexer.next_token() { - Ok(t) => t, - Err(e) => { - diagnostics.push_error(e, None); - Token { - kind: TokenKind::EOF, - location: crate::ast::types::SourceLocation { line: 1, col: 1 }, - } - } - }; - Self { - lexer, - current_token, - diagnostics, - } - } - - fn advance(&mut self) -> Token { - let next = match self.lexer.next_token() { - Ok(t) => t, - Err(e) => { - self.diagnostics - .push_error(e, Some(NodeIdentity::new(self.current_token.location))); - Token { - kind: TokenKind::EOF, - location: self.current_token.location, - } - } - }; - std::mem::replace(&mut self.current_token, next) - } - - fn peek(&self) -> &TokenKind { - &self.current_token.kind - } - - pub fn parse_expression(&mut self) -> UntypedNode { - let token_loc = self.current_token.location; - let identity = NodeIdentity::new(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(); - UntypedNode { - identity: identity.clone(), - kind: UntypedKind::Call { - callee: Box::new(self.make_id_node("quote", identity.clone())), - args: Box::new(UntypedNode { - identity, - kind: UntypedKind::Tuple { - elements: vec![expr], - }, - - }), - }, - - } - } - TokenKind::Backtick => { - self.advance(); // consume ` - let expr = self.parse_expression(); - UntypedNode { - identity, - kind: UntypedKind::Template(Box::new(expr)), - - } - } - TokenKind::Tilde => { - self.advance(); // consume ~ - if *self.peek() == TokenKind::At { - self.advance(); // consume @ - let expr = self.parse_expression(); - UntypedNode { - identity, - kind: UntypedKind::Splice(Box::new(expr)), - - } - } else { - let expr = self.parse_expression(); - UntypedNode { - identity, - kind: UntypedKind::Placeholder(Box::new(expr)), - - } - } - } - _ => self.parse_atom(), - } - } - - pub fn at_eof(&self) -> bool { - matches!(self.current_token.kind, TokenKind::EOF) - } - - fn synchronize(&mut self) { - while !self.at_eof() { - match self.peek() { - TokenKind::RightParen | TokenKind::RightBracket | TokenKind::RightBrace => { - self.advance(); - return; - } - _ => { - self.advance(); - } - } - } - } - - fn parse_atom(&mut self) -> UntypedNode { - let token = self.advance(); - let identity = NodeIdentity::new(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, - s if s.starts_with('.') && s.len() > 1 => { - UntypedKind::FieldAccessor(Keyword::intern(&s[1..])) - } - _ => UntypedKind::Identifier(id.into()), - }, - TokenKind::EOF => UntypedKind::Error, // Error already logged by advance - _ => { - self.diagnostics.push_error( - format!("Unexpected token in atom: {:?}", token.kind), - Some(identity.clone()), - ); - UntypedKind::Error - } - }; - - UntypedNode { - identity, - kind, - } - } - - fn parse_list(&mut self) -> UntypedNode { - let start_loc = self.advance().location; // consume '(' - let identity = NodeIdentity::new(start_loc); - - if *self.peek() == TokenKind::RightParen { - self.diagnostics.push_error( - "Empty list () is not a valid expression", - Some(identity.clone()), - ); - self.advance(); // consume ) - return UntypedNode { - identity, - kind: UntypedKind::Error, - - }; - } - - let head = self.parse_expression(); - - let node = if let UntypedKind::Identifier(ref sym) = head.kind { - match sym.name.as_ref() { - "if" => self.parse_if(identity), - "fn" => self.parse_fn(identity), - "pipe" => self.parse_pipe(identity), - "again" => self.parse_again(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); - node - } - - fn parse_again(&mut self, identity: Identity) -> UntypedNode { - let mut elements = Vec::new(); - - while *self.peek() != TokenKind::RightParen && *self.peek() != TokenKind::EOF { - elements.push(self.parse_expression()); - } - - let args_node = UntypedNode { - identity: identity.clone(), - kind: UntypedKind::Tuple { elements }, - - }; - - UntypedNode { - identity, - kind: UntypedKind::Again { - args: Box::new(args_node), - }, - - } - } - - fn parse_if(&mut self, identity: Identity) -> UntypedNode { - 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())); - } - - UntypedNode { - identity, - kind: UntypedKind::If { - cond, - then_br, - else_br, - }, - - } - } - - fn parse_def(&mut self, identity: Identity) -> UntypedNode { - let target = Box::new(self.parse_pattern()); - let value = Box::new(self.parse_expression()); - - UntypedNode { - identity, - kind: UntypedKind::Def { target, value }, - - } - } - - fn parse_assign(&mut self, identity: Identity) -> UntypedNode { - // (assign target value) - let target = Box::new(self.parse_expression()); - let value = Box::new(self.parse_expression()); - - UntypedNode { - identity, - kind: UntypedKind::Assign { target, value }, - - } - } - - fn parse_do(&mut self, identity: Identity) -> UntypedNode { - let mut exprs = Vec::new(); - while *self.peek() != TokenKind::RightParen && *self.peek() != TokenKind::EOF { - exprs.push(self.parse_expression()); - } - UntypedNode { - identity, - kind: UntypedKind::Block { exprs }, - - } - } - - fn parse_pipe(&mut self, identity: Identity) -> UntypedNode { - let inputs_node = self.parse_expression(); - let inputs = match inputs_node.kind { - UntypedKind::Tuple { elements } => elements, - _ => vec![inputs_node], - }; - let lambda = Box::new(self.parse_expression()); - - UntypedNode { - identity, - kind: UntypedKind::Pipe { inputs, lambda }, - - } - } - - fn parse_fn(&mut self, identity: Identity) -> UntypedNode { - let params = Box::new(self.parse_param_vector()); - let body = self.parse_expression(); - - UntypedNode { - identity, - kind: UntypedKind::Lambda { - params, - body: Rc::new(body), - }, - - } - } - - fn parse_macro_decl(&mut self, identity: Identity) -> UntypedNode { - let name_node = self.parse_expression(); - let name = match name_node.kind { - UntypedKind::Identifier(sym) => sym, - _ => { - self.diagnostics.push_error( - "Expected identifier for macro name", - Some(name_node.identity.clone()), - ); - Symbol::from("error") - } - }; - let params = Box::new(self.parse_param_vector()); - let body = self.parse_expression(); - UntypedNode { - identity, - kind: UntypedKind::MacroDecl { - name, - params, - body: Box::new(body), - }, - - } - } - - fn parse_param_vector(&mut self) -> UntypedNode { - if *self.peek() != TokenKind::LeftBracket { - self.diagnostics.push_error( - format!( - "Expected parameter vector [...] for fn, found {:?}", - self.peek() - ), - Some(NodeIdentity::new(self.current_token.location)), - ); - return UntypedNode { - identity: NodeIdentity::new(self.current_token.location), - kind: UntypedKind::Error, - - }; - } - self.parse_pattern() - } - - fn parse_pattern(&mut self) -> UntypedNode { - let next = self.peek(); - match next { - TokenKind::Identifier(_) => { - let token = self.advance(); - let sym: Symbol = match token.kind { - TokenKind::Identifier(s) => s.into(), - _ => unreachable!(), - }; - UntypedNode { - identity: NodeIdentity::new(token.location), - kind: UntypedKind::Identifier(sym), - - } - } - TokenKind::LeftBracket => { - let token = self.advance(); - let identity = NodeIdentity::new(token.location); - - let mut elements = Vec::new(); - while *self.peek() != TokenKind::RightBracket && *self.peek() != TokenKind::EOF { - elements.push(self.parse_pattern()); - } - self.expect(TokenKind::RightBracket); - - UntypedNode { - identity, - kind: UntypedKind::Tuple { elements }, - - } - } - TokenKind::Tilde => { - let token = self.advance(); // consume ~ - if *self.peek() == TokenKind::At { - self.advance(); // consume @ - let expr = self.parse_expression(); - UntypedNode { - identity: NodeIdentity::new(token.location), - kind: UntypedKind::Splice(Box::new(expr)), - - } - } else { - let expr = self.parse_expression(); - UntypedNode { - identity: NodeIdentity::new(token.location), - kind: UntypedKind::Placeholder(Box::new(expr)), - - } - } - } - _ => { - self.diagnostics.push_error( - format!( - "Expected identifier or pattern vector [...] for definition, found {:?}", - next - ), - Some(NodeIdentity::new(self.current_token.location)), - ); - self.advance(); - UntypedNode { - identity: NodeIdentity::new(self.current_token.location), - kind: UntypedKind::Error, - - } - } - } - } - - fn parse_call(&mut self, callee: UntypedNode, identity: Identity) -> UntypedNode { - 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 = UntypedNode { - identity: identity.clone(), - kind: UntypedKind::Tuple { elements }, - - }; - - UntypedNode { - identity, - kind: UntypedKind::Call { - callee: Box::new(callee), - args: Box::new(args_node), - }, - - } - } - - fn parse_vector_literal(&mut self) -> UntypedNode { - 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); - - UntypedNode { - identity: NodeIdentity::new(token.location), - kind: UntypedKind::Tuple { elements }, - - } - } - - fn parse_record_literal(&mut self) -> UntypedNode { - let token = self.advance(); - let mut fields = Vec::new(); - - while *self.peek() != TokenKind::RightBrace && *self.peek() != TokenKind::EOF { - let key_node = self.parse_expression(); - // We check for keyword kind here (syntactically) to avoid ambiguity, but - // strictly we could allow any expression and check at runtime. - // Delphi enforces keywords. We can do minimal check here. - match &key_node.kind { - UntypedKind::Constant(Value::Keyword(_)) => {} - _ => { - self.diagnostics.push_error( - "Record keys must be keywords (syntactically)", - Some(key_node.identity.clone()), - ); - } - } - - if *self.peek() == TokenKind::RightBrace || *self.peek() == TokenKind::EOF { - self.diagnostics.push_error( - "Record literal must have even number of forms", - Some(NodeIdentity::new(self.current_token.location)), - ); - break; - } - let val_node = self.parse_expression(); - - fields.push((key_node, val_node)); - } - self.expect(TokenKind::RightBrace); - - UntypedNode { - identity: NodeIdentity::new(token.location), - kind: UntypedKind::Record { fields }, - - } - } - - fn expect(&mut self, kind: TokenKind) -> Token { - if self.peek() == &kind { - self.advance() - } else { - self.diagnostics.push_error( - format!("Expected {:?}, but found {:?}", kind, self.peek()), - Some(NodeIdentity::new(self.current_token.location)), - ); - // Recovery: skip until we find what we expected or a synchronization point - self.synchronize(); - Token { - kind, - location: self.current_token.location, - } - } - } - - fn make_id_node(&self, name: &str, identity: Identity) -> UntypedNode { - UntypedNode { - identity, - kind: UntypedKind::Identifier(Symbol::from(name)), - - } - } -} +use crate::ast::diagnostics::Diagnostics; +use crate::ast::lexer::{Lexer, Token, TokenKind}; +use crate::ast::nodes::{Symbol, SyntaxKind, SyntaxNode}; +use crate::ast::types::{Identity, Keyword, NodeIdentity, Value}; +use std::rc::Rc; + +pub struct Parser<'a> { + lexer: Lexer<'a>, + current_token: Token, + pub diagnostics: Diagnostics, +} + +impl<'a> Parser<'a> { + pub fn new(input: &'a str) -> Self { + let mut lexer = Lexer::new(input); + let mut diagnostics = Diagnostics::new(); + let current_token = match lexer.next_token() { + Ok(t) => t, + Err(e) => { + diagnostics.push_error(e, None); + Token { + kind: TokenKind::EOF, + location: crate::ast::types::SourceLocation { line: 1, col: 1 }, + } + } + }; + Self { + lexer, + current_token, + diagnostics, + } + } + + fn advance(&mut self) -> Token { + let next = match self.lexer.next_token() { + Ok(t) => t, + Err(e) => { + self.diagnostics + .push_error(e, Some(NodeIdentity::new(self.current_token.location))); + Token { + kind: TokenKind::EOF, + location: self.current_token.location, + } + } + }; + std::mem::replace(&mut self.current_token, next) + } + + fn peek(&self) -> &TokenKind { + &self.current_token.kind + } + + pub fn parse_expression(&mut self) -> SyntaxNode { + let token_loc = self.current_token.location; + let identity = NodeIdentity::new(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(); + SyntaxNode { + identity: identity.clone(), + kind: SyntaxKind::Call { + callee: Box::new(self.make_id_node("quote", identity.clone())), + args: Box::new(SyntaxNode { + identity, + kind: SyntaxKind::Tuple { + elements: vec![expr], + }, + + }), + }, + + } + } + TokenKind::Backtick => { + self.advance(); // consume ` + let expr = self.parse_expression(); + SyntaxNode { + identity, + kind: SyntaxKind::Template(Box::new(expr)), + + } + } + TokenKind::Tilde => { + self.advance(); // consume ~ + if *self.peek() == TokenKind::At { + self.advance(); // consume @ + let expr = self.parse_expression(); + SyntaxNode { + identity, + kind: SyntaxKind::Splice(Box::new(expr)), + + } + } else { + let expr = self.parse_expression(); + SyntaxNode { + identity, + kind: SyntaxKind::Placeholder(Box::new(expr)), + + } + } + } + _ => self.parse_atom(), + } + } + + pub fn at_eof(&self) -> bool { + matches!(self.current_token.kind, TokenKind::EOF) + } + + fn synchronize(&mut self) { + while !self.at_eof() { + match self.peek() { + TokenKind::RightParen | TokenKind::RightBracket | TokenKind::RightBrace => { + self.advance(); + return; + } + _ => { + self.advance(); + } + } + } + } + + fn parse_atom(&mut self) -> SyntaxNode { + let token = self.advance(); + let identity = NodeIdentity::new(token.location); + + let kind = match token.kind { + TokenKind::Integer(n) => SyntaxKind::Constant(Value::Int(n)), + TokenKind::Float(n) => SyntaxKind::Constant(Value::Float(n)), + TokenKind::String(s) => SyntaxKind::Constant(Value::Text(s)), + TokenKind::Keyword(k) => SyntaxKind::Constant(Value::Keyword(Keyword::intern(&k))), + TokenKind::Identifier(id) => match id.as_ref() { + "..." => SyntaxKind::Nop, + s if s.starts_with('.') && s.len() > 1 => { + SyntaxKind::FieldAccessor(Keyword::intern(&s[1..])) + } + _ => SyntaxKind::Identifier(id.into()), + }, + TokenKind::EOF => SyntaxKind::Error, // Error already logged by advance + _ => { + self.diagnostics.push_error( + format!("Unexpected token in atom: {:?}", token.kind), + Some(identity.clone()), + ); + SyntaxKind::Error + } + }; + + SyntaxNode { + identity, + kind, + } + } + + fn parse_list(&mut self) -> SyntaxNode { + let start_loc = self.advance().location; // consume '(' + let identity = NodeIdentity::new(start_loc); + + if *self.peek() == TokenKind::RightParen { + self.diagnostics.push_error( + "Empty list () is not a valid expression", + Some(identity.clone()), + ); + self.advance(); // consume ) + return SyntaxNode { + identity, + kind: SyntaxKind::Error, + + }; + } + + let head = self.parse_expression(); + + let node = if let SyntaxKind::Identifier(ref sym) = head.kind { + match sym.name.as_ref() { + "if" => self.parse_if(identity), + "fn" => self.parse_fn(identity), + "pipe" => self.parse_pipe(identity), + "again" => self.parse_again(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); + node + } + + fn parse_again(&mut self, identity: Identity) -> SyntaxNode { + let mut elements = Vec::new(); + + while *self.peek() != TokenKind::RightParen && *self.peek() != TokenKind::EOF { + elements.push(self.parse_expression()); + } + + let args_node = SyntaxNode { + identity: identity.clone(), + kind: SyntaxKind::Tuple { elements }, + + }; + + SyntaxNode { + identity, + kind: SyntaxKind::Again { + args: Box::new(args_node), + }, + + } + } + + fn parse_if(&mut self, identity: Identity) -> SyntaxNode { + 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())); + } + + SyntaxNode { + identity, + kind: SyntaxKind::If { + cond, + then_br, + else_br, + }, + + } + } + + fn parse_def(&mut self, identity: Identity) -> SyntaxNode { + let target = Box::new(self.parse_pattern()); + let value = Box::new(self.parse_expression()); + + SyntaxNode { + identity, + kind: SyntaxKind::Def { target, value }, + + } + } + + fn parse_assign(&mut self, identity: Identity) -> SyntaxNode { + // (assign target value) + let target = Box::new(self.parse_expression()); + let value = Box::new(self.parse_expression()); + + SyntaxNode { + identity, + kind: SyntaxKind::Assign { target, value }, + + } + } + + fn parse_do(&mut self, identity: Identity) -> SyntaxNode { + let mut exprs = Vec::new(); + while *self.peek() != TokenKind::RightParen && *self.peek() != TokenKind::EOF { + exprs.push(self.parse_expression()); + } + SyntaxNode { + identity, + kind: SyntaxKind::Block { exprs }, + + } + } + + fn parse_pipe(&mut self, identity: Identity) -> SyntaxNode { + let inputs_node = self.parse_expression(); + let inputs = match inputs_node.kind { + SyntaxKind::Tuple { elements } => elements, + _ => vec![inputs_node], + }; + let lambda = Box::new(self.parse_expression()); + + SyntaxNode { + identity, + kind: SyntaxKind::Pipe { inputs, lambda }, + + } + } + + fn parse_fn(&mut self, identity: Identity) -> SyntaxNode { + let params = Box::new(self.parse_param_vector()); + let body = self.parse_expression(); + + SyntaxNode { + identity, + kind: SyntaxKind::Lambda { + params, + body: Rc::new(body), + }, + + } + } + + fn parse_macro_decl(&mut self, identity: Identity) -> SyntaxNode { + let name_node = self.parse_expression(); + let name = match name_node.kind { + SyntaxKind::Identifier(sym) => sym, + _ => { + self.diagnostics.push_error( + "Expected identifier for macro name", + Some(name_node.identity.clone()), + ); + Symbol::from("error") + } + }; + let params = Box::new(self.parse_param_vector()); + let body = self.parse_expression(); + SyntaxNode { + identity, + kind: SyntaxKind::MacroDecl { + name, + params, + body: Box::new(body), + }, + + } + } + + fn parse_param_vector(&mut self) -> SyntaxNode { + if *self.peek() != TokenKind::LeftBracket { + self.diagnostics.push_error( + format!( + "Expected parameter vector [...] for fn, found {:?}", + self.peek() + ), + Some(NodeIdentity::new(self.current_token.location)), + ); + return SyntaxNode { + identity: NodeIdentity::new(self.current_token.location), + kind: SyntaxKind::Error, + + }; + } + self.parse_pattern() + } + + fn parse_pattern(&mut self) -> SyntaxNode { + let next = self.peek(); + match next { + TokenKind::Identifier(_) => { + let token = self.advance(); + let sym: Symbol = match token.kind { + TokenKind::Identifier(s) => s.into(), + _ => unreachable!(), + }; + SyntaxNode { + identity: NodeIdentity::new(token.location), + kind: SyntaxKind::Identifier(sym), + + } + } + TokenKind::LeftBracket => { + let token = self.advance(); + let identity = NodeIdentity::new(token.location); + + let mut elements = Vec::new(); + while *self.peek() != TokenKind::RightBracket && *self.peek() != TokenKind::EOF { + elements.push(self.parse_pattern()); + } + self.expect(TokenKind::RightBracket); + + SyntaxNode { + identity, + kind: SyntaxKind::Tuple { elements }, + + } + } + TokenKind::Tilde => { + let token = self.advance(); // consume ~ + if *self.peek() == TokenKind::At { + self.advance(); // consume @ + let expr = self.parse_expression(); + SyntaxNode { + identity: NodeIdentity::new(token.location), + kind: SyntaxKind::Splice(Box::new(expr)), + + } + } else { + let expr = self.parse_expression(); + SyntaxNode { + identity: NodeIdentity::new(token.location), + kind: SyntaxKind::Placeholder(Box::new(expr)), + + } + } + } + _ => { + self.diagnostics.push_error( + format!( + "Expected identifier or pattern vector [...] for definition, found {:?}", + next + ), + Some(NodeIdentity::new(self.current_token.location)), + ); + self.advance(); + SyntaxNode { + identity: NodeIdentity::new(self.current_token.location), + kind: SyntaxKind::Error, + + } + } + } + } + + fn parse_call(&mut self, callee: SyntaxNode, identity: Identity) -> SyntaxNode { + 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 = SyntaxNode { + identity: identity.clone(), + kind: SyntaxKind::Tuple { elements }, + + }; + + SyntaxNode { + identity, + kind: SyntaxKind::Call { + callee: Box::new(callee), + args: Box::new(args_node), + }, + + } + } + + fn parse_vector_literal(&mut self) -> SyntaxNode { + 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); + + SyntaxNode { + identity: NodeIdentity::new(token.location), + kind: SyntaxKind::Tuple { elements }, + + } + } + + fn parse_record_literal(&mut self) -> SyntaxNode { + let token = self.advance(); + let mut fields = Vec::new(); + + while *self.peek() != TokenKind::RightBrace && *self.peek() != TokenKind::EOF { + let key_node = self.parse_expression(); + // We check for keyword kind here (syntactically) to avoid ambiguity, but + // strictly we could allow any expression and check at runtime. + // Delphi enforces keywords. We can do minimal check here. + match &key_node.kind { + SyntaxKind::Constant(Value::Keyword(_)) => {} + _ => { + self.diagnostics.push_error( + "Record keys must be keywords (syntactically)", + Some(key_node.identity.clone()), + ); + } + } + + if *self.peek() == TokenKind::RightBrace || *self.peek() == TokenKind::EOF { + self.diagnostics.push_error( + "Record literal must have even number of forms", + Some(NodeIdentity::new(self.current_token.location)), + ); + break; + } + let val_node = self.parse_expression(); + + fields.push((key_node, val_node)); + } + self.expect(TokenKind::RightBrace); + + SyntaxNode { + identity: NodeIdentity::new(token.location), + kind: SyntaxKind::Record { fields }, + + } + } + + fn expect(&mut self, kind: TokenKind) -> Token { + if self.peek() == &kind { + self.advance() + } else { + self.diagnostics.push_error( + format!("Expected {:?}, but found {:?}", kind, self.peek()), + Some(NodeIdentity::new(self.current_token.location)), + ); + // Recovery: skip until we find what we expected or a synchronization point + self.synchronize(); + Token { + kind, + location: self.current_token.location, + } + } + } + + fn make_id_node(&self, name: &str, identity: Identity) -> SyntaxNode { + SyntaxNode { + identity, + kind: SyntaxKind::Identifier(Symbol::from(name)), + + } + } +} diff --git a/src/ast/rtl/core.rs b/src/ast/rtl/core.rs index 31259d1..5b93c7b 100644 --- a/src/ast/rtl/core.rs +++ b/src/ast/rtl/core.rs @@ -1,509 +1,509 @@ -use crate::ast::environment::Environment; -use crate::ast::types::{Purity, Signature, StaticType, Value}; -use std::rc::Rc; - -pub fn register(env: &Environment) { - register_constants(env); - register_arithmetic(env); - register_comparison(env); - register_logic(env); - register_io(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, - }, - // Variadic - Signature { - params: StaticType::Any, - ret: StaticType::Any, - }, - ]); - env.register_native_fn("+", add_ty, Purity::Pure, |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, - }, - // Variadic - Signature { - params: StaticType::Any, - ret: StaticType::Any, - }, - ]); - env.register_native_fn("-", sub_ty, Purity::Pure, |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, - }, - // Variadic - Signature { - params: StaticType::Any, - ret: StaticType::Any, - }, - ]); - env.register_native_fn("*", mul_ty, Purity::Pure, |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_fn("/", div_ty, Purity::Pure, |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_fn("//", int_div_ty, Purity::Pure, |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_fn("%", mod_ty, Purity::Pure, |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_fn(">", cmp_ty.clone(), Purity::Pure, |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_fn("<", cmp_ty.clone(), Purity::Pure, |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_fn(">=", cmp_ty.clone(), Purity::Pure, |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_fn("<=", cmp_ty.clone(), Purity::Pure, |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_fn("=", eq_ty.clone(), Purity::Pure, |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::Record(la, va), Value::Record(lb, vb)) => { - Value::Bool(std::sync::Arc::ptr_eq(la, lb) && va == vb) - } - (Value::Void, Value::Void) => Value::Bool(true), - _ => Value::Bool(false), - } - }); - - // --- Not Equal (<>) --- - env.register_native_fn("<>", eq_ty, Purity::Pure, |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_fn("not", not_ty, Purity::Pure, |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_fn("and", logic_op_ty.clone(), Purity::Pure, |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_fn("or", logic_op_ty.clone(), Purity::Pure, |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_fn("xor", logic_op_ty.clone(), Purity::Pure, |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_fn("<<", shift_ty.clone(), Purity::Pure, |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_fn(">>", shift_ty, Purity::Pure, |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, - } - }); -} - -fn register_io(env: &Environment) { - let print_ty = StaticType::Function(Box::new(Signature { - params: StaticType::Any, - ret: StaticType::Void, - })); - env.register_native_fn("print", print_ty, Purity::Impure, |args| { - for arg in args { - match arg { - Value::Text(t) => print!("{}", t), - _ => print!("{}", arg), - } - } - println!(); - Value::Void - }); -} +use crate::ast::environment::Environment; +use crate::ast::types::{Purity, Signature, StaticType, Value}; +use std::rc::Rc; + +pub fn register(env: &Environment) { + register_constants(env); + register_arithmetic(env); + register_comparison(env); + register_logic(env); + register_io(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, + }, + // Variadic + Signature { + params: StaticType::Any, + ret: StaticType::Any, + }, + ]); + env.register_native_fn("+", add_ty, Purity::Pure, |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, + }, + // Variadic + Signature { + params: StaticType::Any, + ret: StaticType::Any, + }, + ]); + env.register_native_fn("-", sub_ty, Purity::Pure, |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, + }, + // Variadic + Signature { + params: StaticType::Any, + ret: StaticType::Any, + }, + ]); + env.register_native_fn("*", mul_ty, Purity::Pure, |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_fn("/", div_ty, Purity::Pure, |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_fn("//", int_div_ty, Purity::Pure, |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_fn("%", mod_ty, Purity::Pure, |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_fn(">", cmp_ty.clone(), Purity::Pure, |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_fn("<", cmp_ty.clone(), Purity::Pure, |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_fn(">=", cmp_ty.clone(), Purity::Pure, |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_fn("<=", cmp_ty.clone(), Purity::Pure, |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_fn("=", eq_ty.clone(), Purity::Pure, |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::Record(la, va), Value::Record(lb, vb)) => { + Value::Bool(std::sync::Arc::ptr_eq(la, lb) && va == vb) + } + (Value::Void, Value::Void) => Value::Bool(true), + _ => Value::Bool(false), + } + }); + + // --- Not Equal (<>) --- + env.register_native_fn("<>", eq_ty, Purity::Pure, |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_fn("not", not_ty, Purity::Pure, |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_fn("and", logic_op_ty.clone(), Purity::Pure, |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_fn("or", logic_op_ty.clone(), Purity::Pure, |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_fn("xor", logic_op_ty.clone(), Purity::Pure, |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_fn("<<", shift_ty.clone(), Purity::Pure, |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_fn(">>", shift_ty, Purity::Pure, |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, + } + }); +} + +fn register_io(env: &Environment) { + let print_ty = StaticType::Function(Box::new(Signature { + params: StaticType::Any, + ret: StaticType::Void, + })); + env.register_native_fn("print", print_ty, Purity::Impure, |args| { + for arg in args { + match arg { + Value::Text(t) => print!("{}", t), + _ => print!("{}", arg), + } + } + println!(); + Value::Void + }); +} diff --git a/src/ast/rtl/datetime.rs b/src/ast/rtl/datetime.rs index 6c00313..27160df 100644 --- a/src/ast/rtl/datetime.rs +++ b/src/ast/rtl/datetime.rs @@ -1,39 +1,39 @@ -use crate::ast::environment::Environment; -use crate::ast::types::{Purity, 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_fn("date", date_ty, Purity::Pure, |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 - }); - - let now_ty = StaticType::Function(Box::new(Signature { - params: StaticType::Tuple(vec![]), - ret: StaticType::DateTime, - })); - - env.register_native_fn("now", now_ty, Purity::SideEffectFree, |_| { - let ts = chrono::Utc::now().timestamp_millis(); - Value::DateTime(ts) - }); -} +use crate::ast::environment::Environment; +use crate::ast::types::{Purity, 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_fn("date", date_ty, Purity::Pure, |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 + }); + + let now_ty = StaticType::Function(Box::new(Signature { + params: StaticType::Tuple(vec![]), + ret: StaticType::DateTime, + })); + + env.register_native_fn("now", now_ty, Purity::SideEffectFree, |_| { + let ts = chrono::Utc::now().timestamp_millis(); + Value::DateTime(ts) + }); +} diff --git a/src/ast/rtl/intrinsics.rs b/src/ast/rtl/intrinsics.rs index 01e0891..6f53104 100644 --- a/src/ast/rtl/intrinsics.rs +++ b/src/ast/rtl/intrinsics.rs @@ -1,115 +1,115 @@ -use crate::ast::types::{Purity, StaticType, Value}; - -/// 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::make_function(Purity::Pure, |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::make_function(Purity::Pure, |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::make_function(Purity::Pure, |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::make_function(Purity::Pure, |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::make_function(Purity::Pure, |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::make_function(Purity::Pure, |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::make_function(Purity::Pure, |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::make_function(Purity::Pure, |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::make_function(Purity::Pure, |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::{Purity, StaticType, Value}; + +/// 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::make_function(Purity::Pure, |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::make_function(Purity::Pure, |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::make_function(Purity::Pure, |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::make_function(Purity::Pure, |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::make_function(Purity::Pure, |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::make_function(Purity::Pure, |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::make_function(Purity::Pure, |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::make_function(Purity::Pure, |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::make_function(Purity::Pure, |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/math.rs b/src/ast/rtl/math.rs index 59c709e..0250b43 100644 --- a/src/ast/rtl/math.rs +++ b/src/ast/rtl/math.rs @@ -1,176 +1,176 @@ -use crate::ast::environment::Environment; -use crate::ast::types::{Purity, Signature, StaticType, Value}; -use std::f64::consts; - -pub fn register(env: &Environment) { - // Constants - env.register_constant("PI", StaticType::Float, Value::Float(consts::PI)); - env.register_constant("E", StaticType::Float, Value::Float(consts::E)); - - // Helper to get f64 from Value (Int or Float) - fn to_f64(v: &Value) -> Option { - match v { - Value::Float(f) => Some(*f), - Value::Int(i) => Some(*i as f64), - _ => None, - } - } - - // Unary functions (float -> float) - let register_unary = |name: &'static str, f: fn(f64) -> f64| { - let ty = StaticType::Function(Box::new(Signature { - params: StaticType::Tuple(vec![StaticType::Float]), - ret: StaticType::Float, - })); - env.register_native_fn(name, ty, Purity::Pure, move |args| { - if let Some(x) = to_f64(&args[0]) { - Value::Float(f(x)) - } else { - Value::Void - } - }); - }; - - // Unary functions (float -> int) - let register_to_int = |name: &'static str, f: fn(f64) -> f64| { - let ty = StaticType::Function(Box::new(Signature { - params: StaticType::Tuple(vec![StaticType::Float]), - ret: StaticType::Int, - })); - env.register_native_fn(name, ty, Purity::Pure, move |args| { - if let Some(x) = to_f64(&args[0]) { - Value::Int(f(x) as i64) - } else { - Value::Void - } - }); - }; - - register_unary("sqrt", f64::sqrt); - register_unary("sin", f64::sin); - register_unary("cos", f64::cos); - register_unary("tan", f64::tan); - register_unary("asin", f64::asin); - register_unary("acos", f64::acos); - register_unary("atan", f64::atan); - register_unary("exp", f64::exp); - register_unary("ln", f64::ln); - register_unary("log10", f64::log10); - register_unary("fract", f64::fract); - - // Rounding functions returning Int - register_to_int("round", f64::round); - register_to_int("floor", f64::floor); - register_to_int("ceil", f64::ceil); - register_to_int("trunc", f64::trunc); - - // Binary functions (float, float -> float) - let register_binary = |name: &'static str, f: fn(f64, f64) -> f64| { - let ty = StaticType::Function(Box::new(Signature { - params: StaticType::Tuple(vec![StaticType::Float, StaticType::Float]), - ret: StaticType::Float, - })); - env.register_native_fn(name, ty, Purity::Pure, move |args| { - if let (Some(a), Some(b)) = (to_f64(&args[0]), to_f64(&args[1])) { - Value::Float(f(a, b)) - } else { - Value::Void - } - }); - }; - - register_binary("atan2", f64::atan2); - register_binary("pow", f64::powf); - register_binary("log", f64::log); - - // Specialized abs to handle Int -> Int, Float -> Float - let abs_ty = StaticType::Function(Box::new(Signature { - params: StaticType::Tuple(vec![StaticType::Any]), - ret: StaticType::Any, - })); - env.register_native_fn("abs", abs_ty, Purity::Pure, |args| { - if args.len() != 1 { - return Value::Void; - } - match args[0] { - Value::Int(i) => Value::Int(i.abs()), - Value::Float(f) => Value::Float(f.abs()), - _ => Value::Void, - } - }); - - // Variadic min / max - let variadic_ty = StaticType::Function(Box::new(Signature { - params: StaticType::Any, - ret: StaticType::Any, - })); - - env.register_native_fn("min", variadic_ty.clone(), Purity::Pure, |args| { - if args.is_empty() { - return Value::Void; - } - let mut best = args[0].clone(); - for arg in &args[1..] { - if let Some(ord) = arg.partial_cmp(&best) - && ord == std::cmp::Ordering::Less - { - best = arg.clone(); - } - } - best - }); - - env.register_native_fn("max", variadic_ty, Purity::Pure, |args| { - if args.is_empty() { - return Value::Void; - } - let mut best = args[0].clone(); - for arg in &args[1..] { - if let Some(ord) = arg.partial_cmp(&best) - && ord == std::cmp::Ordering::Greater - { - best = arg.clone(); - } - } - best - }); - - // Random generator factory (Closure-based) - let generator_sig = Signature { - params: StaticType::Tuple(vec![]), - ret: StaticType::Float, - }; - - let make_random_ty = StaticType::FunctionOverloads(vec![ - Signature { - params: StaticType::Tuple(vec![]), - ret: StaticType::Function(Box::new(generator_sig.clone())), - }, - Signature { - params: StaticType::Tuple(vec![StaticType::Int]), - ret: StaticType::Function(Box::new(generator_sig)), - }, - ]); - - env.register_native_fn( - "make-random", - make_random_ty, - Purity::SideEffectFree, - |args| { - let rng = if args.is_empty() { - fastrand::Rng::new() - } else if let Value::Int(s) = args[0] { - fastrand::Rng::with_seed(s as u64) - } else { - fastrand::Rng::new() - }; - - let prng = std::rc::Rc::new(std::cell::RefCell::new(rng)); - - Value::Function(std::rc::Rc::new(crate::ast::types::NativeFunction { - func: std::rc::Rc::new(move |_args| Value::Float(prng.borrow_mut().f64())), - purity: Purity::Impure, - })) - }, - ); -} +use crate::ast::environment::Environment; +use crate::ast::types::{Purity, Signature, StaticType, Value}; +use std::f64::consts; + +pub fn register(env: &Environment) { + // Constants + env.register_constant("PI", StaticType::Float, Value::Float(consts::PI)); + env.register_constant("E", StaticType::Float, Value::Float(consts::E)); + + // Helper to get f64 from Value (Int or Float) + fn to_f64(v: &Value) -> Option { + match v { + Value::Float(f) => Some(*f), + Value::Int(i) => Some(*i as f64), + _ => None, + } + } + + // Unary functions (float -> float) + let register_unary = |name: &'static str, f: fn(f64) -> f64| { + let ty = StaticType::Function(Box::new(Signature { + params: StaticType::Tuple(vec![StaticType::Float]), + ret: StaticType::Float, + })); + env.register_native_fn(name, ty, Purity::Pure, move |args| { + if let Some(x) = to_f64(&args[0]) { + Value::Float(f(x)) + } else { + Value::Void + } + }); + }; + + // Unary functions (float -> int) + let register_to_int = |name: &'static str, f: fn(f64) -> f64| { + let ty = StaticType::Function(Box::new(Signature { + params: StaticType::Tuple(vec![StaticType::Float]), + ret: StaticType::Int, + })); + env.register_native_fn(name, ty, Purity::Pure, move |args| { + if let Some(x) = to_f64(&args[0]) { + Value::Int(f(x) as i64) + } else { + Value::Void + } + }); + }; + + register_unary("sqrt", f64::sqrt); + register_unary("sin", f64::sin); + register_unary("cos", f64::cos); + register_unary("tan", f64::tan); + register_unary("asin", f64::asin); + register_unary("acos", f64::acos); + register_unary("atan", f64::atan); + register_unary("exp", f64::exp); + register_unary("ln", f64::ln); + register_unary("log10", f64::log10); + register_unary("fract", f64::fract); + + // Rounding functions returning Int + register_to_int("round", f64::round); + register_to_int("floor", f64::floor); + register_to_int("ceil", f64::ceil); + register_to_int("trunc", f64::trunc); + + // Binary functions (float, float -> float) + let register_binary = |name: &'static str, f: fn(f64, f64) -> f64| { + let ty = StaticType::Function(Box::new(Signature { + params: StaticType::Tuple(vec![StaticType::Float, StaticType::Float]), + ret: StaticType::Float, + })); + env.register_native_fn(name, ty, Purity::Pure, move |args| { + if let (Some(a), Some(b)) = (to_f64(&args[0]), to_f64(&args[1])) { + Value::Float(f(a, b)) + } else { + Value::Void + } + }); + }; + + register_binary("atan2", f64::atan2); + register_binary("pow", f64::powf); + register_binary("log", f64::log); + + // Specialized abs to handle Int -> Int, Float -> Float + let abs_ty = StaticType::Function(Box::new(Signature { + params: StaticType::Tuple(vec![StaticType::Any]), + ret: StaticType::Any, + })); + env.register_native_fn("abs", abs_ty, Purity::Pure, |args| { + if args.len() != 1 { + return Value::Void; + } + match args[0] { + Value::Int(i) => Value::Int(i.abs()), + Value::Float(f) => Value::Float(f.abs()), + _ => Value::Void, + } + }); + + // Variadic min / max + let variadic_ty = StaticType::Function(Box::new(Signature { + params: StaticType::Any, + ret: StaticType::Any, + })); + + env.register_native_fn("min", variadic_ty.clone(), Purity::Pure, |args| { + if args.is_empty() { + return Value::Void; + } + let mut best = args[0].clone(); + for arg in &args[1..] { + if let Some(ord) = arg.partial_cmp(&best) + && ord == std::cmp::Ordering::Less + { + best = arg.clone(); + } + } + best + }); + + env.register_native_fn("max", variadic_ty, Purity::Pure, |args| { + if args.is_empty() { + return Value::Void; + } + let mut best = args[0].clone(); + for arg in &args[1..] { + if let Some(ord) = arg.partial_cmp(&best) + && ord == std::cmp::Ordering::Greater + { + best = arg.clone(); + } + } + best + }); + + // Random generator factory (Closure-based) + let generator_sig = Signature { + params: StaticType::Tuple(vec![]), + ret: StaticType::Float, + }; + + let make_random_ty = StaticType::FunctionOverloads(vec![ + Signature { + params: StaticType::Tuple(vec![]), + ret: StaticType::Function(Box::new(generator_sig.clone())), + }, + Signature { + params: StaticType::Tuple(vec![StaticType::Int]), + ret: StaticType::Function(Box::new(generator_sig)), + }, + ]); + + env.register_native_fn( + "make-random", + make_random_ty, + Purity::SideEffectFree, + |args| { + let rng = if args.is_empty() { + fastrand::Rng::new() + } else if let Value::Int(s) = args[0] { + fastrand::Rng::with_seed(s as u64) + } else { + fastrand::Rng::new() + }; + + let prng = std::rc::Rc::new(std::cell::RefCell::new(rng)); + + Value::Function(std::rc::Rc::new(crate::ast::types::NativeFunction { + func: std::rc::Rc::new(move |_args| Value::Float(prng.borrow_mut().f64())), + purity: Purity::Impure, + })) + }, + ); +} diff --git a/src/ast/rtl/mod.rs b/src/ast/rtl/mod.rs index 1a0cd78..d87e6ee 100644 --- a/src/ast/rtl/mod.rs +++ b/src/ast/rtl/mod.rs @@ -1,17 +1,17 @@ -pub mod core; -pub mod datetime; -pub mod intrinsics; -pub mod math; -pub mod series; -pub mod streams; -pub mod type_registry; - -use crate::ast::environment::Environment; - -pub fn register(env: &Environment) { - core::register(env); - datetime::register(env); - math::register(env); - series::register(env); - streams::register(env); -} +pub mod core; +pub mod datetime; +pub mod intrinsics; +pub mod math; +pub mod series; +pub mod streams; +pub mod type_registry; + +use crate::ast::environment::Environment; + +pub fn register(env: &Environment) { + core::register(env); + datetime::register(env); + math::register(env); + series::register(env); + streams::register(env); +} diff --git a/src/ast/rtl/series.rs b/src/ast/rtl/series.rs index cb2b382..0dbd0be 100644 --- a/src/ast/rtl/series.rs +++ b/src/ast/rtl/series.rs @@ -1,648 +1,648 @@ -use std::any::Any; -use std::collections::VecDeque; -use std::fmt::{self, Debug}; -use std::rc::Rc; -use std::sync::Arc; - -use crate::ast::environment::Environment; -use crate::ast::types::{Keyword, Object, RecordLayout, Series, StaticType, Value}; -use crate::ast::types::{Purity, Signature}; - -// ============================================================================ -// 1. Core Storage Engine -// ============================================================================ - -/// A generic, efficient ring buffer for time series data. -/// (Replaces Delphi's TChunkArray for now with VecDeque for O(1) push/pop) -#[derive(Clone)] -pub struct RingBuffer { - buffer: VecDeque, - total_count: u64, - lookback_limit: usize, -} - -impl RingBuffer { - pub fn new(lookback_limit: usize) -> Self { - // Pre-allocate up to 500 elements to minimize heap fragmentation - let initial_capacity = std::cmp::min(lookback_limit, 500); - Self { - buffer: VecDeque::with_capacity(initial_capacity), - total_count: 0, - lookback_limit, - } - } - - /// Adds an element. Removes the oldest if the lookback limit is reached. - #[inline] - pub fn push(&mut self, item: T) { - self.buffer.push_back(item); - self.total_count += 1; - - while self.buffer.len() > self.lookback_limit { - self.buffer.pop_front(); - } - } - - /// Access in "Financial Style": Index 0 is the newest element. - #[inline] - pub fn get(&self, index: usize) -> Option<&T> { - let len = self.buffer.len(); - if index < len { - self.buffer.get(len - 1 - index) - } else { - None - } - } - - #[inline] - pub fn total_count(&self) -> u64 { - self.total_count - } - - #[inline] - pub fn len(&self) -> usize { - self.buffer.len() - } - - pub fn is_empty(&self) -> bool { - self.buffer.is_empty() - } -} - -// ============================================================================ -// 2. Trait & Primitives for Fast-Paths -// ============================================================================ - -/// Marker trait ensuring our fast arrays only store true, flat scalars -/// (No pointers/heaps like String or Record). -pub trait ScalarValue: Copy + Debug + 'static { - fn to_value(self) -> Value; -} - -impl ScalarValue for f64 { - fn to_value(self) -> Value { - Value::Float(self) - } -} -impl ScalarValue for i64 { - fn to_value(self) -> Value { - Value::Int(self) - } -} -impl ScalarValue for bool { - fn to_value(self) -> Value { - Value::Bool(self) - } -} - -/// A highly optimized, type-safe series for primitive values (Float, Int, Bool). -#[derive(Clone)] -pub struct ScalarSeries { - pub data: std::cell::RefCell>, - pub type_name: &'static str, -} - -impl ScalarSeries { - pub fn new(type_name: &'static str, lookback_limit: usize) -> Self { - Self { - data: std::cell::RefCell::new(RingBuffer::new(lookback_limit)), - type_name, - } - } -} - -impl Debug for ScalarSeries { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "ScalarSeries<{}>[len: {}]", - self.type_name, - self.data.borrow().len() - ) - } -} - -// Makes the series usable as a dynamic Object for the VM. -impl Object for ScalarSeries { - fn type_name(&self) -> &'static str { - self.type_name - } - fn as_any(&self) -> &dyn Any { - self - } - fn as_series(&self) -> Option<&dyn Series> { - Some(self) - } -} - -impl Series for ScalarSeries { - fn get_item(&self, index: usize) -> Option { - self.data.borrow().get(index).map(|&v| v.to_value()) - } - fn len(&self) -> usize { - self.data.borrow().len() - } - fn total_count(&self) -> u64 { - self.data.borrow().total_count() - } -} - -// ============================================================================ -// 2b. Fallback Series for non-scalar types -// ============================================================================ - -/// A generic series for all non-scalar types (Strings, Tuples, etc.) or mixed data. -#[derive(Clone)] -pub struct ValueSeries { - pub data: std::cell::RefCell>, -} - -impl ValueSeries { - pub fn new(lookback_limit: usize) -> Self { - Self { - data: std::cell::RefCell::new(RingBuffer::new(lookback_limit)), - } - } -} - -impl Debug for ValueSeries { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "ValueSeries[len: {}]", self.data.borrow().len()) - } -} - -impl Object for ValueSeries { - fn type_name(&self) -> &'static str { - "ValueSeries" - } - fn as_any(&self) -> &dyn Any { - self - } - fn as_series(&self) -> Option<&dyn Series> { - Some(self) - } -} - -impl Series for ValueSeries { - fn get_item(&self, index: usize) -> Option { - self.data.borrow().get(index).cloned() - } - fn len(&self) -> usize { - self.data.borrow().len() - } - fn total_count(&self) -> u64 { - self.data.borrow().total_count() - } -} - -// ============================================================================ -// 3. RecordSeries (SoA - Struct of Arrays) -// ============================================================================ - -/// An interface allowing iteration over fields of a RecordSeries independently -/// of the exact type (Type Erasure for member arrays). -pub trait SeriesMember: Object + Series { - /// Pushes a dynamic Value into the series, performing the necessary downcast. - fn push_value(&self, value: Value); -} - -impl SeriesMember for ScalarSeries { - fn push_value(&self, value: Value) { - if let Some(v) = value.as_float() { - self.data.borrow_mut().push(v); - } - } -} - -impl SeriesMember for ScalarSeries { - fn push_value(&self, value: Value) { - if let Some(v) = value.as_int() { - self.data.borrow_mut().push(v); - } - } -} - -impl SeriesMember for ScalarSeries { - fn push_value(&self, value: Value) { - if let Value::Bool(v) = value { - self.data.borrow_mut().push(v); - } - } -} - -impl SeriesMember for ValueSeries { - fn push_value(&self, value: Value) { - self.data.borrow_mut().push(value); - } -} - -/// The "Struct of Arrays" implementation for Records (e.g., Ticks). -/// Instead of holding a large array of Records (AoS), this series -/// splits the record and stores each field in a separate (parallel) series. -#[derive(Clone)] -pub struct RecordSeries { - layout: Arc, - /// Each field of the record gets its own series (SoA). - /// We use Rc so we can iterate fields individually, - /// but also pass them as 0-Copy references to indicators! - fields: Vec>>, -} - -impl RecordSeries { - /// Creates a new RecordSeries matching the given layout. - pub fn new(layout: Arc, lookback_limit: usize) -> Self { - let mut fields: Vec>> = - Vec::with_capacity(layout.fields.len()); - - for (_, static_type) in &layout.fields { - // Automatically select the optimal storage representation based on the field's static type. - let member: Rc> = match static_type { - StaticType::Float => Rc::new(std::cell::RefCell::new(ScalarSeries::::new( - "FloatSeries", - lookback_limit, - ))), - StaticType::Int | StaticType::DateTime => Rc::new(std::cell::RefCell::new( - ScalarSeries::::new("IntSeries", lookback_limit), - )), - StaticType::Bool => Rc::new(std::cell::RefCell::new(ScalarSeries::::new( - "BoolSeries", - lookback_limit, - ))), - // Fallback for everything else (Text, Lists, nested Records without SoA, etc.) - _ => Rc::new(std::cell::RefCell::new(ValueSeries::new(lookback_limit))), - }; - fields.push(member); - } - - Self { layout, fields } - } - - /// The magical 0-Copy Field Mapper! - pub fn field(&self, key: Keyword) -> Option>> { - self.layout - .index_of(key) - .map(|idx| self.fields[idx].clone()) - } - - /// Dynamically reconstructs the full Record at the given lookback index. - pub fn get_record(&self, index: usize) -> Option { - if self.fields.is_empty() { - return None; - } - - let mut vals = Vec::with_capacity(self.fields.len()); - for field in &self.fields { - match field.borrow().get_item(index) { - Some(v) => vals.push(v), - None => return None, // Out of bounds or incomplete - } - } - - Some(Value::Record(self.layout.clone(), Rc::new(vals))) - } -} - -impl Debug for RecordSeries { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "RecordSeries[fields: {}, len: {}]", - self.fields.len(), - Series::len(self) - ) - } -} - -impl Object for RecordSeries { - fn type_name(&self) -> &'static str { - "RecordSeries" - } - fn as_any(&self) -> &dyn Any { - self - } - fn as_series(&self) -> Option<&dyn Series> { - Some(self) - } -} - -impl Series for RecordSeries { - fn get_item(&self, index: usize) -> Option { - self.get_record(index) - } - fn len(&self) -> usize { - self.fields.first().map(|f| f.borrow().len()).unwrap_or(0) - } - fn total_count(&self) -> u64 { - self.fields - .first() - .map(|f| f.borrow().total_count()) - .unwrap_or(0) - } -} - -// ============================================================================ -// 4. Series View (Safe, 0-Copy access bridge for the VM) -// ============================================================================ - -#[derive(Clone)] -pub struct SeriesView { - pub inner: Rc>, - pub field_name: Keyword, -} - -impl SeriesView { - pub fn new(inner: Rc>, field_name: Keyword) -> Self { - Self { inner, field_name } - } -} - -impl Debug for SeriesView { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "SeriesView[field: {}, len: {}]", - self.field_name.name(), - Series::len(self) - ) - } -} - -impl Object for SeriesView { - fn type_name(&self) -> &'static str { - "SeriesView" - } - fn as_any(&self) -> &dyn Any { - self - } - fn as_series(&self) -> Option<&dyn Series> { - Some(self) - } -} - -impl Series for SeriesView { - fn get_item(&self, index: usize) -> Option { - self.inner.borrow().get_item(index) - } - fn len(&self) -> usize { - self.inner.borrow().len() - } - fn total_count(&self) -> u64 { - self.inner.borrow().total_count() - } -} - -// ============================================================================ -// 4b. Shared Series (Read-Only Reactive Storage) -// ============================================================================ - -#[derive(Clone)] -pub struct SharedSeries { - pub buffer: Rc>>, - pub type_name: &'static str, - pub converter: fn(T) -> Value, -} - -impl Debug for SharedSeries { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "SharedSeries<{}>[len: {}]", - self.type_name, - self.buffer.borrow().len() - ) - } -} - -impl Object for SharedSeries { - fn type_name(&self) -> &'static str { - self.type_name - } - fn as_any(&self) -> &dyn Any { - self - } - fn as_series(&self) -> Option<&dyn Series> { - Some(self) - } -} - -impl Series for SharedSeries { - fn get_item(&self, index: usize) -> Option { - self.buffer - .borrow() - .get(index) - .map(|&v| (self.converter)(v)) - } - fn len(&self) -> usize { - self.buffer.borrow().len() - } - fn total_count(&self) -> u64 { - self.buffer.borrow().total_count() - } -} - -#[derive(Clone)] -pub struct SharedValueSeries { - pub buffer: Rc>>, -} - -impl Debug for SharedValueSeries { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "SharedValueSeries[len: {}]", self.buffer.borrow().len()) - } -} - -impl Object for SharedValueSeries { - fn type_name(&self) -> &'static str { - "SharedValueSeries" - } - fn as_any(&self) -> &dyn Any { - self - } - fn as_series(&self) -> Option<&dyn Series> { - Some(self) - } -} - -impl Series for SharedValueSeries { - fn get_item(&self, index: usize) -> Option { - self.buffer.borrow().get(index).cloned() - } - fn len(&self) -> usize { - self.buffer.borrow().len() - } - fn total_count(&self) -> u64 { - self.buffer.borrow().total_count() - } -} - -#[derive(Clone)] -pub struct SharedRecordSeries { - pub layout: Arc, - pub field_buffers: Vec>>, -} - -impl Debug for SharedRecordSeries { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "SharedRecordSeries[fields: {}, len: {}]", - self.field_buffers.len(), - Series::len(self) - ) - } -} - -impl Object for SharedRecordSeries { - fn type_name(&self) -> &'static str { - "SharedRecordSeries" - } - fn as_any(&self) -> &dyn Any { - self - } - fn as_series(&self) -> Option<&dyn Series> { - Some(self) - } -} - -impl Series for SharedRecordSeries { - fn get_item(&self, index: usize) -> Option { - if self.field_buffers.is_empty() { - return None; - } - - let mut vals = Vec::with_capacity(self.field_buffers.len()); - for buffer in &self.field_buffers { - match buffer.borrow().get_item(index) { - Some(v) => vals.push(v), - None => return None, - } - } - - Some(Value::Record(self.layout.clone(), Rc::new(vals))) - } - fn len(&self) -> usize { - self.field_buffers - .first() - .map(|f| f.borrow().len()) - .unwrap_or(0) - } - fn total_count(&self) -> u64 { - self.field_buffers - .first() - .map(|f| f.borrow().total_count()) - .unwrap_or(0) - } -} - -// ============================================================================ -// 5. Script Integration (RTL Registration) -// ============================================================================ - -pub fn register(env: &Environment) { - // (series lookback_limit template_or_type_keyword) -> Series - env.register_native_fn( - "series", - StaticType::Function(Box::new(Signature { - params: StaticType::Tuple(vec![StaticType::Int, StaticType::Any]), - ret: StaticType::Any, - })), - Purity::Impure, - |args: &[Value]| { - let lookback_limit = args[0].as_int().unwrap_or(0) as usize; - let arg = &args[1]; - match arg { - Value::Keyword(k) => { - match k.name().to_lowercase().as_str() { - "float" => Value::Object(Rc::new(ScalarSeries::::new("FloatSeries", lookback_limit))), - "int" | "datetime" => Value::Object(Rc::new(ScalarSeries::::new("IntSeries", lookback_limit))), - "bool" => Value::Object(Rc::new(ScalarSeries::::new("BoolSeries", lookback_limit))), - "text" => Value::Object(Rc::new(ValueSeries::new(lookback_limit))), - _ => panic!("Unknown or unsupported series type keyword: :{}", k.name()), - } - } - Value::Record(schema_layout, schema_values) => { - // Interpret the record as a schema: { :field :type_keyword } - let mut fields = Vec::with_capacity(schema_layout.fields.len()); - for (i, (name, _)) in schema_layout.fields.iter().enumerate() { - let type_keyword = match &schema_values[i] { - Value::Keyword(tk) => tk.name().to_lowercase(), - _ => panic!("Field :{} in series schema must be a type keyword (e.g., :float, :int)", name.name()), - }; - let ty = match type_keyword.as_str() { - "float" => StaticType::Float, - "int" => StaticType::Int, - "bool" => StaticType::Bool, - "datetime" => StaticType::DateTime, - "text" => StaticType::Text, - _ => panic!("Unsupported type keyword :{} for series field :{}", type_keyword, name.name()), - }; - fields.push((*name, ty)); - } - let final_layout = RecordLayout::get_or_create(fields); - Value::Object(Rc::new(RecordSeries::new(final_layout, lookback_limit))) - } - _ => panic!("series expects a lookback_limit and a type keyword (:float, :int...) or a schema record (e.g., {{:price :float}})"), - } - }, - ); - - // (push series value) -> Void - env.register_native_fn( - "push", - StaticType::Function(Box::new(Signature { - params: StaticType::Tuple(vec![StaticType::Any, StaticType::Any]), - ret: StaticType::Void, - })), - Purity::Impure, - |args: &[Value]| { - let (series_val, val) = (&args[0], &args[1]); - - if let Value::Object(obj) = series_val { - let any = obj.as_any(); - - // Polymorphic push logic - if let Some(rs) = any.downcast_ref::() { - if let Value::Record(_, values) = val { - for (i, v) in values.iter().enumerate() { - if let Some(f) = rs.fields.get(i) { - f.borrow().push_value(v.clone()); - } - } - } else { - panic!("push to RecordSeries expects a record"); - } - } else if let Some(s) = any.downcast_ref::>() { - s.push_value(val.clone()); - } else if let Some(s) = any.downcast_ref::>() { - s.push_value(val.clone()); - } else if let Some(s) = any.downcast_ref::>() { - s.push_value(val.clone()); - } else if let Some(s) = any.downcast_ref::() { - s.push_value(val.clone()); - } else { - panic!("Object is not a mutable series"); - } - return Value::Void; - } - panic!("push expects a series object as the first argument") - }, - ); - - // (len series) -> Int - env.register_native_fn( - "len", - StaticType::Function(Box::new(Signature { - params: StaticType::Tuple(vec![StaticType::Any]), - ret: StaticType::Int, - })), - Purity::Pure, - |args: &[Value]| { - if let Value::Object(obj) = &args[0] - && let Some(s) = obj.as_series() - { - return Value::Int(s.len() as i64); - } - Value::Int(0) - }, - ); -} +use std::any::Any; +use std::collections::VecDeque; +use std::fmt::{self, Debug}; +use std::rc::Rc; +use std::sync::Arc; + +use crate::ast::environment::Environment; +use crate::ast::types::{Keyword, Object, RecordLayout, Series, StaticType, Value}; +use crate::ast::types::{Purity, Signature}; + +// ============================================================================ +// 1. Core Storage Engine +// ============================================================================ + +/// A generic, efficient ring buffer for time series data. +/// (Replaces Delphi's TChunkArray for now with VecDeque for O(1) push/pop) +#[derive(Clone)] +pub struct RingBuffer { + buffer: VecDeque, + total_count: u64, + lookback_limit: usize, +} + +impl RingBuffer { + pub fn new(lookback_limit: usize) -> Self { + // Pre-allocate up to 500 elements to minimize heap fragmentation + let initial_capacity = std::cmp::min(lookback_limit, 500); + Self { + buffer: VecDeque::with_capacity(initial_capacity), + total_count: 0, + lookback_limit, + } + } + + /// Adds an element. Removes the oldest if the lookback limit is reached. + #[inline] + pub fn push(&mut self, item: T) { + self.buffer.push_back(item); + self.total_count += 1; + + while self.buffer.len() > self.lookback_limit { + self.buffer.pop_front(); + } + } + + /// Access in "Financial Style": Index 0 is the newest element. + #[inline] + pub fn get(&self, index: usize) -> Option<&T> { + let len = self.buffer.len(); + if index < len { + self.buffer.get(len - 1 - index) + } else { + None + } + } + + #[inline] + pub fn total_count(&self) -> u64 { + self.total_count + } + + #[inline] + pub fn len(&self) -> usize { + self.buffer.len() + } + + pub fn is_empty(&self) -> bool { + self.buffer.is_empty() + } +} + +// ============================================================================ +// 2. Trait & Primitives for Fast-Paths +// ============================================================================ + +/// Marker trait ensuring our fast arrays only store true, flat scalars +/// (No pointers/heaps like String or Record). +pub trait ScalarValue: Copy + Debug + 'static { + fn to_value(self) -> Value; +} + +impl ScalarValue for f64 { + fn to_value(self) -> Value { + Value::Float(self) + } +} +impl ScalarValue for i64 { + fn to_value(self) -> Value { + Value::Int(self) + } +} +impl ScalarValue for bool { + fn to_value(self) -> Value { + Value::Bool(self) + } +} + +/// A highly optimized, type-safe series for primitive values (Float, Int, Bool). +#[derive(Clone)] +pub struct ScalarSeries { + pub data: std::cell::RefCell>, + pub type_name: &'static str, +} + +impl ScalarSeries { + pub fn new(type_name: &'static str, lookback_limit: usize) -> Self { + Self { + data: std::cell::RefCell::new(RingBuffer::new(lookback_limit)), + type_name, + } + } +} + +impl Debug for ScalarSeries { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "ScalarSeries<{}>[len: {}]", + self.type_name, + self.data.borrow().len() + ) + } +} + +// Makes the series usable as a dynamic Object for the VM. +impl Object for ScalarSeries { + fn type_name(&self) -> &'static str { + self.type_name + } + fn as_any(&self) -> &dyn Any { + self + } + fn as_series(&self) -> Option<&dyn Series> { + Some(self) + } +} + +impl Series for ScalarSeries { + fn get_item(&self, index: usize) -> Option { + self.data.borrow().get(index).map(|&v| v.to_value()) + } + fn len(&self) -> usize { + self.data.borrow().len() + } + fn total_count(&self) -> u64 { + self.data.borrow().total_count() + } +} + +// ============================================================================ +// 2b. Fallback Series for non-scalar types +// ============================================================================ + +/// A generic series for all non-scalar types (Strings, Tuples, etc.) or mixed data. +#[derive(Clone)] +pub struct ValueSeries { + pub data: std::cell::RefCell>, +} + +impl ValueSeries { + pub fn new(lookback_limit: usize) -> Self { + Self { + data: std::cell::RefCell::new(RingBuffer::new(lookback_limit)), + } + } +} + +impl Debug for ValueSeries { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "ValueSeries[len: {}]", self.data.borrow().len()) + } +} + +impl Object for ValueSeries { + fn type_name(&self) -> &'static str { + "ValueSeries" + } + fn as_any(&self) -> &dyn Any { + self + } + fn as_series(&self) -> Option<&dyn Series> { + Some(self) + } +} + +impl Series for ValueSeries { + fn get_item(&self, index: usize) -> Option { + self.data.borrow().get(index).cloned() + } + fn len(&self) -> usize { + self.data.borrow().len() + } + fn total_count(&self) -> u64 { + self.data.borrow().total_count() + } +} + +// ============================================================================ +// 3. RecordSeries (SoA - Struct of Arrays) +// ============================================================================ + +/// An interface allowing iteration over fields of a RecordSeries independently +/// of the exact type (Type Erasure for member arrays). +pub trait SeriesMember: Object + Series { + /// Pushes a dynamic Value into the series, performing the necessary downcast. + fn push_value(&self, value: Value); +} + +impl SeriesMember for ScalarSeries { + fn push_value(&self, value: Value) { + if let Some(v) = value.as_float() { + self.data.borrow_mut().push(v); + } + } +} + +impl SeriesMember for ScalarSeries { + fn push_value(&self, value: Value) { + if let Some(v) = value.as_int() { + self.data.borrow_mut().push(v); + } + } +} + +impl SeriesMember for ScalarSeries { + fn push_value(&self, value: Value) { + if let Value::Bool(v) = value { + self.data.borrow_mut().push(v); + } + } +} + +impl SeriesMember for ValueSeries { + fn push_value(&self, value: Value) { + self.data.borrow_mut().push(value); + } +} + +/// The "Struct of Arrays" implementation for Records (e.g., Ticks). +/// Instead of holding a large array of Records (AoS), this series +/// splits the record and stores each field in a separate (parallel) series. +#[derive(Clone)] +pub struct RecordSeries { + layout: Arc, + /// Each field of the record gets its own series (SoA). + /// We use Rc so we can iterate fields individually, + /// but also pass them as 0-Copy references to indicators! + fields: Vec>>, +} + +impl RecordSeries { + /// Creates a new RecordSeries matching the given layout. + pub fn new(layout: Arc, lookback_limit: usize) -> Self { + let mut fields: Vec>> = + Vec::with_capacity(layout.fields.len()); + + for (_, static_type) in &layout.fields { + // Automatically select the optimal storage representation based on the field's static type. + let member: Rc> = match static_type { + StaticType::Float => Rc::new(std::cell::RefCell::new(ScalarSeries::::new( + "FloatSeries", + lookback_limit, + ))), + StaticType::Int | StaticType::DateTime => Rc::new(std::cell::RefCell::new( + ScalarSeries::::new("IntSeries", lookback_limit), + )), + StaticType::Bool => Rc::new(std::cell::RefCell::new(ScalarSeries::::new( + "BoolSeries", + lookback_limit, + ))), + // Fallback for everything else (Text, Lists, nested Records without SoA, etc.) + _ => Rc::new(std::cell::RefCell::new(ValueSeries::new(lookback_limit))), + }; + fields.push(member); + } + + Self { layout, fields } + } + + /// The magical 0-Copy Field Mapper! + pub fn field(&self, key: Keyword) -> Option>> { + self.layout + .index_of(key) + .map(|idx| self.fields[idx].clone()) + } + + /// Dynamically reconstructs the full Record at the given lookback index. + pub fn get_record(&self, index: usize) -> Option { + if self.fields.is_empty() { + return None; + } + + let mut vals = Vec::with_capacity(self.fields.len()); + for field in &self.fields { + match field.borrow().get_item(index) { + Some(v) => vals.push(v), + None => return None, // Out of bounds or incomplete + } + } + + Some(Value::Record(self.layout.clone(), Rc::new(vals))) + } +} + +impl Debug for RecordSeries { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "RecordSeries[fields: {}, len: {}]", + self.fields.len(), + Series::len(self) + ) + } +} + +impl Object for RecordSeries { + fn type_name(&self) -> &'static str { + "RecordSeries" + } + fn as_any(&self) -> &dyn Any { + self + } + fn as_series(&self) -> Option<&dyn Series> { + Some(self) + } +} + +impl Series for RecordSeries { + fn get_item(&self, index: usize) -> Option { + self.get_record(index) + } + fn len(&self) -> usize { + self.fields.first().map(|f| f.borrow().len()).unwrap_or(0) + } + fn total_count(&self) -> u64 { + self.fields + .first() + .map(|f| f.borrow().total_count()) + .unwrap_or(0) + } +} + +// ============================================================================ +// 4. Series View (Safe, 0-Copy access bridge for the VM) +// ============================================================================ + +#[derive(Clone)] +pub struct SeriesView { + pub inner: Rc>, + pub field_name: Keyword, +} + +impl SeriesView { + pub fn new(inner: Rc>, field_name: Keyword) -> Self { + Self { inner, field_name } + } +} + +impl Debug for SeriesView { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "SeriesView[field: {}, len: {}]", + self.field_name.name(), + Series::len(self) + ) + } +} + +impl Object for SeriesView { + fn type_name(&self) -> &'static str { + "SeriesView" + } + fn as_any(&self) -> &dyn Any { + self + } + fn as_series(&self) -> Option<&dyn Series> { + Some(self) + } +} + +impl Series for SeriesView { + fn get_item(&self, index: usize) -> Option { + self.inner.borrow().get_item(index) + } + fn len(&self) -> usize { + self.inner.borrow().len() + } + fn total_count(&self) -> u64 { + self.inner.borrow().total_count() + } +} + +// ============================================================================ +// 4b. Shared Series (Read-Only Reactive Storage) +// ============================================================================ + +#[derive(Clone)] +pub struct SharedSeries { + pub buffer: Rc>>, + pub type_name: &'static str, + pub converter: fn(T) -> Value, +} + +impl Debug for SharedSeries { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "SharedSeries<{}>[len: {}]", + self.type_name, + self.buffer.borrow().len() + ) + } +} + +impl Object for SharedSeries { + fn type_name(&self) -> &'static str { + self.type_name + } + fn as_any(&self) -> &dyn Any { + self + } + fn as_series(&self) -> Option<&dyn Series> { + Some(self) + } +} + +impl Series for SharedSeries { + fn get_item(&self, index: usize) -> Option { + self.buffer + .borrow() + .get(index) + .map(|&v| (self.converter)(v)) + } + fn len(&self) -> usize { + self.buffer.borrow().len() + } + fn total_count(&self) -> u64 { + self.buffer.borrow().total_count() + } +} + +#[derive(Clone)] +pub struct SharedValueSeries { + pub buffer: Rc>>, +} + +impl Debug for SharedValueSeries { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "SharedValueSeries[len: {}]", self.buffer.borrow().len()) + } +} + +impl Object for SharedValueSeries { + fn type_name(&self) -> &'static str { + "SharedValueSeries" + } + fn as_any(&self) -> &dyn Any { + self + } + fn as_series(&self) -> Option<&dyn Series> { + Some(self) + } +} + +impl Series for SharedValueSeries { + fn get_item(&self, index: usize) -> Option { + self.buffer.borrow().get(index).cloned() + } + fn len(&self) -> usize { + self.buffer.borrow().len() + } + fn total_count(&self) -> u64 { + self.buffer.borrow().total_count() + } +} + +#[derive(Clone)] +pub struct SharedRecordSeries { + pub layout: Arc, + pub field_buffers: Vec>>, +} + +impl Debug for SharedRecordSeries { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "SharedRecordSeries[fields: {}, len: {}]", + self.field_buffers.len(), + Series::len(self) + ) + } +} + +impl Object for SharedRecordSeries { + fn type_name(&self) -> &'static str { + "SharedRecordSeries" + } + fn as_any(&self) -> &dyn Any { + self + } + fn as_series(&self) -> Option<&dyn Series> { + Some(self) + } +} + +impl Series for SharedRecordSeries { + fn get_item(&self, index: usize) -> Option { + if self.field_buffers.is_empty() { + return None; + } + + let mut vals = Vec::with_capacity(self.field_buffers.len()); + for buffer in &self.field_buffers { + match buffer.borrow().get_item(index) { + Some(v) => vals.push(v), + None => return None, + } + } + + Some(Value::Record(self.layout.clone(), Rc::new(vals))) + } + fn len(&self) -> usize { + self.field_buffers + .first() + .map(|f| f.borrow().len()) + .unwrap_or(0) + } + fn total_count(&self) -> u64 { + self.field_buffers + .first() + .map(|f| f.borrow().total_count()) + .unwrap_or(0) + } +} + +// ============================================================================ +// 5. Script Integration (RTL Registration) +// ============================================================================ + +pub fn register(env: &Environment) { + // (series lookback_limit template_or_type_keyword) -> Series + env.register_native_fn( + "series", + StaticType::Function(Box::new(Signature { + params: StaticType::Tuple(vec![StaticType::Int, StaticType::Any]), + ret: StaticType::Any, + })), + Purity::Impure, + |args: &[Value]| { + let lookback_limit = args[0].as_int().unwrap_or(0) as usize; + let arg = &args[1]; + match arg { + Value::Keyword(k) => { + match k.name().to_lowercase().as_str() { + "float" => Value::Object(Rc::new(ScalarSeries::::new("FloatSeries", lookback_limit))), + "int" | "datetime" => Value::Object(Rc::new(ScalarSeries::::new("IntSeries", lookback_limit))), + "bool" => Value::Object(Rc::new(ScalarSeries::::new("BoolSeries", lookback_limit))), + "text" => Value::Object(Rc::new(ValueSeries::new(lookback_limit))), + _ => panic!("Unknown or unsupported series type keyword: :{}", k.name()), + } + } + Value::Record(schema_layout, schema_values) => { + // Interpret the record as a schema: { :field :type_keyword } + let mut fields = Vec::with_capacity(schema_layout.fields.len()); + for (i, (name, _)) in schema_layout.fields.iter().enumerate() { + let type_keyword = match &schema_values[i] { + Value::Keyword(tk) => tk.name().to_lowercase(), + _ => panic!("Field :{} in series schema must be a type keyword (e.g., :float, :int)", name.name()), + }; + let ty = match type_keyword.as_str() { + "float" => StaticType::Float, + "int" => StaticType::Int, + "bool" => StaticType::Bool, + "datetime" => StaticType::DateTime, + "text" => StaticType::Text, + _ => panic!("Unsupported type keyword :{} for series field :{}", type_keyword, name.name()), + }; + fields.push((*name, ty)); + } + let final_layout = RecordLayout::get_or_create(fields); + Value::Object(Rc::new(RecordSeries::new(final_layout, lookback_limit))) + } + _ => panic!("series expects a lookback_limit and a type keyword (:float, :int...) or a schema record (e.g., {{:price :float}})"), + } + }, + ); + + // (push series value) -> Void + env.register_native_fn( + "push", + StaticType::Function(Box::new(Signature { + params: StaticType::Tuple(vec![StaticType::Any, StaticType::Any]), + ret: StaticType::Void, + })), + Purity::Impure, + |args: &[Value]| { + let (series_val, val) = (&args[0], &args[1]); + + if let Value::Object(obj) = series_val { + let any = obj.as_any(); + + // Polymorphic push logic + if let Some(rs) = any.downcast_ref::() { + if let Value::Record(_, values) = val { + for (i, v) in values.iter().enumerate() { + if let Some(f) = rs.fields.get(i) { + f.borrow().push_value(v.clone()); + } + } + } else { + panic!("push to RecordSeries expects a record"); + } + } else if let Some(s) = any.downcast_ref::>() { + s.push_value(val.clone()); + } else if let Some(s) = any.downcast_ref::>() { + s.push_value(val.clone()); + } else if let Some(s) = any.downcast_ref::>() { + s.push_value(val.clone()); + } else if let Some(s) = any.downcast_ref::() { + s.push_value(val.clone()); + } else { + panic!("Object is not a mutable series"); + } + return Value::Void; + } + panic!("push expects a series object as the first argument") + }, + ); + + // (len series) -> Int + env.register_native_fn( + "len", + StaticType::Function(Box::new(Signature { + params: StaticType::Tuple(vec![StaticType::Any]), + ret: StaticType::Int, + })), + Purity::Pure, + |args: &[Value]| { + if let Value::Object(obj) = &args[0] + && let Some(s) = obj.as_series() + { + return Value::Int(s.len() as i64); + } + Value::Int(0) + }, + ); +} diff --git a/src/ast/rtl/streams.rs b/src/ast/rtl/streams.rs index 4d56ea8..de9775d 100644 --- a/src/ast/rtl/streams.rs +++ b/src/ast/rtl/streams.rs @@ -1,536 +1,536 @@ -use crate::ast::types::{PipeFn, Value}; -use std::cell::RefCell; -use std::rc::Rc; - -/// A Signal is the "packet" flowing through the reactive pipeline. -/// It represents a value produced at a specific logical time (cycle_id). -#[derive(Debug, Clone)] -pub struct Signal { - pub cycle_id: u64, - pub value: Value, -} - -/// A Stream is a stateless provider of signals. -/// It doesn't "own" the data, it just knows how to get the current one. -pub trait Stream { - fn current_signal(&self) -> Option; -} - -/// An Observer is a node in the pipeline that reacts to new signals. -/// (e.g., a Pipe or a SharedSeries buffer). -pub trait Observer { - /// Notifies the observer about a new signal in the current cycle. - /// `source_index` identifies which input stream provided the value. - fn notify(&mut self, source_index: usize, cycle_id: u64, value: Value); -} - -/// A lightweight adapter to map an unknown source index to a specific target index. -/// This prevents index collisions when a Pipe listens to multiple independent RootStreams. -pub struct SourceAdapter { - pub target: Rc>, - pub target_index: usize, -} - -impl Observer for SourceAdapter { - fn notify(&mut self, _ignored_source: usize, cycle_id: u64, value: Value) { - self.target - .borrow_mut() - .notify(self.target_index, cycle_id, value); - } -} - -/// Polymorphic Interface for any stream that can accept observers (like Delphi's IStream). -pub trait ObservableStream { - fn add_observer(&self, observer: Rc>); -} - -impl ObservableStream for std::cell::RefCell { - fn add_observer(&self, observer: Rc>) { - self.borrow().add_observer(observer); - } -} - -/// A generic wrapper to pass ANY ObservableStream (Root, Pipe, etc.) as an Object to the VM. -#[derive(Clone)] -pub struct StreamNode { - pub inner: Rc, -} - -impl std::fmt::Debug for StreamNode { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "StreamNode") - } -} - -impl crate::ast::types::Object for StreamNode { - fn type_name(&self) -> &'static str { - "StreamNode" - } - fn as_any(&self) -> &dyn std::any::Any { - self - } -} - -/// The RootStream is the "Clock" and data source of the entire pipeline. -/// It generates the monotonic `cycle_id` and triggers the observers. -pub struct RootStream { - current_cycle: std::cell::Cell, - observers: RefCell>>>, -} - -impl ObservableStream for RootStream { - fn add_observer(&self, observer: Rc>) { - self.observers.borrow_mut().push(observer); - } -} - -impl Default for RootStream { - fn default() -> Self { - Self::new() - } -} - -impl RootStream { - pub fn new() -> Self { - Self { - current_cycle: std::cell::Cell::new(0), - observers: RefCell::new(Vec::new()), - } - } - - /// Advances the pipeline to the next cycle and propagates a value. - pub fn tick(&self, value: Value) { - let next_cycle = self.current_cycle.get() + 1; - self.current_cycle.set(next_cycle); - - // Propagate to all observers. - // We use a local borrow of the observers list to keep the cell borrow short. - let obs_list = self.observers.borrow(); - for obs in obs_list.iter() { - // Root observers are always at source_index 0. - obs.borrow_mut().notify(0, next_cycle, value.clone()); - } - } - - pub fn current_cycle(&self) -> u64 { - self.current_cycle.get() - } - - pub fn add_observer(&self, observer: Rc>) { - self.observers.borrow_mut().push(observer); - } -} - -/// A PipeStream is a reactive node that transforms inputs via a lambda. -/// It implements "Barrier Synchronization": It only executes when all inputs -/// have reported a value for the same cycle_id. -pub struct PipeStream { - pub name: String, - /// The inputs this pipe is observing. - /// In a real system, these would be other Streams. - /// For the MVP, we assume the Pipe is notified by the Root or its parents. - pub input_count: usize, - /// Tracks the last cycle_id received from each input. - last_cycle_per_input: Vec, - /// Stores the current value for each input to construct the argument tuple. - current_values: Vec, - /// The current output signal of this pipe. - current_signal: RefCell>, - /// The executable closure representing the Lambda. Expects a slice of arguments. - pub executor: Option>, - /// Observers of THIS pipe. - observers: RefCell>>>, -} - -impl PipeStream { - pub fn new( - name: String, - input_count: usize, - executor: Option>, - ) -> Self { - Self { - name, - input_count, - last_cycle_per_input: vec![0; input_count], - current_values: vec![Value::Void; input_count], - current_signal: RefCell::new(None), - executor, - observers: RefCell::new(Vec::new()), - } - } -} - -impl ObservableStream for PipeStream { - fn add_observer(&self, observer: Rc>) { - self.observers.borrow_mut().push(observer); - } -} - -impl Stream for PipeStream { - fn current_signal(&self) -> Option { - self.current_signal.borrow().clone() - } -} - -impl Observer for PipeStream { - fn notify(&mut self, source_index: usize, cycle_id: u64, value: Value) { - let barrier_reached = { - if source_index < self.input_count { - self.last_cycle_per_input[source_index] = cycle_id; - self.current_values[source_index] = value; - } - // Check if all inputs reached the same cycle. - self.last_cycle_per_input.iter().all(|&c| c == cycle_id) - }; - - if barrier_reached { - // 1. Prepare Arguments for Lambda (Current values of all inputs) - NO CLONE NEEDED! - let args = &self.current_values; - - // 2. Execute Lambda using the encapsulated VM executor - let result = if let Some(exec) = &mut self.executor { - exec(args) - } else { - self.current_values[0].clone() // Identity bypass (defaults to first input) - }; - - // 3. Handle Void case! (Filter pattern) - if matches!(result, Value::Void) { - return; // Act as a filter: do not emit, do not push. - } - - // 4. Update Current Signal - let new_signal = Signal { - cycle_id, - value: result, - }; - *self.current_signal.borrow_mut() = Some(new_signal.clone()); - - // 5. Notify Observers (Always at source_index 0 of the NEXT pipe) - let obs_list = self.observers.borrow(); - for obs in obs_list.iter() { - obs.borrow_mut() - .notify(0, cycle_id, new_signal.value.clone()); - } - } - } -} - -/// A specialized observer that pushes incoming signals into a SharedSeries buffer. -pub struct SeriesPusher { - pub buffer: Rc>>, - pub extractor: fn(Value) -> Option, -} - -impl Observer for SeriesPusher { - fn notify(&mut self, _source_index: usize, _cycle_id: u64, value: Value) { - if let Some(v) = (self.extractor)(value) { - self.buffer.borrow_mut().push(v); - } - } -} - -/// A specialized observer that pushes incoming signals into a generic SharedValueSeries buffer. -pub struct ValuePusher { - pub buffer: Rc>>, -} - -impl Observer for ValuePusher { - fn notify(&mut self, _source_index: usize, _cycle_id: u64, value: Value) { - self.buffer.borrow_mut().push(value); - } -} - -/// A specialized observer that splits a Record into its fields and pushes them into SoA buffers. -pub struct RecordPusher { - pub field_buffers: Vec>>, -} - -impl Observer for RecordPusher { - fn notify(&mut self, _source_index: usize, _cycle_id: u64, value: Value) { - if let Value::Record(_, values) = value { - for (i, v) in values.iter().enumerate() { - if let Some(buf) = self.field_buffers.get(i) { - buf.borrow_mut().push_value(v.clone()); - } - } - } - } -} - -/// Factory function to build a specialized pipeline node based on the output type. -/// This keeps the VM "dumb" and moves the buffer selection logic to the RTL. -pub fn build_pipeline_node( - inputs: Vec>, - executor: Box, - _out_type: &StaticType, -) -> Rc { - let pipe = Rc::new(RefCell::new(PipeStream::new( - "pipe".to_string(), - inputs.len(), - Some(executor), - ))); - - // Connect inputs to the pipe - for (i, input) in inputs.into_iter().enumerate() { - let adapter = Rc::new(RefCell::new(SourceAdapter { - target: pipe.clone(), - target_index: i, - })); - input.add_observer(adapter); - } - - Rc::new(StreamNode { inner: pipe }) -} - -pub fn build_map_stream(input: Rc, field: Keyword) -> StreamNode { - let executor: Box = Box::new(move |args: &[Value]| -> Value { - let val = &args[0]; - if let Value::Record(layout, values) = val - && let Some(idx) = layout.index_of(field) - { - return values[idx].clone(); - } - Value::Void // In streams, Void acts as a filter - }); - - let pipe = Rc::new(RefCell::new(PipeStream::new( - format!("map:{}", field.name()), - 1, - Some(executor), - ))); - - let adapter = Rc::new(RefCell::new(SourceAdapter { - target: pipe.clone(), - target_index: 0, - })); - input.add_observer(adapter); - - StreamNode { - inner: pipe, - } -} - -// ============================================================================ -// Script Integration (RTL Registration) -// ============================================================================ - -use crate::ast::environment::Environment; -use crate::ast::types::{Keyword, Purity, RecordLayout, Signature, StaticType}; - -pub fn register(env: &Environment) { - // (create-random-ohlc seed limit) -> StreamNode - let generators = env.pipeline_generators.clone(); - - // Define the OHLC layout for typing - let ohlc_layout = RecordLayout::get_or_create(vec![ - (Keyword::intern("open"), StaticType::Float), - (Keyword::intern("high"), StaticType::Float), - (Keyword::intern("low"), StaticType::Float), - (Keyword::intern("close"), StaticType::Float), - ]); - - env.register_native_fn( - "create-random-ohlc", - StaticType::Function(Box::new(Signature { - params: StaticType::Tuple(vec![StaticType::Int, StaticType::Int]), - ret: StaticType::Stream(Box::new(StaticType::Record(ohlc_layout.clone()))), - })), - Purity::Impure, // Modifies global generator registry - move |args: &[Value]| { - if args.len() != 2 { - panic!("create-random-ohlc expects exactly 2 arguments (seed, limit)"); - } - let seed = if let Value::Int(s) = args[0] { - s as u64 - } else { - 0 - }; - let limit = if let Value::Int(l) = args[1] { - l as usize - } else { - 0 - }; - - // 1. Create the RootStream - let root_stream = Rc::new(RootStream::new()); - let stream_node = StreamNode { - inner: root_stream.clone(), - }; - - // 2. Setup the Layout for OHLC records - let layout = RecordLayout::get_or_create(vec![ - (Keyword::intern("open"), StaticType::Float), - (Keyword::intern("high"), StaticType::Float), - (Keyword::intern("low"), StaticType::Float), - (Keyword::intern("close"), StaticType::Float), - ]); - - // 3. Create the stateful generator closure - let mut current_tick = 0; - let mut last_close = 100.0; - - // We use a local PRNG instance for reproducibility based on the seed - let mut rng = fastrand::Rng::with_seed(seed); - - let generator = move || -> bool { - if current_tick >= limit { - return false; // Exhausted - } - - // Generate random OHLC (Random Walk) - let change = (rng.f64() - 0.5) * 2.0; - let open = last_close; - let high = open + (rng.f64() * 2.0).abs(); - let low = open - (rng.f64() * 2.0).abs(); - let close = open + change; - last_close = close; - - let record = Value::Record( - layout.clone(), - Rc::new(vec![ - Value::Float(open), - Value::Float(high), - Value::Float(low), - Value::Float(close), - ]), - ); - - // Pump the signal into the RootStream - root_stream.tick(record); - - current_tick += 1; - true // Still active - }; - - // 4. Register the generator in the Environment - generators.borrow_mut().push(Box::new(generator)); - - // 5. Return the stream reference to the script - Value::Object(Rc::new(stream_node)) - }, - ); - - // (create-ticker condition-closure) -> StreamNode - let ticker_generators = env.pipeline_generators.clone(); - let globals = env.root_values.clone(); - - env.register_native_fn( - "create-ticker", - StaticType::Function(Box::new(Signature { - params: StaticType::Tuple(vec![StaticType::Any]), // Expects a closure - ret: StaticType::Any, // Returns StreamNode - })), - Purity::Impure, - move |args: &[Value]| { - if args.len() != 1 { - panic!("create-ticker expects exactly 1 argument (the condition closure)"); - } - - let closure_obj = if let Value::Object(obj) = &args[0] { - obj.clone() - } else { - panic!("create-ticker expects a closure as its argument"); - }; - - // 1. Create the RootStream - let root_stream = Rc::new(RootStream::new()); - let stream_node = StreamNode { - inner: root_stream.clone(), - }; - - // 2. Setup isolated VM - let mut ticker_vm = crate::ast::vm::VM::new(globals.clone()); - let my_closure = closure_obj.clone(); - - // 3. Create generator - let generator = move || -> bool { - match ticker_vm.run_with_args(my_closure.clone(), &[]) { - Ok(Value::Bool(b)) => { - if b { - // Ticker pulses with a simple `true` value or `Void` - // We use true here so the pipe receives something tangible. - root_stream.tick(Value::Bool(true)); - true - } else { - false // Exhausted - } - } - Ok(_) => panic!("create-ticker closure must return a boolean"), - Err(e) => panic!("create-ticker closure execution failed: {}", e), - } - }; - - // 4. Register the generator - ticker_generators.borrow_mut().push(Box::new(generator)); - - // 5. Return stream - Value::Object(Rc::new(stream_node)) - }, - ); -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::ast::types::Value; - - #[test] - fn test_root_to_pipe_flow() { - let root = RootStream::new(); - let pipe = Rc::new(RefCell::new(PipeStream::new( - "test-pipe".to_string(), - 1, - None, - ))); - - root.add_observer(pipe.clone()); - - // Cycle 1: Root ticks 10.0 - root.tick(Value::Float(10.0)); - - let sig = pipe.borrow().current_signal().unwrap(); - assert_eq!(sig.cycle_id, 1); - if let Value::Float(v) = sig.value { - assert_eq!(v, 10.0); - } else { - panic!("Value must be Float(10.0)"); - } - - // Cycle 2: Root ticks 20.0 - root.tick(Value::Float(20.0)); - let sig2 = pipe.borrow().current_signal().unwrap(); - assert_eq!(sig2.cycle_id, 2); - if let Value::Float(v) = sig2.value { - assert_eq!(v, 20.0); - } else { - panic!("Value must be Float(20.0)"); - } - } - - #[test] - fn test_barrier_sync() { - // Pipe with 2 inputs - let pipe = Rc::new(RefCell::new(PipeStream::new( - "barrier-pipe".to_string(), - 2, - None, - ))); - - // Manual notifications simulate different input streams - pipe.borrow_mut().notify(0, 1, Value::Float(10.0)); - assert!( - pipe.borrow().current_signal().is_none(), - "Barrier should NOT be reached after 1st input" - ); - - pipe.borrow_mut().notify(1, 1, Value::Float(20.0)); - assert!( - pipe.borrow().current_signal().is_some(), - "Barrier SHOULD be reached after 2nd input" - ); - - let sig = pipe.borrow().current_signal().unwrap(); - assert_eq!(sig.cycle_id, 1); - } -} +use crate::ast::types::{PipeFn, Value}; +use std::cell::RefCell; +use std::rc::Rc; + +/// A Signal is the "packet" flowing through the reactive pipeline. +/// It represents a value produced at a specific logical time (cycle_id). +#[derive(Debug, Clone)] +pub struct Signal { + pub cycle_id: u64, + pub value: Value, +} + +/// A Stream is a stateless provider of signals. +/// It doesn't "own" the data, it just knows how to get the current one. +pub trait Stream { + fn current_signal(&self) -> Option; +} + +/// An Observer is a node in the pipeline that reacts to new signals. +/// (e.g., a Pipe or a SharedSeries buffer). +pub trait Observer { + /// Notifies the observer about a new signal in the current cycle. + /// `source_index` identifies which input stream provided the value. + fn notify(&mut self, source_index: usize, cycle_id: u64, value: Value); +} + +/// A lightweight adapter to map an unknown source index to a specific target index. +/// This prevents index collisions when a Pipe listens to multiple independent RootStreams. +pub struct SourceAdapter { + pub target: Rc>, + pub target_index: usize, +} + +impl Observer for SourceAdapter { + fn notify(&mut self, _ignored_source: usize, cycle_id: u64, value: Value) { + self.target + .borrow_mut() + .notify(self.target_index, cycle_id, value); + } +} + +/// Polymorphic Interface for any stream that can accept observers (like Delphi's IStream). +pub trait ObservableStream { + fn add_observer(&self, observer: Rc>); +} + +impl ObservableStream for std::cell::RefCell { + fn add_observer(&self, observer: Rc>) { + self.borrow().add_observer(observer); + } +} + +/// A generic wrapper to pass ANY ObservableStream (Root, Pipe, etc.) as an Object to the VM. +#[derive(Clone)] +pub struct StreamNode { + pub inner: Rc, +} + +impl std::fmt::Debug for StreamNode { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "StreamNode") + } +} + +impl crate::ast::types::Object for StreamNode { + fn type_name(&self) -> &'static str { + "StreamNode" + } + fn as_any(&self) -> &dyn std::any::Any { + self + } +} + +/// The RootStream is the "Clock" and data source of the entire pipeline. +/// It generates the monotonic `cycle_id` and triggers the observers. +pub struct RootStream { + current_cycle: std::cell::Cell, + observers: RefCell>>>, +} + +impl ObservableStream for RootStream { + fn add_observer(&self, observer: Rc>) { + self.observers.borrow_mut().push(observer); + } +} + +impl Default for RootStream { + fn default() -> Self { + Self::new() + } +} + +impl RootStream { + pub fn new() -> Self { + Self { + current_cycle: std::cell::Cell::new(0), + observers: RefCell::new(Vec::new()), + } + } + + /// Advances the pipeline to the next cycle and propagates a value. + pub fn tick(&self, value: Value) { + let next_cycle = self.current_cycle.get() + 1; + self.current_cycle.set(next_cycle); + + // Propagate to all observers. + // We use a local borrow of the observers list to keep the cell borrow short. + let obs_list = self.observers.borrow(); + for obs in obs_list.iter() { + // Root observers are always at source_index 0. + obs.borrow_mut().notify(0, next_cycle, value.clone()); + } + } + + pub fn current_cycle(&self) -> u64 { + self.current_cycle.get() + } + + pub fn add_observer(&self, observer: Rc>) { + self.observers.borrow_mut().push(observer); + } +} + +/// A PipeStream is a reactive node that transforms inputs via a lambda. +/// It implements "Barrier Synchronization": It only executes when all inputs +/// have reported a value for the same cycle_id. +pub struct PipeStream { + pub name: String, + /// The inputs this pipe is observing. + /// In a real system, these would be other Streams. + /// For the MVP, we assume the Pipe is notified by the Root or its parents. + pub input_count: usize, + /// Tracks the last cycle_id received from each input. + last_cycle_per_input: Vec, + /// Stores the current value for each input to construct the argument tuple. + current_values: Vec, + /// The current output signal of this pipe. + current_signal: RefCell>, + /// The executable closure representing the Lambda. Expects a slice of arguments. + pub executor: Option>, + /// Observers of THIS pipe. + observers: RefCell>>>, +} + +impl PipeStream { + pub fn new( + name: String, + input_count: usize, + executor: Option>, + ) -> Self { + Self { + name, + input_count, + last_cycle_per_input: vec![0; input_count], + current_values: vec![Value::Void; input_count], + current_signal: RefCell::new(None), + executor, + observers: RefCell::new(Vec::new()), + } + } +} + +impl ObservableStream for PipeStream { + fn add_observer(&self, observer: Rc>) { + self.observers.borrow_mut().push(observer); + } +} + +impl Stream for PipeStream { + fn current_signal(&self) -> Option { + self.current_signal.borrow().clone() + } +} + +impl Observer for PipeStream { + fn notify(&mut self, source_index: usize, cycle_id: u64, value: Value) { + let barrier_reached = { + if source_index < self.input_count { + self.last_cycle_per_input[source_index] = cycle_id; + self.current_values[source_index] = value; + } + // Check if all inputs reached the same cycle. + self.last_cycle_per_input.iter().all(|&c| c == cycle_id) + }; + + if barrier_reached { + // 1. Prepare Arguments for Lambda (Current values of all inputs) - NO CLONE NEEDED! + let args = &self.current_values; + + // 2. Execute Lambda using the encapsulated VM executor + let result = if let Some(exec) = &mut self.executor { + exec(args) + } else { + self.current_values[0].clone() // Identity bypass (defaults to first input) + }; + + // 3. Handle Void case! (Filter pattern) + if matches!(result, Value::Void) { + return; // Act as a filter: do not emit, do not push. + } + + // 4. Update Current Signal + let new_signal = Signal { + cycle_id, + value: result, + }; + *self.current_signal.borrow_mut() = Some(new_signal.clone()); + + // 5. Notify Observers (Always at source_index 0 of the NEXT pipe) + let obs_list = self.observers.borrow(); + for obs in obs_list.iter() { + obs.borrow_mut() + .notify(0, cycle_id, new_signal.value.clone()); + } + } + } +} + +/// A specialized observer that pushes incoming signals into a SharedSeries buffer. +pub struct SeriesPusher { + pub buffer: Rc>>, + pub extractor: fn(Value) -> Option, +} + +impl Observer for SeriesPusher { + fn notify(&mut self, _source_index: usize, _cycle_id: u64, value: Value) { + if let Some(v) = (self.extractor)(value) { + self.buffer.borrow_mut().push(v); + } + } +} + +/// A specialized observer that pushes incoming signals into a generic SharedValueSeries buffer. +pub struct ValuePusher { + pub buffer: Rc>>, +} + +impl Observer for ValuePusher { + fn notify(&mut self, _source_index: usize, _cycle_id: u64, value: Value) { + self.buffer.borrow_mut().push(value); + } +} + +/// A specialized observer that splits a Record into its fields and pushes them into SoA buffers. +pub struct RecordPusher { + pub field_buffers: Vec>>, +} + +impl Observer for RecordPusher { + fn notify(&mut self, _source_index: usize, _cycle_id: u64, value: Value) { + if let Value::Record(_, values) = value { + for (i, v) in values.iter().enumerate() { + if let Some(buf) = self.field_buffers.get(i) { + buf.borrow_mut().push_value(v.clone()); + } + } + } + } +} + +/// Factory function to build a specialized pipeline node based on the output type. +/// This keeps the VM "dumb" and moves the buffer selection logic to the RTL. +pub fn build_pipeline_node( + inputs: Vec>, + executor: Box, + _out_type: &StaticType, +) -> Rc { + let pipe = Rc::new(RefCell::new(PipeStream::new( + "pipe".to_string(), + inputs.len(), + Some(executor), + ))); + + // Connect inputs to the pipe + for (i, input) in inputs.into_iter().enumerate() { + let adapter = Rc::new(RefCell::new(SourceAdapter { + target: pipe.clone(), + target_index: i, + })); + input.add_observer(adapter); + } + + Rc::new(StreamNode { inner: pipe }) +} + +pub fn build_map_stream(input: Rc, field: Keyword) -> StreamNode { + let executor: Box = Box::new(move |args: &[Value]| -> Value { + let val = &args[0]; + if let Value::Record(layout, values) = val + && let Some(idx) = layout.index_of(field) + { + return values[idx].clone(); + } + Value::Void // In streams, Void acts as a filter + }); + + let pipe = Rc::new(RefCell::new(PipeStream::new( + format!("map:{}", field.name()), + 1, + Some(executor), + ))); + + let adapter = Rc::new(RefCell::new(SourceAdapter { + target: pipe.clone(), + target_index: 0, + })); + input.add_observer(adapter); + + StreamNode { + inner: pipe, + } +} + +// ============================================================================ +// Script Integration (RTL Registration) +// ============================================================================ + +use crate::ast::environment::Environment; +use crate::ast::types::{Keyword, Purity, RecordLayout, Signature, StaticType}; + +pub fn register(env: &Environment) { + // (create-random-ohlc seed limit) -> StreamNode + let generators = env.pipeline_generators.clone(); + + // Define the OHLC layout for typing + let ohlc_layout = RecordLayout::get_or_create(vec![ + (Keyword::intern("open"), StaticType::Float), + (Keyword::intern("high"), StaticType::Float), + (Keyword::intern("low"), StaticType::Float), + (Keyword::intern("close"), StaticType::Float), + ]); + + env.register_native_fn( + "create-random-ohlc", + StaticType::Function(Box::new(Signature { + params: StaticType::Tuple(vec![StaticType::Int, StaticType::Int]), + ret: StaticType::Stream(Box::new(StaticType::Record(ohlc_layout.clone()))), + })), + Purity::Impure, // Modifies global generator registry + move |args: &[Value]| { + if args.len() != 2 { + panic!("create-random-ohlc expects exactly 2 arguments (seed, limit)"); + } + let seed = if let Value::Int(s) = args[0] { + s as u64 + } else { + 0 + }; + let limit = if let Value::Int(l) = args[1] { + l as usize + } else { + 0 + }; + + // 1. Create the RootStream + let root_stream = Rc::new(RootStream::new()); + let stream_node = StreamNode { + inner: root_stream.clone(), + }; + + // 2. Setup the Layout for OHLC records + let layout = RecordLayout::get_or_create(vec![ + (Keyword::intern("open"), StaticType::Float), + (Keyword::intern("high"), StaticType::Float), + (Keyword::intern("low"), StaticType::Float), + (Keyword::intern("close"), StaticType::Float), + ]); + + // 3. Create the stateful generator closure + let mut current_tick = 0; + let mut last_close = 100.0; + + // We use a local PRNG instance for reproducibility based on the seed + let mut rng = fastrand::Rng::with_seed(seed); + + let generator = move || -> bool { + if current_tick >= limit { + return false; // Exhausted + } + + // Generate random OHLC (Random Walk) + let change = (rng.f64() - 0.5) * 2.0; + let open = last_close; + let high = open + (rng.f64() * 2.0).abs(); + let low = open - (rng.f64() * 2.0).abs(); + let close = open + change; + last_close = close; + + let record = Value::Record( + layout.clone(), + Rc::new(vec![ + Value::Float(open), + Value::Float(high), + Value::Float(low), + Value::Float(close), + ]), + ); + + // Pump the signal into the RootStream + root_stream.tick(record); + + current_tick += 1; + true // Still active + }; + + // 4. Register the generator in the Environment + generators.borrow_mut().push(Box::new(generator)); + + // 5. Return the stream reference to the script + Value::Object(Rc::new(stream_node)) + }, + ); + + // (create-ticker condition-closure) -> StreamNode + let ticker_generators = env.pipeline_generators.clone(); + let globals = env.root_values.clone(); + + env.register_native_fn( + "create-ticker", + StaticType::Function(Box::new(Signature { + params: StaticType::Tuple(vec![StaticType::Any]), // Expects a closure + ret: StaticType::Any, // Returns StreamNode + })), + Purity::Impure, + move |args: &[Value]| { + if args.len() != 1 { + panic!("create-ticker expects exactly 1 argument (the condition closure)"); + } + + let closure_obj = if let Value::Object(obj) = &args[0] { + obj.clone() + } else { + panic!("create-ticker expects a closure as its argument"); + }; + + // 1. Create the RootStream + let root_stream = Rc::new(RootStream::new()); + let stream_node = StreamNode { + inner: root_stream.clone(), + }; + + // 2. Setup isolated VM + let mut ticker_vm = crate::ast::vm::VM::new(globals.clone()); + let my_closure = closure_obj.clone(); + + // 3. Create generator + let generator = move || -> bool { + match ticker_vm.run_with_args(my_closure.clone(), &[]) { + Ok(Value::Bool(b)) => { + if b { + // Ticker pulses with a simple `true` value or `Void` + // We use true here so the pipe receives something tangible. + root_stream.tick(Value::Bool(true)); + true + } else { + false // Exhausted + } + } + Ok(_) => panic!("create-ticker closure must return a boolean"), + Err(e) => panic!("create-ticker closure execution failed: {}", e), + } + }; + + // 4. Register the generator + ticker_generators.borrow_mut().push(Box::new(generator)); + + // 5. Return stream + Value::Object(Rc::new(stream_node)) + }, + ); +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::ast::types::Value; + + #[test] + fn test_root_to_pipe_flow() { + let root = RootStream::new(); + let pipe = Rc::new(RefCell::new(PipeStream::new( + "test-pipe".to_string(), + 1, + None, + ))); + + root.add_observer(pipe.clone()); + + // Cycle 1: Root ticks 10.0 + root.tick(Value::Float(10.0)); + + let sig = pipe.borrow().current_signal().unwrap(); + assert_eq!(sig.cycle_id, 1); + if let Value::Float(v) = sig.value { + assert_eq!(v, 10.0); + } else { + panic!("Value must be Float(10.0)"); + } + + // Cycle 2: Root ticks 20.0 + root.tick(Value::Float(20.0)); + let sig2 = pipe.borrow().current_signal().unwrap(); + assert_eq!(sig2.cycle_id, 2); + if let Value::Float(v) = sig2.value { + assert_eq!(v, 20.0); + } else { + panic!("Value must be Float(20.0)"); + } + } + + #[test] + fn test_barrier_sync() { + // Pipe with 2 inputs + let pipe = Rc::new(RefCell::new(PipeStream::new( + "barrier-pipe".to_string(), + 2, + None, + ))); + + // Manual notifications simulate different input streams + pipe.borrow_mut().notify(0, 1, Value::Float(10.0)); + assert!( + pipe.borrow().current_signal().is_none(), + "Barrier should NOT be reached after 1st input" + ); + + pipe.borrow_mut().notify(1, 1, Value::Float(20.0)); + assert!( + pipe.borrow().current_signal().is_some(), + "Barrier SHOULD be reached after 2nd input" + ); + + let sig = pipe.borrow().current_signal().unwrap(); + assert_eq!(sig.cycle_id, 1); + } +} diff --git a/src/ast/rtl/type_registry.rs b/src/ast/rtl/type_registry.rs index cd21064..5e67f66 100644 --- a/src/ast/rtl/type_registry.rs +++ b/src/ast/rtl/type_registry.rs @@ -1,273 +1,273 @@ -use crate::ast::types::{Keyword, Purity, Signature, StaticType, Value}; -use std::any::TypeId; -use std::collections::HashMap; - -/// 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(&[Value]) -> Result + 'static, - { - // The factory is a script function that calls the Rust factory, gets T, then wraps it. - let closure = move |args: &[Value]| -> 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::make_function(Purity::Impure, 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(&[Value]) -> Value + 'static, - { - let key = Keyword::intern(name); - self.fields - .push((key, Value::make_function(Purity::Impure, 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(&[Value]) -> Result + 'static, - { - let closure = move |args: &[Value]| 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(crate::ast::types::RecordLayout::get_or_create(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(layout) = st { - assert!( - layout - .fields - .iter() - .any(|(k, _)| *k == Keyword::intern("greet")) - ); - } else { - panic!("Expected Record type"); - } - - // Check runtime behavior - if let Value::Record(layout, values) = wrapped { - let idx = layout.index_of(Keyword::intern("greet")).unwrap(); - let greet_fn = &values[idx]; - - if let Value::Function(f) = greet_fn { - let res = (f.func)(&[]); - 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: &[Value]| { - 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.func)(&[Value::Text("Bob".into()), Value::Int(40)]); - if let Value::Record(layout, values) = instance { - let idx = layout.index_of(Keyword::intern("greet")).unwrap(); - let greet_fn = &values[idx]; - - if let Value::Function(gf) = greet_fn { - let res = (gf.func)(&[]); - 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, Purity, Signature, StaticType, Value}; +use std::any::TypeId; +use std::collections::HashMap; + +/// 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(&[Value]) -> Result + 'static, + { + // The factory is a script function that calls the Rust factory, gets T, then wraps it. + let closure = move |args: &[Value]| -> 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::make_function(Purity::Impure, 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(&[Value]) -> Value + 'static, + { + let key = Keyword::intern(name); + self.fields + .push((key, Value::make_function(Purity::Impure, 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(&[Value]) -> Result + 'static, + { + let closure = move |args: &[Value]| 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(crate::ast::types::RecordLayout::get_or_create(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(layout) = st { + assert!( + layout + .fields + .iter() + .any(|(k, _)| *k == Keyword::intern("greet")) + ); + } else { + panic!("Expected Record type"); + } + + // Check runtime behavior + if let Value::Record(layout, values) = wrapped { + let idx = layout.index_of(Keyword::intern("greet")).unwrap(); + let greet_fn = &values[idx]; + + if let Value::Function(f) = greet_fn { + let res = (f.func)(&[]); + 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: &[Value]| { + 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.func)(&[Value::Text("Bob".into()), Value::Int(40)]); + if let Value::Record(layout, values) = instance { + let idx = layout.index_of(Keyword::intern("greet")).unwrap(); + let greet_fn = &values[idx]; + + if let Value::Function(gf) = greet_fn { + let res = (gf.func)(&[]); + 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 42f3d19..809d561 100644 --- a/src/ast/types.rs +++ b/src/ast/types.rs @@ -1,682 +1,682 @@ -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. The identity is defined by the object instance (unique ID). -/// SourceLocation is kept as optional metadata. -#[derive(Debug, Clone)] -pub struct NodeIdentity { - pub id: u64, - pub location: Option, -} - -impl PartialEq for NodeIdentity { - fn eq(&self, other: &Self) -> bool { - self.id == other.id - } -} - -impl Eq for NodeIdentity {} - -impl std::hash::Hash for NodeIdentity { - fn hash(&self, state: &mut H) { - self.id.hash(state); - } -} - -pub type Identity = Rc; - -static NODE_ID_COUNTER: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(1); - -impl NodeIdentity { - pub fn next_id() -> u64 { - NODE_ID_COUNTER.fetch_add(1, std::sync::atomic::Ordering::SeqCst) - } - - /// Creates a new unique identity at the given location. - pub fn new(location: SourceLocation) -> Identity { - Rc::new(NodeIdentity { - id: Self::next_id(), - location: Some(location), - }) - } - - /// Creates a new unique identity without location metadata (for synthetic nodes). - pub fn anonymous() -> Identity { - Rc::new(NodeIdentity { - id: Self::next_id(), - location: None, - }) - } -} - -/// Interned string identifier -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct Keyword(pub u32); - -static KEYWORD_REGISTRY: OnceLock>> = OnceLock::new(); -// We use String here instead of Arc because benchmarks (e.g. record_optimizations.myc) -// showed a 4% performance regression with Arc due to atomic overhead during formatting. -// Since name() is mostly used for diagnostics and UI, the deep clone is acceptable. -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() - } -} - -/// A shared schema for Records, providing O(1) field lookup via FMap optimization. -#[derive(Debug, Clone)] -pub struct RecordLayout { - pub fields: Vec<(Keyword, StaticType)>, - /// Optimization: maps (Keyword.idx - min_idx) to index in fields. - fmap: Vec, - min_idx: u32, -} - -impl PartialEq for RecordLayout { - fn eq(&self, other: &Self) -> bool { - self.fields == other.fields - } -} - -impl Eq for RecordLayout {} - -impl std::hash::Hash for RecordLayout { - fn hash(&self, state: &mut H) { - self.fields.hash(state); - } -} - -type LayoutMap = HashMap, std::sync::Arc>; - -static LAYOUT_REGISTRY: OnceLock> = OnceLock::new(); - -impl RecordLayout { - pub fn get_or_create(fields: Vec<(Keyword, StaticType)>) -> std::sync::Arc { - let mut reg = LAYOUT_REGISTRY - .get_or_init(|| Mutex::new(HashMap::new())) - .lock() - .unwrap(); - - if let Some(layout) = reg.get(&fields) { - return layout.clone(); - } - - let mut min_idx = u32::MAX; - let mut max_idx = 0; - for (k, _) in &fields { - min_idx = min_idx.min(k.0); - max_idx = max_idx.max(k.0); - } - - let mut fmap = vec![-1; (max_idx - min_idx + 1) as usize]; - for (i, (k, _)) in fields.iter().enumerate() { - fmap[(k.0 - min_idx) as usize] = i as i32; - } - - let layout = std::sync::Arc::new(RecordLayout { - fields: fields.clone(), - fmap, - min_idx, - }); - - reg.insert(fields, layout.clone()); - layout - } - - #[inline(always)] - pub fn index_of(&self, key: Keyword) -> Option { - if key.0 < self.min_idx { - return None; - } - let offset = (key.0 - self.min_idx) as usize; - if offset >= self.fmap.len() { - return None; - } - let idx = self.fmap[offset]; - if idx < 0 { None } else { Some(idx as usize) } - } -} - -/// Interface for custom objects (Closures, Series, Streams) -pub trait Object: fmt::Debug { - fn type_name(&self) -> &'static str; - fn as_any(&self) -> &dyn Any; - - /// Optional optimization: If this object behaves like a time series (indexable lookbacks), - /// it can return a reference to its Series trait implementation. - fn as_series(&self) -> Option<&dyn Series> { - None - } -} - -/// Unified interface for all series types (Local RecordSeries, SeriesViews, and future SharedSeries). -pub trait Series { - /// Gets the value at the specified lookback index (0 = newest). - fn get_item(&self, index: usize) -> Option; - - /// Returns the current number of elements in the buffer. - fn len(&self) -> usize; - - /// Returns the total number of elements that have passed through this series. - fn total_count(&self) -> u64; - - /// Returns true if the series is empty. - fn is_empty(&self) -> bool { - self.len() == 0 - } -} - -/// A shared sequence of values, used by both Tuples and Records. -pub type ValueList = Rc>; - -/// Purity level of an expression or function. -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum Purity { - /// Has side effects (e.g. Set, Print) - Impure, - /// No side effects, but not deterministic (e.g. now(), random()) - SideEffectFree, - /// No side effects and deterministic (e.g. sin(x), 1 + 2) - Pure, -} - -pub type NativeFn = dyn Fn(&[Value]) -> Value; -pub type PipeFn = dyn FnMut(&[Value]) -> Value; - -/// A native host function with metadata. -pub struct NativeFunction { - pub func: Rc, - pub purity: Purity, -} - -impl fmt::Debug for NativeFunction { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "", self.purity) - } -} - -/// 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(std::sync::Arc, ValueList), - FieldAccessor(Keyword), - Function(Rc), - 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(la, va), Value::Record(lb, vb)) => { - std::sync::Arc::ptr_eq(la, lb) && va == vb - } - (Value::FieldAccessor(a), Value::FieldAccessor(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, - } - } -} - -impl PartialOrd for Value { - fn partial_cmp(&self, other: &Self) -> Option { - match (self, other) { - (Value::Int(a), Value::Int(b)) => a.partial_cmp(b), - (Value::Float(a), Value::Float(b)) => a.partial_cmp(b), - (Value::Int(a), Value::Float(b)) => (*a as f64).partial_cmp(b), - (Value::Float(a), Value::Int(b)) => a.partial_cmp(&(*b as f64)), - (Value::DateTime(a), Value::DateTime(b)) => a.partial_cmp(b), - (Value::Text(a), Value::Text(b)) => a.partial_cmp(b), - _ => None, - } - } -} - -#[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, - Optional(Box), // Represents T | Void (e.g. for filter pipes) - List(Box), // Legacy / Dynamic list - Series(Box), // Time series of a specific type - Stream(Box), // Reactive stream of a specific type - Tuple(Vec), // Heterogeneous fixed-size - Vector(Box, usize), // Homogeneous fixed-size - Matrix(Box, Vec), // Multi-dimensional homogeneous - Record(std::sync::Arc), - FieldAccessor(Keyword), - Function(Box), - FunctionOverloads(Vec), - Object(&'static str), - /// A diagnostic poison type, allowing type-checking to continue after an error. - Error, -} - -impl fmt::Display for StaticType { - 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::Optional(inner) => write!(f, "optional({})", inner), - StaticType::List(inner) => write!(f, "list({})", inner), - StaticType::Series(inner) => write!(f, "series<{}>", inner), - StaticType::Stream(inner) => write!(f, "stream<{}>", 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(layout) => { - write!(f, "{{")?; - for (i, (k, v)) in layout.fields.iter().enumerate() { - if i > 0 { - write!(f, ", ")?; - } - write!(f, ":{} {}", k.name(), v)?; - } - write!(f, "}}") - } - StaticType::FieldAccessor(k) => write!(f, ".{}", k.name()), - 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), - StaticType::Error => write!(f, ""), - } - } -} - -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) - || matches!(self, StaticType::Error) - || matches!(other, StaticType::Error) - { - return true; - } - - match (self, other) { - // Implicit Coercions - (StaticType::Float, StaticType::Int) => true, - (StaticType::Int, StaticType::Bool) => true, - (StaticType::Int, StaticType::DateTime) => true, - (StaticType::DateTime, StaticType::Int) => true, - - // Optional(T) is assignable from T or Void - (StaticType::Optional(inner), other) => { - matches!(other, StaticType::Void) || inner.is_assignable_from(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)) - } - // Tuple to Tuple (Element-wise) - (StaticType::Tuple(elements), StaticType::Tuple(other_elements)) => { - if elements.len() != other_elements.len() { - return false; - } - elements - .iter() - .zip(other_elements.iter()) - .all(|(e, o)| e.is_assignable_from(o)) - } - // 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)) - } - } - // Records are assignable if their layouts match (Structural identity via interning) - (StaticType::Record(a), StaticType::Record(b)) => std::sync::Arc::ptr_eq(a, b), - // Series are assignable if their inner types are assignable - (StaticType::Series(inner_a), StaticType::Series(inner_b)) => { - inner_a.is_assignable_from(inner_b) - } - // Streams are assignable if their inner types are assignable - (StaticType::Stream(inner_a), StaticType::Stream(inner_b)) => { - inner_a.is_assignable_from(inner_b) - } - _ => 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::FieldAccessor(k) => { - // (.name { :name val }) - // The args_ty should be a Tuple of [Record] - let rec_ty = if let StaticType::Tuple(t) = args_ty { - t.first()? - } else { - args_ty - }; - - match rec_ty { - StaticType::Record(layout) => { - if let Some(idx) = layout.index_of(*k) { - return Some(layout.fields[idx].1.clone()); - } - } - StaticType::Series(inner) => { - if let StaticType::Record(layout) = inner.as_ref() - && let Some(idx) = layout.index_of(*k) - { - return Some(StaticType::Series(Box::new(layout.fields[idx].1.clone()))); - } - } - StaticType::Stream(inner) => { - if let StaticType::Record(layout) = inner.as_ref() - && let Some(idx) = layout.index_of(*k) - { - return Some(StaticType::Stream(Box::new(layout.fields[idx].1.clone()))); - } - } - StaticType::Any => return Some(StaticType::Any), - _ => {} - } - None - } - 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 == *args_ty) // 1. Try exact match first - .or_else(|| sigs.iter().find(|sig| sig.params.is_assignable_from(args_ty))) // 2. Fallback to implicit coercion - .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 as_float(&self) -> Option { - match self { - Value::Float(f) => Some(*f), - Value::Int(i) => Some(*i as f64), - _ => None, - } - } - - pub fn as_int(&self) -> Option { - match self { - Value::Int(i) => Some(*i), - Value::DateTime(d) => Some(*d), - Value::Bool(b) => Some(if *b { 1 } else { 0 }), - _ => None, - } - } - - 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. - pub fn as_slice(&self) -> Option<&[Value]> { - match self { - Value::Tuple(v) => Some(v), - _ => None, - } - } - - pub fn make_tuple(values: Vec) -> Self { - Value::Tuple(Rc::new(values)) - } - - pub fn make_record(keys: Vec, values: Vec) -> Self { - let fields: Vec<_> = keys - .into_iter() - .zip(values.iter().map(|v| v.static_type())) - .collect(); - let layout = RecordLayout::get_or_create(fields); - Value::Record(layout, Rc::new(values)) - } - - pub fn make_function(purity: Purity, func: impl Fn(&[Value]) -> Value + 'static) -> Self { - Value::Function(Rc::new(NativeFunction { - func: Rc::new(func), - purity, - })) - } - - 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(layout, _) => StaticType::Record(layout.clone()), - Value::FieldAccessor(k) => StaticType::FieldAccessor(*k), - 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(layout, values) => { - write!(f, "{{")?; - for i in 0..values.len() { - if i > 0 { - write!(f, ", ")?; - } - write!(f, ":{} {}", layout.fields[i].0.name(), values[i])?; - } - write!(f, "}}") - } - Value::FieldAccessor(k) => write!(f, ".{}", k.name()), - Value::Function(f_meta) => write!(f, "", f_meta.purity), - Value::Object(o) => { - if let Some(val_series) = - o.as_any() - .downcast_ref::() - { - write!( - f, - "SharedValueSeries[len: {}]", - val_series.buffer.borrow().len() - ) - } else { - 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. The identity is defined by the object instance (unique ID). +/// SourceLocation is kept as optional metadata. +#[derive(Debug, Clone)] +pub struct NodeIdentity { + pub id: u64, + pub location: Option, +} + +impl PartialEq for NodeIdentity { + fn eq(&self, other: &Self) -> bool { + self.id == other.id + } +} + +impl Eq for NodeIdentity {} + +impl std::hash::Hash for NodeIdentity { + fn hash(&self, state: &mut H) { + self.id.hash(state); + } +} + +pub type Identity = Rc; + +static NODE_ID_COUNTER: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(1); + +impl NodeIdentity { + pub fn next_id() -> u64 { + NODE_ID_COUNTER.fetch_add(1, std::sync::atomic::Ordering::SeqCst) + } + + /// Creates a new unique identity at the given location. + pub fn new(location: SourceLocation) -> Identity { + Rc::new(NodeIdentity { + id: Self::next_id(), + location: Some(location), + }) + } + + /// Creates a new unique identity without location metadata (for synthetic nodes). + pub fn anonymous() -> Identity { + Rc::new(NodeIdentity { + id: Self::next_id(), + location: None, + }) + } +} + +/// Interned string identifier +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct Keyword(pub u32); + +static KEYWORD_REGISTRY: OnceLock>> = OnceLock::new(); +// We use String here instead of Arc because benchmarks (e.g. record_optimizations.myc) +// showed a 4% performance regression with Arc due to atomic overhead during formatting. +// Since name() is mostly used for diagnostics and UI, the deep clone is acceptable. +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() + } +} + +/// A shared schema for Records, providing O(1) field lookup via FMap optimization. +#[derive(Debug, Clone)] +pub struct RecordLayout { + pub fields: Vec<(Keyword, StaticType)>, + /// Optimization: maps (Keyword.idx - min_idx) to index in fields. + fmap: Vec, + min_idx: u32, +} + +impl PartialEq for RecordLayout { + fn eq(&self, other: &Self) -> bool { + self.fields == other.fields + } +} + +impl Eq for RecordLayout {} + +impl std::hash::Hash for RecordLayout { + fn hash(&self, state: &mut H) { + self.fields.hash(state); + } +} + +type LayoutMap = HashMap, std::sync::Arc>; + +static LAYOUT_REGISTRY: OnceLock> = OnceLock::new(); + +impl RecordLayout { + pub fn get_or_create(fields: Vec<(Keyword, StaticType)>) -> std::sync::Arc { + let mut reg = LAYOUT_REGISTRY + .get_or_init(|| Mutex::new(HashMap::new())) + .lock() + .unwrap(); + + if let Some(layout) = reg.get(&fields) { + return layout.clone(); + } + + let mut min_idx = u32::MAX; + let mut max_idx = 0; + for (k, _) in &fields { + min_idx = min_idx.min(k.0); + max_idx = max_idx.max(k.0); + } + + let mut fmap = vec![-1; (max_idx - min_idx + 1) as usize]; + for (i, (k, _)) in fields.iter().enumerate() { + fmap[(k.0 - min_idx) as usize] = i as i32; + } + + let layout = std::sync::Arc::new(RecordLayout { + fields: fields.clone(), + fmap, + min_idx, + }); + + reg.insert(fields, layout.clone()); + layout + } + + #[inline(always)] + pub fn index_of(&self, key: Keyword) -> Option { + if key.0 < self.min_idx { + return None; + } + let offset = (key.0 - self.min_idx) as usize; + if offset >= self.fmap.len() { + return None; + } + let idx = self.fmap[offset]; + if idx < 0 { None } else { Some(idx as usize) } + } +} + +/// Interface for custom objects (Closures, Series, Streams) +pub trait Object: fmt::Debug { + fn type_name(&self) -> &'static str; + fn as_any(&self) -> &dyn Any; + + /// Optional optimization: If this object behaves like a time series (indexable lookbacks), + /// it can return a reference to its Series trait implementation. + fn as_series(&self) -> Option<&dyn Series> { + None + } +} + +/// Unified interface for all series types (Local RecordSeries, SeriesViews, and future SharedSeries). +pub trait Series { + /// Gets the value at the specified lookback index (0 = newest). + fn get_item(&self, index: usize) -> Option; + + /// Returns the current number of elements in the buffer. + fn len(&self) -> usize; + + /// Returns the total number of elements that have passed through this series. + fn total_count(&self) -> u64; + + /// Returns true if the series is empty. + fn is_empty(&self) -> bool { + self.len() == 0 + } +} + +/// A shared sequence of values, used by both Tuples and Records. +pub type ValueList = Rc>; + +/// Purity level of an expression or function. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum Purity { + /// Has side effects (e.g. Set, Print) + Impure, + /// No side effects, but not deterministic (e.g. now(), random()) + SideEffectFree, + /// No side effects and deterministic (e.g. sin(x), 1 + 2) + Pure, +} + +pub type NativeFn = dyn Fn(&[Value]) -> Value; +pub type PipeFn = dyn FnMut(&[Value]) -> Value; + +/// A native host function with metadata. +pub struct NativeFunction { + pub func: Rc, + pub purity: Purity, +} + +impl fmt::Debug for NativeFunction { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "", self.purity) + } +} + +/// 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(std::sync::Arc, ValueList), + FieldAccessor(Keyword), + Function(Rc), + 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(la, va), Value::Record(lb, vb)) => { + std::sync::Arc::ptr_eq(la, lb) && va == vb + } + (Value::FieldAccessor(a), Value::FieldAccessor(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, + } + } +} + +impl PartialOrd for Value { + fn partial_cmp(&self, other: &Self) -> Option { + match (self, other) { + (Value::Int(a), Value::Int(b)) => a.partial_cmp(b), + (Value::Float(a), Value::Float(b)) => a.partial_cmp(b), + (Value::Int(a), Value::Float(b)) => (*a as f64).partial_cmp(b), + (Value::Float(a), Value::Int(b)) => a.partial_cmp(&(*b as f64)), + (Value::DateTime(a), Value::DateTime(b)) => a.partial_cmp(b), + (Value::Text(a), Value::Text(b)) => a.partial_cmp(b), + _ => None, + } + } +} + +#[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, + Optional(Box), // Represents T | Void (e.g. for filter pipes) + List(Box), // Legacy / Dynamic list + Series(Box), // Time series of a specific type + Stream(Box), // Reactive stream of a specific type + Tuple(Vec), // Heterogeneous fixed-size + Vector(Box, usize), // Homogeneous fixed-size + Matrix(Box, Vec), // Multi-dimensional homogeneous + Record(std::sync::Arc), + FieldAccessor(Keyword), + Function(Box), + FunctionOverloads(Vec), + Object(&'static str), + /// A diagnostic poison type, allowing type-checking to continue after an error. + Error, +} + +impl fmt::Display for StaticType { + 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::Optional(inner) => write!(f, "optional({})", inner), + StaticType::List(inner) => write!(f, "list({})", inner), + StaticType::Series(inner) => write!(f, "series<{}>", inner), + StaticType::Stream(inner) => write!(f, "stream<{}>", 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(layout) => { + write!(f, "{{")?; + for (i, (k, v)) in layout.fields.iter().enumerate() { + if i > 0 { + write!(f, ", ")?; + } + write!(f, ":{} {}", k.name(), v)?; + } + write!(f, "}}") + } + StaticType::FieldAccessor(k) => write!(f, ".{}", k.name()), + 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), + StaticType::Error => write!(f, ""), + } + } +} + +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) + || matches!(self, StaticType::Error) + || matches!(other, StaticType::Error) + { + return true; + } + + match (self, other) { + // Implicit Coercions + (StaticType::Float, StaticType::Int) => true, + (StaticType::Int, StaticType::Bool) => true, + (StaticType::Int, StaticType::DateTime) => true, + (StaticType::DateTime, StaticType::Int) => true, + + // Optional(T) is assignable from T or Void + (StaticType::Optional(inner), other) => { + matches!(other, StaticType::Void) || inner.is_assignable_from(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)) + } + // Tuple to Tuple (Element-wise) + (StaticType::Tuple(elements), StaticType::Tuple(other_elements)) => { + if elements.len() != other_elements.len() { + return false; + } + elements + .iter() + .zip(other_elements.iter()) + .all(|(e, o)| e.is_assignable_from(o)) + } + // 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)) + } + } + // Records are assignable if their layouts match (Structural identity via interning) + (StaticType::Record(a), StaticType::Record(b)) => std::sync::Arc::ptr_eq(a, b), + // Series are assignable if their inner types are assignable + (StaticType::Series(inner_a), StaticType::Series(inner_b)) => { + inner_a.is_assignable_from(inner_b) + } + // Streams are assignable if their inner types are assignable + (StaticType::Stream(inner_a), StaticType::Stream(inner_b)) => { + inner_a.is_assignable_from(inner_b) + } + _ => 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::FieldAccessor(k) => { + // (.name { :name val }) + // The args_ty should be a Tuple of [Record] + let rec_ty = if let StaticType::Tuple(t) = args_ty { + t.first()? + } else { + args_ty + }; + + match rec_ty { + StaticType::Record(layout) => { + if let Some(idx) = layout.index_of(*k) { + return Some(layout.fields[idx].1.clone()); + } + } + StaticType::Series(inner) => { + if let StaticType::Record(layout) = inner.as_ref() + && let Some(idx) = layout.index_of(*k) + { + return Some(StaticType::Series(Box::new(layout.fields[idx].1.clone()))); + } + } + StaticType::Stream(inner) => { + if let StaticType::Record(layout) = inner.as_ref() + && let Some(idx) = layout.index_of(*k) + { + return Some(StaticType::Stream(Box::new(layout.fields[idx].1.clone()))); + } + } + StaticType::Any => return Some(StaticType::Any), + _ => {} + } + None + } + 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 == *args_ty) // 1. Try exact match first + .or_else(|| sigs.iter().find(|sig| sig.params.is_assignable_from(args_ty))) // 2. Fallback to implicit coercion + .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 as_float(&self) -> Option { + match self { + Value::Float(f) => Some(*f), + Value::Int(i) => Some(*i as f64), + _ => None, + } + } + + pub fn as_int(&self) -> Option { + match self { + Value::Int(i) => Some(*i), + Value::DateTime(d) => Some(*d), + Value::Bool(b) => Some(if *b { 1 } else { 0 }), + _ => None, + } + } + + 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. + pub fn as_slice(&self) -> Option<&[Value]> { + match self { + Value::Tuple(v) => Some(v), + _ => None, + } + } + + pub fn make_tuple(values: Vec) -> Self { + Value::Tuple(Rc::new(values)) + } + + pub fn make_record(keys: Vec, values: Vec) -> Self { + let fields: Vec<_> = keys + .into_iter() + .zip(values.iter().map(|v| v.static_type())) + .collect(); + let layout = RecordLayout::get_or_create(fields); + Value::Record(layout, Rc::new(values)) + } + + pub fn make_function(purity: Purity, func: impl Fn(&[Value]) -> Value + 'static) -> Self { + Value::Function(Rc::new(NativeFunction { + func: Rc::new(func), + purity, + })) + } + + 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(layout, _) => StaticType::Record(layout.clone()), + Value::FieldAccessor(k) => StaticType::FieldAccessor(*k), + 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(layout, values) => { + write!(f, "{{")?; + for i in 0..values.len() { + if i > 0 { + write!(f, ", ")?; + } + write!(f, ":{} {}", layout.fields[i].0.name(), values[i])?; + } + write!(f, "}}") + } + Value::FieldAccessor(k) => write!(f, ".{}", k.name()), + Value::Function(f_meta) => write!(f, "", f_meta.purity), + Value::Object(o) => { + if let Some(val_series) = + o.as_any() + .downcast_ref::() + { + write!( + f, + "SharedValueSeries[len: {}]", + val_series.buffer.borrow().len() + ) + } else { + 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/bin/ast.rs b/src/bin/ast.rs index a1f1760..7ecbb53 100644 --- a/src/bin/ast.rs +++ b/src/bin/ast.rs @@ -1,171 +1,171 @@ -use clap::Parser; -use myc::ast::environment::Environment; -use myc::utils::tester; -use std::fs; -use std::io::{self, IsTerminal, Read}; -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, - - /// Library directories to load before execution - #[arg(short, long)] - lib: Vec, - - /// Disable optimization - #[arg(long)] - no_opt: bool, -} - -fn main() { - let cli = Cli::parse(); - let mut env = Environment::new(); - env.optimization = !cli.no_opt; - - // Load libraries (Now just search paths) - for lib_path in &cli.lib { - env.add_search_path(lib_path); - } - - 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 filter = cli - .file - .as_ref() - .and_then(|p| p.file_name()) - .map(|n| n.to_string_lossy().to_string()); - let results = tester::run_benchmarks(cli.update_bench, filter.as_deref()); - 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; - } - - // Determine the source code to execute - let source = if let Some(script_str) = cli.eval { - Some(script_str) - } else if let Some(file_path) = cli.file { - if file_path.to_str() == Some("-") { - read_stdin() - } else { - match fs::read_to_string(&file_path) { - Ok(content) => Some(content), - Err(e) => { - eprintln!("Error reading file {:?}: {}", file_path, e); - std::process::exit(1); - } - } - } - } else if !io::stdin().is_terminal() { - read_stdin() - } else { - None - }; - - if let Some(content) = source { - 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(&env, &content); - } else { - execute(&env, &content); - } - } else { - println!("MYC AST Compiler CLI. Use --help for usage."); - } -} - -fn read_stdin() -> Option { - let mut buffer = String::new(); - match io::stdin().read_to_string(&mut buffer) { - Ok(_) => Some(buffer), - Err(e) => { - eprintln!("Error reading from stdin: {}", e); - std::process::exit(1); - } - } -} - -fn execute(env: &Environment, source: &str) { - let result = env.compile(source); - for diag in &result.diagnostics.items { - let level = format!("{:?}", diag.level).to_uppercase(); - let loc = diag - .identity - .as_ref() - .and_then(|id| id.location) - .map(|l| format!(" at {}:{}", l.line, l.col)) - .unwrap_or_default(); - eprintln!("{}{} : {}", level, loc, diag.message); - } - - if result.diagnostics.has_errors() { - std::process::exit(1); - } - - match env.run_script_compiled(result.ast.unwrap()) { - Ok(res) => println!("{}", res), - Err(e) => { - eprintln!("Runtime Error: {}", e); - std::process::exit(1); - } - } -} - -fn execute_trace(env: &Environment, source: &str) { - match env.run_debug(source) { - Ok((Ok(result), logs)) => { - for line in logs { - println!("{}", line); - } - println!("Result: {}", result); - } - Ok((Err(e), logs)) => { - for line in 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::io::{self, IsTerminal, Read}; +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, + + /// Library directories to load before execution + #[arg(short, long)] + lib: Vec, + + /// Disable optimization + #[arg(long)] + no_opt: bool, +} + +fn main() { + let cli = Cli::parse(); + let mut env = Environment::new(); + env.optimization = !cli.no_opt; + + // Load libraries (Now just search paths) + for lib_path in &cli.lib { + env.add_search_path(lib_path); + } + + 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 filter = cli + .file + .as_ref() + .and_then(|p| p.file_name()) + .map(|n| n.to_string_lossy().to_string()); + let results = tester::run_benchmarks(cli.update_bench, filter.as_deref()); + 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; + } + + // Determine the source code to execute + let source = if let Some(script_str) = cli.eval { + Some(script_str) + } else if let Some(file_path) = cli.file { + if file_path.to_str() == Some("-") { + read_stdin() + } else { + match fs::read_to_string(&file_path) { + Ok(content) => Some(content), + Err(e) => { + eprintln!("Error reading file {:?}: {}", file_path, e); + std::process::exit(1); + } + } + } + } else if !io::stdin().is_terminal() { + read_stdin() + } else { + None + }; + + if let Some(content) = source { + 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(&env, &content); + } else { + execute(&env, &content); + } + } else { + println!("MYC AST Compiler CLI. Use --help for usage."); + } +} + +fn read_stdin() -> Option { + let mut buffer = String::new(); + match io::stdin().read_to_string(&mut buffer) { + Ok(_) => Some(buffer), + Err(e) => { + eprintln!("Error reading from stdin: {}", e); + std::process::exit(1); + } + } +} + +fn execute(env: &Environment, source: &str) { + let result = env.compile(source); + for diag in &result.diagnostics.items { + let level = format!("{:?}", diag.level).to_uppercase(); + let loc = diag + .identity + .as_ref() + .and_then(|id| id.location) + .map(|l| format!(" at {}:{}", l.line, l.col)) + .unwrap_or_default(); + eprintln!("{}{} : {}", level, loc, diag.message); + } + + if result.diagnostics.has_errors() { + std::process::exit(1); + } + + match env.run_script_compiled(result.ast.unwrap()) { + Ok(res) => println!("{}", res), + Err(e) => { + eprintln!("Runtime Error: {}", e); + std::process::exit(1); + } + } +} + +fn execute_trace(env: &Environment, source: &str) { + match env.run_debug(source) { + Ok((Ok(result), logs)) => { + for line in logs { + println!("{}", line); + } + println!("Result: {}", result); + } + Ok((Err(e), logs)) => { + for line in 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/integration_test.rs b/src/integration_test.rs index 5119b68..41c6fdc 100644 --- a/src/integration_test.rs +++ b/src/integration_test.rs @@ -1,7 +1,7 @@ #[cfg(test)] mod tests { use crate::ast::environment::Environment; - use crate::ast::nodes::UntypedKind; + use crate::ast::nodes::SyntaxKind; use crate::ast::parser::Parser; use crate::ast::types::Value; @@ -11,7 +11,7 @@ mod tests { let mut parser = Parser::new(source); let ast = parser.parse_expression(); - if let UntypedKind::Constant(Value::Int(val)) = ast.kind { + if let SyntaxKind::Constant(Value::Int(val)) = ast.kind { assert_eq!(val, 123); } else { panic!("Expected Integer constant, got {:?}", ast.kind); @@ -24,7 +24,7 @@ mod tests { let mut parser = Parser::new(source); let ast = parser.parse_expression(); - if let UntypedKind::Constant(Value::Int(val)) = ast.kind { + if let SyntaxKind::Constant(Value::Int(val)) = ast.kind { assert_eq!(val, -42); } else { panic!("Expected Integer constant, got {:?}", ast.kind); @@ -37,7 +37,7 @@ mod tests { let mut parser = Parser::new(source); let ast = parser.parse_expression(); - if let UntypedKind::Constant(Value::Float(val)) = ast.kind { + if let SyntaxKind::Constant(Value::Float(val)) = ast.kind { assert_eq!(val, 123.45); } else { panic!("Expected Float constant, got {:?}", ast.kind); @@ -50,7 +50,7 @@ mod tests { let mut parser = Parser::new(source); let ast = parser.parse_expression(); - if let UntypedKind::Constant(Value::Float(val)) = ast.kind { + if let SyntaxKind::Constant(Value::Float(val)) = ast.kind { assert_eq!(val, -10.5); } else { panic!("Expected Float constant, got {:?}", ast.kind); diff --git a/src/main.rs b/src/main.rs index 93af138..990bdfc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,696 +1,696 @@ -use eframe::egui; -use myc::ast::environment::Environment; -use std::path::PathBuf; - -fn main() -> eframe::Result { - let options = eframe::NativeOptions { - viewport: egui::ViewportBuilder::default().with_inner_size([1024.0, 768.0]), - ..Default::default() - }; - eframe::run_native( - "Compiler GUI", - options, - Box::new(|_cc| Ok(Box::new(CompilerApp::default()))), - ) -} - -struct Example { - name: String, - path: PathBuf, - content: String, -} - -#[derive(PartialEq)] -enum AppTab { - Output, - Trace, -} - -struct CompilerApp { - source_code: String, - current_example_path: Option, - save_as_name: String, - show_save_as: bool, - output_log: String, - trace_logs: Vec, - active_tab: AppTab, - debug_enabled: bool, - env: Environment, - is_first_frame: bool, - examples: Vec, -} - -impl Default for CompilerApp { - fn default() -> Self { - let examples = std::fs::read_dir("examples") - .map(|entries| { - entries - .filter_map(|e| e.ok()) - .filter(|e| e.path().extension().is_some_and(|ext| ext == "myc")) - .filter_map(|e| { - let path = e.path(); - let name = e.file_name().into_string().ok()?; - let content = std::fs::read_to_string(&path).ok()?; - Some(Example { - name, - path, - content, - }) - }) - .collect() - }) - .unwrap_or_default(); - - let env = Environment::new(); - - Self { - source_code: String::from( - r#" -(do - (def fib (fn [n] - (if (< n 2) - n - (+ (fib (- n 1)) (fib (- n 2)))))) - - (fib 10) -) -"#, - ), - current_example_path: None, - save_as_name: String::new(), - show_save_as: false, - output_log: String::from(""), - trace_logs: Vec::new(), - active_tab: AppTab::Output, - debug_enabled: false, - env, - is_first_frame: true, - examples, - } - } -} - -impl CompilerApp { - fn compile(&mut self) { - let start_total = std::time::Instant::now(); - self.trace_logs.clear(); - - // Reset environment for a fresh run - self.env = Environment::new(); - self.env.optimization = true; - - let res = (|| -> Result<(myc::ast::types::Value, std::time::Duration), String> { - let start_run = std::time::Instant::now(); - let result = if self.debug_enabled { - let (res, logs) = self.env.run_debug(&self.source_code)?; - - // Batch-truncate logs for GUI performance - let mut display_logs = logs; - if display_logs.len() > 1000 { - display_logs.truncate(1000); - display_logs.push("... [Trace truncated to 1,000 entries] ...".to_string()); - } - - for line in &mut display_logs { - if line.len() > 255 - && let Some((idx, _)) = line.char_indices().nth(255) - { - line.truncate(idx); - line.push_str("..."); - } - } - - self.trace_logs = display_logs; - self.active_tab = AppTab::Trace; - res? - } else { - self.env.run_script(&self.source_code)? - }; - let duration_run = start_run.elapsed(); - - Ok((result, duration_run)) - })(); - - match res { - Ok((val, d_run)) => { - let d_total = start_total.elapsed(); - let now = chrono::Local::now().format("%H:%M:%S").to_string(); - - let mut stats = format!( - "Execution Successful.\nResult: {}\n\nTotal Duration: {:?}\nFinished at: {}", - val, d_total, now, - ); - - if !self.debug_enabled { - stats.push_str(&format!("\nRun Phase: {:?}", d_run)); - } - - self.output_log = stats; - if !self.debug_enabled { - self.active_tab = AppTab::Output; - } - } - Err(e) => { - self.output_log = format!("Error during Compilation/Execution: {}", e); - self.active_tab = AppTab::Output; - } - } - } - - fn dump_ast(&mut self) { - self.env = Environment::new(); - self.env.optimization = true; // Enable optimization for AST dump - - match self.env.dump_ast(&self.source_code) { - Ok(dump) => { - self.output_log = format!("--- BOUND AST DUMP ---\n\n{}", dump); - } - Err(e) => { - self.output_log = format!("Error during AST Binding: {}", e); - } - } - } - - fn save(&mut self) { - if let Some(path) = &self.current_example_path { - match std::fs::write(path, &self.source_code) { - Ok(_) => { - self.output_log = format!("Successfully saved to {:?}", path); - // Update the cached content in examples list so it stays in sync - if let Some(ex) = self.examples.iter_mut().find(|e| e.path == *path) { - ex.content = self.source_code.clone(); - } - } - Err(e) => { - self.output_log = format!("Failed to save: {}", e); - } - } - } else { - self.output_log = "No file loaded to save. Use 'Examples' to load one.".to_string(); - } - } - - fn save_as(&mut self) { - if self.save_as_name.trim().is_empty() { - self.output_log = "Error: Please enter a filename.".to_string(); - return; - } - - let mut filename = self.save_as_name.trim().to_string(); - if !filename.ends_with(".myc") { - filename.push_str(".myc"); - } - - let path = PathBuf::from("examples").join(filename); - match std::fs::write(&path, &self.source_code) { - Ok(_) => { - self.output_log = format!("Successfully saved new file to {:?}", path); - self.current_example_path = Some(path); - self.show_save_as = false; - self.save_as_name.clear(); - self.refresh_examples(); - } - Err(e) => { - self.output_log = format!("Failed to save: {}", e); - } - } - } - - fn refresh_examples(&mut self) { - if let Ok(entries) = std::fs::read_dir("examples") { - self.examples = entries - .filter_map(|e| e.ok()) - .filter(|e| e.path().extension().is_some_and(|ext| ext == "myc")) - .filter_map(|e| { - let path = e.path(); - let name = e.file_name().into_string().ok()?; - let content = std::fs::read_to_string(&path).ok()?; - Some(Example { - name, - path, - content, - }) - }) - .collect(); - } - } - - fn run_all_tests(&mut self) { - use myc::utils::tester; - let results = tester::run_functional_tests(); - let mut log = String::from("FUNCTIONAL TEST RESULTS:\n\n"); - let mut passed = 0; - for res in &results { - log.push_str(&format!( - "{}: {} - {}\n", - if res.success { "โœ…" } else { "โŒ" }, - res.name, - res.message - )); - if res.success { - passed += 1; - } - } - log.push_str(&format!("\nTotal: {}/{}", passed, results.len())); - self.output_log = log; - } - - fn run_benchmarks(&mut self, update: bool, only_current: bool) { - use myc::utils::tester; - - let filter = if only_current { - self.current_example_path - .as_ref() - .and_then(|p| p.file_name()) - .map(|n| n.to_string_lossy().to_string()) - } else { - None - }; - - if cfg!(debug_assertions) && !update { - self.output_log = String::from( - "โš ๏ธ WARNING: Benchmarks in Debug mode are inaccurate!\nUse Release build for real measurements.\n\n", - ); - } else { - self.output_log = format!( - "{}...\n\n", - match (update, only_current) { - (true, true) => "UPDATING BASELINE FOR CURRENT SCRIPT", - (true, false) => "UPDATING ALL BASELINES", - (false, _) => "RUNNING BENCHMARKS", - } - ); - } - - let results = tester::run_benchmarks(update, filter.as_deref()); - let mut log = self.output_log.clone(); - - for res in results { - let diff = res - .diff_pct - .map_or(String::new(), |d| format!(" ({:+.1}%)", d)); - log.push_str(&format!( - "{}: {} - {:?}{}\n", - res.status, res.name, res.median, diff - )); - } - self.output_log = log; - } -} - -impl eframe::App for CompilerApp { - fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { - // Top Panel: Menu Bar & Toolbar - egui::TopBottomPanel::top("top_panel").show(ctx, |ui| { - egui::MenuBar::new().ui(ui, |ui: &mut egui::Ui| { - ui.menu_button("File", |ui: &mut egui::Ui| { - let file_name = self - .current_example_path - .as_ref() - .map(|p| { - p.file_name() - .unwrap_or_default() - .to_string_lossy() - .into_owned() - }) - .unwrap_or_else(|| "None".to_string()); - - if ui.button(format!("Save {} (Ctrl+S)", file_name)).clicked() { - self.save(); - ui.close(); - } - if ui.button("Save As...").clicked() { - self.show_save_as = true; - ui.close(); - } - }); - - ui.menu_button("Build", |ui: &mut egui::Ui| { - if ui.button("Compile (Shift+Enter)").clicked() { - self.compile(); - ui.close(); - } - if ui.button("Dump AST").clicked() { - self.dump_ast(); - ui.close(); - } - ui.separator(); - ui.checkbox(&mut self.debug_enabled, "Debug Mode"); - }); - - ui.menu_button("Tools", |ui: &mut egui::Ui| { - if ui.button("Test All").clicked() { - self.run_all_tests(); - ui.close(); - } - ui.separator(); - - let is_debug = cfg!(debug_assertions); - ui.add_enabled_ui(!is_debug, |ui: &mut egui::Ui| { - let btn = ui.button("Run All Benchmarks"); - if is_debug { - btn.on_hover_text("Benchmarks are only available in Release builds."); - } else if btn.clicked() { - self.run_benchmarks(false, false); - ui.close(); - } - - let btn_base_all = ui.button("Update All Baselines"); - if is_debug { - btn_base_all.on_hover_text( - "Updating baselines is only allowed in Release builds.", - ); - } else if btn_base_all.clicked() { - self.run_benchmarks(true, false); - ui.close(); - } - - let btn_base_curr = ui.button("Update Current Baseline"); - if is_debug { - btn_base_curr.on_hover_text( - "Updating baselines is only allowed in Release builds.", - ); - } else if btn_base_curr.clicked() { - self.run_benchmarks(true, true); - ui.close(); - } - }); - }); - }); - - ui.separator(); - - // Toolbar (Speed Buttons) - ui.horizontal(|ui| { - ui.style_mut().spacing.item_spacing.x = 4.0; // tighter spacing for speed buttons - - if ui - .button("โ–ถ Run") - .on_hover_text("Compile and Run (Shift+Enter)") - .clicked() - { - self.compile(); - } - - if ui - .button("๐Ÿ’พ Save") - .on_hover_text("Save current file (Ctrl+S)") - .clicked() - { - self.save(); - } - - ui.separator(); - - if ui - .button("๐Ÿ” AST") - .on_hover_text("Dump Bound AST") - .clicked() - { - self.dump_ast(); - } - - ui.separator(); - - ui.toggle_value(&mut self.debug_enabled, "๐Ÿ› Debug"); - - ui.separator(); - - if ui - .button("๐Ÿงช Test") - .on_hover_text("Run all functional tests") - .clicked() - { - self.run_all_tests(); - } - - ui.separator(); - - let is_debug = cfg!(debug_assertions); - ui.add_enabled_ui(!is_debug, |ui| { - let bench_btn = ui.button("โฑ Bench"); - if is_debug { - bench_btn.on_hover_text("Benchmarks are only available in Release builds."); - } else if bench_btn.clicked() { - self.run_benchmarks(false, false); - } - }); - }); - ui.add_space(2.0); - }); - - // Left Side Panel: Examples - egui::SidePanel::left("examples_panel") - .resizable(true) - .default_width(150.0) - .show(ctx, |ui| { - ui.heading("Examples"); - ui.add_space(5.0); - egui::ScrollArea::vertical().show(ui, |ui| { - ui.with_layout(egui::Layout::top_down_justified(egui::Align::LEFT), |ui| { - for example in &self.examples { - if ui.button(&example.name).clicked() { - self.source_code = example.content.clone(); - self.current_example_path = Some(example.path.clone()); - } - } - }); - }); - }); - - // Bottom Panel: Output Log & Controls (Resizable) - egui::TopBottomPanel::bottom("bottom_panel") - .resizable(true) - .min_height(100.0) - .default_height(200.0) - .max_height(ctx.available_rect().height() * 0.5) - .show(ctx, |ui| { - ui.add_space(5.0); - - if self.show_save_as { - ui.group(|ui| { - ui.horizontal(|ui| { - ui.label("New Filename:"); - ui.text_edit_singleline(&mut self.save_as_name); - if ui.button("Save Now").clicked() { - self.save_as(); - } - if ui.button("Cancel").clicked() { - self.show_save_as = false; - } - }); - }); - ui.add_space(5.0); - } - - ui.horizontal(|ui| { - ui.selectable_value(&mut self.active_tab, AppTab::Output, "Output"); - ui.selectable_value(&mut self.active_tab, AppTab::Trace, "Execution Trace"); - ui.add_space(20.0); - - if ui.button("Copy Content").clicked() { - let text = match self.active_tab { - AppTab::Output => self.output_log.clone(), - AppTab::Trace => self.trace_logs.join("\n"), - }; - ui.ctx().copy_text(text); - } - - if ui.button("Clear Log").clicked() { - self.output_log.clear(); - self.trace_logs.clear(); - } - }); - - ui.add_space(5.0); - - // Log Output Area - match self.active_tab { - AppTab::Output => { - egui::ScrollArea::both() - .id_salt("output_log_scroll") - .auto_shrink(false) - .show(ui, |ui| { - ui.add( - egui::TextEdit::multiline(&mut self.output_log) - .font(egui::TextStyle::Monospace) - .hint_text("Ready to compile...") - .code_editor() - .lock_focus(false) - .desired_width(f32::INFINITY) - .frame(false), - ); - }); - } - AppTab::Trace => { - egui::ScrollArea::both() - .id_salt("trace_scroll") - .auto_shrink(false) - .show(ui, |ui| { - ui.style_mut().override_text_style = - Some(egui::TextStyle::Monospace); - ui.with_layout(egui::Layout::top_down(egui::Align::LEFT), |ui| { - for line in &self.trace_logs { - ui.label(line); - } - }); - }); - } - } - }); - - // Central Panel: Source Code Editor (Fills remaining space) - egui::CentralPanel::default().show(ctx, |ui| { - ui.heading("Source Code Editor"); - ui.label("Input Source:"); - - let editor_id = ui.id().with("source_code_editor"); - - // Handle Shift+Enter shortcut BEFORE the editor handles it - let compile_shortcut = - egui::KeyboardShortcut::new(egui::Modifiers::SHIFT, egui::Key::Enter); - if ui.input_mut(|i| i.consume_shortcut(&compile_shortcut)) { - self.compile(); - } - - let save_shortcut = egui::KeyboardShortcut::new(egui::Modifiers::COMMAND, egui::Key::S); - if ui.input_mut(|i| i.consume_shortcut(&save_shortcut)) { - self.save(); - } - - // Set initial focus - if self.is_first_frame { - ui.ctx().memory_mut(|m| m.request_focus(editor_id)); - self.is_first_frame = false; - } - - let mut layouter = |ui: &egui::Ui, buffer: &dyn egui::TextBuffer, _wrap_width: f32| { - let mut layout_job = highlight_myc(ui.ctx(), buffer.as_str()); - layout_job.wrap.max_width = f32::INFINITY; // Disable wrapping for code editor - ui.painter().layout_job(layout_job) - }; - - egui::ScrollArea::both() - .id_salt("source_editor_scroll") - .auto_shrink(false) - .show(ui, |ui| { - ui.add( - egui::TextEdit::multiline(&mut self.source_code) - .id(editor_id) - .font(egui::TextStyle::Monospace) - .code_editor() - .lock_focus(true) - .desired_width(f32::INFINITY) - .frame(false) - .layouter(&mut layouter), - ); - }); - }); - } -} - -fn highlight_myc(ctx: &egui::Context, code: &str) -> egui::text::LayoutJob { - let mut job = egui::text::LayoutJob::default(); - let theme = &ctx.style().visuals; - - let color_kw = egui::Color32::from_rgb(86, 156, 214); // Blue - let color_str = egui::Color32::from_rgb(206, 145, 120); // Orange/Brown - let color_num = egui::Color32::from_rgb(181, 206, 168); // Greenish - let color_comment = egui::Color32::from_rgb(106, 153, 85); // Green - let color_bracket = [ - egui::Color32::from_rgb(255, 215, 0), // Gold - egui::Color32::from_rgb(218, 112, 214), // Orchid - egui::Color32::from_rgb(24, 131, 215), // Blue - ]; - - let mut chars = code.chars().peekable(); - let mut bracket_depth = 0; - - while let Some(c) = chars.next() { - let mut text = String::from(c); - let mut color = theme.widgets.noninteractive.text_color(); - - match c { - ';' => { - while let Some(&next) = chars.peek() { - if next == '\n' { - break; - } - text.push(chars.next().unwrap()); - } - color = color_comment; - } - '"' => { - while let Some(next) = chars.next() { - text.push(next); - if next == '"' { - break; - } - if next == '\\' - && let Some(escaped) = chars.next() - { - text.push(escaped); - } - } - color = color_str; - } - ':' => { - while let Some(&next) = chars.peek() { - if next.is_whitespace() || "()[]{}".contains(next) { - break; - } - text.push(chars.next().unwrap()); - } - color = color_kw; - } - '(' | '[' | '{' => { - color = color_bracket[bracket_depth % 3]; - bracket_depth += 1; - } - ')' | ']' | '}' => { - bracket_depth = bracket_depth.saturating_sub(1); - color = color_bracket[bracket_depth % 3]; - } - '`' | '~' | '@' => { - color = color_kw; - } - '0'..='9' => { - while let Some(&next) = chars.peek() { - if next.is_ascii_digit() || next == '.' { - text.push(chars.next().unwrap()); - } else { - break; - } - } - color = color_num; - } - _ if c.is_alphabetic() || "+-*/<=>!$%&_?.".contains(c) => { - while let Some(&next) = chars.peek() { - if next.is_alphanumeric() || "+-*/<=>!$%&_?.".contains(next) { - text.push(chars.next().unwrap()); - } else { - break; - } - } - if ["fn", "def", "do", "if", "assign", "recur", "cond", "macro"] - .contains(&text.as_str()) - { - color = color_kw; - } - } - _ => {} - } - - job.append( - &text, - 0.0, - egui::TextFormat { - font_id: egui::FontId::monospace(14.0), - color, - ..Default::default() - }, - ); - } - - job -} +use eframe::egui; +use myc::ast::environment::Environment; +use std::path::PathBuf; + +fn main() -> eframe::Result { + let options = eframe::NativeOptions { + viewport: egui::ViewportBuilder::default().with_inner_size([1024.0, 768.0]), + ..Default::default() + }; + eframe::run_native( + "Compiler GUI", + options, + Box::new(|_cc| Ok(Box::new(CompilerApp::default()))), + ) +} + +struct Example { + name: String, + path: PathBuf, + content: String, +} + +#[derive(PartialEq)] +enum AppTab { + Output, + Trace, +} + +struct CompilerApp { + source_code: String, + current_example_path: Option, + save_as_name: String, + show_save_as: bool, + output_log: String, + trace_logs: Vec, + active_tab: AppTab, + debug_enabled: bool, + env: Environment, + is_first_frame: bool, + examples: Vec, +} + +impl Default for CompilerApp { + fn default() -> Self { + let examples = std::fs::read_dir("examples") + .map(|entries| { + entries + .filter_map(|e| e.ok()) + .filter(|e| e.path().extension().is_some_and(|ext| ext == "myc")) + .filter_map(|e| { + let path = e.path(); + let name = e.file_name().into_string().ok()?; + let content = std::fs::read_to_string(&path).ok()?; + Some(Example { + name, + path, + content, + }) + }) + .collect() + }) + .unwrap_or_default(); + + let env = Environment::new(); + + Self { + source_code: String::from( + r#" +(do + (def fib (fn [n] + (if (< n 2) + n + (+ (fib (- n 1)) (fib (- n 2)))))) + + (fib 10) +) +"#, + ), + current_example_path: None, + save_as_name: String::new(), + show_save_as: false, + output_log: String::from(""), + trace_logs: Vec::new(), + active_tab: AppTab::Output, + debug_enabled: false, + env, + is_first_frame: true, + examples, + } + } +} + +impl CompilerApp { + fn compile(&mut self) { + let start_total = std::time::Instant::now(); + self.trace_logs.clear(); + + // Reset environment for a fresh run + self.env = Environment::new(); + self.env.optimization = true; + + let res = (|| -> Result<(myc::ast::types::Value, std::time::Duration), String> { + let start_run = std::time::Instant::now(); + let result = if self.debug_enabled { + let (res, logs) = self.env.run_debug(&self.source_code)?; + + // Batch-truncate logs for GUI performance + let mut display_logs = logs; + if display_logs.len() > 1000 { + display_logs.truncate(1000); + display_logs.push("... [Trace truncated to 1,000 entries] ...".to_string()); + } + + for line in &mut display_logs { + if line.len() > 255 + && let Some((idx, _)) = line.char_indices().nth(255) + { + line.truncate(idx); + line.push_str("..."); + } + } + + self.trace_logs = display_logs; + self.active_tab = AppTab::Trace; + res? + } else { + self.env.run_script(&self.source_code)? + }; + let duration_run = start_run.elapsed(); + + Ok((result, duration_run)) + })(); + + match res { + Ok((val, d_run)) => { + let d_total = start_total.elapsed(); + let now = chrono::Local::now().format("%H:%M:%S").to_string(); + + let mut stats = format!( + "Execution Successful.\nResult: {}\n\nTotal Duration: {:?}\nFinished at: {}", + val, d_total, now, + ); + + if !self.debug_enabled { + stats.push_str(&format!("\nRun Phase: {:?}", d_run)); + } + + self.output_log = stats; + if !self.debug_enabled { + self.active_tab = AppTab::Output; + } + } + Err(e) => { + self.output_log = format!("Error during Compilation/Execution: {}", e); + self.active_tab = AppTab::Output; + } + } + } + + fn dump_ast(&mut self) { + self.env = Environment::new(); + self.env.optimization = true; // Enable optimization for AST dump + + match self.env.dump_ast(&self.source_code) { + Ok(dump) => { + self.output_log = format!("--- BOUND AST DUMP ---\n\n{}", dump); + } + Err(e) => { + self.output_log = format!("Error during AST Binding: {}", e); + } + } + } + + fn save(&mut self) { + if let Some(path) = &self.current_example_path { + match std::fs::write(path, &self.source_code) { + Ok(_) => { + self.output_log = format!("Successfully saved to {:?}", path); + // Update the cached content in examples list so it stays in sync + if let Some(ex) = self.examples.iter_mut().find(|e| e.path == *path) { + ex.content = self.source_code.clone(); + } + } + Err(e) => { + self.output_log = format!("Failed to save: {}", e); + } + } + } else { + self.output_log = "No file loaded to save. Use 'Examples' to load one.".to_string(); + } + } + + fn save_as(&mut self) { + if self.save_as_name.trim().is_empty() { + self.output_log = "Error: Please enter a filename.".to_string(); + return; + } + + let mut filename = self.save_as_name.trim().to_string(); + if !filename.ends_with(".myc") { + filename.push_str(".myc"); + } + + let path = PathBuf::from("examples").join(filename); + match std::fs::write(&path, &self.source_code) { + Ok(_) => { + self.output_log = format!("Successfully saved new file to {:?}", path); + self.current_example_path = Some(path); + self.show_save_as = false; + self.save_as_name.clear(); + self.refresh_examples(); + } + Err(e) => { + self.output_log = format!("Failed to save: {}", e); + } + } + } + + fn refresh_examples(&mut self) { + if let Ok(entries) = std::fs::read_dir("examples") { + self.examples = entries + .filter_map(|e| e.ok()) + .filter(|e| e.path().extension().is_some_and(|ext| ext == "myc")) + .filter_map(|e| { + let path = e.path(); + let name = e.file_name().into_string().ok()?; + let content = std::fs::read_to_string(&path).ok()?; + Some(Example { + name, + path, + content, + }) + }) + .collect(); + } + } + + fn run_all_tests(&mut self) { + use myc::utils::tester; + let results = tester::run_functional_tests(); + let mut log = String::from("FUNCTIONAL TEST RESULTS:\n\n"); + let mut passed = 0; + for res in &results { + log.push_str(&format!( + "{}: {} - {}\n", + if res.success { "โœ…" } else { "โŒ" }, + res.name, + res.message + )); + if res.success { + passed += 1; + } + } + log.push_str(&format!("\nTotal: {}/{}", passed, results.len())); + self.output_log = log; + } + + fn run_benchmarks(&mut self, update: bool, only_current: bool) { + use myc::utils::tester; + + let filter = if only_current { + self.current_example_path + .as_ref() + .and_then(|p| p.file_name()) + .map(|n| n.to_string_lossy().to_string()) + } else { + None + }; + + if cfg!(debug_assertions) && !update { + self.output_log = String::from( + "โš ๏ธ WARNING: Benchmarks in Debug mode are inaccurate!\nUse Release build for real measurements.\n\n", + ); + } else { + self.output_log = format!( + "{}...\n\n", + match (update, only_current) { + (true, true) => "UPDATING BASELINE FOR CURRENT SCRIPT", + (true, false) => "UPDATING ALL BASELINES", + (false, _) => "RUNNING BENCHMARKS", + } + ); + } + + let results = tester::run_benchmarks(update, filter.as_deref()); + let mut log = self.output_log.clone(); + + for res in results { + let diff = res + .diff_pct + .map_or(String::new(), |d| format!(" ({:+.1}%)", d)); + log.push_str(&format!( + "{}: {} - {:?}{}\n", + res.status, res.name, res.median, diff + )); + } + self.output_log = log; + } +} + +impl eframe::App for CompilerApp { + fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { + // Top Panel: Menu Bar & Toolbar + egui::TopBottomPanel::top("top_panel").show(ctx, |ui| { + egui::MenuBar::new().ui(ui, |ui: &mut egui::Ui| { + ui.menu_button("File", |ui: &mut egui::Ui| { + let file_name = self + .current_example_path + .as_ref() + .map(|p| { + p.file_name() + .unwrap_or_default() + .to_string_lossy() + .into_owned() + }) + .unwrap_or_else(|| "None".to_string()); + + if ui.button(format!("Save {} (Ctrl+S)", file_name)).clicked() { + self.save(); + ui.close(); + } + if ui.button("Save As...").clicked() { + self.show_save_as = true; + ui.close(); + } + }); + + ui.menu_button("Build", |ui: &mut egui::Ui| { + if ui.button("Compile (Shift+Enter)").clicked() { + self.compile(); + ui.close(); + } + if ui.button("Dump AST").clicked() { + self.dump_ast(); + ui.close(); + } + ui.separator(); + ui.checkbox(&mut self.debug_enabled, "Debug Mode"); + }); + + ui.menu_button("Tools", |ui: &mut egui::Ui| { + if ui.button("Test All").clicked() { + self.run_all_tests(); + ui.close(); + } + ui.separator(); + + let is_debug = cfg!(debug_assertions); + ui.add_enabled_ui(!is_debug, |ui: &mut egui::Ui| { + let btn = ui.button("Run All Benchmarks"); + if is_debug { + btn.on_hover_text("Benchmarks are only available in Release builds."); + } else if btn.clicked() { + self.run_benchmarks(false, false); + ui.close(); + } + + let btn_base_all = ui.button("Update All Baselines"); + if is_debug { + btn_base_all.on_hover_text( + "Updating baselines is only allowed in Release builds.", + ); + } else if btn_base_all.clicked() { + self.run_benchmarks(true, false); + ui.close(); + } + + let btn_base_curr = ui.button("Update Current Baseline"); + if is_debug { + btn_base_curr.on_hover_text( + "Updating baselines is only allowed in Release builds.", + ); + } else if btn_base_curr.clicked() { + self.run_benchmarks(true, true); + ui.close(); + } + }); + }); + }); + + ui.separator(); + + // Toolbar (Speed Buttons) + ui.horizontal(|ui| { + ui.style_mut().spacing.item_spacing.x = 4.0; // tighter spacing for speed buttons + + if ui + .button("โ–ถ Run") + .on_hover_text("Compile and Run (Shift+Enter)") + .clicked() + { + self.compile(); + } + + if ui + .button("๐Ÿ’พ Save") + .on_hover_text("Save current file (Ctrl+S)") + .clicked() + { + self.save(); + } + + ui.separator(); + + if ui + .button("๐Ÿ” AST") + .on_hover_text("Dump Bound AST") + .clicked() + { + self.dump_ast(); + } + + ui.separator(); + + ui.toggle_value(&mut self.debug_enabled, "๐Ÿ› Debug"); + + ui.separator(); + + if ui + .button("๐Ÿงช Test") + .on_hover_text("Run all functional tests") + .clicked() + { + self.run_all_tests(); + } + + ui.separator(); + + let is_debug = cfg!(debug_assertions); + ui.add_enabled_ui(!is_debug, |ui| { + let bench_btn = ui.button("โฑ Bench"); + if is_debug { + bench_btn.on_hover_text("Benchmarks are only available in Release builds."); + } else if bench_btn.clicked() { + self.run_benchmarks(false, false); + } + }); + }); + ui.add_space(2.0); + }); + + // Left Side Panel: Examples + egui::SidePanel::left("examples_panel") + .resizable(true) + .default_width(150.0) + .show(ctx, |ui| { + ui.heading("Examples"); + ui.add_space(5.0); + egui::ScrollArea::vertical().show(ui, |ui| { + ui.with_layout(egui::Layout::top_down_justified(egui::Align::LEFT), |ui| { + for example in &self.examples { + if ui.button(&example.name).clicked() { + self.source_code = example.content.clone(); + self.current_example_path = Some(example.path.clone()); + } + } + }); + }); + }); + + // Bottom Panel: Output Log & Controls (Resizable) + egui::TopBottomPanel::bottom("bottom_panel") + .resizable(true) + .min_height(100.0) + .default_height(200.0) + .max_height(ctx.available_rect().height() * 0.5) + .show(ctx, |ui| { + ui.add_space(5.0); + + if self.show_save_as { + ui.group(|ui| { + ui.horizontal(|ui| { + ui.label("New Filename:"); + ui.text_edit_singleline(&mut self.save_as_name); + if ui.button("Save Now").clicked() { + self.save_as(); + } + if ui.button("Cancel").clicked() { + self.show_save_as = false; + } + }); + }); + ui.add_space(5.0); + } + + ui.horizontal(|ui| { + ui.selectable_value(&mut self.active_tab, AppTab::Output, "Output"); + ui.selectable_value(&mut self.active_tab, AppTab::Trace, "Execution Trace"); + ui.add_space(20.0); + + if ui.button("Copy Content").clicked() { + let text = match self.active_tab { + AppTab::Output => self.output_log.clone(), + AppTab::Trace => self.trace_logs.join("\n"), + }; + ui.ctx().copy_text(text); + } + + if ui.button("Clear Log").clicked() { + self.output_log.clear(); + self.trace_logs.clear(); + } + }); + + ui.add_space(5.0); + + // Log Output Area + match self.active_tab { + AppTab::Output => { + egui::ScrollArea::both() + .id_salt("output_log_scroll") + .auto_shrink(false) + .show(ui, |ui| { + ui.add( + egui::TextEdit::multiline(&mut self.output_log) + .font(egui::TextStyle::Monospace) + .hint_text("Ready to compile...") + .code_editor() + .lock_focus(false) + .desired_width(f32::INFINITY) + .frame(false), + ); + }); + } + AppTab::Trace => { + egui::ScrollArea::both() + .id_salt("trace_scroll") + .auto_shrink(false) + .show(ui, |ui| { + ui.style_mut().override_text_style = + Some(egui::TextStyle::Monospace); + ui.with_layout(egui::Layout::top_down(egui::Align::LEFT), |ui| { + for line in &self.trace_logs { + ui.label(line); + } + }); + }); + } + } + }); + + // Central Panel: Source Code Editor (Fills remaining space) + egui::CentralPanel::default().show(ctx, |ui| { + ui.heading("Source Code Editor"); + ui.label("Input Source:"); + + let editor_id = ui.id().with("source_code_editor"); + + // Handle Shift+Enter shortcut BEFORE the editor handles it + let compile_shortcut = + egui::KeyboardShortcut::new(egui::Modifiers::SHIFT, egui::Key::Enter); + if ui.input_mut(|i| i.consume_shortcut(&compile_shortcut)) { + self.compile(); + } + + let save_shortcut = egui::KeyboardShortcut::new(egui::Modifiers::COMMAND, egui::Key::S); + if ui.input_mut(|i| i.consume_shortcut(&save_shortcut)) { + self.save(); + } + + // Set initial focus + if self.is_first_frame { + ui.ctx().memory_mut(|m| m.request_focus(editor_id)); + self.is_first_frame = false; + } + + let mut layouter = |ui: &egui::Ui, buffer: &dyn egui::TextBuffer, _wrap_width: f32| { + let mut layout_job = highlight_myc(ui.ctx(), buffer.as_str()); + layout_job.wrap.max_width = f32::INFINITY; // Disable wrapping for code editor + ui.painter().layout_job(layout_job) + }; + + egui::ScrollArea::both() + .id_salt("source_editor_scroll") + .auto_shrink(false) + .show(ui, |ui| { + ui.add( + egui::TextEdit::multiline(&mut self.source_code) + .id(editor_id) + .font(egui::TextStyle::Monospace) + .code_editor() + .lock_focus(true) + .desired_width(f32::INFINITY) + .frame(false) + .layouter(&mut layouter), + ); + }); + }); + } +} + +fn highlight_myc(ctx: &egui::Context, code: &str) -> egui::text::LayoutJob { + let mut job = egui::text::LayoutJob::default(); + let theme = &ctx.style().visuals; + + let color_kw = egui::Color32::from_rgb(86, 156, 214); // Blue + let color_str = egui::Color32::from_rgb(206, 145, 120); // Orange/Brown + let color_num = egui::Color32::from_rgb(181, 206, 168); // Greenish + let color_comment = egui::Color32::from_rgb(106, 153, 85); // Green + let color_bracket = [ + egui::Color32::from_rgb(255, 215, 0), // Gold + egui::Color32::from_rgb(218, 112, 214), // Orchid + egui::Color32::from_rgb(24, 131, 215), // Blue + ]; + + let mut chars = code.chars().peekable(); + let mut bracket_depth = 0; + + while let Some(c) = chars.next() { + let mut text = String::from(c); + let mut color = theme.widgets.noninteractive.text_color(); + + match c { + ';' => { + while let Some(&next) = chars.peek() { + if next == '\n' { + break; + } + text.push(chars.next().unwrap()); + } + color = color_comment; + } + '"' => { + while let Some(next) = chars.next() { + text.push(next); + if next == '"' { + break; + } + if next == '\\' + && let Some(escaped) = chars.next() + { + text.push(escaped); + } + } + color = color_str; + } + ':' => { + while let Some(&next) = chars.peek() { + if next.is_whitespace() || "()[]{}".contains(next) { + break; + } + text.push(chars.next().unwrap()); + } + color = color_kw; + } + '(' | '[' | '{' => { + color = color_bracket[bracket_depth % 3]; + bracket_depth += 1; + } + ')' | ']' | '}' => { + bracket_depth = bracket_depth.saturating_sub(1); + color = color_bracket[bracket_depth % 3]; + } + '`' | '~' | '@' => { + color = color_kw; + } + '0'..='9' => { + while let Some(&next) = chars.peek() { + if next.is_ascii_digit() || next == '.' { + text.push(chars.next().unwrap()); + } else { + break; + } + } + color = color_num; + } + _ if c.is_alphabetic() || "+-*/<=>!$%&_?.".contains(c) => { + while let Some(&next) = chars.peek() { + if next.is_alphanumeric() || "+-*/<=>!$%&_?.".contains(next) { + text.push(chars.next().unwrap()); + } else { + break; + } + } + if ["fn", "def", "do", "if", "assign", "recur", "cond", "macro"] + .contains(&text.as_str()) + { + color = color_kw; + } + } + _ => {} + } + + job.append( + &text, + 0.0, + egui::TextFormat { + font_id: egui::FontId::monospace(14.0), + color, + ..Default::default() + }, + ); + } + + job +} diff --git a/src/utils/tester.rs b/src/utils/tester.rs index a9125d9..06bce00 100644 --- a/src/utils/tester.rs +++ b/src/utils/tester.rs @@ -1,337 +1,337 @@ -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, filter: Option<&str>) -> 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 name = path.file_name().unwrap().to_string_lossy().to_string(); - if let Some(f) = filter - && name != f - { - continue; - } - - let content = fs::read_to_string(&path).unwrap(); - - let baseline_match = baseline_re.captures(&content); - if !update && baseline_match.is_none() { - continue; - } - 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 env = Environment::new(); - let compiled_once = match env.compile(&content).into_result() { - 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 func = env.instantiate(linked); - let mut total = Duration::ZERO; - for _ in 0..n { - let start = Instant::now(); - let _ = (func.func)(&[]); - 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(), - }); - } - } - 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, None); - 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, filter: Option<&str>) -> 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 name = path.file_name().unwrap().to_string_lossy().to_string(); + if let Some(f) = filter + && name != f + { + continue; + } + + let content = fs::read_to_string(&path).unwrap(); + + let baseline_match = baseline_re.captures(&content); + if !update && baseline_match.is_none() { + continue; + } + 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 env = Environment::new(); + let compiled_once = match env.compile(&content).into_result() { + 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 func = env.instantiate(linked); + let mut total = Duration::ZERO; + for _ in 0..n { + let start = Instant::now(); + let _ = (func.func)(&[]); + 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(), + }); + } + } + 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, None); + 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); + } + } +}