diff --git a/src/ast/compiler/binder.rs b/src/ast/compiler/binder.rs index 3c0d056..3cc44c0 100644 --- a/src/ast/compiler/binder.rs +++ b/src/ast/compiler/binder.rs @@ -1,14 +1,17 @@ -use std::collections::{HashMap, BTreeMap}; +use std::collections::HashMap; use std::rc::Rc; use std::cell::RefCell; use crate::ast::nodes::{Node, UntypedKind, Symbol}; -use crate::ast::compiler::bound_nodes::{BoundKind, Address}; -use crate::ast::types::{Identity, StaticType, Value}; +use crate::ast::compiler::bound_nodes::{BoundKind, Address, BoundNode}; +use crate::ast::types::{Identity, StaticType}; #[derive(Debug, Clone)] struct LocalInfo { slot: u32, - ty: StaticType, + // Note: Binder doesn't strictly need the type anymore, + // but it might be useful for built-ins during resolution. + // For now we keep it as Any or Unknown. + ty: StaticType, } #[derive(Debug, Clone)] @@ -25,12 +28,12 @@ impl CompilerScope { } } - fn define(&mut self, sym: &Symbol, ty: StaticType) -> Result { + fn define(&mut self, sym: &Symbol) -> Result { if self.locals.contains_key(sym) { return Err(format!("Variable '{}' is already defined in this scope level.", sym.name)); } let slot = self.slot_count; - self.locals.insert(sym.clone(), LocalInfo { slot, ty }); + self.locals.insert(sym.clone(), LocalInfo { slot, ty: StaticType::Any }); self.slot_count += 1; Ok(slot) } @@ -42,7 +45,7 @@ impl CompilerScope { struct FunctionCompiler { scope: CompilerScope, - upvalues: Vec<(Address, StaticType)>, + upvalues: Vec
, } impl FunctionCompiler { @@ -53,30 +56,30 @@ impl FunctionCompiler { } } - fn add_upvalue(&mut self, addr: Address, ty: StaticType) -> u32 { - if let Some(idx) = self.upvalues.iter().position(|(a, _)| *a == addr) { + fn add_upvalue(&mut self, addr: Address) -> u32 { + if let Some(idx) = self.upvalues.iter().position(|&a| a == addr) { return idx as u32; } let idx = self.upvalues.len() as u32; - self.upvalues.push((addr, ty)); + self.upvalues.push(addr); idx } } pub struct Binder { functions: Vec, - // Globals mapping: Symbol -> (Index, Type) - globals: Rc>>, + // Globals mapping: Symbol -> Index + globals: Rc>>, // Map of Declaration Identity -> List of Lambda Identities that capture it capture_map: HashMap>, } impl Binder { - pub fn new(globals: Rc>>) -> Self { + pub fn new(globals: Rc>>) -> Self { Self::with_boxed(globals, HashMap::new()) } - fn with_boxed(globals: Rc>>, captures: HashMap>) -> Self { + fn with_boxed(globals: Rc>>, captures: HashMap>) -> Self { let mut binder = Self { functions: Vec::new(), globals, @@ -86,52 +89,37 @@ impl Binder { binder } - pub fn bind_root(globals: Rc>>, node: &Node) -> Result, String> { + pub fn bind_root(globals: Rc>>, node: &Node) -> Result { let captures = crate::ast::compiler::upvalues::UpvalueAnalyzer::analyze(node); let mut binder = Self::with_boxed(globals, captures); binder.bind(node) } - pub fn bind(&mut self, node: &Node) -> Result, String> { + pub fn bind(&mut self, node: &Node) -> Result { match &node.kind { - UntypedKind::Nop => Ok(self.make_node(node.identity.clone(), BoundKind::Nop, StaticType::Void)), + UntypedKind::Nop => Ok(self.make_node(node.identity.clone(), BoundKind::Nop)), UntypedKind::Constant(v) => { - let ty = v.static_type(); - Ok(self.make_node(node.identity.clone(), BoundKind::Constant(v.clone()), ty)) + Ok(self.make_node(node.identity.clone(), BoundKind::Constant(v.clone()))) }, UntypedKind::Identifier(sym) => { - let (addr, ty) = self.resolve_variable(sym)?; - Ok(self.make_node(node.identity.clone(), BoundKind::Get(addr), ty)) + let addr = self.resolve_variable(sym)?; + Ok(self.make_node(node.identity.clone(), BoundKind::Get(addr))) }, UntypedKind::If { cond, then_br, else_br } => { let cond = self.bind(cond)?; - // Type check condition: Must be Bool or Any - if cond.ty != StaticType::Bool && cond.ty != StaticType::Any { - return Err(format!("Condition must be boolean, found {:?}", cond.ty)); - } - let then_br = self.bind(then_br)?; let mut else_br_bound = None; - let final_ty = if let Some(e) = else_br { - let eb = self.bind(e)?; - let ty = if then_br.ty == eb.ty { - then_br.ty.clone() - } else { - StaticType::Any // Common supertype logic could go here - }; - else_br_bound = Some(Box::new(eb)); - ty - } else { - StaticType::Void // If without else returns Void if false - }; + if let Some(e) = else_br { + else_br_bound = Some(Box::new(self.bind(e)?)); + } Ok(self.make_node(node.identity.clone(), BoundKind::If { cond: Box::new(cond), then_br: Box::new(then_br), else_br: else_br_bound - }, final_ty)) + })) }, UntypedKind::Def { name, value } => { @@ -142,42 +130,29 @@ impl Binder { return Err(format!("Global variable '{}' is already defined.", name.name)); } let idx = globals.len() as u32; - globals.insert(name.clone(), (idx, StaticType::Any)); + globals.insert(name.clone(), idx); idx } else { let current_fn = self.functions.last_mut().unwrap(); - current_fn.scope.define(name, StaticType::Any)? + current_fn.scope.define(name)? }; // 2. Bind Value (now 'name' is visible) let val_node = self.bind(value)?; - let ty = val_node.ty.clone(); - // 3. Update Type and Return Node + // 3. Return Node if self.functions.len() == 1 { - { - let mut globals = self.globals.borrow_mut(); - if let Some(entry) = globals.get_mut(name) { - entry.1 = ty.clone(); - } - } Ok(self.make_node(node.identity.clone(), BoundKind::DefGlobal { global_index: slot_or_idx, value: Box::new(val_node) - }, ty)) + })) } else { - { - let current_fn = self.functions.last_mut().unwrap(); - if let Some(info) = current_fn.scope.locals.get_mut(name) { - info.ty = ty.clone(); - } - } let captured_by = self.capture_map.get(&node.identity).cloned().unwrap_or_default(); Ok(self.make_node(node.identity.clone(), BoundKind::DefLocal { slot: slot_or_idx, value: Box::new(val_node) , captured_by - }, ty)) + })) } }, @@ -185,17 +160,11 @@ impl Binder { let val_node = self.bind(value)?; if let UntypedKind::Identifier(sym) = &target.kind { - let (addr, target_ty) = self.resolve_variable(sym)?; - - // Type check: value must be compatible with target - if target_ty != StaticType::Any && val_node.ty != StaticType::Any && target_ty != val_node.ty { - return Err(format!("Cannot assign {:?} to variable of type {:?}", val_node.ty, target_ty)); - } - + let addr = self.resolve_variable(sym)?; Ok(self.make_node(node.identity.clone(), BoundKind::Set { addr, value: Box::new(val_node) - }, target_ty)) + })) } else { Err("Assignment target must be an identifier".to_string()) } @@ -204,65 +173,42 @@ impl Binder { UntypedKind::Lambda { params, body } => { self.functions.push(FunctionCompiler::new()); - let mut param_types = Vec::new(); { let current_fn = self.functions.last_mut().unwrap(); for param in params { - // Lambda params must also be unique in their scope - current_fn.scope.define(param, StaticType::Any)?; - param_types.push(StaticType::Any); + current_fn.scope.define(param)?; } } let body_bound = self.bind(body)?; - let ret_ty = body_bound.ty.clone(); - let compiled_fn = self.functions.pop().unwrap(); - let upvalues: Vec
= compiled_fn.upvalues.into_iter().map(|(a, _)| a).collect(); - - let fn_ty = StaticType::Function { - params: param_types, - ret: Box::new(ret_ty), - }; Ok(self.make_node(node.identity.clone(), BoundKind::Lambda { param_count: params.len() as u32, - upvalues, + upvalues: compiled_fn.upvalues, body: Rc::new(body_bound), - }, fn_ty)) + })) }, UntypedKind::Call { callee, args } => { let callee = self.bind(callee)?; let mut bound_args = Vec::new(); - let mut arg_types = Vec::new(); for arg in args { - let b = self.bind(arg)?; - arg_types.push(b.ty.clone()); - bound_args.push(b); + bound_args.push(self.bind(arg)?); } - let ret_ty = match &callee.ty { - StaticType::Function { ret, .. } => *ret.clone(), - StaticType::Any => StaticType::Any, - _ => return Err(format!("Attempt to call non-function of type {:?}", callee.ty)), - }; - Ok(self.make_node(node.identity.clone(), BoundKind::Call { callee: Box::new(callee), args: bound_args - }, ret_ty)) + })) }, UntypedKind::Block { exprs } => { let mut bound_exprs = Vec::new(); - let mut last_ty = StaticType::Void; for expr in exprs { - let b = self.bind(expr)?; - last_ty = b.ty.clone(); - bound_exprs.push(b); + bound_exprs.push(self.bind(expr)?); } - Ok(self.make_node(node.identity.clone(), BoundKind::Block { exprs: bound_exprs }, last_ty)) + Ok(self.make_node(node.identity.clone(), BoundKind::Block { exprs: bound_exprs })) }, UntypedKind::Tuple { elements } => { @@ -270,46 +216,23 @@ impl Binder { for e in elements { bound_elems.push(self.bind(e)?); } - // For now, tuple type is List(Any) - let ty = StaticType::List(Box::new(StaticType::Any)); - Ok(self.make_node(node.identity.clone(), BoundKind::Tuple { elements: bound_elems }, ty)) + Ok(self.make_node(node.identity.clone(), BoundKind::Tuple { elements: bound_elems })) }, UntypedKind::Map { entries } => { let mut bound_entries = Vec::new(); - let mut key_types = BTreeMap::new(); // For Record type inference if keys are constant keywords - for (k, v) in entries { - // Keys must be compile-time constants for now (for Record type), - // or at least we enforce keywords. - // But `bind` processes runtime expressions too. - // Let's bind both. - let bound_k = self.bind(k)?; - let bound_v = self.bind(v)?; - - // If key is a constant keyword, we can build a static record type. - if let BoundKind::Constant(Value::Keyword(kw)) = &bound_k.kind { - key_types.insert(*kw, bound_v.ty.clone()); - } - - bound_entries.push((bound_k, bound_v)); + bound_entries.push((self.bind(k)?, self.bind(v)?)); } - - // If all keys are known, we produce a Record type. Else Map(Any, Any) which we don't have yet. - // We default to Record(Any) if keys are dynamic (not supported well yet) or known. - // Since our parser enforced keywords, we assume we have a Record. - let ty = StaticType::Record(Rc::new(key_types)); - - Ok(self.make_node(node.identity.clone(), BoundKind::Map { entries: bound_entries }, ty)) + Ok(self.make_node(node.identity.clone(), BoundKind::Map { entries: bound_entries })) }, UntypedKind::Expansion { call, expanded } => { let bound_expanded = self.bind(expanded)?; - let ty = bound_expanded.ty.clone(); Ok(self.make_node(node.identity.clone(), BoundKind::Expansion { original_call: Rc::from(call.as_ref().clone()), bound_expanded: Box::new(bound_expanded), - }, ty)) + })) }, UntypedKind::Template(_) | UntypedKind::Placeholder(_) | UntypedKind::Splice(_) | UntypedKind::MacroDecl { .. } => { @@ -317,52 +240,51 @@ impl Binder { } UntypedKind::Extension(_) => { + // Future: Delegate to extension binder Err("Custom extensions not supported in Binder yet".to_string()) } } } - fn resolve_variable(&mut self, sym: &Symbol) -> Result<(Address, StaticType), String> { + fn resolve_variable(&mut self, sym: &Symbol) -> Result { let current_fn_idx = self.functions.len() - 1; // 1. Try local in current function if let Some(info) = self.functions[current_fn_idx].scope.resolve(sym) { - return Ok((Address::Local(info.slot), info.ty)); + return Ok(Address::Local(info.slot)); } // 2. Try enclosing scopes (capture chain) for i in (0..current_fn_idx).rev() { if let Some(info) = self.functions[i].scope.resolve(sym) { let mut addr = Address::Local(info.slot); - let ty = info.ty.clone(); for k in (i + 1)..=current_fn_idx { - addr = Address::Upvalue(self.functions[k].add_upvalue(addr, ty.clone())); + addr = Address::Upvalue(self.functions[k].add_upvalue(addr)); } - return Ok((addr, ty)); + return Ok(addr); } } // 3. Try Global let globals = self.globals.borrow(); - if let Some((idx, ty)) = globals.get(sym) { - return Ok((Address::Global(*idx), ty.clone())); + if let Some(idx) = globals.get(sym) { + return Ok(Address::Global(*idx)); } - // 4. Global Fallback: If not found with context, try without context - // (Allows macros to access global built-ins like *, +, etc.) + // 4. Global Fallback if sym.context.is_some() { let fallback_sym = Symbol { name: sym.name.clone(), context: None }; - if let Some((idx, ty)) = globals.get(&fallback_sym) { - return Ok((Address::Global(*idx), ty.clone())); + if let Some(idx) = globals.get(&fallback_sym) { + return Ok(Address::Global(*idx)); } } Err(format!("Undefined variable '{}'", sym.name)) } - fn make_node(&self, identity: Identity, kind: BoundKind, ty: StaticType) -> Node { - Node { identity, kind, ty } + fn make_node(&self, identity: Identity, kind: BoundKind<()>) -> BoundNode { + Node { identity, kind, ty: () } } } diff --git a/src/ast/compiler/bound_nodes.rs b/src/ast/compiler/bound_nodes.rs index fd73bda..bc418b5 100644 --- a/src/ast/compiler/bound_nodes.rs +++ b/src/ast/compiler/bound_nodes.rs @@ -2,15 +2,27 @@ use std::rc::Rc; use crate::ast::types::{Value, StaticType, Identity}; use crate::ast::nodes::Node; -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum Address { Local(u32), // Stack-Slot index (relative to frame base) Upvalue(u32), // Index in the closure's upvalue array Global(u32), // Index in the global environment vector } +/// Trait for DSL-specific AST extensions (like Pipe, Series-Access, etc.) +pub trait BoundExtension: std::fmt::Debug { + fn clone_box(&self) -> Box>; + fn display_name(&self) -> String; +} + +impl Clone for Box> { + fn clone(&self) -> Self { + self.clone_box() + } +} + #[derive(Debug, Clone)] -pub enum BoundKind { +pub enum BoundKind { Nop, Constant(Value), @@ -20,66 +32,76 @@ pub enum BoundKind { // Variable Update (Assignment) Set { addr: Address, - value: Box>, + value: Box, T>>, }, // Variable Declaration (Local) DefLocal { slot: u32, - value: Box>, + value: Box, T>>, captured_by: Vec, }, If { - cond: Box>, - then_br: Box>, - else_br: Option>>, + cond: Box, T>>, + then_br: Box, T>>, + else_br: Option, T>>>, }, // Global Definition (Locals are implicit by stack position) DefGlobal { global_index: u32, - value: Box>, + value: Box, T>>, }, Lambda { param_count: u32, // The list of variables captured from enclosing scopes upvalues: Vec
, - body: Rc>, + body: Rc, T>>, }, Call { - callee: Box>, - args: Vec>, + callee: Box, T>>, + args: Vec, T>>, }, TailCall { - callee: Box>, - args: Vec>, + callee: Box, T>>, + args: Vec, T>>, }, Block { - exprs: Vec>, + exprs: Vec, T>>, }, - // NEW Tuple { - elements: Vec>, + elements: Vec, T>>, }, + Map { - entries: Vec<(Node, Node)>, + entries: Vec<(Node, T>, Node, T>)>, }, + /// An expanded macro call, preserving the original call for debugging and UI. Expansion { /// The original call from the untyped AST. original_call: Rc>, /// The result of binding the expanded AST. - bound_expanded: Box>, + bound_expanded: Box, T>>, }, + + /// DSL-specific extension slot + Extension(Box>), } -impl BoundKind { +/// Type alias for a node that has been bound (addresses resolved) but not yet type-checked. +pub type BoundNode = Node, ()>; + +/// Type alias for a node that has been fully type-checked and is ready for execution. +pub type TypedNode = Node, StaticType>; + +impl BoundKind { pub fn display_name(&self) -> String { match self { BoundKind::Nop => "NOP".to_string(), @@ -96,6 +118,7 @@ impl BoundKind { BoundKind::Tuple { elements } => format!("TUPLE({})", elements.len()), BoundKind::Map { entries } => format!("MAP({})", entries.len()), BoundKind::Expansion { .. } => "EXPANSION".to_string(), + BoundKind::Extension(ext) => ext.display_name(), } } } diff --git a/src/ast/compiler/dumper.rs b/src/ast/compiler/dumper.rs index 9aa7768..3c3d484 100644 --- a/src/ast/compiler/dumper.rs +++ b/src/ast/compiler/dumper.rs @@ -1,6 +1,6 @@ use crate::ast::nodes::Node; use crate::ast::compiler::bound_nodes::BoundKind; -use crate::ast::types::StaticType; +use std::fmt::Debug; /// Human-readable AST dumper for the bound AST. pub struct Dumper { @@ -10,7 +10,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) -> String { + pub fn dump(node: &Node, T>) -> String { let mut dumper = Self { output: String::new(), indent: 0, @@ -25,13 +25,13 @@ impl Dumper { } } - fn log(&mut self, label: &str, node: &Node) { + fn log(&mut self, label: &str, node: &Node, T>) { self.write_indent(); self.output.push_str(label); - self.output.push_str(&format!(" \n", node.ty)); + self.output.push_str(&format!(" \n", node.ty)); } - fn visit(&mut self, node: &Node) { + fn visit(&mut self, node: &Node, T>) { match &node.kind { BoundKind::Nop => self.log("Nop", node), BoundKind::Constant(v) => self.log(&format!("Constant: {}", v), node), @@ -156,6 +156,10 @@ impl Dumper { self.visit(bound_expanded); self.indent -= 1; } + + BoundKind::Extension(ext) => { + self.log(&ext.display_name(), node); + } } } } diff --git a/src/ast/compiler/macros.rs b/src/ast/compiler/macros.rs index 694558e..23ab03b 100644 --- a/src/ast/compiler/macros.rs +++ b/src/ast/compiler/macros.rs @@ -547,7 +547,7 @@ mod tests { let expanded = expander.expand(untyped).unwrap(); let mut global_names = HashMap::new(); - global_names.insert(Symbol::from("*"), (0, StaticType::Any)); + global_names.insert(Symbol::from("*"), 0); let globals = Rc::new(RefCell::new(global_names)); let result = Binder::bind_root(globals, &expanded); diff --git a/src/ast/compiler/tco.rs b/src/ast/compiler/tco.rs index b9e5454..644bf91 100644 --- a/src/ast/compiler/tco.rs +++ b/src/ast/compiler/tco.rs @@ -1,19 +1,18 @@ use std::rc::Rc; use crate::ast::nodes::Node; use crate::ast::compiler::bound_nodes::BoundKind; -use crate::ast::types::StaticType; pub struct TCO; impl TCO { - pub fn optimize(node: Node) -> Node { + pub fn optimize(node: Node, T>) -> Node, T> { // Top-level starts NOT in tail position usually, unless the script result is returned immediately // which implies tail position for the script body if viewed as a function. // Let's assume standard execution context (not tail call) for the script root. Self::transform(node, false) } - fn transform(node: Node, is_tail_position: bool) -> Node { + fn transform(node: Node, T>, is_tail_position: bool) -> Node, T> { match node.kind { BoundKind::Call { callee, args } => { let new_callee = Box::new(Self::transform(*callee, false)); @@ -78,8 +77,6 @@ impl TCO { BoundKind::Lambda { param_count, upvalues, body } => { // The body of a lambda is implicitly in tail position when the lambda is called. - // However, we process the lambda definition here. - // The body node inside needs to be transformed assuming it IS in tail position relative to the function. let new_body = Rc::new(Self::transform((*body).clone(), true)); Node { diff --git a/src/ast/vm.rs b/src/ast/vm.rs index aaa751b..b557bf0 100644 --- a/src/ast/vm.rs +++ b/src/ast/vm.rs @@ -2,13 +2,13 @@ use std::rc::Rc; use std::cell::RefCell; use std::collections::HashMap; use std::any::Any; -use crate::ast::compiler::bound_nodes::{Address, BoundKind}; +use crate::ast::compiler::bound_nodes::{Address, BoundKind, TypedNode}; use crate::ast::nodes::Node; use crate::ast::types::{Value, Object, StaticType}; #[derive(Debug, Clone)] pub struct Closure { - pub function_node: Rc>, + pub function_node: Rc, pub upvalues: Vec>>, } @@ -30,8 +30,8 @@ struct CallFrame { /// Hook for observing VM execution (zero-cost when O::ACTIVE is false) pub trait VMObserver { const ACTIVE: bool = false; - fn before_eval(&mut self, _vm: &VM, _node: &Node) {} - fn after_eval(&mut self, _vm: &VM, _node: &Node, _res: &Result) {} + fn before_eval(&mut self, _vm: &VM, _node: &TypedNode) {} + fn after_eval(&mut self, _vm: &VM, _node: &TypedNode, _res: &Result) {} } /// Default observer that does nothing and should be optimized away @@ -63,13 +63,13 @@ impl Default for TracingObserver { impl VMObserver for TracingObserver { const ACTIVE: bool = true; - fn before_eval(&mut self, _vm: &VM, node: &Node) { + fn before_eval(&mut self, _vm: &VM, node: &TypedNode) { let pad = self.pad(); self.logs.push(format!("{}{} [{}]: {{", pad, node.kind.display_name(), node.ty)); self.indent += 1; } - fn after_eval(&mut self, vm: &VM, node: &Node, res: &Result) { + fn after_eval(&mut self, vm: &VM, node: &TypedNode, res: &Result) { self.indent = self.indent.saturating_sub(1); let pad = self.pad(); @@ -262,6 +262,11 @@ macro_rules! dispatch_eval { BoundKind::Expansion { original_call: _, bound_expanded } => { $self.$eval_method($($observer,)? bound_expanded) } + + BoundKind::Extension(ext) => { + // Future: Delegate execution to extension + Err(format!("Execution of extension '{}' not implemented yet", ext.display_name())) + } } }; } @@ -275,7 +280,7 @@ impl VM { } } - pub fn run(&mut self, root: &Node) -> Result { + pub fn run(&mut self, root: &TypedNode) -> Result { self.stack.clear(); self.frames.clear(); @@ -290,7 +295,7 @@ impl VM { result } - pub fn run_with_observer(&mut self, observer: &mut O, root: &Node) -> Result { + pub fn run_with_observer(&mut self, observer: &mut O, root: &TypedNode) -> Result { self.stack.clear(); self.frames.clear(); @@ -307,12 +312,12 @@ impl VM { /// The pure, original, zero-cost hot path. #[inline(always)] - fn eval(&mut self, node: &Node) -> Result { + fn eval(&mut self, node: &TypedNode) -> Result { dispatch_eval!(self, node, eval) } /// The observed path for debugging. - fn eval_observed(&mut self, observer: &mut O, node: &Node) -> Result { + fn eval_observed(&mut self, observer: &mut O, node: &TypedNode) -> Result { observer.before_eval(self, node); // Wrap in a closure to ensure after_eval is called even on early returns (e.g. from ?) diff --git a/src/integration_test.rs b/src/integration_test.rs index 8cf0d11..64a7beb 100644 --- a/src/integration_test.rs +++ b/src/integration_test.rs @@ -80,9 +80,12 @@ mod tests { let mut binder = Binder::new(globals.clone()); let bound_ast = binder.bind(&ast).expect("Failed to bind"); + // BRIDGE: Binder -> VM requires a TypedNode + let typed_ast = crate::ast::environment::dummy_type_check(bound_ast); + let vm_globals = Rc::new(RefCell::new(Vec::new())); let mut vm = VM::new(vm_globals); - let result = vm.run(&bound_ast); + let result = vm.run(&typed_ast); match result { Ok(Value::Int(val)) => assert_eq!(val, 20, "Closure should modify outer variable"),