diff --git a/.gitignore b/.gitignore index 3468236..8622d40 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /target Delphi *.snap.new +repomix-output.xml diff --git a/gemini.md b/gemini.md index c231307..1cb3501 100644 --- a/gemini.md +++ b/gemini.md @@ -74,3 +74,7 @@ Options: * Dieses Dokument sollte aktuell gehalten werden. @docs/BNF.md + +# Repository + +@repomix-output.xml diff --git a/src/ast/compiler/analyzer.rs b/src/ast/compiler/analyzer.rs index 5e65987..31fe2d9 100644 --- a/src/ast/compiler/analyzer.rs +++ b/src/ast/compiler/analyzer.rs @@ -1,386 +1,386 @@ -use crate::ast::compiler::bound_nodes::{ - Address, AnalyzedNode, BoundKind, GlobalIdx, NodeMetrics, TypedNode, -}; -use crate::ast::types::Purity; -use std::collections::{HashMap, HashSet}; -use std::rc::Rc; - -pub struct Analyzer<'a> { - root_purity: &'a [Purity], - /// Stack of currently visiting lambdas to detect direct recursion. - lambda_stack: Vec, - /// Map of global index to its Lambda identity if known. - globals_to_lambdas: HashMap, - /// Set of identities that were found to be recursive. - recursive_identities: HashSet, -} - -impl<'a> Analyzer<'a> { - pub fn analyze( - node: &TypedNode, - root_purity: &'a [Purity], - ) -> AnalyzedNode { - let mut analyzer = Self { - root_purity, - lambda_stack: Vec::new(), - globals_to_lambdas: HashMap::new(), - recursive_identities: HashSet::new(), - }; - - // First pass: map globals to their lambda identities - analyzer.collect_globals(node); - - // Second pass: full analysis (decorating TypedNode into AnalyzedNode) - analyzer.visit(Rc::new(node.clone())) - } - - fn collect_globals(&mut self, node: &TypedNode) { - match &node.kind { - BoundKind::Define { - addr: Address::Global(global_index), - value, - .. - } => { - if let BoundKind::Lambda { .. } = &value.kind { - self.globals_to_lambdas - .insert(*global_index, value.identity.clone()); - } - self.collect_globals(value); - } - BoundKind::Block { exprs } => { - for e in exprs { - self.collect_globals(e); - } - } - _ => { - node.kind - .for_each_child(|child| self.collect_globals(child)); - } - } - } - - fn visit(&mut self, node_rc: Rc) -> AnalyzedNode { - let node = &*node_rc; - let mut is_recursive = false; - - let (new_kind, purity) = match &node.kind { - BoundKind::Constant(v) => (BoundKind::Constant(v.clone()), Purity::Pure), - BoundKind::Nop => (BoundKind::Nop, Purity::Pure), - - BoundKind::Get { addr, name } => { - let p = match addr { - Address::Global(idx) => { - self.root_purity.get(idx.0 as usize).cloned().unwrap_or(Purity::Pure) - } - _ => Purity::Pure, - }; - ( - BoundKind::Get { - addr: *addr, - name: name.clone(), - }, - p, - ) - } - - BoundKind::FieldAccessor(k) => (BoundKind::FieldAccessor(*k), Purity::Pure), - - BoundKind::GetField { rec, field } => { - let rec_m = self.visit(rec.clone()); - let p = rec_m.ty.purity; - ( - BoundKind::GetField { - rec: Rc::new(rec_m), - field: *field, - }, - p, - ) - } - - BoundKind::Set { addr, value } => { - let val_m = self.visit(value.clone()); - ( - BoundKind::Set { - addr: *addr, - value: Rc::new(val_m), - }, - Purity::Impure, - ) - } - - BoundKind::Define { - name, - addr, - kind, - value, - captured_by, - } => { - let val_m = self.visit(value.clone()); - ( - BoundKind::Define { - name: name.clone(), - addr: *addr, - kind: *kind, - value: Rc::new(val_m), - captured_by: captured_by.clone(), - }, - Purity::Impure, - ) - } - - BoundKind::If { - cond, - then_br, - else_br, - } => { - let cond_m = self.visit(cond.clone()); - let then_m = self.visit(then_br.clone()); - let else_m = else_br.as_ref().map(|e| self.visit(e.clone())); - - let mut p = cond_m.ty.purity.min(then_m.ty.purity); - if let Some(ref em) = else_m { - p = p.min(em.ty.purity); - } - ( - BoundKind::If { - cond: Rc::new(cond_m), - then_br: Rc::new(then_m), - else_br: else_m.map(Rc::new), - }, - p, - ) - } - - BoundKind::Lambda { - params, - upvalues, - body, - positional_count, - } => { - self.lambda_stack.push(node.identity.clone()); - let params_m = self.visit(params.clone()); - let body_m = self.visit(body.clone()); - self.lambda_stack.pop(); - - is_recursive = self.recursive_identities.contains(&node.identity); - ( - BoundKind::Lambda { - params: Rc::new(params_m), - upvalues: upvalues.clone(), - body: Rc::new(body_m), - positional_count: *positional_count, - }, - Purity::Pure, - ) - } - - BoundKind::Destructure { pattern, value } => { - let pat_m = self.visit(pattern.clone()); - let val_m = self.visit(value.clone()); - ( - BoundKind::Destructure { - pattern: Rc::new(pat_m), - value: Rc::new(val_m), - }, - Purity::Impure, - ) - } - - BoundKind::Call { callee, args } => { - let callee_m = self.visit(callee.clone()); - let args_m = self.visit(args.clone()); - - if let BoundKind::Get { - addr: Address::Global(idx), - .. - } = &callee.kind - && let Some(lambda_id) = self.globals_to_lambdas.get(idx) - && self.lambda_stack.contains(lambda_id) - { - self.recursive_identities.insert(lambda_id.clone()); - is_recursive = true; - } - - let p_func = if let BoundKind::Get { - addr: Address::Global(idx), - .. - } = &callee.kind - { - self.root_purity - .get(idx.0 as usize) - .cloned() - .unwrap_or(Purity::Impure) - } else { - Purity::Impure - }; - let p = callee_m.ty.purity.min(args_m.ty.purity).min(p_func); - ( - BoundKind::Call { - callee: Rc::new(callee_m), - args: Rc::new(args_m), - }, - p, - ) - } - - BoundKind::Again { args } => { - let args_m = self.visit(args.clone()); - if let Some(lambda_id) = self.lambda_stack.last() { - self.recursive_identities.insert(lambda_id.clone()); - is_recursive = true; - } - ( - BoundKind::Again { - args: Rc::new(args_m), - }, - Purity::Impure, - ) - } - BoundKind::Pipe { - inputs, - lambda, - out_type, - } => { - let mut analyzed_inputs = Vec::with_capacity(inputs.len()); - for input in inputs { - analyzed_inputs.push(Rc::new(self.visit(input.clone()))); - } - let a_lambda = Rc::new(self.visit(lambda.clone())); - ( - BoundKind::Pipe { - inputs: analyzed_inputs, - lambda: a_lambda, - out_type: out_type.clone(), - }, - Purity::Impure, - ) - } - - BoundKind::Block { exprs } => { - let mut new_exprs = Vec::with_capacity(exprs.len()); - let mut p = Purity::Pure; - for e in exprs { - let em = self.visit(e.clone()); - p = p.min(em.ty.purity); - new_exprs.push(Rc::new(em)); - } - (BoundKind::Block { exprs: new_exprs }, p) - } - - BoundKind::Tuple { elements } => { - let mut new_elements = Vec::with_capacity(elements.len()); - let mut p = Purity::Pure; - for e in elements { - let em = self.visit(e.clone()); - p = p.min(em.ty.purity); - new_elements.push(Rc::new(em)); - } - ( - BoundKind::Tuple { - elements: new_elements, - }, - p, - ) - } - - BoundKind::Record { layout, values } => { - let mut new_values = Vec::with_capacity(values.len()); - let mut p = Purity::Pure; - for v in values { - let vm = self.visit(v.clone()); - p = p.min(vm.ty.purity); - new_values.push(Rc::new(vm)); - } - ( - BoundKind::Record { - layout: layout.clone(), - values: new_values, - }, - p, - ) - } - - BoundKind::Expansion { - original_call, - bound_expanded, - } => { - let expanded_m = self.visit(bound_expanded.clone()); - ( - BoundKind::Expansion { - original_call: original_call.clone(), - bound_expanded: Rc::new(expanded_m.clone()), - }, - expanded_m.ty.purity, - ) - } - - BoundKind::Extension(_) => (BoundKind::Nop, Purity::Impure), - BoundKind::Error => (BoundKind::Error, Purity::Impure), - }; - - crate::ast::nodes::Node { - identity: node.identity.clone(), - kind: new_kind, - ty: NodeMetrics { - original: node_rc, - purity, - is_recursive, - }, - } - } -} - -trait NodeExt { - fn for_each_child(&self, f: F); -} - -impl NodeExt for BoundKind { - fn for_each_child(&self, mut f: F) { - match self { - BoundKind::If { - cond, - then_br, - else_br, - } => { - f(cond); - f(then_br); - if let Some(e) = else_br { - f(e); - } - } - BoundKind::Define { value, .. } | BoundKind::Set { value, .. } => { - f(value); - } - BoundKind::GetField { rec, .. } => { - f(rec); - } - BoundKind::Lambda { params, body, .. } => { - f(params); - f(body); - } - BoundKind::Call { callee, args } => { - f(callee); - f(args); - } - BoundKind::Block { exprs } => { - for e in exprs { - f(e); - } - } - BoundKind::Tuple { elements } => { - for e in elements { - f(e); - } - } - BoundKind::Record { values, .. } => { - for v in values { - f(v); - } - } - BoundKind::Expansion { bound_expanded, .. } => { - f(bound_expanded); - } - _ => {} - } - } -} +use crate::ast::compiler::bound_nodes::{ + Address, AnalyzedNode, BoundKind, GlobalIdx, NodeMetrics, TypedNode, +}; +use crate::ast::types::Purity; +use std::collections::{HashMap, HashSet}; +use std::rc::Rc; + +pub struct Analyzer<'a> { + root_purity: &'a [Purity], + /// Stack of currently visiting lambdas to detect direct recursion. + lambda_stack: Vec, + /// Map of global index to its Lambda identity if known. + globals_to_lambdas: HashMap, + /// Set of identities that were found to be recursive. + recursive_identities: HashSet, +} + +impl<'a> Analyzer<'a> { + pub fn analyze( + node: &TypedNode, + root_purity: &'a [Purity], + ) -> AnalyzedNode { + let mut analyzer = Self { + root_purity, + lambda_stack: Vec::new(), + globals_to_lambdas: HashMap::new(), + recursive_identities: HashSet::new(), + }; + + // First pass: map globals to their lambda identities + analyzer.collect_globals(node); + + // Second pass: full analysis (decorating TypedNode into AnalyzedNode) + analyzer.visit(Rc::new(node.clone())) + } + + fn collect_globals(&mut self, node: &TypedNode) { + match &node.kind { + BoundKind::Define { + addr: Address::Global(global_index), + value, + .. + } => { + if let BoundKind::Lambda { .. } = &value.kind { + self.globals_to_lambdas + .insert(*global_index, value.identity.clone()); + } + self.collect_globals(value); + } + BoundKind::Block { exprs } => { + for e in exprs { + self.collect_globals(e); + } + } + _ => { + node.kind + .for_each_child(|child| self.collect_globals(child)); + } + } + } + + fn visit(&mut self, node_rc: Rc) -> AnalyzedNode { + let node = &*node_rc; + let mut is_recursive = false; + + let (new_kind, purity) = match &node.kind { + BoundKind::Constant(v) => (BoundKind::Constant(v.clone()), Purity::Pure), + BoundKind::Nop => (BoundKind::Nop, Purity::Pure), + + BoundKind::Get { addr, name } => { + let p = match addr { + Address::Global(idx) => { + self.root_purity.get(idx.0 as usize).cloned().unwrap_or(Purity::Pure) + } + _ => Purity::Pure, + }; + ( + BoundKind::Get { + addr: *addr, + name: name.clone(), + }, + p, + ) + } + + BoundKind::FieldAccessor(k) => (BoundKind::FieldAccessor(*k), Purity::Pure), + + BoundKind::GetField { rec, field } => { + let rec_m = self.visit(rec.clone()); + let p = rec_m.ty.purity; + ( + BoundKind::GetField { + rec: Rc::new(rec_m), + field: *field, + }, + p, + ) + } + + BoundKind::Set { addr, value } => { + let val_m = self.visit(value.clone()); + ( + BoundKind::Set { + addr: *addr, + value: Rc::new(val_m), + }, + Purity::Impure, + ) + } + + BoundKind::Define { + name, + addr, + kind, + value, + captured_by, + } => { + let val_m = self.visit(value.clone()); + ( + BoundKind::Define { + name: name.clone(), + addr: *addr, + kind: *kind, + value: Rc::new(val_m), + captured_by: captured_by.clone(), + }, + Purity::Impure, + ) + } + + BoundKind::If { + cond, + then_br, + else_br, + } => { + let cond_m = self.visit(cond.clone()); + let then_m = self.visit(then_br.clone()); + let else_m = else_br.as_ref().map(|e| self.visit(e.clone())); + + let mut p = cond_m.ty.purity.min(then_m.ty.purity); + if let Some(ref em) = else_m { + p = p.min(em.ty.purity); + } + ( + BoundKind::If { + cond: Rc::new(cond_m), + then_br: Rc::new(then_m), + else_br: else_m.map(Rc::new), + }, + p, + ) + } + + BoundKind::Lambda { + params, + upvalues, + body, + positional_count, + } => { + self.lambda_stack.push(node.identity.clone()); + let params_m = self.visit(params.clone()); + let body_m = self.visit(body.clone()); + self.lambda_stack.pop(); + + is_recursive = self.recursive_identities.contains(&node.identity); + ( + BoundKind::Lambda { + params: Rc::new(params_m), + upvalues: upvalues.clone(), + body: Rc::new(body_m), + positional_count: *positional_count, + }, + Purity::Pure, + ) + } + + BoundKind::Destructure { pattern, value } => { + let pat_m = self.visit(pattern.clone()); + let val_m = self.visit(value.clone()); + ( + BoundKind::Destructure { + pattern: Rc::new(pat_m), + value: Rc::new(val_m), + }, + Purity::Impure, + ) + } + + BoundKind::Call { callee, args } => { + let callee_m = self.visit(callee.clone()); + let args_m = self.visit(args.clone()); + + if let BoundKind::Get { + addr: Address::Global(idx), + .. + } = &callee.kind + && let Some(lambda_id) = self.globals_to_lambdas.get(idx) + && self.lambda_stack.contains(lambda_id) + { + self.recursive_identities.insert(lambda_id.clone()); + is_recursive = true; + } + + let p_func = if let BoundKind::Get { + addr: Address::Global(idx), + .. + } = &callee.kind + { + self.root_purity + .get(idx.0 as usize) + .cloned() + .unwrap_or(Purity::Impure) + } else { + Purity::Impure + }; + let p = callee_m.ty.purity.min(args_m.ty.purity).min(p_func); + ( + BoundKind::Call { + callee: Rc::new(callee_m), + args: Rc::new(args_m), + }, + p, + ) + } + + BoundKind::Again { args } => { + let args_m = self.visit(args.clone()); + if let Some(lambda_id) = self.lambda_stack.last() { + self.recursive_identities.insert(lambda_id.clone()); + is_recursive = true; + } + ( + BoundKind::Again { + args: Rc::new(args_m), + }, + Purity::Impure, + ) + } + BoundKind::Pipe { + inputs, + lambda, + out_type, + } => { + let mut analyzed_inputs = Vec::with_capacity(inputs.len()); + for input in inputs { + analyzed_inputs.push(Rc::new(self.visit(input.clone()))); + } + let a_lambda = Rc::new(self.visit(lambda.clone())); + ( + BoundKind::Pipe { + inputs: analyzed_inputs, + lambda: a_lambda, + out_type: out_type.clone(), + }, + Purity::Impure, + ) + } + + BoundKind::Block { exprs } => { + let mut new_exprs = Vec::with_capacity(exprs.len()); + let mut p = Purity::Pure; + for e in exprs { + let em = self.visit(e.clone()); + p = p.min(em.ty.purity); + new_exprs.push(Rc::new(em)); + } + (BoundKind::Block { exprs: new_exprs }, p) + } + + BoundKind::Tuple { elements } => { + let mut new_elements = Vec::with_capacity(elements.len()); + let mut p = Purity::Pure; + for e in elements { + let em = self.visit(e.clone()); + p = p.min(em.ty.purity); + new_elements.push(Rc::new(em)); + } + ( + BoundKind::Tuple { + elements: new_elements, + }, + p, + ) + } + + BoundKind::Record { layout, values } => { + let mut new_values = Vec::with_capacity(values.len()); + let mut p = Purity::Pure; + for v in values { + let vm = self.visit(v.clone()); + p = p.min(vm.ty.purity); + new_values.push(Rc::new(vm)); + } + ( + BoundKind::Record { + layout: layout.clone(), + values: new_values, + }, + p, + ) + } + + BoundKind::Expansion { + original_call, + bound_expanded, + } => { + let expanded_m = self.visit(bound_expanded.clone()); + ( + BoundKind::Expansion { + original_call: original_call.clone(), + bound_expanded: Rc::new(expanded_m.clone()), + }, + expanded_m.ty.purity, + ) + } + + BoundKind::Extension(_) => (BoundKind::Nop, Purity::Impure), + BoundKind::Error => (BoundKind::Error, Purity::Impure), + }; + + crate::ast::compiler::bound_nodes::Node { + identity: node.identity.clone(), + kind: new_kind, + ty: NodeMetrics { + original: node_rc, + purity, + is_recursive, + }, + } + } +} + +trait NodeExt { + fn for_each_child(&self, f: F); +} + +impl NodeExt for BoundKind { + fn for_each_child(&self, mut f: F) { + match self { + BoundKind::If { + cond, + then_br, + else_br, + } => { + f(cond); + f(then_br); + if let Some(e) = else_br { + f(e); + } + } + BoundKind::Define { value, .. } | BoundKind::Set { value, .. } => { + f(value); + } + BoundKind::GetField { rec, .. } => { + f(rec); + } + BoundKind::Lambda { params, body, .. } => { + f(params); + f(body); + } + BoundKind::Call { callee, args } => { + f(callee); + f(args); + } + BoundKind::Block { exprs } => { + for e in exprs { + f(e); + } + } + BoundKind::Tuple { elements } => { + for e in elements { + f(e); + } + } + BoundKind::Record { values, .. } => { + for v in values { + f(v); + } + } + BoundKind::Expansion { bound_expanded, .. } => { + f(bound_expanded); + } + _ => {} + } + } +} diff --git a/src/ast/compiler/binder.rs b/src/ast/compiler/binder.rs index 6d7d3ca..5b0cc88 100644 --- a/src/ast/compiler/binder.rs +++ b/src/ast/compiler/binder.rs @@ -1,8 +1,8 @@ use crate::ast::compiler::bound_nodes::{ - Address, BoundKind, BoundNode, DeclarationKind, GlobalIdx, LocalSlot, UpvalueIdx, + Address, BoundKind, DeclarationKind, GlobalIdx, LocalSlot, Node, UpvalueIdx, }; use crate::ast::diagnostics::Diagnostics; -use crate::ast::nodes::{Node, Symbol, UntypedKind}; +use crate::ast::nodes::{Symbol, UntypedKind, UntypedNode}; use crate::ast::types::{Identity, StaticType, Purity}; use std::collections::HashMap; use std::rc::Rc; @@ -114,7 +114,7 @@ pub enum ExprContext { } pub type BindingResult = ( - BoundNode, + Node, HashMap>, Vec, u32, @@ -152,7 +152,7 @@ impl Binder { initial_scopes: Vec, initial_slot_count: u32, fixed_scope_idx: i32, - node: &Node, + node: &UntypedNode, diagnostics: &mut Diagnostics, ) -> Result { let mut binder = Self::new(initial_scopes, initial_slot_count, fixed_scope_idx); @@ -208,10 +208,10 @@ impl Binder { pub fn bind( &mut self, - node: &Node, + node: &UntypedNode, ctx: ExprContext, diag: &mut Diagnostics, - ) -> BoundNode { + ) -> Node { match &node.kind { UntypedKind::Nop => self.make_node(node.identity.clone(), BoundKind::Nop), UntypedKind::Constant(v) => { @@ -241,10 +241,10 @@ impl Binder { then_br, else_br, } => { - let cond = self.bind(cond, ExprContext::Expression, diag); + let cond = self.bind(cond.as_ref(), ExprContext::Expression, diag); self.functions.last_mut().unwrap().push_scope(); - let then_br = self.bind(then_br, ctx, diag); + let then_br = self.bind(then_br.as_ref(), ctx, diag); self.functions.last_mut().unwrap().pop_scope(); let mut else_br_bound = None; @@ -296,7 +296,7 @@ impl Binder { } } else { let val_node = self.bind(value, ExprContext::Expression, diag); - let target_node = self.bind_pattern(target, DeclarationKind::Variable, diag); + let target_node = self.bind_pattern(target.as_ref(), DeclarationKind::Variable, diag); self.make_node( node.identity.clone(), @@ -324,7 +324,7 @@ impl Binder { self.make_node(node.identity.clone(), BoundKind::Error) } } else { - let target_node = self.bind_assign_pattern(target, diag); + let target_node = self.bind_assign_pattern(target.as_ref(), diag); self.make_node( node.identity.clone(), BoundKind::Destructure { @@ -357,11 +357,11 @@ impl Binder { self.functions .push(FunctionCompiler::new(identity.clone(), vec![], 0)); - let params_bound = self.bind_pattern(params, DeclarationKind::Parameter, diag); - let body_bound = self.bind(body, ExprContext::Expression, diag); + let params_bound = self.bind_pattern(params.as_ref(), DeclarationKind::Parameter, diag); + let body_bound = self.bind(body.as_ref(), ExprContext::Expression, diag); let compiled_fn = self.functions.pop().unwrap(); - fn count_params(node: &BoundNode) -> Option { + fn count_params(node: &Node) -> Option { match &node.kind { BoundKind::Define { kind: DeclarationKind::Parameter, @@ -394,7 +394,7 @@ impl Binder { UntypedKind::Call { callee, args } => { let callee = self.bind(callee, ExprContext::Expression, diag); - let args = self.bind(args, ExprContext::Expression, diag); + let args = self.bind(args.as_ref(), ExprContext::Expression, diag); self.make_node( node.identity.clone(), @@ -413,7 +413,7 @@ impl Binder { ); return self.make_node(node.identity.clone(), BoundKind::Error); } - let args = self.bind(args, ExprContext::Expression, diag); + let args = self.bind(args.as_ref(), ExprContext::Expression, diag); self.make_node( node.identity.clone(), BoundKind::Again { @@ -489,7 +489,7 @@ impl Binder { } UntypedKind::Expansion { call, expanded } => { - let bound_expanded = self.bind(expanded, ctx, diag); + let bound_expanded = self.bind(expanded.as_ref(), ctx, diag); self.make_node( node.identity.clone(), BoundKind::Expansion { @@ -520,7 +520,7 @@ impl Binder { ); self.make_node(node.identity.clone(), BoundKind::Error) } - UntypedKind::Error => crate::ast::compiler::bound_nodes::BoundNode { + UntypedKind::Error => crate::ast::compiler::bound_nodes::Node { identity: node.identity.clone(), kind: crate::ast::compiler::bound_nodes::BoundKind::Error, ty: (), @@ -600,10 +600,10 @@ impl Binder { fn bind_pattern( &mut self, - node: &Node, + node: &UntypedNode, kind: DeclarationKind, diag: &mut Diagnostics, - ) -> BoundNode { + ) -> Node { match &node.kind { UntypedKind::Identifier(sym) => { if let Some(addr) = self.declare_variable(sym, node.identity.clone(), kind, diag) { @@ -645,9 +645,9 @@ impl Binder { fn bind_assign_pattern( &mut self, - node: &Node, + node: &UntypedNode, diag: &mut Diagnostics, - ) -> BoundNode { + ) -> Node { match &node.kind { UntypedKind::Identifier(sym) => { if let Some(addr) = self.resolve_variable(sym, diag, &node.identity) { @@ -684,7 +684,7 @@ impl Binder { } } - fn make_node(&self, identity: Identity, kind: BoundKind<()>) -> BoundNode { + fn make_node(&self, identity: Identity, kind: BoundKind<()>) -> Node { Node { identity, kind, diff --git a/src/ast/compiler/bound_nodes.rs b/src/ast/compiler/bound_nodes.rs index acf1bb6..4489d52 100644 --- a/src/ast/compiler/bound_nodes.rs +++ b/src/ast/compiler/bound_nodes.rs @@ -1,4 +1,4 @@ -use crate::ast::nodes::{Node, Symbol}; +use crate::ast::nodes::Symbol; use crate::ast::types::{Identity, StaticType, Value}; use std::rc::Rc; @@ -55,13 +55,18 @@ impl Clone for Box> { } /// A bound AST node, decorated with type or metric information T. -pub type BoundNode = Node, 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 = BoundNode; +pub type TypedNode = Node; /// Type alias for the global function registry. -pub type GlobalFunctionRegistry = std::collections::HashMap>; +pub type GlobalFunctionRegistry = std::collections::HashMap>; /// Metrics collected during the analysis phase. #[derive(Debug, Clone, PartialEq)] @@ -72,7 +77,7 @@ pub struct NodeMetrics { } /// Type alias for a node that has been analyzed. -pub type AnalyzedNode = BoundNode; +pub type AnalyzedNode = Node; /// Type alias for the global analyzed function registry. pub type GlobalAnalyzedRegistry = std::collections::HashMap>; @@ -91,7 +96,7 @@ pub enum BoundKind { // Variable Update (Assignment) Set { addr: Address, - value: Rc>, + value: Rc>, }, // Variable Declaration (Unified for Local/Global/Parameter) @@ -99,7 +104,7 @@ pub enum BoundKind { name: Symbol, addr: Address, kind: DeclarationKind, - value: Rc>, + value: Rc>, captured_by: Vec, }, @@ -108,64 +113,64 @@ pub enum BoundKind { /// Specialized field access (O(1) via RecordLayout) GetField { - rec: Rc>, + rec: Rc>, field: crate::ast::types::Keyword, }, If { - cond: Rc>, - then_br: Rc>, - else_br: Option>>, + 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>, + pattern: Rc>, + value: Rc>, }, Lambda { - params: Rc>, + params: Rc>, // The list of variables captured from enclosing scopes upvalues: Vec
, - body: Rc>, + body: Rc>, /// Static optimization: number of positional parameters if the pattern is flat. positional_count: Option, }, Call { - callee: Rc>, - args: Rc>, + callee: Rc>, + args: Rc>, }, Again { - args: Rc>, + args: Rc>, }, Pipe { - inputs: Vec>>, - lambda: Rc>, + inputs: Vec>>, + lambda: Rc>, out_type: crate::ast::types::StaticType, }, Block { - exprs: Vec>>, + exprs: Vec>>, }, Tuple { - elements: Vec>>, + elements: Vec>>, }, Record { layout: std::sync::Arc, - values: Vec>>, + 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>, + original_call: Rc, /// The result of binding the expanded AST. - bound_expanded: Rc>, + bound_expanded: Rc>, }, /// A diagnostic poison node, allowing compilation to continue after an error. @@ -302,7 +307,7 @@ where } /// A single field in a Record literal (Key-Value pair) -pub type RecordField = (BoundNode, BoundNode); +pub type RecordField = (Node, Node); impl BoundKind { pub fn display_name(&self) -> String { diff --git a/src/ast/compiler/captures.rs b/src/ast/compiler/captures.rs index 58eaf83..42c0b28 100644 --- a/src/ast/compiler/captures.rs +++ b/src/ast/compiler/captures.rs @@ -1,15 +1,15 @@ -use crate::ast::compiler::bound_nodes::{BoundKind, BoundNode}; +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: BoundNode, capture_map: &HashMap>) -> BoundNode { + pub fn apply(node: Node, capture_map: &HashMap>) -> Node { Self::transform(node, capture_map) } - fn transform(mut node: BoundNode, capture_map: &HashMap>) -> BoundNode { + fn transform(mut node: Node, capture_map: &HashMap>) -> Node { use std::rc::Rc; match node.kind { BoundKind::Define { diff --git a/src/ast/compiler/dumper.rs b/src/ast/compiler/dumper.rs index 26cbb58..e490b76 100644 --- a/src/ast/compiler/dumper.rs +++ b/src/ast/compiler/dumper.rs @@ -1,5 +1,4 @@ -use crate::ast::compiler::bound_nodes::BoundKind; -use crate::ast::nodes::Node; +use crate::ast::compiler::bound_nodes::{BoundKind, Node}; use crate::ast::types::Value; use crate::ast::vm::Closure; use std::fmt::Debug; @@ -12,7 +11,7 @@ pub struct Dumper { impl Dumper { /// Produces a formatted string representation of the given bound AST node and its children. - pub fn dump(node: &Node, T>) -> String { + pub fn dump(node: &Node) -> String { let mut dumper = Self { output: String::new(), indent: 0, @@ -27,14 +26,14 @@ impl Dumper { } } - fn log(&mut self, label: &str, node: &Node, T>) { + 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, T>) { + fn visit(&mut self, node: &Node) { match &node.kind { BoundKind::Nop => self.log("Nop", node), BoundKind::Constant(v) => { diff --git a/src/ast/compiler/lambda_collector.rs b/src/ast/compiler/lambda_collector.rs index 2a40d1e..1429120 100644 --- a/src/ast/compiler/lambda_collector.rs +++ b/src/ast/compiler/lambda_collector.rs @@ -1,21 +1,21 @@ -use crate::ast::compiler::bound_nodes::{Address, BoundKind, BoundNode, GlobalIdx}; +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>>, + registry: &'a mut HashMap>>, } impl<'a, T: Clone> LambdaCollector<'a, T> { /// Performs a full traversal of the AST and populates the provided registry. - pub fn collect(node: &BoundNode, registry: &'a mut HashMap>>) { + pub fn collect(node: &Node, registry: &'a mut HashMap>>) { let mut collector = Self { registry }; collector.visit(node); } - fn visit(&mut self, node: &BoundNode) { + fn visit(&mut self, node: &Node) { match &node.kind { BoundKind::Block { exprs } => { for expr in exprs { diff --git a/src/ast/compiler/lowering.rs b/src/ast/compiler/lowering.rs index fb9d0cc..e867b61 100644 --- a/src/ast/compiler/lowering.rs +++ b/src/ast/compiler/lowering.rs @@ -1,5 +1,4 @@ -use crate::ast::compiler::bound_nodes::{Address, AnalyzedNode, BoundKind}; -use crate::ast::nodes::Node; +use crate::ast::compiler::bound_nodes::{Address, AnalyzedNode, BoundKind, Node}; use crate::ast::types::StaticType; use std::fmt::Debug; use std::rc::Rc; @@ -24,12 +23,12 @@ impl Debug for RuntimeMetadata { } /// The ExecNode is the AST used by the VM. It carries TCO flags and links to metrics. -pub type ExecNode = Node, RuntimeMetadata>; +pub type ExecNode = Node; -fn calc_stack_size(root_node: &Node, T>) -> u32 { +fn calc_stack_size(root_node: &Node) -> u32 { let mut max_slot = -1i32; - fn visit(node: &Node, T>, max_slot: &mut i32) { + fn visit(node: &Node, max_slot: &mut i32) { match &node.kind { BoundKind::Get { addr: Address::Local(slot), diff --git a/src/ast/compiler/macros.rs b/src/ast/compiler/macros.rs index d5c31d3..52d2eeb 100644 --- a/src/ast/compiler/macros.rs +++ b/src/ast/compiler/macros.rs @@ -1,4 +1,4 @@ -use crate::ast::nodes::{Node, Symbol, UntypedKind}; +use crate::ast::nodes::{Symbol, UntypedKind, UntypedNode}; 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: &Node, - bindings: &HashMap, Node>, + node: &UntypedNode, + bindings: &HashMap, UntypedNode>, ) -> Result; } /// Internal state for template expansion (Hygiene context + Parameter binding) struct ExpansionState<'a> { - bindings: &'a HashMap, Node>, + bindings: &'a HashMap, UntypedNode>, /// The identity of the current expansion instance, used to "color" internal symbols. expansion_id: Identity, } -type MacroMap = HashMap, (Vec>, Node)>; +type MacroMap = HashMap, (Vec>, UntypedNode)>; /// A registry for macro declarations. #[derive(Clone)] @@ -50,7 +50,7 @@ impl MacroRegistry { } } - pub fn define(&mut self, name: Rc, params: Vec>, body: Node) { + pub fn define(&mut self, name: Rc, params: Vec>, body: UntypedNode) { 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>, Node)> { + pub fn lookup(&self, name: &str) -> Option<(Vec>, UntypedNode)> { for scope in self.scopes.iter().rev() { if let Some(m) = scope.get(name) { return Some(m.clone()); @@ -85,19 +85,19 @@ impl MacroExpander { self.registry } - pub fn expand(&mut self, node: Node) -> Result, String> { + pub fn expand(&mut self, node: UntypedNode) -> Result { self.expand_recursive(node) } - fn expand_recursive(&mut self, node: Node) -> Result, String> { + fn expand_recursive(&mut self, node: UntypedNode) -> Result { match node.kind { UntypedKind::MacroDecl { name, params, body } => { let p_names = self.extract_param_names(¶ms)?; self.registry.define(name.name, p_names, *body); - Ok(Node { + Ok(UntypedNode { identity: node.identity, kind: UntypedKind::Nop, - ty: (), + }) } @@ -127,13 +127,13 @@ impl MacroExpander { let expanded_callee = self.expand_recursive(*callee)?; let expanded_args = self.expand_recursive(*args)?; - Ok(Node { + Ok(UntypedNode { identity: node.identity, kind: UntypedKind::Call { callee: Box::new(expanded_callee), args: Box::new(expanded_args), }, - ty: (), + }) } @@ -145,12 +145,12 @@ impl MacroExpander { } self.registry.pop(); - Ok(Node { + Ok(UntypedNode { identity: node.identity, kind: UntypedKind::Block { exprs: expanded_exprs, }, - ty: (), + }) } @@ -160,29 +160,29 @@ impl MacroExpander { expanded_inputs.push(self.expand_recursive(input)?); } let expanded_lambda = Box::new(self.expand_recursive(*lambda)?); - Ok(Node { + Ok(UntypedNode { identity: node.identity, kind: UntypedKind::Pipe { inputs: expanded_inputs, lambda: expanded_lambda, }, - ty: (), + }) } UntypedKind::Lambda { params, body } => { self.registry.push(); let expanded_params = self.expand_recursive(*params)?; - let expanded_body = self.expand_recursive(Node::clone(&body))?; + let expanded_body = self.expand_recursive((*body).clone())?; self.registry.pop(); - Ok(Node { + Ok(UntypedNode { identity: node.identity, kind: UntypedKind::Lambda { params: Box::new(expanded_params), body: Rc::new(expanded_body), }, - ty: (), + }) } @@ -198,40 +198,40 @@ impl MacroExpander { } else { None }; - Ok(Node { + Ok(UntypedNode { identity: node.identity, kind: UntypedKind::If { cond: Box::new(cond), then_br: Box::new(then_br), else_br, }, - ty: (), + }) } UntypedKind::Def { target, value } => { let target = self.expand_recursive(*target)?; let value = self.expand_recursive(*value)?; - Ok(Node { + Ok(UntypedNode { identity: node.identity, kind: UntypedKind::Def { target: Box::new(target), value: Box::new(value), }, - ty: (), + }) } UntypedKind::Assign { target, value } => { let target = self.expand_recursive(*target)?; let value = self.expand_recursive(*value)?; - Ok(Node { + Ok(UntypedNode { identity: node.identity, kind: UntypedKind::Assign { target: Box::new(target), value: Box::new(value), }, - ty: (), + }) } @@ -240,12 +240,12 @@ impl MacroExpander { for e in elements { expanded_elements.push(self.expand_recursive(e)?); } - Ok(Node { + Ok(UntypedNode { identity: node.identity, kind: UntypedKind::Tuple { elements: expanded_elements, }, - ty: (), + }) } @@ -254,35 +254,35 @@ impl MacroExpander { for (k, v) in fields { expanded_fields.push((self.expand_recursive(k)?, self.expand_recursive(v)?)); } - Ok(Node { + Ok(UntypedNode { identity: node.identity, kind: UntypedKind::Record { fields: expanded_fields, }, - ty: (), + }) } UntypedKind::Expansion { call, expanded } => { let expanded = self.expand_recursive(*expanded)?; - Ok(Node { + Ok(UntypedNode { identity: node.identity, kind: UntypedKind::Expansion { call, expanded: Box::new(expanded), }, - ty: (), + }) } UntypedKind::Again { args } => { let expanded_args = self.expand_recursive(*args)?; - Ok(Node { + Ok(UntypedNode { identity: node.identity, kind: UntypedKind::Again { args: Box::new(expanded_args), }, - ty: (), + }) } @@ -295,9 +295,9 @@ impl MacroExpander { identity: Identity, name: &str, params: Vec>, - args: Vec>, - body: Node, - ) -> Result, String> { + args: Vec, + body: UntypedNode, + ) -> Result { if params.len() != args.len() { return Err(format!( "Macro {} expects {} arguments, but got {}", @@ -324,36 +324,36 @@ impl MacroExpander { body }; - let original_call = Node { + let original_call = UntypedNode { identity: identity.clone(), kind: UntypedKind::Call { - callee: Box::new(Node { + callee: Box::new(UntypedNode { identity: identity.clone(), kind: UntypedKind::Identifier(Symbol::from(name)), - ty: (), + }), - args: Box::new(Node { + args: Box::new(UntypedNode { identity: identity.clone(), kind: UntypedKind::Tuple { elements: bindings.into_values().collect(), }, - ty: (), + }), }, - ty: (), + }; - Ok(Node { + Ok(UntypedNode { identity, kind: UntypedKind::Expansion { call: Box::new(original_call), expanded: Box::new(expanded_body), }, - ty: (), + }) } - fn extract_param_names(&self, node: &Node) -> Result>, String> { + fn extract_param_names(&self, node: &UntypedNode) -> Result>, String> { match &node.kind { UntypedKind::Identifier(sym) => Ok(vec![sym.name.clone()]), UntypedKind::Tuple { elements } => { @@ -369,17 +369,17 @@ impl MacroExpander { fn expand_template( &self, - node: Node, + node: UntypedNode, state: &mut ExpansionState, - ) -> Result, String> { + ) -> Result { match node.kind { UntypedKind::Identifier(mut sym) => { // Inside a template, all internal identifiers are colored. sym.context = Some(state.expansion_id.clone()); - Ok(Node { + Ok(UntypedNode { identity: node.identity, kind: UntypedKind::Identifier(sym), - ty: (), + }) } @@ -408,26 +408,26 @@ impl MacroExpander { UntypedKind::Def { target, value } => { let target = self.expand_template(*target, state)?; let val = self.expand_template(*value, state)?; - Ok(Node { + Ok(UntypedNode { identity: node.identity, kind: UntypedKind::Def { target: Box::new(target), value: Box::new(val), }, - ty: (), + }) } UntypedKind::Assign { target, value } => { let target = self.expand_template(*target, state)?; let value = self.expand_template(*value, state)?; - Ok(Node { + Ok(UntypedNode { identity: node.identity, kind: UntypedKind::Assign { target: Box::new(target), value: Box::new(value), }, - ty: (), + }) } @@ -441,12 +441,12 @@ impl MacroExpander { new_elements.push(self.expand_template(e, state)?); } } - Ok(Node { + Ok(UntypedNode { identity: node.identity, kind: UntypedKind::Tuple { elements: new_elements, }, - ty: (), + }) } @@ -460,10 +460,10 @@ impl MacroExpander { new_exprs.push(self.expand_template(e, state)?); } } - Ok(Node { + Ok(UntypedNode { identity: node.identity, kind: UntypedKind::Block { exprs: new_exprs }, - ty: (), + }) } @@ -475,23 +475,23 @@ impl MacroExpander { self.expand_template(v, state)?, )); } - Ok(Node { + Ok(UntypedNode { identity: node.identity, kind: UntypedKind::Record { fields: new_fields }, - ty: (), + }) } UntypedKind::Call { callee, args } => { let expanded_callee = self.expand_template(*callee, state)?; let expanded_args = self.expand_template(*args, state)?; - Ok(Node { + Ok(UntypedNode { identity: node.identity, kind: UntypedKind::Call { callee: Box::new(expanded_callee), args: Box::new(expanded_args), }, - ty: (), + }) } @@ -507,14 +507,14 @@ impl MacroExpander { } else { None }; - Ok(Node { + Ok(UntypedNode { identity: node.identity, kind: UntypedKind::If { cond: Box::new(cond), then_br: Box::new(then_br), else_br, }, - ty: (), + }) } @@ -524,37 +524,37 @@ impl MacroExpander { expanded_inputs.push(self.expand_template(input, state)?); } let expanded_lambda = Box::new(self.expand_template(*lambda, state)?); - Ok(Node { + Ok(UntypedNode { identity: node.identity, kind: UntypedKind::Pipe { inputs: expanded_inputs, lambda: expanded_lambda, }, - ty: (), + }) } UntypedKind::Lambda { params, body } => { let expanded_params = self.expand_template(*params, state)?; - let body = self.expand_template(Node::clone(&body), state)?; - Ok(Node { + let body = self.expand_template((*body).clone(), state)?; + Ok(UntypedNode { identity: node.identity, kind: UntypedKind::Lambda { params: Box::new(expanded_params), body: Rc::new(body), }, - ty: (), + }) } UntypedKind::Again { args } => { let expanded_args = self.expand_template(*args, state)?; - Ok(Node { + Ok(UntypedNode { identity: node.identity, kind: UntypedKind::Again { args: Box::new(expanded_args), }, - ty: (), + }) } @@ -566,11 +566,11 @@ 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 } => { for e in elements { @@ -599,22 +599,22 @@ impl MacroExpander { } } - fn value_to_node(&self, val: Value, identity: Identity) -> Node { + fn value_to_node(&self, val: Value, identity: Identity) -> UntypedNode { 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(); } - Node { + UntypedNode { identity, kind: UntypedKind::Constant(Value::Object(obj)), - ty: (), + } } - _ => Node { + _ => UntypedNode { identity, kind: UntypedKind::Constant(val), - ty: (), + }, } } @@ -632,8 +632,8 @@ mod tests { impl MacroEvaluator for SimpleEvaluator { fn evaluate( &self, - node: &Node, - bindings: &HashMap, Node>, + node: &UntypedNode, + bindings: &HashMap, UntypedNode>, ) -> Result { if let UntypedKind::Identifier(ref sym) = node.kind && let Some(arg) = bindings.get(&sym.name) diff --git a/src/ast/compiler/optimizer/engine.rs b/src/ast/compiler/optimizer/engine.rs index cc5056e..a5cd683 100644 --- a/src/ast/compiler/optimizer/engine.rs +++ b/src/ast/compiler/optimizer/engine.rs @@ -1,7 +1,6 @@ use crate::ast::compiler::bound_nodes::{ - Address, AnalyzedNode, BoundKind, GlobalAnalyzedRegistry, UpvalueIdx, + Address, AnalyzedNode, BoundKind, GlobalAnalyzedRegistry, Node, UpvalueIdx, }; -use crate::ast::nodes::Node; use crate::ast::types::{Purity, Value}; use crate::ast::vm::Closure; use std::cell::RefCell; diff --git a/src/ast/compiler/optimizer/folder.rs b/src/ast/compiler/optimizer/folder.rs index bc18c16..52d7f65 100644 --- a/src/ast/compiler/optimizer/folder.rs +++ b/src/ast/compiler/optimizer/folder.rs @@ -1,5 +1,4 @@ -use crate::ast::compiler::bound_nodes::{Address, AnalyzedNode, BoundKind, NodeMetrics}; -use crate::ast::nodes::Node; +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; diff --git a/src/ast/compiler/optimizer/substitution_map.rs b/src/ast/compiler/optimizer/substitution_map.rs index 30ddc48..8ee9a90 100644 --- a/src/ast/compiler/optimizer/substitution_map.rs +++ b/src/ast/compiler/optimizer/substitution_map.rs @@ -1,5 +1,4 @@ -use crate::ast::compiler::bound_nodes::{Address, AnalyzedNode, BoundKind, LocalSlot, UpvalueIdx}; -use crate::ast::nodes::Node; +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; diff --git a/src/ast/compiler/specializer.rs b/src/ast/compiler/specializer.rs index c64204b..84e2410 100644 --- a/src/ast/compiler/specializer.rs +++ b/src/ast/compiler/specializer.rs @@ -1,5 +1,4 @@ -use crate::ast::compiler::bound_nodes::{Address, AnalyzedNode, BoundKind, BoundNode, NodeMetrics}; -use crate::ast::nodes::Node; +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; @@ -11,11 +10,11 @@ pub struct MonoCacheKey { pub arg_types: Vec, } -pub type CompileFunc = Rc, &[StaticType]) -> Result<(Value, StaticType), String>>; +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(&self, addr: Address) -> Option>; fn resolve_analyzed(&self, _addr: Address) -> Option> { None } diff --git a/src/ast/compiler/type_checker.rs b/src/ast/compiler/type_checker.rs index 806f1f2..71692c3 100644 --- a/src/ast/compiler/type_checker.rs +++ b/src/ast/compiler/type_checker.rs @@ -1,6 +1,5 @@ -use crate::ast::compiler::bound_nodes::{Address, BoundKind, BoundNode, TypedNode}; +use crate::ast::compiler::bound_nodes::{Address, BoundKind, Node, TypedNode}; use crate::ast::diagnostics::Diagnostics; -use crate::ast::nodes::Node; use crate::ast::types::StaticType; use std::rc::Rc; @@ -83,7 +82,7 @@ impl TypeChecker { pub fn check( &self, - node: &BoundNode, + node: &Node, arg_types: &[StaticType], diag: &mut Diagnostics, ) -> TypedNode { @@ -151,7 +150,7 @@ impl TypeChecker { fn check_params( &self, - node: &BoundNode, + node: &Node, specialized_ty: &StaticType, ctx: &mut TypeContext, diag: &mut Diagnostics, @@ -272,7 +271,7 @@ impl TypeChecker { fn check_node( &self, - node: &BoundNode, + node: &Node, ctx: &mut TypeContext, diag: &mut Diagnostics, ) -> TypedNode { diff --git a/src/ast/environment.rs b/src/ast/environment.rs index be7e3a4..37f367c 100644 --- a/src/ast/environment.rs +++ b/src/ast/environment.rs @@ -1,854 +1,855 @@ -use crate::ast::compiler::analyzer::Analyzer; -use crate::ast::compiler::binder::Binder; -use crate::ast::compiler::{TypeChecker, TypedNode}; -use crate::ast::nodes::{Node, Symbol, UntypedKind}; -use crate::ast::parser::Parser; -use crate::ast::vm::{TracingObserver, VM}; -use std::cell::RefCell; -use std::collections::{HashMap, HashSet}; -use std::path::{Path, PathBuf}; -use std::rc::Rc; - -use crate::ast::compiler::bound_nodes::{ - Address, AnalyzedNode, BoundKind, BoundNode, GlobalAnalyzedRegistry, GlobalFunctionRegistry, - GlobalIdx, -}; -use crate::ast::compiler::dumper::Dumper; -use crate::ast::compiler::lambda_collector::LambdaCollector; -use crate::ast::compiler::lowering::{ExecNode, Lowering}; -use crate::ast::compiler::macros::{MacroEvaluator, MacroExpander, MacroRegistry}; -use crate::ast::compiler::optimizer::Optimizer; -use crate::ast::compiler::specializer::{FunctionRegistry, MonoCache, Specializer}; -use crate::ast::rtl::intrinsics; -use crate::ast::types::{ - NodeIdentity, Object, Purity, SourceLocation, StaticType, Value, -}; - -use crate::ast::diagnostics::{DiagnosticLevel, Diagnostics}; -pub type PipelineGenerator = Box bool>; - -const SYSTEM_LIB_SOURCE: &str = include_str!("system.myc"); -const SYSTEM_LIB_PATH: &str = "/system.myc"; - -pub struct CompilationResult { - pub ast: Option, - pub diagnostics: Diagnostics, -} - -impl CompilationResult { - pub fn success(ast: TypedNode) -> Self { - Self { - ast: Some(ast), - diagnostics: Diagnostics::new(), - } - } - - pub fn error(msg: impl Into) -> Self { - let mut diag = Diagnostics::new(); - diag.push_error(msg, None); - Self { - ast: None, - diagnostics: diag, - } - } - - pub fn into_result(self) -> Result { - if self.diagnostics.has_errors() { - let errors: Vec = self - .diagnostics - .items - .into_iter() - .filter(|d| d.level == DiagnosticLevel::Error) - .map(|d| d.message) - .collect(); - Err(errors.join("\n")) - } else if let Some(ast) = self.ast { - Ok(ast) - } else { - Err("Compilation failed without diagnostics".to_string()) - } - } -} - -pub struct Environment { - pub root_types: Rc>>, - pub root_purity: Rc>>, - pub root_values: Rc>>, - pub fixed_scope_idx: i32, - pub root_scopes: Rc>>, - pub root_slot_count: Rc>, - pub function_registry: Rc>, - pub typed_function_registry: Rc>, - pub monomorph_cache: Rc>, - pub debug_mode: bool, - pub optimization: bool, - pub macro_registry: Rc>, - pub pipeline_generators: Rc>>, - pub search_paths: Rc>>, - pub loaded_modules: Rc>>, -} - -struct EnvFunctionRegistry { - registry: Rc>, - analyzed_registry: Rc>, -} - -impl FunctionRegistry for EnvFunctionRegistry { - fn resolve(&self, addr: Address) -> Option> { - if let Address::Global(idx) = addr { - self.registry.borrow().get(&idx).cloned() - } else { - None - } - } - fn resolve_analyzed(&self, addr: Address) -> Option> { - if let Address::Global(idx) = addr { - self.analyzed_registry.borrow().get(&idx).cloned() - } else { - None - } - } -} - -struct RuntimeMacroEvaluator { - root_scopes: Rc>>, - root_slot_count: Rc>, - root_types: Rc>>, - root_values: Rc>>, - root_purity: Rc>>, - fixed_scope_idx: i32, -} - -impl MacroEvaluator for RuntimeMacroEvaluator { - fn evaluate( - &self, - node: &Node, - bindings: &HashMap, Node>, - ) -> Result { - if let UntypedKind::Identifier(sym) = &node.kind - && let Some(arg_node) = bindings.get(&sym.name) - { - return Ok(Value::Object(Rc::new(arg_node.clone()) as Rc)); - } - - let mut diag = Diagnostics::new(); - let initial_scopes = self.root_scopes.borrow().clone(); - let initial_slot_count = *self.root_slot_count.borrow(); - - let (bound_ast, captures, _, _) = Binder::bind_root(initial_scopes, initial_slot_count, self.fixed_scope_idx, node, &mut diag)?; - let bound_ast = crate::ast::compiler::captures::CapturePass::apply(bound_ast, &captures); - let checker = TypeChecker::new(self.root_types.clone()); - let typed_ast = checker.check(&bound_ast, &[], &mut diag); - - if diag.has_errors() { - return Err(diag - .items - .into_iter() - .map(|d| d.message) - .collect::>() - .join("\n")); - } - - let exec_ast = Lowering::lower(Analyzer::analyze(&typed_ast, &self.root_purity.borrow())); // Minimal analysis for macro eval - - let mut vm = VM::new(self.root_values.clone()); - vm.run(&exec_ast) - } -} - -impl Default for Environment { - fn default() -> Self { - Self::new() - } -} - -impl Environment { - pub fn new() -> Self { - let env = Self { - root_types: Rc::new(RefCell::new(Vec::new())), - root_purity: Rc::new(RefCell::new(Vec::new())), - root_values: Rc::new(RefCell::new(Vec::new())), - fixed_scope_idx: -1, - root_scopes: Rc::new(RefCell::new(vec![crate::ast::compiler::binder::CompilerScope::new()])), - root_slot_count: Rc::new(RefCell::new(0)), - function_registry: Rc::new(RefCell::new(HashMap::new())), - typed_function_registry: Rc::new(RefCell::new(HashMap::new())), - monomorph_cache: Rc::new(RefCell::new(HashMap::new())), - debug_mode: false, - optimization: true, - macro_registry: Rc::new(RefCell::new(MacroRegistry::new())), - pipeline_generators: Rc::new(RefCell::new(Vec::new())), - search_paths: Rc::new(RefCell::new(Vec::new())), - loaded_modules: Rc::new(RefCell::new(HashSet::new())), - }; - crate::ast::rtl::register(&env); - - let mut env = env; - env.fixed_scope_idx = 0; - // Push the first mutable user scope (Level 1) - env.root_scopes.borrow_mut().push(crate::ast::compiler::binder::CompilerScope::new()); - - // Automatically add standard search paths (CWD and CWD/rtl) - if let Ok(cwd) = std::env::current_dir() { - env.add_search_path(&cwd); - let rtl_path = cwd.join("rtl"); - if rtl_path.exists() { - env.add_search_path(rtl_path); - } - } - - env - } - - pub fn add_search_path(&self, path: impl AsRef) { - self.search_paths.borrow_mut().push(path.as_ref().to_path_buf()); - } - - pub fn set_debug_mode(&mut self, enabled: bool) { - self.debug_mode = enabled; - } - - /// Pumps data through all registered pipeline generators until they are exhausted. - pub fn run_pipeline(&self) { - let mut generators = self.pipeline_generators.borrow_mut(); - let mut any_active = true; - while any_active { - any_active = false; - for generator in generators.iter_mut() { - if generator() { - any_active = true; - } - } - } - } - - fn get_expander(&self) -> MacroExpander { - let evaluator = RuntimeMacroEvaluator { - root_scopes: self.root_scopes.clone(), - root_slot_count: self.root_slot_count.clone(), - root_types: self.root_types.clone(), - root_values: self.root_values.clone(), - root_purity: self.root_purity.clone(), - fixed_scope_idx: self.fixed_scope_idx, - }; - MacroExpander::new(self.macro_registry.borrow().clone(), evaluator) - } - - - /// Resolves a #use module path relative to a base path, then falls back to search paths. - /// Returns a list of all matching .myc files (single file, or all files in a directory). - fn resolve_module_paths(&self, module_path: &str, base_path: &Path) -> Result, String> { - let relative_path = module_path.replace("->", std::path::MAIN_SEPARATOR_STR); - let file_name = format!("{}.myc", relative_path); - - let check_path = |base: &Path| -> Option> { - // Check for file first - let mut file_p = base.to_path_buf(); - file_p.push(&file_name); - if file_p.is_file() && let Ok(canon) = file_p.canonicalize() { - return Some(vec![canon]); - } - - // Check for directory - let mut dir_p = base.to_path_buf(); - dir_p.push(&relative_path); - if dir_p.is_dir() { - let mut files = Vec::new(); - if let Ok(entries) = std::fs::read_dir(&dir_p) { - let mut valid_entries: Vec<_> = entries.filter_map(|e| e.ok()).collect(); - valid_entries.sort_by_key(|e| e.path()); - for entry in valid_entries { - let p = entry.path(); - if p.is_file() && p.extension().is_some_and(|ext| ext == "myc") && let Ok(canon) = p.canonicalize() { - files.push(canon); - } - } - } - if !files.is_empty() { - return Some(files); - } - } - None - }; - - // 1. Try relative to base_path - if let Some(paths) = check_path(base_path) { - return Ok(paths); - } - - // 2. Try search paths - for sp in self.search_paths.borrow().iter() { - if let Some(paths) = check_path(sp) { - return Ok(paths); - } - } - - Err(format!("Could not find module or directory '{}'", module_path)) - } - - fn collect_dependencies( - &self, - source: &str, - base_path: &Path, - all_files: &mut Vec<(PathBuf, Node)>, - ) -> Result<(), String> { - let directives = self.extract_use_directives(source); - - for module_path in directives { - let abs_paths = self.resolve_module_paths(&module_path, base_path)?; - - for abs_path in abs_paths { - if self.loaded_modules.borrow().contains(&abs_path) { - continue; - } - - self.loaded_modules.borrow_mut().insert(abs_path.clone()); - - let lib_source = std::fs::read_to_string(&abs_path) - .map_err(|e| format!("Failed to read module {:?}: {}", abs_path, e))?; - - // Pre-process dependencies of the library BEFORE adding it, ensuring topological order - let lib_base = abs_path.parent().unwrap_or(Path::new(".")); - self.collect_dependencies(&lib_source, lib_base, all_files)?; - - let mut parser = Parser::new(&lib_source); - let untyped_ast = parser.parse_expression(); - - if parser.diagnostics.has_errors() { - return Err(format!( - "Parser error in module {:?}:\n{}", - abs_path, - parser.diagnostics.items[0].message - )); - } - - all_files.push((abs_path, untyped_ast)); - } - } - Ok(()) - } - - fn extract_use_directives(&self, source: &str) -> Vec { - let mut paths = Vec::new(); - for line in source.lines() { - let trimmed = line.trim(); - if trimmed.is_empty() || trimmed.starts_with(';') { - continue; - } - if let Some(stripped) = trimmed.strip_prefix("#use ") { - let path = stripped.trim(); - // Strip optional quotes if they somehow got in, though user spec says no spaces/quotes. - let clean_path = if (path.starts_with('"') && path.ends_with('"')) - || (path.starts_with('\'') && path.ends_with('\'')) - { - &path[1..path.len() - 1] - } else { - path - }; - paths.push(clean_path.to_string()); - } else { - // Stop at first non-directive/non-comment line - break; - } - } - paths - } - - /// Used to pre-load all dependencies of a script before compiling it. - /// 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::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(); - 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)); - } - - // 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); - } - - // Pass 2: Compilation and Initialization - for (path, untyped_ast) in files { - let typed_ast = self.compile_untyped(untyped_ast).map_err(|e: String| { - format!("Compilation error in {}:\n{}", path.display(), e) - })?; - self.run_script_compiled(typed_ast).map_err(|e: String| { - format!("Initialization error in {}:\n{}", path.display(), e) - })?; - } - - Ok(()) - } - - fn discover_globals(&self, node: &Node) { - match &node.kind { - UntypedKind::Def { target, .. } => { - if let UntypedKind::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]; - - if !current_scope.locals.contains_key(sym) { - let mut slot_count = self.root_slot_count.borrow_mut(); - let slot = crate::ast::compiler::bound_nodes::LocalSlot(*slot_count); - current_scope.locals.insert( - sym.clone(), - crate::ast::compiler::binder::LocalInfo { - addr: Address::Local(slot), - identity: node.identity.clone(), - _ty: StaticType::Any, - purity: Purity::Impure, - }, - ); - *slot_count += 1; - } - } - } - UntypedKind::MacroDecl { name, params, body } => { - let mut registry = self.macro_registry.borrow_mut(); - - fn extract_names(node: &Node) -> Vec> { - match &node.kind { - UntypedKind::Identifier(sym) => vec![sym.name.clone()], - UntypedKind::Tuple { elements } => { elements.iter().flat_map(extract_names).collect() - } - _ => vec![], - } - } - - let p_names = extract_names(params); - registry.define(name.name.clone(), p_names, body.as_ref().clone()); - } - UntypedKind::Block { exprs } => { - for expr in exprs { - self.discover_globals(expr); - } - } - _ => {} - } - } - - fn compile_pipeline(&self, untyped_ast: Node, diagnostics: &mut Diagnostics) -> Option { - let expanded_ast = match self.get_expander().expand(untyped_ast) { - Ok(ast) => ast, - Err(e) => { - diagnostics.push_error(e, None); - return None; - } - }; - - let initial_scopes = self.root_scopes.borrow().clone(); - let initial_slot_count = *self.root_slot_count.borrow(); - - let (bound_ast, captures, final_scopes, final_slot_count) = - match Binder::bind_root(initial_scopes, initial_slot_count, self.fixed_scope_idx, &expanded_ast, diagnostics) { - Ok(res) => res, - Err(e) => { - diagnostics.push_error(e, None); - return None; - } - }; - - // Update environment state with new bindings from this script - *self.root_scopes.borrow_mut() = final_scopes; - *self.root_slot_count.borrow_mut() = final_slot_count; - - let bound_ast = crate::ast::compiler::captures::CapturePass::apply(bound_ast, &captures); - - // Pre-allocate global slots to prevent out-of-bounds during specialization/optimization - { - let mut values = self.root_values.borrow_mut(); - let count = *self.root_slot_count.borrow(); - if (count as usize) > values.len() { - values.resize(count as usize, Value::Void); - } - } - - LambdaCollector::collect(&bound_ast, &mut self.function_registry.borrow_mut()); - - let checker = TypeChecker::new(self.root_types.clone()); - let wrapped_ast = if let crate::ast::compiler::bound_nodes::BoundKind::Lambda { .. } = bound_ast.kind { - bound_ast - } else { - crate::ast::compiler::bound_nodes::BoundNode { - identity: bound_ast.identity.clone(), - kind: crate::ast::compiler::bound_nodes::BoundKind::Lambda { - params: std::rc::Rc::new(crate::ast::nodes::Node { - identity: bound_ast.identity.clone(), - kind: crate::ast::compiler::bound_nodes::BoundKind::Tuple { elements: vec![] }, - ty: (), - }), - upvalues: vec![], - body: std::rc::Rc::new(bound_ast), - positional_count: Some(0), - }, - ty: (), - } - }; - Some(checker.check(&wrapped_ast, &[], diagnostics)) - } - - fn compile_untyped(&self, untyped_ast: Node) -> Result { - let mut diagnostics = Diagnostics::new(); - - let typed_ast_opt = self.compile_pipeline(untyped_ast, &mut diagnostics); - - if diagnostics.has_errors() || typed_ast_opt.is_none() { - return Err(diagnostics - .items - .into_iter() - .map(|d| d.message) - .collect::>() - .join("\n")); - } - - Ok(typed_ast_opt.unwrap()) - } - - pub fn register_native( - &self, - name: &str, - ty: StaticType, - func: Rc, - ) { - let mut types = self.root_types.borrow_mut(); - let mut values = self.root_values.borrow_mut(); - let mut purity = self.root_purity.borrow_mut(); - - let identity = NodeIdentity::new(SourceLocation { line: 0, col: 0 }); - - // populate root_scopes[0] - let mut root_scopes = self.root_scopes.borrow_mut(); - let mut slot_count = self.root_slot_count.borrow_mut(); - let slot = crate::ast::compiler::bound_nodes::LocalSlot(*slot_count); - root_scopes[0].locals.insert( - Symbol::from(name), - crate::ast::compiler::binder::LocalInfo { - addr: Address::Global(GlobalIdx(slot.0)), - identity, - _ty: ty.clone(), - purity: func.purity, - }, - ); - *slot_count += 1; - - // Ensure arrays are large enough - let idx = slot.0 as usize; - if idx >= values.len() { - values.resize(idx + 1, Value::Void); - types.resize(idx + 1, StaticType::Any); - purity.resize(idx + 1, Purity::Impure); - } - - types[idx] = ty; - purity[idx] = func.purity; - values[idx] = Value::Function(func); - } - - pub fn register_native_fn( - &self, - name: &str, - ty: StaticType, - purity_level: Purity, - func: impl Fn(&[Value]) -> Value + 'static, - ) { - self.register_native( - name, - ty, - Rc::new(crate::ast::types::NativeFunction { - func: Rc::new(func), - purity: purity_level, - }), - ); - } - - pub fn register_constant(&self, name: &str, ty: StaticType, val: Value) { - let mut types = self.root_types.borrow_mut(); - let mut values = self.root_values.borrow_mut(); - let mut purity = self.root_purity.borrow_mut(); - - let identity = NodeIdentity::new(SourceLocation { line: 0, col: 0 }); - - // populate root_scopes[0] - let mut root_scopes = self.root_scopes.borrow_mut(); - let mut slot_count = self.root_slot_count.borrow_mut(); - let slot = crate::ast::compiler::bound_nodes::LocalSlot(*slot_count); - root_scopes[0].locals.insert( - Symbol::from(name), - crate::ast::compiler::binder::LocalInfo { - addr: Address::Global(GlobalIdx(slot.0)), - identity, - _ty: ty.clone(), - purity: Purity::Pure, - }, - ); - *slot_count += 1; - - let idx = slot.0 as usize; - if idx >= values.len() { - values.resize(idx + 1, Value::Void); - types.resize(idx + 1, StaticType::Any); - purity.resize(idx + 1, Purity::Impure); - } - - types[idx] = ty; - purity[idx] = Purity::Pure; - values[idx] = val; - } - - - pub fn dump_ast(&self, source: &str) -> Result { - self.preload_dependencies(source, None)?; - let compiled = self.compile(source).into_result()?; - let linked = self.link(compiled); - Ok(Dumper::dump(&linked)) - } - - pub fn compile(&self, source: &str) -> CompilationResult { - if let Err(e) = self.preload_dependencies(source, None) { - return CompilationResult::error(format!("Dependency Error: {}", e)); - } - - let mut parser = Parser::new(source); - let untyped_ast = parser.parse_expression(); - - if !parser.at_eof() { - parser - .diagnostics - .push_error("Unexpected trailing expressions in script.", None); - return CompilationResult { - ast: None, - diagnostics: parser.diagnostics, - }; - } - let mut diagnostics = parser.diagnostics; - - let typed_ast = self.compile_pipeline(untyped_ast, &mut diagnostics); - - CompilationResult { - ast: typed_ast, - diagnostics, - } - } - - pub fn link(&self, node: TypedNode) -> ExecNode { - // 1. Analyze - let analyzed = Analyzer::analyze(&node, &self.root_purity.borrow()); - - // 2. Collect Analyzed Lambdas - LambdaCollector::collect(&analyzed, &mut self.typed_function_registry.borrow_mut()); - - // 3. Specialize - let specialized = self.specialize_node(analyzed); - - // 4. Optimize - let optimizer = Optimizer::new(self.optimization) - .with_globals(self.root_values.clone()) - .with_purity(self.root_purity.clone()) - .with_registry(self.typed_function_registry.clone()); - let optimized = optimizer.optimize(specialized); - - // 5. Lowering - Lowering::lower(optimized) - } - - pub fn instantiate(&self, node: ExecNode) -> Rc { - let root_values = self.root_values.clone(); - if let BoundKind::Lambda { - params, - upvalues, - body, - positional_count, - } = &node.kind - && upvalues.is_empty() - { - let stack_size = node.ty.stack_size; - let closure = Rc::new(crate::ast::vm::Closure::new( - params.ty.original.clone(), - body.ty.original.clone(), - body.clone(), - vec![], - *positional_count, - stack_size, - )); - let closure_obj: Rc = closure; - - return Rc::new(crate::ast::types::NativeFunction { - purity: Purity::Impure, - func: Rc::new(move |args| { - let mut vm = VM::new(root_values.clone()); - match vm.run_with_args(closure_obj.clone(), args) { - Ok(v) => v, - Err(e) => panic!("Myc Runtime Error: {}", e), - } - }), - }); - } - - let exec_node = Rc::new(node); - Rc::new(crate::ast::types::NativeFunction { - purity: Purity::Impure, - func: Rc::new(move |args| { - let mut vm = VM::new(root_values.clone()); - let res = match vm.run(&exec_node) { - Ok(v) => v, - Err(e) => panic!("Myc Runtime Error: {}", e), - }; - - if let Value::Object(obj) = &res - && obj - .as_any() - .downcast_ref::() - .is_some() - { - match vm.run_with_args(obj.clone(), args) { - Ok(v) => v, - Err(e) => panic!("Myc Runtime Error (Closure): {}", e), - } - } else { - res - } - }), - }) - } - - fn specialize_node(&self, node: AnalyzedNode) -> AnalyzedNode { - let registry = Rc::new(EnvFunctionRegistry { - registry: self.function_registry.clone(), - analyzed_registry: self.typed_function_registry.clone(), - }); - - 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 mono_cache = self.monomorph_cache.clone(); - let root_values = self.root_values.clone(); - let root_types = self.root_types.clone(); - let root_purity = self.root_purity.clone(); - let optimization = self.optimization; - - let compiler = Rc::new( - move |func_template: Rc, - arg_types: &[StaticType]| - -> Result<(Value, StaticType), String> { - let mut diag = Diagnostics::new(); - let checker = TypeChecker::new(root_types.clone()); - let retyped_ast = checker.check(func_template.as_ref(), arg_types, &mut diag); - - if diag.has_errors() { - return Err(diag - .items - .into_iter() - .map(|d| d.message) - .collect::>() - .join("\n")); - } - - let analyzed = Analyzer::analyze(&retyped_ast, &root_purity.borrow()); - - let sub_registry = Rc::new(EnvFunctionRegistry { - registry: untyped_reg.clone(), - analyzed_registry: typed_reg.clone(), - }); - let sub_rtl_lookup = - Rc::new(|name: &str, args: &[StaticType]| intrinsics::lookup(name, args)); - - let sub_specializer = Specializer::new( - Some(sub_registry), - None, - Some(sub_rtl_lookup), - Some(mono_cache.clone()), - ); - - let specialized_ast = sub_specializer.specialize(analyzed); - - let optimizer = Optimizer::new(optimization) - .with_globals(root_values.clone()) - .with_purity(root_purity.clone()); - let optimized_ast = optimizer.optimize(specialized_ast); - - let exec_ast = Lowering::lower(optimized_ast); - let mut vm = VM::new(root_values.clone()); - let compiled_val = match vm.run(&exec_ast) { - Ok(v) => v, - Err(e) => return Err(format!("VM Error during specialization: {}", e)), - }; - - let ret_type = exec_ast.ty.ty.clone(); - Ok((compiled_val, ret_type)) - }, - ); - - let specializer = Specializer::new( - Some(registry), - Some(compiler), - Some(rtl_lookup), - Some(self.monomorph_cache.clone()), - ); - - specializer.specialize(node) - } - - pub fn run_script(&self, source: &str) -> Result { - self.preload_dependencies(source, None)?; - if self.debug_mode { - let (res, logs) = self.run_debug(source)?; - for line in logs { - println!("{}", line); - } - res - } else { - self.compile(source) - .into_result() - .and_then(|ast| self.run_script_compiled(ast)) - } - } - - pub fn run_script_compiled(&self, compiled: TypedNode) -> Result { - let linked = self.link(compiled); - let func = self.instantiate(linked); - let res = (func.func)(&[]); - self.run_pipeline(); - Ok(res) - } - - pub fn run_debug(&self, source: &str) -> Result<(Result, Vec), String> { - self.preload_dependencies(source, None)?; - let compiled = self.compile(source).into_result()?; - let linked = self.link(compiled); - - let mut vm = VM::new(self.root_values.clone()); - let mut observer = TracingObserver::new(); - - // 1. Run the script wrapper (returns a closure representing the script) - let result = vm.run_with_observer(&mut observer, &linked); - // 2. Execute the root closure immediately to get the actual script result. - // All Myc scripts are wrapped in a parameterless lambda for consistency. - let mut final_result = result; - if let Ok(Value::Object(obj)) = &final_result - && let Some(_closure) = obj.as_any().downcast_ref::() - { - final_result = vm.run_with_args_observed(&mut observer, obj.clone(), &[]); - } - - self.run_pipeline(); - - Ok((final_result, observer.logs)) - } -} +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::parser::Parser; +use crate::ast::vm::{TracingObserver, VM}; +use std::cell::RefCell; +use std::collections::{HashMap, HashSet}; +use std::path::{Path, PathBuf}; +use std::rc::Rc; + +use crate::ast::compiler::bound_nodes::{ + Address, AnalyzedNode, BoundKind, GlobalAnalyzedRegistry, GlobalFunctionRegistry, GlobalIdx, + Node, +}; +use crate::ast::compiler::dumper::Dumper; +use crate::ast::compiler::lambda_collector::LambdaCollector; +use crate::ast::compiler::lowering::{ExecNode, Lowering}; +use crate::ast::compiler::macros::{MacroEvaluator, MacroExpander, MacroRegistry}; +use crate::ast::compiler::optimizer::Optimizer; +use crate::ast::compiler::specializer::{FunctionRegistry, MonoCache, Specializer}; +use crate::ast::rtl::intrinsics; +use crate::ast::types::{ + NodeIdentity, Object, Purity, SourceLocation, StaticType, Value, +}; + +use crate::ast::diagnostics::{DiagnosticLevel, Diagnostics}; +pub type PipelineGenerator = Box bool>; + +const SYSTEM_LIB_SOURCE: &str = include_str!("system.myc"); +const SYSTEM_LIB_PATH: &str = "/system.myc"; + +pub struct CompilationResult { + pub ast: Option, + pub diagnostics: Diagnostics, +} + +impl CompilationResult { + pub fn success(ast: TypedNode) -> Self { + Self { + ast: Some(ast), + diagnostics: Diagnostics::new(), + } + } + + pub fn error(msg: impl Into) -> Self { + let mut diag = Diagnostics::new(); + diag.push_error(msg, None); + Self { + ast: None, + diagnostics: diag, + } + } + + pub fn into_result(self) -> Result { + if self.diagnostics.has_errors() { + let errors: Vec = self + .diagnostics + .items + .into_iter() + .filter(|d| d.level == DiagnosticLevel::Error) + .map(|d| d.message) + .collect(); + Err(errors.join("\n")) + } else if let Some(ast) = self.ast { + Ok(ast) + } else { + Err("Compilation failed without diagnostics".to_string()) + } + } +} + +pub struct Environment { + pub root_types: Rc>>, + pub root_purity: Rc>>, + pub root_values: Rc>>, + pub fixed_scope_idx: i32, + pub root_scopes: Rc>>, + pub root_slot_count: Rc>, + pub function_registry: Rc>, + pub typed_function_registry: Rc>, + pub monomorph_cache: Rc>, + pub debug_mode: bool, + pub optimization: bool, + pub macro_registry: Rc>, + pub pipeline_generators: Rc>>, + pub search_paths: Rc>>, + pub loaded_modules: Rc>>, +} + +struct EnvFunctionRegistry { + registry: Rc>, + analyzed_registry: Rc>, +} + +impl FunctionRegistry for EnvFunctionRegistry { + fn resolve(&self, addr: Address) -> Option> { + if let Address::Global(idx) = addr { + self.registry.borrow().get(&idx).cloned() + } else { + None + } + } + fn resolve_analyzed(&self, addr: Address) -> Option> { + if let Address::Global(idx) = addr { + self.analyzed_registry.borrow().get(&idx).cloned() + } else { + None + } + } +} + +struct RuntimeMacroEvaluator { + root_scopes: Rc>>, + root_slot_count: Rc>, + root_types: Rc>>, + root_values: Rc>>, + root_purity: Rc>>, + fixed_scope_idx: i32, +} + +impl MacroEvaluator for RuntimeMacroEvaluator { + fn evaluate( + &self, + node: &UntypedNode, + bindings: &HashMap, UntypedNode>, + ) -> Result { + if let UntypedKind::Identifier(sym) = &node.kind + && let Some(arg_node) = bindings.get(&sym.name) + { + return Ok(Value::Object(Rc::new(arg_node.clone()) as Rc)); + } + + let mut diag = Diagnostics::new(); + let initial_scopes = self.root_scopes.borrow().clone(); + let initial_slot_count = *self.root_slot_count.borrow(); + + let (bound_ast, captures, _, _) = Binder::bind_root(initial_scopes, initial_slot_count, self.fixed_scope_idx, node, &mut diag)?; + let bound_ast = crate::ast::compiler::captures::CapturePass::apply(bound_ast, &captures); + let checker = TypeChecker::new(self.root_types.clone()); + let typed_ast = checker.check(&bound_ast, &[], &mut diag); + + if diag.has_errors() { + return Err(diag + .items + .into_iter() + .map(|d| d.message) + .collect::>() + .join("\n")); + } + + let exec_ast = Lowering::lower(Analyzer::analyze(&typed_ast, &self.root_purity.borrow())); // Minimal analysis for macro eval + + let mut vm = VM::new(self.root_values.clone()); + vm.run(&exec_ast) + } +} + +impl Default for Environment { + fn default() -> Self { + Self::new() + } +} + +impl Environment { + pub fn new() -> Self { + let env = Self { + root_types: Rc::new(RefCell::new(Vec::new())), + root_purity: Rc::new(RefCell::new(Vec::new())), + root_values: Rc::new(RefCell::new(Vec::new())), + fixed_scope_idx: -1, + root_scopes: Rc::new(RefCell::new(vec![crate::ast::compiler::binder::CompilerScope::new()])), + root_slot_count: Rc::new(RefCell::new(0)), + function_registry: Rc::new(RefCell::new(HashMap::new())), + typed_function_registry: Rc::new(RefCell::new(HashMap::new())), + monomorph_cache: Rc::new(RefCell::new(HashMap::new())), + debug_mode: false, + optimization: true, + macro_registry: Rc::new(RefCell::new(MacroRegistry::new())), + pipeline_generators: Rc::new(RefCell::new(Vec::new())), + search_paths: Rc::new(RefCell::new(Vec::new())), + loaded_modules: Rc::new(RefCell::new(HashSet::new())), + }; + crate::ast::rtl::register(&env); + + let mut env = env; + env.fixed_scope_idx = 0; + // Push the first mutable user scope (Level 1) + env.root_scopes.borrow_mut().push(crate::ast::compiler::binder::CompilerScope::new()); + + // Automatically add standard search paths (CWD and CWD/rtl) + if let Ok(cwd) = std::env::current_dir() { + env.add_search_path(&cwd); + let rtl_path = cwd.join("rtl"); + if rtl_path.exists() { + env.add_search_path(rtl_path); + } + } + + env + } + + pub fn add_search_path(&self, path: impl AsRef) { + self.search_paths.borrow_mut().push(path.as_ref().to_path_buf()); + } + + pub fn set_debug_mode(&mut self, enabled: bool) { + self.debug_mode = enabled; + } + + /// Pumps data through all registered pipeline generators until they are exhausted. + pub fn run_pipeline(&self) { + let mut generators = self.pipeline_generators.borrow_mut(); + let mut any_active = true; + while any_active { + any_active = false; + for generator in generators.iter_mut() { + if generator() { + any_active = true; + } + } + } + } + + fn get_expander(&self) -> MacroExpander { + let evaluator = RuntimeMacroEvaluator { + root_scopes: self.root_scopes.clone(), + root_slot_count: self.root_slot_count.clone(), + root_types: self.root_types.clone(), + root_values: self.root_values.clone(), + root_purity: self.root_purity.clone(), + fixed_scope_idx: self.fixed_scope_idx, + }; + MacroExpander::new(self.macro_registry.borrow().clone(), evaluator) + } + + + /// Resolves a #use module path relative to a base path, then falls back to search paths. + /// Returns a list of all matching .myc files (single file, or all files in a directory). + fn resolve_module_paths(&self, module_path: &str, base_path: &Path) -> Result, String> { + let relative_path = module_path.replace("->", std::path::MAIN_SEPARATOR_STR); + let file_name = format!("{}.myc", relative_path); + + let check_path = |base: &Path| -> Option> { + // Check for file first + let mut file_p = base.to_path_buf(); + file_p.push(&file_name); + if file_p.is_file() && let Ok(canon) = file_p.canonicalize() { + return Some(vec![canon]); + } + + // Check for directory + let mut dir_p = base.to_path_buf(); + dir_p.push(&relative_path); + if dir_p.is_dir() { + let mut files = Vec::new(); + if let Ok(entries) = std::fs::read_dir(&dir_p) { + let mut valid_entries: Vec<_> = entries.filter_map(|e| e.ok()).collect(); + valid_entries.sort_by_key(|e| e.path()); + for entry in valid_entries { + let p = entry.path(); + if p.is_file() && p.extension().is_some_and(|ext| ext == "myc") && let Ok(canon) = p.canonicalize() { + files.push(canon); + } + } + } + if !files.is_empty() { + return Some(files); + } + } + None + }; + + // 1. Try relative to base_path + if let Some(paths) = check_path(base_path) { + return Ok(paths); + } + + // 2. Try search paths + for sp in self.search_paths.borrow().iter() { + if let Some(paths) = check_path(sp) { + return Ok(paths); + } + } + + Err(format!("Could not find module or directory '{}'", module_path)) + } + + fn collect_dependencies( + &self, + source: &str, + base_path: &Path, + all_files: &mut Vec<(PathBuf, UntypedNode)>, + ) -> Result<(), String> { + let directives = self.extract_use_directives(source); + + for module_path in directives { + let abs_paths = self.resolve_module_paths(&module_path, base_path)?; + + for abs_path in abs_paths { + if self.loaded_modules.borrow().contains(&abs_path) { + continue; + } + + self.loaded_modules.borrow_mut().insert(abs_path.clone()); + + let lib_source = std::fs::read_to_string(&abs_path) + .map_err(|e| format!("Failed to read module {:?}: {}", abs_path, e))?; + + // Pre-process dependencies of the library BEFORE adding it, ensuring topological order + let lib_base = abs_path.parent().unwrap_or(Path::new(".")); + self.collect_dependencies(&lib_source, lib_base, all_files)?; + + let mut parser = Parser::new(&lib_source); + let untyped_ast = parser.parse_expression(); + + if parser.diagnostics.has_errors() { + return Err(format!( + "Parser error in module {:?}:\n{}", + abs_path, + parser.diagnostics.items[0].message + )); + } + + all_files.push((abs_path, untyped_ast)); + } + } + Ok(()) + } + + fn extract_use_directives(&self, source: &str) -> Vec { + let mut paths = Vec::new(); + for line in source.lines() { + let trimmed = line.trim(); + if trimmed.is_empty() || trimmed.starts_with(';') { + continue; + } + if let Some(stripped) = trimmed.strip_prefix("#use ") { + let path = stripped.trim(); + // Strip optional quotes if they somehow got in, though user spec says no spaces/quotes. + let clean_path = if (path.starts_with('"') && path.ends_with('"')) + || (path.starts_with('\'') && path.ends_with('\'')) + { + &path[1..path.len() - 1] + } else { + path + }; + paths.push(clean_path.to_string()); + } else { + // Stop at first non-directive/non-comment line + break; + } + } + paths + } + + /// Used to pre-load all dependencies of a script before compiling it. + /// 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(); + + // 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(); + 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)); + } + + // 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); + } + + // Pass 2: Compilation and Initialization + for (path, untyped_ast) in files { + let typed_ast = self.compile_untyped(untyped_ast).map_err(|e: String| { + format!("Compilation error in {}:\n{}", path.display(), e) + })?; + self.run_script_compiled(typed_ast).map_err(|e: String| { + format!("Initialization error in {}:\n{}", path.display(), e) + })?; + } + + Ok(()) + } + + fn discover_globals(&self, node: &UntypedNode) { + match &node.kind { + UntypedKind::Def { target, .. } => { + if let UntypedKind::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]; + + if !current_scope.locals.contains_key(sym) { + let mut slot_count = self.root_slot_count.borrow_mut(); + let slot = crate::ast::compiler::bound_nodes::LocalSlot(*slot_count); + current_scope.locals.insert( + sym.clone(), + crate::ast::compiler::binder::LocalInfo { + addr: Address::Local(slot), + identity: node.identity.clone(), + _ty: StaticType::Any, + purity: Purity::Impure, + }, + ); + *slot_count += 1; + } + } + } + UntypedKind::MacroDecl { name, params, body } => { + let mut registry = self.macro_registry.borrow_mut(); + + fn extract_names(node: &UntypedNode) -> Vec> { + match &node.kind { + UntypedKind::Identifier(sym) => vec![sym.name.clone()], + UntypedKind::Tuple { elements } => { + elements.iter().flat_map(extract_names).collect() + } + _ => vec![], + } + } + + let p_names = extract_names(params); + registry.define(name.name.clone(), p_names, body.as_ref().clone()); + } + UntypedKind::Block { exprs } => { + for expr in exprs { + self.discover_globals(expr); + } + } + _ => {} + } + } + + fn compile_pipeline(&self, untyped_ast: UntypedNode, diagnostics: &mut Diagnostics) -> Option { + let expanded_ast = match self.get_expander().expand(untyped_ast) { + Ok(ast) => ast, + Err(e) => { + diagnostics.push_error(e, None); + return None; + } + }; + + let initial_scopes = self.root_scopes.borrow().clone(); + let initial_slot_count = *self.root_slot_count.borrow(); + + let (bound_ast, captures, final_scopes, final_slot_count) = + match Binder::bind_root(initial_scopes, initial_slot_count, self.fixed_scope_idx, &expanded_ast, diagnostics) { + Ok(res) => res, + Err(e) => { + diagnostics.push_error(e, None); + return None; + } + }; + + // Update environment state with new bindings from this script + *self.root_scopes.borrow_mut() = final_scopes; + *self.root_slot_count.borrow_mut() = final_slot_count; + + let bound_ast = crate::ast::compiler::captures::CapturePass::apply(bound_ast, &captures); + + // Pre-allocate global slots to prevent out-of-bounds during specialization/optimization + { + let mut values = self.root_values.borrow_mut(); + let count = *self.root_slot_count.borrow(); + if (count as usize) > values.len() { + values.resize(count as usize, Value::Void); + } + } + + LambdaCollector::collect(&bound_ast, &mut self.function_registry.borrow_mut()); + + let checker = TypeChecker::new(self.root_types.clone()); + let wrapped_ast = if let crate::ast::compiler::bound_nodes::BoundKind::Lambda { .. } = bound_ast.kind { + bound_ast + } else { + crate::ast::compiler::bound_nodes::Node { + identity: bound_ast.identity.clone(), + kind: crate::ast::compiler::bound_nodes::BoundKind::Lambda { + params: std::rc::Rc::new(crate::ast::compiler::bound_nodes::Node { + identity: bound_ast.identity.clone(), + kind: crate::ast::compiler::bound_nodes::BoundKind::Tuple { elements: vec![] }, + ty: (), + }), + upvalues: vec![], + body: std::rc::Rc::new(bound_ast), + positional_count: Some(0), + }, + ty: (), + } + }; + Some(checker.check(&wrapped_ast, &[], diagnostics)) + } + + fn compile_untyped(&self, untyped_ast: UntypedNode) -> Result { + let mut diagnostics = Diagnostics::new(); + + let typed_ast_opt = self.compile_pipeline(untyped_ast, &mut diagnostics); + + if diagnostics.has_errors() || typed_ast_opt.is_none() { + return Err(diagnostics + .items + .into_iter() + .map(|d| d.message) + .collect::>() + .join("\n")); + } + + Ok(typed_ast_opt.unwrap()) + } + + pub fn register_native( + &self, + name: &str, + ty: StaticType, + func: Rc, + ) { + let mut types = self.root_types.borrow_mut(); + let mut values = self.root_values.borrow_mut(); + let mut purity = self.root_purity.borrow_mut(); + + let identity = NodeIdentity::new(SourceLocation { line: 0, col: 0 }); + + // populate root_scopes[0] + let mut root_scopes = self.root_scopes.borrow_mut(); + let mut slot_count = self.root_slot_count.borrow_mut(); + let slot = crate::ast::compiler::bound_nodes::LocalSlot(*slot_count); + root_scopes[0].locals.insert( + Symbol::from(name), + crate::ast::compiler::binder::LocalInfo { + addr: Address::Global(GlobalIdx(slot.0)), + identity, + _ty: ty.clone(), + purity: func.purity, + }, + ); + *slot_count += 1; + + // Ensure arrays are large enough + let idx = slot.0 as usize; + if idx >= values.len() { + values.resize(idx + 1, Value::Void); + types.resize(idx + 1, StaticType::Any); + purity.resize(idx + 1, Purity::Impure); + } + + types[idx] = ty; + purity[idx] = func.purity; + values[idx] = Value::Function(func); + } + + pub fn register_native_fn( + &self, + name: &str, + ty: StaticType, + purity_level: Purity, + func: impl Fn(&[Value]) -> Value + 'static, + ) { + self.register_native( + name, + ty, + Rc::new(crate::ast::types::NativeFunction { + func: Rc::new(func), + purity: purity_level, + }), + ); + } + + pub fn register_constant(&self, name: &str, ty: StaticType, val: Value) { + let mut types = self.root_types.borrow_mut(); + let mut values = self.root_values.borrow_mut(); + let mut purity = self.root_purity.borrow_mut(); + + let identity = NodeIdentity::new(SourceLocation { line: 0, col: 0 }); + + // populate root_scopes[0] + let mut root_scopes = self.root_scopes.borrow_mut(); + let mut slot_count = self.root_slot_count.borrow_mut(); + let slot = crate::ast::compiler::bound_nodes::LocalSlot(*slot_count); + root_scopes[0].locals.insert( + Symbol::from(name), + crate::ast::compiler::binder::LocalInfo { + addr: Address::Global(GlobalIdx(slot.0)), + identity, + _ty: ty.clone(), + purity: Purity::Pure, + }, + ); + *slot_count += 1; + + let idx = slot.0 as usize; + if idx >= values.len() { + values.resize(idx + 1, Value::Void); + types.resize(idx + 1, StaticType::Any); + purity.resize(idx + 1, Purity::Impure); + } + + types[idx] = ty; + purity[idx] = Purity::Pure; + values[idx] = val; + } + + + pub fn dump_ast(&self, source: &str) -> Result { + self.preload_dependencies(source, None)?; + let compiled = self.compile(source).into_result()?; + let linked = self.link(compiled); + Ok(Dumper::dump(&linked)) + } + + pub fn compile(&self, source: &str) -> CompilationResult { + if let Err(e) = self.preload_dependencies(source, None) { + return CompilationResult::error(format!("Dependency Error: {}", e)); + } + + let mut parser = Parser::new(source); + let untyped_ast = parser.parse_expression(); + + if !parser.at_eof() { + parser + .diagnostics + .push_error("Unexpected trailing expressions in script.", None); + return CompilationResult { + ast: None, + diagnostics: parser.diagnostics, + }; + } + let mut diagnostics = parser.diagnostics; + + let typed_ast = self.compile_pipeline(untyped_ast, &mut diagnostics); + + CompilationResult { + ast: typed_ast, + diagnostics, + } + } + + pub fn link(&self, node: TypedNode) -> ExecNode { + // 1. Analyze + let analyzed = Analyzer::analyze(&node, &self.root_purity.borrow()); + + // 2. Collect Analyzed Lambdas + LambdaCollector::collect(&analyzed, &mut self.typed_function_registry.borrow_mut()); + + // 3. Specialize + let specialized = self.specialize_node(analyzed); + + // 4. Optimize + let optimizer = Optimizer::new(self.optimization) + .with_globals(self.root_values.clone()) + .with_purity(self.root_purity.clone()) + .with_registry(self.typed_function_registry.clone()); + let optimized = optimizer.optimize(specialized); + + // 5. Lowering + Lowering::lower(optimized) + } + + pub fn instantiate(&self, node: ExecNode) -> Rc { + let root_values = self.root_values.clone(); + if let BoundKind::Lambda { + params, + upvalues, + body, + positional_count, + } = &node.kind + && upvalues.is_empty() + { + let stack_size = node.ty.stack_size; + let closure = Rc::new(crate::ast::vm::Closure::new( + params.ty.original.clone(), + body.ty.original.clone(), + body.clone(), + vec![], + *positional_count, + stack_size, + )); + let closure_obj: Rc = closure; + + return Rc::new(crate::ast::types::NativeFunction { + purity: Purity::Impure, + func: Rc::new(move |args| { + let mut vm = VM::new(root_values.clone()); + match vm.run_with_args(closure_obj.clone(), args) { + Ok(v) => v, + Err(e) => panic!("Myc Runtime Error: {}", e), + } + }), + }); + } + + let exec_node = Rc::new(node); + Rc::new(crate::ast::types::NativeFunction { + purity: Purity::Impure, + func: Rc::new(move |args| { + let mut vm = VM::new(root_values.clone()); + let res = match vm.run(&exec_node) { + Ok(v) => v, + Err(e) => panic!("Myc Runtime Error: {}", e), + }; + + if let Value::Object(obj) = &res + && obj + .as_any() + .downcast_ref::() + .is_some() + { + match vm.run_with_args(obj.clone(), args) { + Ok(v) => v, + Err(e) => panic!("Myc Runtime Error (Closure): {}", e), + } + } else { + res + } + }), + }) + } + + fn specialize_node(&self, node: AnalyzedNode) -> AnalyzedNode { + let registry = Rc::new(EnvFunctionRegistry { + registry: self.function_registry.clone(), + analyzed_registry: self.typed_function_registry.clone(), + }); + + 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 mono_cache = self.monomorph_cache.clone(); + let root_values = self.root_values.clone(); + let root_types = self.root_types.clone(); + let root_purity = self.root_purity.clone(); + let optimization = self.optimization; + + let compiler = Rc::new( + move |func_template: Rc, + arg_types: &[StaticType]| + -> Result<(Value, StaticType), String> { + let mut diag = Diagnostics::new(); + let checker = TypeChecker::new(root_types.clone()); + let retyped_ast = checker.check(func_template.as_ref(), arg_types, &mut diag); + + if diag.has_errors() { + return Err(diag + .items + .into_iter() + .map(|d| d.message) + .collect::>() + .join("\n")); + } + + let analyzed = Analyzer::analyze(&retyped_ast, &root_purity.borrow()); + + let sub_registry = Rc::new(EnvFunctionRegistry { + registry: untyped_reg.clone(), + analyzed_registry: typed_reg.clone(), + }); + let sub_rtl_lookup = + Rc::new(|name: &str, args: &[StaticType]| intrinsics::lookup(name, args)); + + let sub_specializer = Specializer::new( + Some(sub_registry), + None, + Some(sub_rtl_lookup), + Some(mono_cache.clone()), + ); + + let specialized_ast = sub_specializer.specialize(analyzed); + + let optimizer = Optimizer::new(optimization) + .with_globals(root_values.clone()) + .with_purity(root_purity.clone()); + let optimized_ast = optimizer.optimize(specialized_ast); + + let exec_ast = Lowering::lower(optimized_ast); + let mut vm = VM::new(root_values.clone()); + let compiled_val = match vm.run(&exec_ast) { + Ok(v) => v, + Err(e) => return Err(format!("VM Error during specialization: {}", e)), + }; + + let ret_type = exec_ast.ty.ty.clone(); + Ok((compiled_val, ret_type)) + }, + ); + + let specializer = Specializer::new( + Some(registry), + Some(compiler), + Some(rtl_lookup), + Some(self.monomorph_cache.clone()), + ); + + specializer.specialize(node) + } + + pub fn run_script(&self, source: &str) -> Result { + self.preload_dependencies(source, None)?; + if self.debug_mode { + let (res, logs) = self.run_debug(source)?; + for line in logs { + println!("{}", line); + } + res + } else { + self.compile(source) + .into_result() + .and_then(|ast| self.run_script_compiled(ast)) + } + } + + pub fn run_script_compiled(&self, compiled: TypedNode) -> Result { + let linked = self.link(compiled); + let func = self.instantiate(linked); + let res = (func.func)(&[]); + self.run_pipeline(); + Ok(res) + } + + pub fn run_debug(&self, source: &str) -> Result<(Result, Vec), String> { + self.preload_dependencies(source, None)?; + let compiled = self.compile(source).into_result()?; + let linked = self.link(compiled); + + let mut vm = VM::new(self.root_values.clone()); + let mut observer = TracingObserver::new(); + + // 1. Run the script wrapper (returns a closure representing the script) + let result = vm.run_with_observer(&mut observer, &linked); + // 2. Execute the root closure immediately to get the actual script result. + // All Myc scripts are wrapped in a parameterless lambda for consistency. + let mut final_result = result; + if let Ok(Value::Object(obj)) = &final_result + && let Some(_closure) = obj.as_any().downcast_ref::() + { + final_result = vm.run_with_args_observed(&mut observer, obj.clone(), &[]); + } + + self.run_pipeline(); + + Ok((final_result, observer.logs)) + } +} diff --git a/src/ast/nodes.rs b/src/ast/nodes.rs index 2a2c7f3..3e5b1a1 100644 --- a/src/ast/nodes.rs +++ b/src/ast/nodes.rs @@ -30,15 +30,14 @@ impl From<&str> for Symbol { } } -/// A generic AST Node wrapper to preserve identity and metadata +/// A parser AST Node wrapper to preserve identity and metadata #[derive(Debug, Clone, PartialEq)] -pub struct Node { +pub struct UntypedNode { pub identity: Identity, - pub kind: K, - pub ty: T, + pub kind: UntypedKind, } -impl Object for Node { +impl Object for UntypedNode { fn type_name(&self) -> &'static str { "ast-node" } @@ -59,7 +58,7 @@ impl Clone for Box { } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub enum UntypedKind { Nop, Constant(Value), @@ -68,62 +67,68 @@ pub enum UntypedKind { /// A first-class field accessor (e.g. .name) FieldAccessor(crate::ast::types::Keyword), If { - cond: Box>, - then_br: Box>, - else_br: Option>>, + cond: Box, + then_br: Box, + else_br: Option>, }, Def { - target: Box>, - value: Box>, + target: Box, + value: Box, }, Assign { - target: Box>, - value: Box>, + target: Box, + value: Box, }, Lambda { - params: Box>, - body: Rc>, + params: Box, + body: Rc, }, Call { - callee: Box>, - args: Box>, + callee: Box, + args: Box, }, Again { - args: Box>, + args: Box, }, Pipe { - inputs: Vec>, - lambda: Box>, + inputs: Vec, + lambda: Box, }, Block { - exprs: Vec>, + exprs: Vec, }, Tuple { - elements: Vec>, + elements: Vec, }, Record { - fields: Vec<(Node, Node)>, + fields: Vec<(UntypedNode, UntypedNode)>, }, /// A macro declaration that can be expanded at compile time. MacroDecl { name: Symbol, - params: Box>, - body: Box>, + params: Box, + body: Box, }, /// A template for AST nodes, allowing for substitutions. (Quasiquote) - Template(Box>), + Template(Box), /// A placeholder inside a template to be replaced by a node or value. (Unquote) - Placeholder(Box>), + Placeholder(Box), /// A placeholder that flattens a sequence or merges a record into its surroundings. (Splice) - Splice(Box>), + Splice(Box), /// Represents an expanded macro call, preserving the original call for debugging. Expansion { /// The original call from the source AST. - call: Box>, + call: Box, /// The resulting AST after macro expansion. - expanded: Box>, + 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 2e56028..1017f0b 100644 --- a/src/ast/parser.rs +++ b/src/ast/parser.rs @@ -1,6 +1,6 @@ use crate::ast::diagnostics::Diagnostics; use crate::ast::lexer::{Lexer, Token, TokenKind}; -use crate::ast::nodes::{Node, Symbol, UntypedKind}; +use crate::ast::nodes::{Symbol, UntypedKind, UntypedNode}; use crate::ast::types::{Identity, Keyword, NodeIdentity, Value}; use std::rc::Rc; @@ -50,7 +50,7 @@ impl<'a> Parser<'a> { &self.current_token.kind } - pub fn parse_expression(&mut self) -> Node { + pub fn parse_expression(&mut self) -> UntypedNode { let token_loc = self.current_token.location; let identity = NodeIdentity::new(token_loc); @@ -61,28 +61,28 @@ impl<'a> Parser<'a> { TokenKind::Quote => { self.advance(); // consume ' let expr = self.parse_expression(); - Node { + UntypedNode { identity: identity.clone(), kind: UntypedKind::Call { callee: Box::new(self.make_id_node("quote", identity.clone())), - args: Box::new(Node { + args: Box::new(UntypedNode { identity, kind: UntypedKind::Tuple { elements: vec![expr], }, - ty: (), + }), }, - ty: (), + } } TokenKind::Backtick => { self.advance(); // consume ` let expr = self.parse_expression(); - Node { + UntypedNode { identity, kind: UntypedKind::Template(Box::new(expr)), - ty: (), + } } TokenKind::Tilde => { @@ -90,17 +90,17 @@ impl<'a> Parser<'a> { if *self.peek() == TokenKind::At { self.advance(); // consume @ let expr = self.parse_expression(); - Node { + UntypedNode { identity, kind: UntypedKind::Splice(Box::new(expr)), - ty: (), + } } else { let expr = self.parse_expression(); - Node { + UntypedNode { identity, kind: UntypedKind::Placeholder(Box::new(expr)), - ty: (), + } } } @@ -126,7 +126,7 @@ impl<'a> Parser<'a> { } } - fn parse_atom(&mut self) -> Node { + fn parse_atom(&mut self) -> UntypedNode { let token = self.advance(); let identity = NodeIdentity::new(token.location); @@ -152,14 +152,13 @@ impl<'a> Parser<'a> { } }; - Node { + UntypedNode { identity, kind, - ty: (), } } - fn parse_list(&mut self) -> Node { + fn parse_list(&mut self) -> UntypedNode { let start_loc = self.advance().location; // consume '(' let identity = NodeIdentity::new(start_loc); @@ -169,10 +168,10 @@ impl<'a> Parser<'a> { Some(identity.clone()), ); self.advance(); // consume ) - return Node { + return UntypedNode { identity, kind: UntypedKind::Error, - ty: (), + }; } @@ -198,29 +197,29 @@ impl<'a> Parser<'a> { node } - fn parse_again(&mut self, identity: Identity) -> 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 = Node { + let args_node = UntypedNode { identity: identity.clone(), kind: UntypedKind::Tuple { elements }, - ty: (), + }; - Node { + UntypedNode { identity, kind: UntypedKind::Again { args: Box::new(args_node), }, - ty: (), + } } - fn parse_if(&mut self, identity: Identity) -> 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; @@ -229,53 +228,53 @@ impl<'a> Parser<'a> { else_br = Some(Box::new(self.parse_expression())); } - Node { + UntypedNode { identity, kind: UntypedKind::If { cond, then_br, else_br, }, - ty: (), + } } - fn parse_def(&mut self, identity: Identity) -> Node { + fn parse_def(&mut self, identity: Identity) -> UntypedNode { let target = Box::new(self.parse_pattern()); let value = Box::new(self.parse_expression()); - Node { + UntypedNode { identity, kind: UntypedKind::Def { target, value }, - ty: (), + } } - fn parse_assign(&mut self, identity: Identity) -> Node { + 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()); - Node { + UntypedNode { identity, kind: UntypedKind::Assign { target, value }, - ty: (), + } } - fn parse_do(&mut self, identity: Identity) -> Node { + 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()); } - Node { + UntypedNode { identity, kind: UntypedKind::Block { exprs }, - ty: (), + } } - fn parse_pipe(&mut self, identity: Identity) -> Node { + fn parse_pipe(&mut self, identity: Identity) -> UntypedNode { let inputs_node = self.parse_expression(); let inputs = match inputs_node.kind { UntypedKind::Tuple { elements } => elements, @@ -283,28 +282,28 @@ impl<'a> Parser<'a> { }; let lambda = Box::new(self.parse_expression()); - Node { + UntypedNode { identity, kind: UntypedKind::Pipe { inputs, lambda }, - ty: (), + } } - fn parse_fn(&mut self, identity: Identity) -> Node { + fn parse_fn(&mut self, identity: Identity) -> UntypedNode { let params = Box::new(self.parse_param_vector()); let body = self.parse_expression(); - Node { + UntypedNode { identity, kind: UntypedKind::Lambda { params, body: Rc::new(body), }, - ty: (), + } } - fn parse_macro_decl(&mut self, identity: Identity) -> Node { + 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, @@ -318,18 +317,18 @@ impl<'a> Parser<'a> { }; let params = Box::new(self.parse_param_vector()); let body = self.parse_expression(); - Node { + UntypedNode { identity, kind: UntypedKind::MacroDecl { name, params, body: Box::new(body), }, - ty: (), + } } - fn parse_param_vector(&mut self) -> Node { + fn parse_param_vector(&mut self) -> UntypedNode { if *self.peek() != TokenKind::LeftBracket { self.diagnostics.push_error( format!( @@ -338,16 +337,16 @@ impl<'a> Parser<'a> { ), Some(NodeIdentity::new(self.current_token.location)), ); - return Node { + return UntypedNode { identity: NodeIdentity::new(self.current_token.location), kind: UntypedKind::Error, - ty: (), + }; } self.parse_pattern() } - fn parse_pattern(&mut self) -> Node { + fn parse_pattern(&mut self) -> UntypedNode { let next = self.peek(); match next { TokenKind::Identifier(_) => { @@ -356,10 +355,10 @@ impl<'a> Parser<'a> { TokenKind::Identifier(s) => s.into(), _ => unreachable!(), }; - Node { + UntypedNode { identity: NodeIdentity::new(token.location), kind: UntypedKind::Identifier(sym), - ty: (), + } } TokenKind::LeftBracket => { @@ -372,10 +371,10 @@ impl<'a> Parser<'a> { } self.expect(TokenKind::RightBracket); - Node { + UntypedNode { identity, kind: UntypedKind::Tuple { elements }, - ty: (), + } } TokenKind::Tilde => { @@ -383,17 +382,17 @@ impl<'a> Parser<'a> { if *self.peek() == TokenKind::At { self.advance(); // consume @ let expr = self.parse_expression(); - Node { + UntypedNode { identity: NodeIdentity::new(token.location), kind: UntypedKind::Splice(Box::new(expr)), - ty: (), + } } else { let expr = self.parse_expression(); - Node { + UntypedNode { identity: NodeIdentity::new(token.location), kind: UntypedKind::Placeholder(Box::new(expr)), - ty: (), + } } } @@ -406,16 +405,16 @@ impl<'a> Parser<'a> { Some(NodeIdentity::new(self.current_token.location)), ); self.advance(); - Node { + UntypedNode { identity: NodeIdentity::new(self.current_token.location), kind: UntypedKind::Error, - ty: (), + } } } } - fn parse_call(&mut self, callee: Node, identity: Identity) -> Node { + fn parse_call(&mut self, callee: UntypedNode, identity: Identity) -> UntypedNode { let mut elements = Vec::new(); while *self.peek() != TokenKind::RightParen && *self.peek() != TokenKind::EOF { @@ -423,23 +422,23 @@ impl<'a> Parser<'a> { } // The arguments are wrapped in a Tuple node, reusing the call's identity/location. - let args_node = Node { + let args_node = UntypedNode { identity: identity.clone(), kind: UntypedKind::Tuple { elements }, - ty: (), + }; - Node { + UntypedNode { identity, kind: UntypedKind::Call { callee: Box::new(callee), args: Box::new(args_node), }, - ty: (), + } } - fn parse_vector_literal(&mut self) -> Node { + fn parse_vector_literal(&mut self) -> UntypedNode { let token = self.advance(); let mut elements = Vec::new(); @@ -450,14 +449,14 @@ impl<'a> Parser<'a> { self.expect(TokenKind::RightBracket); - Node { + UntypedNode { identity: NodeIdentity::new(token.location), kind: UntypedKind::Tuple { elements }, - ty: (), + } } - fn parse_record_literal(&mut self) -> Node { + fn parse_record_literal(&mut self) -> UntypedNode { let token = self.advance(); let mut fields = Vec::new(); @@ -489,10 +488,10 @@ impl<'a> Parser<'a> { } self.expect(TokenKind::RightBrace); - Node { + UntypedNode { identity: NodeIdentity::new(token.location), kind: UntypedKind::Record { fields }, - ty: (), + } } @@ -513,11 +512,11 @@ impl<'a> Parser<'a> { } } - fn make_id_node(&self, name: &str, identity: Identity) -> Node { - Node { + fn make_id_node(&self, name: &str, identity: Identity) -> UntypedNode { + UntypedNode { identity, kind: UntypedKind::Identifier(Symbol::from(name)), - ty: (), + } } } diff --git a/src/ast/vm.rs b/src/ast/vm.rs index 4c9e352..fbd3f5d 100644 --- a/src/ast/vm.rs +++ b/src/ast/vm.rs @@ -1,1063 +1,1063 @@ -use crate::ast::compiler::bound_nodes::{Address, AnalyzedNode, BoundKind}; -use crate::ast::compiler::lowering::ExecNode; -use crate::ast::nodes::Node; -use crate::ast::types::{Object, Value}; -use std::any::Any; -use std::cell::RefCell; -use std::rc::Rc; - -#[derive(Debug, Clone)] -pub struct Closure { - /// The analyzed parameter pattern. - pub parameter_node: Rc, - /// The analyzed body (before TCO). - pub function_node: Rc, - /// The executable node (after TCO). - pub exec_node: Rc, - pub upvalues: Vec>>, - pub positional_count: Option, - /// The number of stack slots required for this closure (calculated late). - pub stack_size: u32, -} - -impl Closure { - #[inline] - pub fn new( - params: Rc, - body: Rc, - exec: Rc, - upvalues: Vec>>, - positional_count: Option, - stack_size: u32, - ) -> Self { - Self { - parameter_node: params, - function_node: body, - exec_node: exec, - upvalues, - positional_count, - stack_size, - } - } -} - -impl Object for Closure { - fn type_name(&self) -> &'static str { - "closure" - } - fn as_any(&self) -> &dyn Any { - self - } -} - -#[derive(Debug)] -struct CallFrame { - stack_base: usize, - closure: Option>, -} - -pub trait VMObserver { - const ACTIVE: bool = false; - fn before_eval(&mut self, _vm: &VM, _node: &ExecNode) {} - fn after_eval(&mut self, _vm: &VM, _node: &ExecNode, _res: &Result) {} -} - -pub struct NoOpObserver; -impl VMObserver for NoOpObserver {} - -pub struct TracingObserver { - pub logs: Vec, - indent: usize, -} - -impl TracingObserver { - pub fn new() -> Self { - Self { - logs: Vec::new(), - indent: 0, - } - } - fn pad(&self) -> String { - "| ".repeat(self.indent) - } -} - -impl Default for TracingObserver { - fn default() -> Self { - Self::new() - } -} - -impl VMObserver for TracingObserver { - const ACTIVE: bool = true; - fn before_eval(&mut self, _vm: &VM, node: &ExecNode) { - let pad = self.pad(); - let metrics = &node.ty.original.ty; - self.logs.push(format!( - "{}{} [{} | P:{:?}{}]: {{", - pad, - node.kind.display_name(), - node.ty.ty, - metrics.purity, - if metrics.is_recursive { " | REC" } else { "" } - )); - self.indent += 1; - } - fn after_eval(&mut self, vm: &VM, node: &ExecNode, res: &Result) { - self.indent = self.indent.saturating_sub(1); - let pad = self.pad(); - match &node.kind { - BoundKind::Define { .. } | BoundKind::Set { .. } => { - let s_pad = format!("{}| ", pad); - self.logs.push(format!("{}--- Scope Status ---", s_pad)); - self.logs.push(format!( - "{}Stack (top 5): {:?}", - s_pad, - vm.stack.iter().rev().take(5).collect::>() - )); - } - _ => {} - } - let res_str = match res { - Ok(v) => format!("{}", v), - Err(e) => format!("ERROR: {}", e), - }; - self.logs.push(format!("{}}} -> {}", pad, res_str)); - } -} - -pub struct VM { - stack: Vec, - globals: Rc>>, - frames: Vec, -} - -impl VM { - pub fn new(globals: Rc>>) -> Self { - Self { - stack: Vec::new(), - globals, - frames: Vec::new(), - } - } - - pub fn run(&mut self, root: &ExecNode) -> Result { - self.run_with_observer(&mut NoOpObserver, root) - } - - pub fn resolve_tail_calls( - &mut self, - observer: &mut O, - mut result: Result, - ) -> Result { - loop { - match result { - Ok(Value::TailCallRequest(payload)) => { - let (next_obj, next_args) = *payload; - if let Some(closure) = next_obj.as_any().downcast_ref::() { - self.stack.clear(); - // frames should be empty here since we popped before entering the loop - self.frames.push(CallFrame { - stack_base: 0, - closure: Some(next_obj.clone()), - }); - if let Some(count) = closure.positional_count - && next_args.len() == count as usize - { - self.stack.extend(next_args); - } else { - self.unpack(&closure.parameter_node, &next_args, &mut 0)?; - } - - // PRE-ALLOCATION - let current_stack = self.stack.len() as u32; - if closure.stack_size > current_stack { - self.stack.resize(closure.stack_size as usize, Value::Void); - } - - result = self.eval_observed(observer, &closure.exec_node); - self.frames.pop(); - } else if let Some(series) = next_obj.as_series() { - if next_args.len() != 1 { - return Err(format!( - "{} indexer expects exactly 1 argument (the lookback index), got {}", - next_obj.type_name(), - next_args.len() - )); - } - if let Value::Int(idx) = &next_args[0] { - if *idx < 0 { - return Err(format!( - "{} lookback index cannot be negative: {}", - next_obj.type_name(), - idx - )); - } - result = Ok(series.get_item(*idx as usize).unwrap_or(Value::Void)); - } else { - return Err(format!( - "{} index must be an integer, got {}", - next_obj.type_name(), - next_args[0] - )); - } - } else { - return Err(format!( - "Tail call target is not callable: {}", - next_obj.type_name() - )); - } - } - _ => return result, - } - } - } - - pub fn run_with_args( - &mut self, - closure_obj: Rc, - args: &[Value], - ) -> Result { - self.run_with_args_observed(&mut NoOpObserver, closure_obj, args) - } - - pub fn run_with_args_observed( - &mut self, - observer: &mut O, - closure_obj: Rc, - args: &[Value], - ) -> Result { - let closure = closure_obj - .as_any() - .downcast_ref::() - .ok_or_else(|| "Object is not a closure".to_string())?; - - self.stack.clear(); - self.frames.clear(); - - if let Some(count) = closure.positional_count - && args.len() == count as usize - { - self.stack.extend_from_slice(args); - } else { - self.unpack(&closure.parameter_node, args, &mut 0)?; - } - - // Fill remaining stack slots with Void - let current_stack = self.stack.len() as u32; - if closure.stack_size > current_stack { - self.stack.resize(closure.stack_size as usize, Value::Void); - } - - self.frames.push(CallFrame { - stack_base: 0, - closure: Some(closure_obj.clone()), - }); - - let result = self.eval_internal(observer, &closure.exec_node); - self.frames.pop(); - self.resolve_tail_calls(observer, result) - } - - pub fn run_with_observer( - &mut self, - observer: &mut O, - root: &ExecNode, - ) -> Result { - self.stack.clear(); - self.frames.clear(); - - self.stack.resize(root.ty.stack_size as usize, Value::Void); - - self.frames.push(CallFrame { - stack_base: 0, - closure: None, - }); - let result = self.eval_internal(observer, root); - self.frames.pop(); - self.resolve_tail_calls(observer, result) - } - - fn eval_observed( - &mut self, - observer: &mut O, - node: &ExecNode, - ) -> Result { - self.eval_internal(observer, node) - } - - #[inline(always)] - fn eval_internal( - &mut self, - obs: &mut O, - node: &ExecNode, - ) -> Result { - if O::ACTIVE { - obs.before_eval(self, node); - let result = self.eval_core(obs, node); - obs.after_eval(self, node, &result); - result - } else { - self.eval_core(obs, node) - } - } - - #[inline(always)] - fn eval_core(&mut self, obs: &mut O, node: &ExecNode) -> Result { - match &node.kind { - BoundKind::Nop => Ok(Value::Void), - BoundKind::Constant(v) => Ok(v.clone()), - BoundKind::Define { - addr, - value, - captured_by, - .. - } => { - let val = self.eval_internal(obs, value)?; - - let mut needs_cell_wrap = false; - if !captured_by.is_empty() - && let Address::Local(slot) = addr - { - let frame = self.frames.last().unwrap(); - let abs_index = frame.stack_base + (slot.0 as usize); - - // Robustness Fix: If the slot is already a Cell (due to forward capture - // in a recursive scenario or complex pre-allocation), don't wrap it again. - // This prevents Cell(Cell(Value)) nesting which causes type errors. - if abs_index >= self.stack.len() || !matches!(self.stack[abs_index], Value::Cell(_)) { - needs_cell_wrap = true; - } - } - - let store_val = if needs_cell_wrap { - Value::Cell(Rc::new(RefCell::new(val.clone()))) - } else { - val.clone() - }; - - self.set_value(*addr, store_val)?; - // Define always evaluates to the unwrapped value for immediate use. - Ok(val) - } - BoundKind::Destructure { pattern, value } => { - let val = self.eval_internal(obs, value)?; - let mut offset = 0; - - // Destructuring works on tuples/vectors, or single values wrapped in a slice - if let Some(vals) = val.as_slice() { - self.unpack(pattern, vals, &mut offset)?; - } else { - self.unpack(pattern, std::slice::from_ref(&val), &mut offset)?; - } - Ok(val) - } - BoundKind::Get { addr, .. } => self.get_value(*addr), - - BoundKind::FieldAccessor(k) => Ok(Value::FieldAccessor(*k)), - - BoundKind::GetField { rec, field } => { - let rec_val = self.eval_internal(obs, rec)?; - - // In Rust, pattern matching (`match`) is the idiomatic way to handle variants safely. - // Previously, this only handled `Value::Record`. Now, we handle objects (like `RecordSeries`) polymorphically. - match rec_val { - // Case 1: The classic Record. - // This is a struct-like tuple containing an Arc and a Vec. - Value::Record(layout, values) => { - if let Some(idx) = layout.index_of(*field) { - Ok(values[idx].clone()) - } else { - Err(format!("Record does not have field :{}", field.name())) - } - } - - // Case 2: A dynamic Object (our SoA / Struct-of-Arrays optimization). - // `Value::Object` holds an `Rc` - a reference-counted trait object (type-erased). - Value::Object(obj) => { - // 1. We get the raw `&dyn Any` reference (Rust's standard mechanism for runtime type reflection). - let any_ptr = obj.as_any(); - - // 2. Downcast! We check at runtime if the pointer actually points to a `RecordSeries`. - // `downcast_ref` is very fast (essentially an O(1) type ID comparison under the hood). - if let Some(record_series) = - any_ptr.downcast_ref::() - { - // 3. We call our highly performant 0-copy method on the series. - // It returns an `Rc>`, which is a shared pointer - // to the concrete column array (e.g., a `ScalarSeries`). - if let Some(field_series) = record_series.field(*field) { - // 4. We wrap this RefCell in our `SeriesView` struct. - // The `SeriesView` acts as a pure `Object` for the VM, holding the reference. - // CRITICAL: No array elements are copied here! This is pure, fast pointer juggling. - // This single operation turns a SoA `RecordSeries` into a high-speed `FloatSeries` view. - let view = - crate::ast::rtl::series::SeriesView::new(field_series, *field); - return Ok(Value::Object(std::rc::Rc::new(view))); - } else { - return Err(format!( - "RecordSeries does not have field :{}", - field.name() - )); - } - } else if let Some(sn) = obj.as_any().downcast_ref::() { - let mapped = crate::ast::rtl::streams::build_map_stream(sn.inner.clone(), *field); - return Ok(Value::Object(Rc::new(mapped))); - } - - // Fallback if it's another type of object that is not a RecordSeries. - Err(format!( - "Attempt to access field on non-record object: {}", - obj.type_name() - )) - } - - // Error handling for primitives (Int, Float, etc.). - _ => Err(format!( - "Attempt to access field on non-record: {}", - rec_val - )), - } - } - - BoundKind::Set { addr, value } => { - let val = self.eval_internal(obs, value)?; - self.set_value(*addr, val.clone())?; - Ok(val) - } - BoundKind::If { - cond, - then_br, - else_br, - } => { - let c = self.eval_internal(obs, cond)?; - if c.is_truthy() { - self.eval_internal(obs, then_br) - } else if let Some(e) = else_br { - self.eval_internal(obs, e) - } else { - Ok(Value::Void) - } - } - BoundKind::Pipe { - inputs, - lambda, - out_type, - } => { - use crate::ast::rtl::streams::StreamNode; - - let mut obs_streams = Vec::new(); - for input in inputs { - let val = self.eval_internal(obs, input)?; - if let Value::Object(obj) = val { - if let Some(s) = obj.as_any().downcast_ref::() { - obs_streams.push(s.inner.clone()); - } else { - return Err(format!( - "Pipe input must be a stream, found {}", - obj.type_name() - )); - } - } else { - return Err("Pipe input must be an object (stream)".to_string()); - } - } - - let lambda_val = self.eval_internal(obs, lambda)?; - - // Create the persistent execution closure for the PipeStream - let mut pipe_vm = crate::ast::vm::VM::new(self.globals.clone()); - - let executor: Box = match lambda_val { - Value::Object(obj) => { - let my_closure = obj.clone(); - Box::new(move |args: &[Value]| -> Value { - match pipe_vm.run_with_args(my_closure.clone(), args) { - Ok(res) => res, - Err(e) => panic!("Pipeline lambda execution failed: {}", e), - } - }) - } - Value::Function(func) => { - let my_func = func.clone(); - Box::new(move |args: &[Value]| -> Value { - (my_func.func)(args) - }) - } - _ => return Err("Pipe lambda must be a function/closure".to_string()), - }; - - // Delegate to the RTL Factory for specialized buffer instantiation - let node = - crate::ast::rtl::streams::build_pipeline_node(obs_streams, executor, out_type); - - Ok(Value::Object(node)) - } - BoundKind::Block { exprs } => { - let mut last = Value::Void; - for e in exprs { - last = self.eval_internal(obs, e)?; - } - Ok(last) - } - BoundKind::Lambda { - params, - upvalues, - body, - positional_count, - } => { - let mut captured = Vec::with_capacity(upvalues.len()); - for addr in upvalues { - captured.push(self.capture_upvalue(*addr)?); - } - let stack_size = node.ty.stack_size; - let closure = Closure::new( - params.ty.original.clone(), - body.ty.original.clone(), - body.clone(), - captured, - *positional_count, - stack_size, - ); - Ok(Value::Object(Rc::new(closure))) - } - BoundKind::Call { callee, args } => { - let func_val = self.eval_internal(obs, callee)?; - - let base = self.stack.len(); - if let Err(e) = self.eval_args_to_stack(obs, args) { - self.stack.truncate(base); - return Err(e); - } - - if node.ty.is_tail { - let arg_vals = self.stack[base..].to_vec(); - self.stack.truncate(base); - - match func_val { - Value::Object(obj) => { - return Ok(Value::TailCallRequest(Box::new((obj, arg_vals)))); - } - Value::Function(f) => return Ok((f.func)(&arg_vals)), - Value::FieldAccessor(k) => { - if arg_vals.len() != 1 { - return Err(format!( - "Field accessor .{} expects exactly 1 argument, got {}", - k.name(), - arg_vals.len() - )); - } - let rec = &arg_vals[0]; - if let Value::Record(layout, values) = rec { - if let Some(idx) = layout.index_of(k) { - return Ok(values[idx].clone()); - } else { - return Err(format!( - "Record does not have field :{}", - k.name() - )); - } - } else if let Value::Object(obj) = rec { - // Polymorphic Field Access: Allow `.field` on a RecordSeries - if let Some(rs) = obj - .as_any() - .downcast_ref::() - { - if let Some(field_series) = rs.field(k) { - let view = crate::ast::rtl::series::SeriesView::new( - field_series, - k, - ); - return Ok(Value::Object(std::rc::Rc::new(view))); - } else { - return Err(format!( - "RecordSeries does not have field :{}", - k.name() - )); - } - } else if let Some(sn) = obj.as_any().downcast_ref::() { - let mapped = crate::ast::rtl::streams::build_map_stream(sn.inner.clone(), k); - return Ok(Value::Object(Rc::new(mapped))); - } - return Err(format!( - "Field accessor .{} expects a record, RecordSeries or Stream, got {}", - k.name(), - obj.type_name() - )); - } else { - return Err(format!( - "Field accessor .{} expects a record, RecordSeries or Stream, got {}", - k.name(), - rec - )); - } - } - - _ => { - return Err(format!( - "Tail call target is not a function: {}", - func_val - )); - } - } - } - - // Standard Call Path - let mut current_func = func_val; - loop { - let result = match ¤t_func { - Value::Function(f) => { - let res = (f.func)(&self.stack[base..]); - self.stack.truncate(base); - return Ok(res); - } - Value::FieldAccessor(k) => { - let arg_len = self.stack.len() - base; - let res = if arg_len != 1 { - Err(format!( - "Field accessor .{} expects exactly 1 argument, got {}", - k.name(), - arg_len - )) - } else { - let rec = &self.stack[base]; - if let Value::Record(layout, values) = rec { - if let Some(idx) = layout.index_of(*k) { - Ok(values[idx].clone()) - } else { - Err(format!("Record does not have field :{}", k.name())) - } - } else if let Value::Object(obj) = rec { - if let Some(rs) = obj - .as_any() - .downcast_ref::() - { - if let Some(field_series) = rs.field(*k) { - let view = crate::ast::rtl::series::SeriesView::new( - field_series, - *k, - ); - Ok(Value::Object(std::rc::Rc::new(view))) - } else { - Err(format!( - "RecordSeries does not have field :{}", - k.name() - )) - } - } else if let Some(sn) = obj.as_any().downcast_ref::() { - let mapped = crate::ast::rtl::streams::build_map_stream(sn.inner.clone(), *k); - Ok(Value::Object(Rc::new(mapped))) - } else { - Err(format!( - "Field accessor .{} expects a record, RecordSeries or Stream, got {}", - k.name(), - obj.type_name() - )) - } - } else { - Err(format!( - "Field accessor .{} expects a record, RecordSeries or Stream, got {}", - k.name(), - rec - )) - } - }; - self.stack.truncate(base); - return res; - } - Value::Object(obj) => { - if let Some(closure) = obj.as_any().downcast_ref::() { - self.frames.push(CallFrame { - stack_base: base, - closure: Some(obj.clone()), - }); - - let unpack_res = if let Some(count) = closure.positional_count - && (self.stack.len() - base) == count as usize - { - Ok(()) - } else { - let args_for_unpack = self.stack[base..].to_vec(); - self.stack.truncate(base); - if let BoundKind::Tuple { elements } = - &closure.parameter_node.kind - { - let mut offset = 0; - let mut res = Ok(()); - for el in elements { - if let Err(e) = - self.unpack(el, &args_for_unpack, &mut offset) - { - res = Err(e); - break; - } - } - res - } else { - self.unpack(&closure.parameter_node, &args_for_unpack, &mut 0) - } - }; - - let res = match unpack_res { - Ok(_) => { - // PRE-ALLOCATION - let current_stack = (self.stack.len() - base) as u32; - if closure.stack_size > current_stack { - self.stack.resize(base + closure.stack_size as usize, Value::Void); - } - self.eval_internal(obs, &closure.exec_node) - } - Err(e) => Err(e), - }; - - self.frames.pop(); - res - } else if let Some(series) = obj.as_series() { - let arg_len = self.stack.len() - base; - let res = if arg_len != 1 { - Err(format!( - "{} indexer expects exactly 1 argument (the lookback index)", - obj.type_name() - )) - } else if let Value::Int(idx) = self.stack[base] { - if idx < 0 { - Err(format!( - "{} lookback index cannot be negative", - obj.type_name() - )) - } else if let Some(val) = series.get_item(idx as usize) { - Ok(val) - } else { - Ok(Value::Void) - } - } else { - Err(format!( - "{} index must be an integer", - obj.type_name() - )) - }; - self.stack.truncate(base); - return res; - } else { - self.stack.truncate(base); - return Err(format!("Object is not callable: {}", obj.type_name())); - } - } - _ => { - self.stack.truncate(base); - let variant_name = match current_func { - Value::Void => "Void", - Value::Bool(_) => "Bool", - Value::Int(_) => "Int", - Value::Float(_) => "Float", - Value::DateTime(_) => "DateTime", - Value::Text(_) => "Text", - Value::Keyword(_) => "Keyword", - Value::Tuple(_) => "Tuple", - Value::Record(_, _) => "Record", - Value::FieldAccessor(_) => "FieldAccessor", - Value::Function(_) => "Function", - Value::Object(_) => "Object", - Value::Cell(_) => "Cell", - Value::TailCallRequest(_) => "TailCallRequest", - }; - return Err(format!("Attempt to call non-function: {} (Variant: {})", current_func, variant_name)); - } - }; - - match result { - Ok(Value::TailCallRequest(payload)) => { - let (next_obj, next_args) = *payload; - current_func = Value::Object(next_obj); - self.stack.truncate(base); - self.stack.extend(next_args); - continue; - } - res => { - self.stack.truncate(base); - return res; - } - } - } - } - BoundKind::Again { args } => { - let base = self.stack.len(); - if let Err(e) = self.eval_args_to_stack(obs, args) { - self.stack.truncate(base); - return Err(e); - } - let arg_vals = self.stack[base..].to_vec(); - self.stack.truncate(base); - - let frame = self.frames.last().ok_or("No call frame for 'again'")?; - if let Some(closure_obj) = &frame.closure { - Ok(Value::TailCallRequest(Box::new(( - closure_obj.clone(), - arg_vals, - )))) - } else { - Err("'again' called outside of a closure".to_string()) - } - } - BoundKind::Tuple { elements } => { - let mut vals = Vec::with_capacity(elements.len()); - for e in elements { - vals.push(self.eval_internal(obs, e)?); - } - Ok(Value::make_tuple(vals)) - } - BoundKind::Record { layout, values } => { - let mut evaluated_values = Vec::with_capacity(values.len()); - for v in values { - evaluated_values.push(self.eval_internal(obs, v)?); - } - Ok(Value::Record( - layout.clone(), - std::rc::Rc::new(evaluated_values), - )) - } - BoundKind::Expansion { bound_expanded, .. } => { - let mut curr = bound_expanded; - if !O::ACTIVE { - while let BoundKind::Expansion { - bound_expanded: next, - .. - } = &curr.kind - { - curr = next; - } - } - self.eval_internal(obs, curr) - } - BoundKind::Extension(ext) => Err(format!( - "Execution of extension '{}' not implemented yet", - ext.display_name() - )), - BoundKind::Error => Err("Cannot execute a poisoned AST node".to_string()), - } - } - - fn eval_args_to_stack( - &mut self, - obs: &mut O, - args: &ExecNode, - ) -> Result<(), String> { - match &args.kind { - BoundKind::Tuple { elements } => { - for e in elements { - let mut curr = e.as_ref(); - if !O::ACTIVE { - while let BoundKind::Expansion { - bound_expanded: next, - .. - } = &curr.kind - { - curr = next; - } - } - - match &curr.kind { - BoundKind::Constant(v) if !O::ACTIVE => self.stack.push(v.clone()), - _ => { - let val = self.eval_internal(obs, curr)?; - self.stack.push(val); - } - } - } - Ok(()) - } - BoundKind::Constant(v) => { - if let Some(slice) = v.as_slice() { - self.stack.extend_from_slice(slice); - } else { - self.stack.push(v.clone()); - } - Ok(()) - } - BoundKind::Expansion { bound_expanded, .. } => { - let mut curr = bound_expanded; - if !O::ACTIVE { - while let BoundKind::Expansion { - bound_expanded: next, - .. - } = &curr.kind - { - curr = next; - } - } - let val = self.eval_internal(obs, curr)?; - if let Some(slice) = val.as_slice() { - self.stack.extend_from_slice(slice); - } else { - self.stack.push(val); - } - Ok(()) - } - _ => { - let val = self.eval_internal(obs, args)?; - if let Some(slice) = val.as_slice() { - self.stack.extend_from_slice(slice); - } else { - self.stack.push(val); - } - Ok(()) - } - } - } - - fn capture_upvalue(&mut self, addr: Address) -> Result>, String> { - match addr { - Address::Local(slot) => { - let frame = self.frames.last().ok_or("No call frame")?; - let abs_index = frame.stack_base + (slot.0 as usize); - if abs_index < self.stack.len() { - if let Value::Cell(cell) = &self.stack[abs_index] { - Ok(cell.clone()) - } else { - let val = self.stack[abs_index].clone(); - let cell = Rc::new(RefCell::new(val)); - self.stack[abs_index] = Value::Cell(cell.clone()); - Ok(cell) - } - } else { - Err(format!("Stack access out of bounds: trying to capture local {} at index {} but stack size is only {}", slot, abs_index, self.stack.len())) - } - } - Address::Upvalue(idx) => { - let frame = self.frames.last().ok_or("No call frame")?; - if let Some(closure_obj) = &frame.closure { - let closure = closure_obj.as_any().downcast_ref::().unwrap(); - let u_idx = idx.0 as usize; - if u_idx < closure.upvalues.len() { - Ok(closure.upvalues[u_idx].clone()) - } else { - Err(format!("Upvalue access out of bounds capture {}", idx)) - } - } else { - Err("Current frame has no closure".to_string()) - } - } - Address::Global(_) => Err("Cannot capture global directly".to_string()), - } - } - - fn get_value(&self, addr: Address) -> Result { - match addr { - Address::Local(slot) => { - let frame = self.frames.last().ok_or("No call frame")?; - let abs_index = frame.stack_base + (slot.0 as usize); - if abs_index < self.stack.len() { - match &self.stack[abs_index] { - Value::Cell(cell) => Ok(cell.borrow().clone()), - val => Ok(val.clone()), - } - } else { - Err(format!("Stack underflow access local {}", slot)) - } - } - Address::Global(idx) => { - let g_idx = idx.0 as usize; - let globals = self.globals.borrow(); - if g_idx < globals.len() { - Ok(globals[g_idx].clone()) - } else { - Err(format!("Global access out of bounds {}", idx)) - } - } - Address::Upvalue(idx) => { - let frame = self.frames.last().ok_or("No call frame")?; - if let Some(closure_obj) = &frame.closure { - let closure = closure_obj.as_any().downcast_ref::().unwrap(); - let u_idx = idx.0 as usize; - if u_idx < closure.upvalues.len() { - Ok(closure.upvalues[u_idx].borrow().clone()) - } else { - Err(format!("Upvalue access out of bounds {}", idx)) - } - } else { - Err("Current frame has no closure (cannot access upvalues)".to_string()) - } - } - } - } - - fn set_value(&mut self, addr: Address, value: Value) -> Result<(), String> { - match addr { - Address::Local(slot) => { - let frame = self.frames.last().ok_or("No call frame")?; - let abs_index = frame.stack_base + (slot.0 as usize); - if abs_index < self.stack.len() { - if let Value::Cell(cell) = &self.stack[abs_index] { - *cell.borrow_mut() = value; - } else { - self.stack[abs_index] = value; - } - } else if abs_index == self.stack.len() { - self.stack.push(value); - } else { - return Err(format!("Stack gap write local {}", slot)); - } - Ok(()) - } - Address::Global(idx) => { - let g_idx = idx.0 as usize; - let mut globals = self.globals.borrow_mut(); - if g_idx >= globals.len() { - globals.resize(g_idx + 1, Value::Void); - } - globals[g_idx] = value; - Ok(()) - } - Address::Upvalue(idx) => { - let frame = self.frames.last().ok_or("No call frame")?; - if let Some(closure_obj) = &frame.closure { - let closure = closure_obj.as_any().downcast_ref::().unwrap(); - let u_idx = idx.0 as usize; - if u_idx < closure.upvalues.len() { - *closure.upvalues[u_idx].borrow_mut() = value; - Ok(()) - } else { - Err(format!("Upvalue assignment out of bounds {}", idx)) - } - } else { - Err("Current frame has no closure".to_string()) - } - } - } - } - - fn unpack( - &mut self, - pattern: &Node, T>, - values: &[Value], - offset: &mut usize, - ) -> Result<(), String> { - match &pattern.kind { - BoundKind::Define { addr, .. } => { - let val = values.get(*offset).cloned().unwrap_or(Value::Void); - *offset += 1; - self.set_value(*addr, val) - } - BoundKind::Set { addr, .. } => { - let val = values.get(*offset).cloned().unwrap_or(Value::Void); - *offset += 1; - self.set_value(*addr, val) - } - BoundKind::Tuple { elements } => { - if let Some(sub_values) = values.get(*offset).and_then(|v| v.as_slice()) { - *offset += 1; - let mut sub_offset = 0; - for el in elements { - self.unpack(el, sub_values, &mut sub_offset)?; - } - return Ok(()); - } - for el in elements { - self.unpack(el, values, offset)?; - } - Ok(()) - } - _ => Err("Invalid node in parameter pattern".to_string()), - } - } -} +use crate::ast::compiler::bound_nodes::{Address, AnalyzedNode, BoundKind}; +use crate::ast::compiler::lowering::ExecNode; +use crate::ast::compiler::bound_nodes::Node; +use crate::ast::types::{Object, Value}; +use std::any::Any; +use std::cell::RefCell; +use std::rc::Rc; + +#[derive(Debug, Clone)] +pub struct Closure { + /// The analyzed parameter pattern. + pub parameter_node: Rc, + /// The analyzed body (before TCO). + pub function_node: Rc, + /// The executable node (after TCO). + pub exec_node: Rc, + pub upvalues: Vec>>, + pub positional_count: Option, + /// The number of stack slots required for this closure (calculated late). + pub stack_size: u32, +} + +impl Closure { + #[inline] + pub fn new( + params: Rc, + body: Rc, + exec: Rc, + upvalues: Vec>>, + positional_count: Option, + stack_size: u32, + ) -> Self { + Self { + parameter_node: params, + function_node: body, + exec_node: exec, + upvalues, + positional_count, + stack_size, + } + } +} + +impl Object for Closure { + fn type_name(&self) -> &'static str { + "closure" + } + fn as_any(&self) -> &dyn Any { + self + } +} + +#[derive(Debug)] +struct CallFrame { + stack_base: usize, + closure: Option>, +} + +pub trait VMObserver { + const ACTIVE: bool = false; + fn before_eval(&mut self, _vm: &VM, _node: &ExecNode) {} + fn after_eval(&mut self, _vm: &VM, _node: &ExecNode, _res: &Result) {} +} + +pub struct NoOpObserver; +impl VMObserver for NoOpObserver {} + +pub struct TracingObserver { + pub logs: Vec, + indent: usize, +} + +impl TracingObserver { + pub fn new() -> Self { + Self { + logs: Vec::new(), + indent: 0, + } + } + fn pad(&self) -> String { + "| ".repeat(self.indent) + } +} + +impl Default for TracingObserver { + fn default() -> Self { + Self::new() + } +} + +impl VMObserver for TracingObserver { + const ACTIVE: bool = true; + fn before_eval(&mut self, _vm: &VM, node: &ExecNode) { + let pad = self.pad(); + let metrics = &node.ty.original.ty; + self.logs.push(format!( + "{}{} [{} | P:{:?}{}]: {{", + pad, + node.kind.display_name(), + node.ty.ty, + metrics.purity, + if metrics.is_recursive { " | REC" } else { "" } + )); + self.indent += 1; + } + fn after_eval(&mut self, vm: &VM, node: &ExecNode, res: &Result) { + self.indent = self.indent.saturating_sub(1); + let pad = self.pad(); + match &node.kind { + BoundKind::Define { .. } | BoundKind::Set { .. } => { + let s_pad = format!("{}| ", pad); + self.logs.push(format!("{}--- Scope Status ---", s_pad)); + self.logs.push(format!( + "{}Stack (top 5): {:?}", + s_pad, + vm.stack.iter().rev().take(5).collect::>() + )); + } + _ => {} + } + let res_str = match res { + Ok(v) => format!("{}", v), + Err(e) => format!("ERROR: {}", e), + }; + self.logs.push(format!("{}}} -> {}", pad, res_str)); + } +} + +pub struct VM { + stack: Vec, + globals: Rc>>, + frames: Vec, +} + +impl VM { + pub fn new(globals: Rc>>) -> Self { + Self { + stack: Vec::new(), + globals, + frames: Vec::new(), + } + } + + pub fn run(&mut self, root: &ExecNode) -> Result { + self.run_with_observer(&mut NoOpObserver, root) + } + + pub fn resolve_tail_calls( + &mut self, + observer: &mut O, + mut result: Result, + ) -> Result { + loop { + match result { + Ok(Value::TailCallRequest(payload)) => { + let (next_obj, next_args) = *payload; + if let Some(closure) = next_obj.as_any().downcast_ref::() { + self.stack.clear(); + // frames should be empty here since we popped before entering the loop + self.frames.push(CallFrame { + stack_base: 0, + closure: Some(next_obj.clone()), + }); + if let Some(count) = closure.positional_count + && next_args.len() == count as usize + { + self.stack.extend(next_args); + } else { + self.unpack(closure.parameter_node.as_ref(), &next_args, &mut 0)?; + } + + // PRE-ALLOCATION + let current_stack = self.stack.len() as u32; + if closure.stack_size > current_stack { + self.stack.resize(closure.stack_size as usize, Value::Void); + } + + result = self.eval_observed(observer, &closure.exec_node); + self.frames.pop(); + } else if let Some(series) = next_obj.as_series() { + if next_args.len() != 1 { + return Err(format!( + "{} indexer expects exactly 1 argument (the lookback index), got {}", + next_obj.type_name(), + next_args.len() + )); + } + if let Value::Int(idx) = &next_args[0] { + if *idx < 0 { + return Err(format!( + "{} lookback index cannot be negative: {}", + next_obj.type_name(), + idx + )); + } + result = Ok(series.get_item(*idx as usize).unwrap_or(Value::Void)); + } else { + return Err(format!( + "{} index must be an integer, got {}", + next_obj.type_name(), + next_args[0] + )); + } + } else { + return Err(format!( + "Tail call target is not callable: {}", + next_obj.type_name() + )); + } + } + _ => return result, + } + } + } + + pub fn run_with_args( + &mut self, + closure_obj: Rc, + args: &[Value], + ) -> Result { + self.run_with_args_observed(&mut NoOpObserver, closure_obj, args) + } + + pub fn run_with_args_observed( + &mut self, + observer: &mut O, + closure_obj: Rc, + args: &[Value], + ) -> Result { + let closure = closure_obj + .as_any() + .downcast_ref::() + .ok_or_else(|| "Object is not a closure".to_string())?; + + self.stack.clear(); + self.frames.clear(); + + if let Some(count) = closure.positional_count + && args.len() == count as usize + { + self.stack.extend_from_slice(args); + } else { + self.unpack(closure.parameter_node.as_ref(), args, &mut 0)?; + } + + // Fill remaining stack slots with Void + let current_stack = self.stack.len() as u32; + if closure.stack_size > current_stack { + self.stack.resize(closure.stack_size as usize, Value::Void); + } + + self.frames.push(CallFrame { + stack_base: 0, + closure: Some(closure_obj.clone()), + }); + + let result = self.eval_internal(observer, &closure.exec_node); + self.frames.pop(); + self.resolve_tail_calls(observer, result) + } + + pub fn run_with_observer( + &mut self, + observer: &mut O, + root: &ExecNode, + ) -> Result { + self.stack.clear(); + self.frames.clear(); + + self.stack.resize(root.ty.stack_size as usize, Value::Void); + + self.frames.push(CallFrame { + stack_base: 0, + closure: None, + }); + let result = self.eval_internal(observer, root); + self.frames.pop(); + self.resolve_tail_calls(observer, result) + } + + fn eval_observed( + &mut self, + observer: &mut O, + node: &ExecNode, + ) -> Result { + self.eval_internal(observer, node) + } + + #[inline(always)] + fn eval_internal( + &mut self, + obs: &mut O, + node: &ExecNode, + ) -> Result { + if O::ACTIVE { + obs.before_eval(self, node); + let result = self.eval_core(obs, node); + obs.after_eval(self, node, &result); + result + } else { + self.eval_core(obs, node) + } + } + + #[inline(always)] + fn eval_core(&mut self, obs: &mut O, node: &ExecNode) -> Result { + match &node.kind { + BoundKind::Nop => Ok(Value::Void), + BoundKind::Constant(v) => Ok(v.clone()), + BoundKind::Define { + addr, + value, + captured_by, + .. + } => { + let val = self.eval_internal(obs, value)?; + + let mut needs_cell_wrap = false; + if !captured_by.is_empty() + && let Address::Local(slot) = addr + { + let frame = self.frames.last().unwrap(); + let abs_index = frame.stack_base + (slot.0 as usize); + + // Robustness Fix: If the slot is already a Cell (due to forward capture + // in a recursive scenario or complex pre-allocation), don't wrap it again. + // This prevents Cell(Cell(Value)) nesting which causes type errors. + if abs_index >= self.stack.len() || !matches!(self.stack[abs_index], Value::Cell(_)) { + needs_cell_wrap = true; + } + } + + let store_val = if needs_cell_wrap { + Value::Cell(Rc::new(RefCell::new(val.clone()))) + } else { + val.clone() + }; + + self.set_value(*addr, store_val)?; + // Define always evaluates to the unwrapped value for immediate use. + Ok(val) + } + BoundKind::Destructure { pattern, value } => { + let val = self.eval_internal(obs, value)?; + let mut offset = 0; + + // Destructuring works on tuples/vectors, or single values wrapped in a slice + if let Some(vals) = val.as_slice() { + self.unpack(pattern.as_ref(), vals, &mut offset)?; + } else { + self.unpack(pattern.as_ref(), std::slice::from_ref(&val), &mut offset)?; + } + Ok(val) + } + BoundKind::Get { addr, .. } => self.get_value(*addr), + + BoundKind::FieldAccessor(k) => Ok(Value::FieldAccessor(*k)), + + BoundKind::GetField { rec, field } => { + let rec_val = self.eval_internal(obs, rec)?; + + // In Rust, pattern matching (`match`) is the idiomatic way to handle variants safely. + // Previously, this only handled `Value::Record`. Now, we handle objects (like `RecordSeries`) polymorphically. + match rec_val { + // Case 1: The classic Record. + // This is a struct-like tuple containing an Arc and a Vec. + Value::Record(layout, values) => { + if let Some(idx) = layout.index_of(*field) { + Ok(values[idx].clone()) + } else { + Err(format!("Record does not have field :{}", field.name())) + } + } + + // Case 2: A dynamic Object (our SoA / Struct-of-Arrays optimization). + // `Value::Object` holds an `Rc` - a reference-counted trait object (type-erased). + Value::Object(obj) => { + // 1. We get the raw `&dyn Any` reference (Rust's standard mechanism for runtime type reflection). + let any_ptr = obj.as_any(); + + // 2. Downcast! We check at runtime if the pointer actually points to a `RecordSeries`. + // `downcast_ref` is very fast (essentially an O(1) type ID comparison under the hood). + if let Some(record_series) = + any_ptr.downcast_ref::() + { + // 3. We call our highly performant 0-copy method on the series. + // It returns an `Rc>`, which is a shared pointer + // to the concrete column array (e.g., a `ScalarSeries`). + if let Some(field_series) = record_series.field(*field) { + // 4. We wrap this RefCell in our `SeriesView` struct. + // The `SeriesView` acts as a pure `Object` for the VM, holding the reference. + // CRITICAL: No array elements are copied here! This is pure, fast pointer juggling. + // This single operation turns a SoA `RecordSeries` into a high-speed `FloatSeries` view. + let view = + crate::ast::rtl::series::SeriesView::new(field_series, *field); + return Ok(Value::Object(std::rc::Rc::new(view))); + } else { + return Err(format!( + "RecordSeries does not have field :{}", + field.name() + )); + } + } else if let Some(sn) = obj.as_any().downcast_ref::() { + let mapped = crate::ast::rtl::streams::build_map_stream(sn.inner.clone(), *field); + return Ok(Value::Object(Rc::new(mapped))); + } + + // Fallback if it's another type of object that is not a RecordSeries. + Err(format!( + "Attempt to access field on non-record object: {}", + obj.type_name() + )) + } + + // Error handling for primitives (Int, Float, etc.). + _ => Err(format!( + "Attempt to access field on non-record: {}", + rec_val + )), + } + } + + BoundKind::Set { addr, value } => { + let val = self.eval_internal(obs, value)?; + self.set_value(*addr, val.clone())?; + Ok(val) + } + BoundKind::If { + cond, + then_br, + else_br, + } => { + let c = self.eval_internal(obs, cond)?; + if c.is_truthy() { + self.eval_internal(obs, then_br) + } else if let Some(e) = else_br { + self.eval_internal(obs, e) + } else { + Ok(Value::Void) + } + } + BoundKind::Pipe { + inputs, + lambda, + out_type, + } => { + use crate::ast::rtl::streams::StreamNode; + + let mut obs_streams = Vec::new(); + for input in inputs { + let val = self.eval_internal(obs, input)?; + if let Value::Object(obj) = val { + if let Some(s) = obj.as_any().downcast_ref::() { + obs_streams.push(s.inner.clone()); + } else { + return Err(format!( + "Pipe input must be a stream, found {}", + obj.type_name() + )); + } + } else { + return Err("Pipe input must be an object (stream)".to_string()); + } + } + + let lambda_val = self.eval_internal(obs, lambda)?; + + // Create the persistent execution closure for the PipeStream + let mut pipe_vm = crate::ast::vm::VM::new(self.globals.clone()); + + let executor: Box = match lambda_val { + Value::Object(obj) => { + let my_closure = obj.clone(); + Box::new(move |args: &[Value]| -> Value { + match pipe_vm.run_with_args(my_closure.clone(), args) { + Ok(res) => res, + Err(e) => panic!("Pipeline lambda execution failed: {}", e), + } + }) + } + Value::Function(func) => { + let my_func = func.clone(); + Box::new(move |args: &[Value]| -> Value { + (my_func.func)(args) + }) + } + _ => return Err("Pipe lambda must be a function/closure".to_string()), + }; + + // Delegate to the RTL Factory for specialized buffer instantiation + let node = + crate::ast::rtl::streams::build_pipeline_node(obs_streams, executor, out_type); + + Ok(Value::Object(node)) + } + BoundKind::Block { exprs } => { + let mut last = Value::Void; + for e in exprs { + last = self.eval_internal(obs, e)?; + } + Ok(last) + } + BoundKind::Lambda { + params, + upvalues, + body, + positional_count, + } => { + let mut captured = Vec::with_capacity(upvalues.len()); + for addr in upvalues { + captured.push(self.capture_upvalue(*addr)?); + } + let stack_size = node.ty.stack_size; + let closure = Closure::new( + params.ty.original.clone(), + body.ty.original.clone(), + body.clone(), + captured, + *positional_count, + stack_size, + ); + Ok(Value::Object(Rc::new(closure))) + } + BoundKind::Call { callee, args } => { + let func_val = self.eval_internal(obs, callee)?; + + let base = self.stack.len(); + if let Err(e) = self.eval_args_to_stack(obs, args) { + self.stack.truncate(base); + return Err(e); + } + + if node.ty.is_tail { + let arg_vals = self.stack[base..].to_vec(); + self.stack.truncate(base); + + match func_val { + Value::Object(obj) => { + return Ok(Value::TailCallRequest(Box::new((obj, arg_vals)))); + } + Value::Function(f) => return Ok((f.func)(&arg_vals)), + Value::FieldAccessor(k) => { + if arg_vals.len() != 1 { + return Err(format!( + "Field accessor .{} expects exactly 1 argument, got {}", + k.name(), + arg_vals.len() + )); + } + let rec = &arg_vals[0]; + if let Value::Record(layout, values) = rec { + if let Some(idx) = layout.index_of(k) { + return Ok(values[idx].clone()); + } else { + return Err(format!( + "Record does not have field :{}", + k.name() + )); + } + } else if let Value::Object(obj) = rec { + // Polymorphic Field Access: Allow `.field` on a RecordSeries + if let Some(rs) = obj + .as_any() + .downcast_ref::() + { + if let Some(field_series) = rs.field(k) { + let view = crate::ast::rtl::series::SeriesView::new( + field_series, + k, + ); + return Ok(Value::Object(std::rc::Rc::new(view))); + } else { + return Err(format!( + "RecordSeries does not have field :{}", + k.name() + )); + } + } else if let Some(sn) = obj.as_any().downcast_ref::() { + let mapped = crate::ast::rtl::streams::build_map_stream(sn.inner.clone(), k); + return Ok(Value::Object(Rc::new(mapped))); + } + return Err(format!( + "Field accessor .{} expects a record, RecordSeries or Stream, got {}", + k.name(), + obj.type_name() + )); + } else { + return Err(format!( + "Field accessor .{} expects a record, RecordSeries or Stream, got {}", + k.name(), + rec + )); + } + } + + _ => { + return Err(format!( + "Tail call target is not a function: {}", + func_val + )); + } + } + } + + // Standard Call Path + let mut current_func = func_val; + loop { + let result = match ¤t_func { + Value::Function(f) => { + let res = (f.func)(&self.stack[base..]); + self.stack.truncate(base); + return Ok(res); + } + Value::FieldAccessor(k) => { + let arg_len = self.stack.len() - base; + let res = if arg_len != 1 { + Err(format!( + "Field accessor .{} expects exactly 1 argument, got {}", + k.name(), + arg_len + )) + } else { + let rec = &self.stack[base]; + if let Value::Record(layout, values) = rec { + if let Some(idx) = layout.index_of(*k) { + Ok(values[idx].clone()) + } else { + Err(format!("Record does not have field :{}", k.name())) + } + } else if let Value::Object(obj) = rec { + if let Some(rs) = obj + .as_any() + .downcast_ref::() + { + if let Some(field_series) = rs.field(*k) { + let view = crate::ast::rtl::series::SeriesView::new( + field_series, + *k, + ); + Ok(Value::Object(std::rc::Rc::new(view))) + } else { + Err(format!( + "RecordSeries does not have field :{}", + k.name() + )) + } + } else if let Some(sn) = obj.as_any().downcast_ref::() { + let mapped = crate::ast::rtl::streams::build_map_stream(sn.inner.clone(), *k); + Ok(Value::Object(Rc::new(mapped))) + } else { + Err(format!( + "Field accessor .{} expects a record, RecordSeries or Stream, got {}", + k.name(), + obj.type_name() + )) + } + } else { + Err(format!( + "Field accessor .{} expects a record, RecordSeries or Stream, got {}", + k.name(), + rec + )) + } + }; + self.stack.truncate(base); + return res; + } + Value::Object(obj) => { + if let Some(closure) = obj.as_any().downcast_ref::() { + self.frames.push(CallFrame { + stack_base: base, + closure: Some(obj.clone()), + }); + + let unpack_res = if let Some(count) = closure.positional_count + && (self.stack.len() - base) == count as usize + { + Ok(()) + } else { + let args_for_unpack = self.stack[base..].to_vec(); + self.stack.truncate(base); + if let BoundKind::Tuple { elements } = + &closure.parameter_node.kind + { + let mut offset = 0; + let mut res = Ok(()); + for el in elements { + if let Err(e) = + self.unpack(el.as_ref(), &args_for_unpack, &mut offset) + { + res = Err(e); + break; + } + } + res + } else { + self.unpack(closure.parameter_node.as_ref(), &args_for_unpack, &mut 0) + } + }; + + let res = match unpack_res { + Ok(_) => { + // PRE-ALLOCATION + let current_stack = (self.stack.len() - base) as u32; + if closure.stack_size > current_stack { + self.stack.resize(base + closure.stack_size as usize, Value::Void); + } + self.eval_internal(obs, &closure.exec_node) + } + Err(e) => Err(e), + }; + + self.frames.pop(); + res + } else if let Some(series) = obj.as_series() { + let arg_len = self.stack.len() - base; + let res = if arg_len != 1 { + Err(format!( + "{} indexer expects exactly 1 argument (the lookback index)", + obj.type_name() + )) + } else if let Value::Int(idx) = self.stack[base] { + if idx < 0 { + Err(format!( + "{} lookback index cannot be negative", + obj.type_name() + )) + } else if let Some(val) = series.get_item(idx as usize) { + Ok(val) + } else { + Ok(Value::Void) + } + } else { + Err(format!( + "{} index must be an integer", + obj.type_name() + )) + }; + self.stack.truncate(base); + return res; + } else { + self.stack.truncate(base); + return Err(format!("Object is not callable: {}", obj.type_name())); + } + } + _ => { + self.stack.truncate(base); + let variant_name = match current_func { + Value::Void => "Void", + Value::Bool(_) => "Bool", + Value::Int(_) => "Int", + Value::Float(_) => "Float", + Value::DateTime(_) => "DateTime", + Value::Text(_) => "Text", + Value::Keyword(_) => "Keyword", + Value::Tuple(_) => "Tuple", + Value::Record(_, _) => "Record", + Value::FieldAccessor(_) => "FieldAccessor", + Value::Function(_) => "Function", + Value::Object(_) => "Object", + Value::Cell(_) => "Cell", + Value::TailCallRequest(_) => "TailCallRequest", + }; + return Err(format!("Attempt to call non-function: {} (Variant: {})", current_func, variant_name)); + } + }; + + match result { + Ok(Value::TailCallRequest(payload)) => { + let (next_obj, next_args) = *payload; + current_func = Value::Object(next_obj); + self.stack.truncate(base); + self.stack.extend(next_args); + continue; + } + res => { + self.stack.truncate(base); + return res; + } + } + } + } + BoundKind::Again { args } => { + let base = self.stack.len(); + if let Err(e) = self.eval_args_to_stack(obs, args) { + self.stack.truncate(base); + return Err(e); + } + let arg_vals = self.stack[base..].to_vec(); + self.stack.truncate(base); + + let frame = self.frames.last().ok_or("No call frame for 'again'")?; + if let Some(closure_obj) = &frame.closure { + Ok(Value::TailCallRequest(Box::new(( + closure_obj.clone(), + arg_vals, + )))) + } else { + Err("'again' called outside of a closure".to_string()) + } + } + BoundKind::Tuple { elements } => { + let mut vals = Vec::with_capacity(elements.len()); + for e in elements { + vals.push(self.eval_internal(obs, e)?); + } + Ok(Value::make_tuple(vals)) + } + BoundKind::Record { layout, values } => { + let mut evaluated_values = Vec::with_capacity(values.len()); + for v in values { + evaluated_values.push(self.eval_internal(obs, v)?); + } + Ok(Value::Record( + layout.clone(), + std::rc::Rc::new(evaluated_values), + )) + } + BoundKind::Expansion { bound_expanded, .. } => { + let mut curr = bound_expanded; + if !O::ACTIVE { + while let BoundKind::Expansion { + bound_expanded: next, + .. + } = &curr.kind + { + curr = next; + } + } + self.eval_internal(obs, curr) + } + BoundKind::Extension(ext) => Err(format!( + "Execution of extension '{}' not implemented yet", + ext.display_name() + )), + BoundKind::Error => Err("Cannot execute a poisoned AST node".to_string()), + } + } + + fn eval_args_to_stack( + &mut self, + obs: &mut O, + args: &ExecNode, + ) -> Result<(), String> { + match &args.kind { + BoundKind::Tuple { elements } => { + for e in elements { + let mut curr = e.as_ref(); + if !O::ACTIVE { + while let BoundKind::Expansion { + bound_expanded: next, + .. + } = &curr.kind + { + curr = next; + } + } + + match &curr.kind { + BoundKind::Constant(v) if !O::ACTIVE => self.stack.push(v.clone()), + _ => { + let val = self.eval_internal(obs, curr)?; + self.stack.push(val); + } + } + } + Ok(()) + } + BoundKind::Constant(v) => { + if let Some(slice) = v.as_slice() { + self.stack.extend_from_slice(slice); + } else { + self.stack.push(v.clone()); + } + Ok(()) + } + BoundKind::Expansion { bound_expanded, .. } => { + let mut curr = bound_expanded; + if !O::ACTIVE { + while let BoundKind::Expansion { + bound_expanded: next, + .. + } = &curr.kind + { + curr = next; + } + } + let val = self.eval_internal(obs, curr)?; + if let Some(slice) = val.as_slice() { + self.stack.extend_from_slice(slice); + } else { + self.stack.push(val); + } + Ok(()) + } + _ => { + let val = self.eval_internal(obs, args)?; + if let Some(slice) = val.as_slice() { + self.stack.extend_from_slice(slice); + } else { + self.stack.push(val); + } + Ok(()) + } + } + } + + fn capture_upvalue(&mut self, addr: Address) -> Result>, String> { + match addr { + Address::Local(slot) => { + let frame = self.frames.last().ok_or("No call frame")?; + let abs_index = frame.stack_base + (slot.0 as usize); + if abs_index < self.stack.len() { + if let Value::Cell(cell) = &self.stack[abs_index] { + Ok(cell.clone()) + } else { + let val = self.stack[abs_index].clone(); + let cell = Rc::new(RefCell::new(val)); + self.stack[abs_index] = Value::Cell(cell.clone()); + Ok(cell) + } + } else { + Err(format!("Stack access out of bounds: trying to capture local {} at index {} but stack size is only {}", slot, abs_index, self.stack.len())) + } + } + Address::Upvalue(idx) => { + let frame = self.frames.last().ok_or("No call frame")?; + if let Some(closure_obj) = &frame.closure { + let closure = closure_obj.as_any().downcast_ref::().unwrap(); + let u_idx = idx.0 as usize; + if u_idx < closure.upvalues.len() { + Ok(closure.upvalues[u_idx].clone()) + } else { + Err(format!("Upvalue access out of bounds capture {}", idx)) + } + } else { + Err("Current frame has no closure".to_string()) + } + } + Address::Global(_) => Err("Cannot capture global directly".to_string()), + } + } + + fn get_value(&self, addr: Address) -> Result { + match addr { + Address::Local(slot) => { + let frame = self.frames.last().ok_or("No call frame")?; + let abs_index = frame.stack_base + (slot.0 as usize); + if abs_index < self.stack.len() { + match &self.stack[abs_index] { + Value::Cell(cell) => Ok(cell.borrow().clone()), + val => Ok(val.clone()), + } + } else { + Err(format!("Stack underflow access local {}", slot)) + } + } + Address::Global(idx) => { + let g_idx = idx.0 as usize; + let globals = self.globals.borrow(); + if g_idx < globals.len() { + Ok(globals[g_idx].clone()) + } else { + Err(format!("Global access out of bounds {}", idx)) + } + } + Address::Upvalue(idx) => { + let frame = self.frames.last().ok_or("No call frame")?; + if let Some(closure_obj) = &frame.closure { + let closure = closure_obj.as_any().downcast_ref::().unwrap(); + let u_idx = idx.0 as usize; + if u_idx < closure.upvalues.len() { + Ok(closure.upvalues[u_idx].borrow().clone()) + } else { + Err(format!("Upvalue access out of bounds {}", idx)) + } + } else { + Err("Current frame has no closure (cannot access upvalues)".to_string()) + } + } + } + } + + fn set_value(&mut self, addr: Address, value: Value) -> Result<(), String> { + match addr { + Address::Local(slot) => { + let frame = self.frames.last().ok_or("No call frame")?; + let abs_index = frame.stack_base + (slot.0 as usize); + if abs_index < self.stack.len() { + if let Value::Cell(cell) = &self.stack[abs_index] { + *cell.borrow_mut() = value; + } else { + self.stack[abs_index] = value; + } + } else if abs_index == self.stack.len() { + self.stack.push(value); + } else { + return Err(format!("Stack gap write local {}", slot)); + } + Ok(()) + } + Address::Global(idx) => { + let g_idx = idx.0 as usize; + let mut globals = self.globals.borrow_mut(); + if g_idx >= globals.len() { + globals.resize(g_idx + 1, Value::Void); + } + globals[g_idx] = value; + Ok(()) + } + Address::Upvalue(idx) => { + let frame = self.frames.last().ok_or("No call frame")?; + if let Some(closure_obj) = &frame.closure { + let closure = closure_obj.as_any().downcast_ref::().unwrap(); + let u_idx = idx.0 as usize; + if u_idx < closure.upvalues.len() { + *closure.upvalues[u_idx].borrow_mut() = value; + Ok(()) + } else { + Err(format!("Upvalue assignment out of bounds {}", idx)) + } + } else { + Err("Current frame has no closure".to_string()) + } + } + } + } + + fn unpack( + &mut self, + pattern: &Node, + values: &[Value], + offset: &mut usize, + ) -> Result<(), String> { + match &pattern.kind { + BoundKind::Define { addr, .. } => { + let val = values.get(*offset).cloned().unwrap_or(Value::Void); + *offset += 1; + self.set_value(*addr, val) + } + BoundKind::Set { addr, .. } => { + let val = values.get(*offset).cloned().unwrap_or(Value::Void); + *offset += 1; + self.set_value(*addr, val) + } + BoundKind::Tuple { elements } => { + if let Some(sub_values) = values.get(*offset).and_then(|v| v.as_slice()) { + *offset += 1; + let mut sub_offset = 0; + for el in elements { + self.unpack(el.as_ref(), sub_values, &mut sub_offset)?; + } + return Ok(()); + } + for el in elements { + self.unpack(el.as_ref(), values, offset)?; + } + Ok(()) + } + _ => Err("Invalid node in parameter pattern".to_string()), + } + } +}