Refactor Binder to use simpler types

The Binder has been refactored to simplify its internal types and reduce
redundancy.
Key changes include:
- Removed StaticType from LocalInfo, as it's not strictly needed for the
  binder.
- Simplified `define` in CompilerScope to not take a StaticType.
- Removed StaticType from upvalues in FunctionCompiler.
- Adjusted global mappings to store only the index, not the type.
- Updated BoundKind to use a generic type parameter `T` for metadata,
  defaulting to `()` for untyped bound nodes.
- Introduced `BoundNode` and `TypedNode` type aliases for clarity.
- Minor adjustments to dumper and VM to accommodate the new generic
  BoundKind.
This commit is contained in:
Michael Schimmel
2026-02-19 12:07:28 +01:00
parent c49561255e
commit 4673ea78b9
7 changed files with 130 additions and 176 deletions
+57 -135
View File
@@ -1,14 +1,17 @@
use std::collections::{HashMap, BTreeMap}; use std::collections::HashMap;
use std::rc::Rc; use std::rc::Rc;
use std::cell::RefCell; use std::cell::RefCell;
use crate::ast::nodes::{Node, UntypedKind, Symbol}; use crate::ast::nodes::{Node, UntypedKind, Symbol};
use crate::ast::compiler::bound_nodes::{BoundKind, Address}; use crate::ast::compiler::bound_nodes::{BoundKind, Address, BoundNode};
use crate::ast::types::{Identity, StaticType, Value}; use crate::ast::types::{Identity, StaticType};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
struct LocalInfo { struct LocalInfo {
slot: u32, 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)] #[derive(Debug, Clone)]
@@ -25,12 +28,12 @@ impl CompilerScope {
} }
} }
fn define(&mut self, sym: &Symbol, ty: StaticType) -> Result<u32, String> { fn define(&mut self, sym: &Symbol) -> Result<u32, String> {
if self.locals.contains_key(sym) { if self.locals.contains_key(sym) {
return Err(format!("Variable '{}' is already defined in this scope level.", sym.name)); return Err(format!("Variable '{}' is already defined in this scope level.", sym.name));
} }
let slot = self.slot_count; 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; self.slot_count += 1;
Ok(slot) Ok(slot)
} }
@@ -42,7 +45,7 @@ impl CompilerScope {
struct FunctionCompiler { struct FunctionCompiler {
scope: CompilerScope, scope: CompilerScope,
upvalues: Vec<(Address, StaticType)>, upvalues: Vec<Address>,
} }
impl FunctionCompiler { impl FunctionCompiler {
@@ -53,30 +56,30 @@ impl FunctionCompiler {
} }
} }
fn add_upvalue(&mut self, addr: Address, ty: StaticType) -> u32 { fn add_upvalue(&mut self, addr: Address) -> u32 {
if let Some(idx) = self.upvalues.iter().position(|(a, _)| *a == addr) { if let Some(idx) = self.upvalues.iter().position(|&a| a == addr) {
return idx as u32; return idx as u32;
} }
let idx = self.upvalues.len() as u32; let idx = self.upvalues.len() as u32;
self.upvalues.push((addr, ty)); self.upvalues.push(addr);
idx idx
} }
} }
pub struct Binder { pub struct Binder {
functions: Vec<FunctionCompiler>, functions: Vec<FunctionCompiler>,
// Globals mapping: Symbol -> (Index, Type) // Globals mapping: Symbol -> Index
globals: Rc<RefCell<HashMap<Symbol, (u32, StaticType)>>>, globals: Rc<RefCell<HashMap<Symbol, u32>>>,
// Map of Declaration Identity -> List of Lambda Identities that capture it // Map of Declaration Identity -> List of Lambda Identities that capture it
capture_map: HashMap<Identity, Vec<Identity>>, capture_map: HashMap<Identity, Vec<Identity>>,
} }
impl Binder { impl Binder {
pub fn new(globals: Rc<RefCell<HashMap<Symbol, (u32, StaticType)>>>) -> Self { pub fn new(globals: Rc<RefCell<HashMap<Symbol, u32>>>) -> Self {
Self::with_boxed(globals, HashMap::new()) Self::with_boxed(globals, HashMap::new())
} }
fn with_boxed(globals: Rc<RefCell<HashMap<Symbol, (u32, StaticType)>>>, captures: HashMap<Identity, Vec<Identity>>) -> Self { fn with_boxed(globals: Rc<RefCell<HashMap<Symbol, u32>>>, captures: HashMap<Identity, Vec<Identity>>) -> Self {
let mut binder = Self { let mut binder = Self {
functions: Vec::new(), functions: Vec::new(),
globals, globals,
@@ -86,52 +89,37 @@ impl Binder {
binder binder
} }
pub fn bind_root(globals: Rc<RefCell<HashMap<Symbol, (u32, StaticType)>>>, node: &Node<UntypedKind>) -> Result<Node<BoundKind, StaticType>, String> { pub fn bind_root(globals: Rc<RefCell<HashMap<Symbol, u32>>>, node: &Node<UntypedKind>) -> Result<BoundNode, String> {
let captures = crate::ast::compiler::upvalues::UpvalueAnalyzer::analyze(node); let captures = crate::ast::compiler::upvalues::UpvalueAnalyzer::analyze(node);
let mut binder = Self::with_boxed(globals, captures); let mut binder = Self::with_boxed(globals, captures);
binder.bind(node) binder.bind(node)
} }
pub fn bind(&mut self, node: &Node<UntypedKind>) -> Result<Node<BoundKind, StaticType>, String> { pub fn bind(&mut self, node: &Node<UntypedKind>) -> Result<BoundNode, String> {
match &node.kind { 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) => { UntypedKind::Constant(v) => {
let ty = v.static_type(); Ok(self.make_node(node.identity.clone(), BoundKind::Constant(v.clone())))
Ok(self.make_node(node.identity.clone(), BoundKind::Constant(v.clone()), ty))
}, },
UntypedKind::Identifier(sym) => { UntypedKind::Identifier(sym) => {
let (addr, ty) = self.resolve_variable(sym)?; let addr = self.resolve_variable(sym)?;
Ok(self.make_node(node.identity.clone(), BoundKind::Get(addr), ty)) Ok(self.make_node(node.identity.clone(), BoundKind::Get(addr)))
}, },
UntypedKind::If { cond, then_br, else_br } => { UntypedKind::If { cond, then_br, else_br } => {
let cond = self.bind(cond)?; 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 then_br = self.bind(then_br)?;
let mut else_br_bound = None; let mut else_br_bound = None;
let final_ty = if let Some(e) = else_br { if let Some(e) = else_br {
let eb = self.bind(e)?; else_br_bound = Some(Box::new(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
};
Ok(self.make_node(node.identity.clone(), BoundKind::If { Ok(self.make_node(node.identity.clone(), BoundKind::If {
cond: Box::new(cond), cond: Box::new(cond),
then_br: Box::new(then_br), then_br: Box::new(then_br),
else_br: else_br_bound else_br: else_br_bound
}, final_ty)) }))
}, },
UntypedKind::Def { name, value } => { UntypedKind::Def { name, value } => {
@@ -142,42 +130,29 @@ impl Binder {
return Err(format!("Global variable '{}' is already defined.", name.name)); return Err(format!("Global variable '{}' is already defined.", name.name));
} }
let idx = globals.len() as u32; let idx = globals.len() as u32;
globals.insert(name.clone(), (idx, StaticType::Any)); globals.insert(name.clone(), idx);
idx idx
} else { } else {
let current_fn = self.functions.last_mut().unwrap(); 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) // 2. Bind Value (now 'name' is visible)
let val_node = self.bind(value)?; 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 { 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 { Ok(self.make_node(node.identity.clone(), BoundKind::DefGlobal {
global_index: slot_or_idx, global_index: slot_or_idx,
value: Box::new(val_node) value: Box::new(val_node)
}, ty)) }))
} else { } 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(); let captured_by = self.capture_map.get(&node.identity).cloned().unwrap_or_default();
Ok(self.make_node(node.identity.clone(), BoundKind::DefLocal { Ok(self.make_node(node.identity.clone(), BoundKind::DefLocal {
slot: slot_or_idx, slot: slot_or_idx,
value: Box::new(val_node) , value: Box::new(val_node) ,
captured_by captured_by
}, ty)) }))
} }
}, },
@@ -185,17 +160,11 @@ impl Binder {
let val_node = self.bind(value)?; let val_node = self.bind(value)?;
if let UntypedKind::Identifier(sym) = &target.kind { if let UntypedKind::Identifier(sym) = &target.kind {
let (addr, target_ty) = self.resolve_variable(sym)?; let addr = 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));
}
Ok(self.make_node(node.identity.clone(), BoundKind::Set { Ok(self.make_node(node.identity.clone(), BoundKind::Set {
addr, addr,
value: Box::new(val_node) value: Box::new(val_node)
}, target_ty)) }))
} else { } else {
Err("Assignment target must be an identifier".to_string()) Err("Assignment target must be an identifier".to_string())
} }
@@ -204,65 +173,42 @@ impl Binder {
UntypedKind::Lambda { params, body } => { UntypedKind::Lambda { params, body } => {
self.functions.push(FunctionCompiler::new()); self.functions.push(FunctionCompiler::new());
let mut param_types = Vec::new();
{ {
let current_fn = self.functions.last_mut().unwrap(); let current_fn = self.functions.last_mut().unwrap();
for param in params { for param in params {
// Lambda params must also be unique in their scope current_fn.scope.define(param)?;
current_fn.scope.define(param, StaticType::Any)?;
param_types.push(StaticType::Any);
} }
} }
let body_bound = self.bind(body)?; let body_bound = self.bind(body)?;
let ret_ty = body_bound.ty.clone();
let compiled_fn = self.functions.pop().unwrap(); let compiled_fn = self.functions.pop().unwrap();
let upvalues: Vec<Address> = 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 { Ok(self.make_node(node.identity.clone(), BoundKind::Lambda {
param_count: params.len() as u32, param_count: params.len() as u32,
upvalues, upvalues: compiled_fn.upvalues,
body: Rc::new(body_bound), body: Rc::new(body_bound),
}, fn_ty)) }))
}, },
UntypedKind::Call { callee, args } => { UntypedKind::Call { callee, args } => {
let callee = self.bind(callee)?; let callee = self.bind(callee)?;
let mut bound_args = Vec::new(); let mut bound_args = Vec::new();
let mut arg_types = Vec::new();
for arg in args { for arg in args {
let b = self.bind(arg)?; bound_args.push(self.bind(arg)?);
arg_types.push(b.ty.clone());
bound_args.push(b);
} }
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 { Ok(self.make_node(node.identity.clone(), BoundKind::Call {
callee: Box::new(callee), callee: Box::new(callee),
args: bound_args args: bound_args
}, ret_ty)) }))
}, },
UntypedKind::Block { exprs } => { UntypedKind::Block { exprs } => {
let mut bound_exprs = Vec::new(); let mut bound_exprs = Vec::new();
let mut last_ty = StaticType::Void;
for expr in exprs { for expr in exprs {
let b = self.bind(expr)?; bound_exprs.push(self.bind(expr)?);
last_ty = b.ty.clone();
bound_exprs.push(b);
} }
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 } => { UntypedKind::Tuple { elements } => {
@@ -270,46 +216,23 @@ impl Binder {
for e in elements { for e in elements {
bound_elems.push(self.bind(e)?); bound_elems.push(self.bind(e)?);
} }
// For now, tuple type is List(Any) Ok(self.make_node(node.identity.clone(), BoundKind::Tuple { elements: bound_elems }))
let ty = StaticType::List(Box::new(StaticType::Any));
Ok(self.make_node(node.identity.clone(), BoundKind::Tuple { elements: bound_elems }, ty))
}, },
UntypedKind::Map { entries } => { UntypedKind::Map { entries } => {
let mut bound_entries = Vec::new(); 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 { for (k, v) in entries {
// Keys must be compile-time constants for now (for Record type), bound_entries.push((self.bind(k)?, self.bind(v)?));
// 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));
} }
Ok(self.make_node(node.identity.clone(), BoundKind::Map { entries: bound_entries }))
// 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))
}, },
UntypedKind::Expansion { call, expanded } => { UntypedKind::Expansion { call, expanded } => {
let bound_expanded = self.bind(expanded)?; let bound_expanded = self.bind(expanded)?;
let ty = bound_expanded.ty.clone();
Ok(self.make_node(node.identity.clone(), BoundKind::Expansion { Ok(self.make_node(node.identity.clone(), BoundKind::Expansion {
original_call: Rc::from(call.as_ref().clone()), original_call: Rc::from(call.as_ref().clone()),
bound_expanded: Box::new(bound_expanded), bound_expanded: Box::new(bound_expanded),
}, ty)) }))
}, },
UntypedKind::Template(_) | UntypedKind::Placeholder(_) | UntypedKind::Splice(_) | UntypedKind::MacroDecl { .. } => { UntypedKind::Template(_) | UntypedKind::Placeholder(_) | UntypedKind::Splice(_) | UntypedKind::MacroDecl { .. } => {
@@ -317,52 +240,51 @@ impl Binder {
} }
UntypedKind::Extension(_) => { UntypedKind::Extension(_) => {
// Future: Delegate to extension binder
Err("Custom extensions not supported in Binder yet".to_string()) 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<Address, String> {
let current_fn_idx = self.functions.len() - 1; let current_fn_idx = self.functions.len() - 1;
// 1. Try local in current function // 1. Try local in current function
if let Some(info) = self.functions[current_fn_idx].scope.resolve(sym) { 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) // 2. Try enclosing scopes (capture chain)
for i in (0..current_fn_idx).rev() { for i in (0..current_fn_idx).rev() {
if let Some(info) = self.functions[i].scope.resolve(sym) { if let Some(info) = self.functions[i].scope.resolve(sym) {
let mut addr = Address::Local(info.slot); let mut addr = Address::Local(info.slot);
let ty = info.ty.clone();
for k in (i + 1)..=current_fn_idx { 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 // 3. Try Global
let globals = self.globals.borrow(); let globals = self.globals.borrow();
if let Some((idx, ty)) = globals.get(sym) { if let Some(idx) = globals.get(sym) {
return Ok((Address::Global(*idx), ty.clone())); return Ok(Address::Global(*idx));
} }
// 4. Global Fallback: If not found with context, try without context // 4. Global Fallback
// (Allows macros to access global built-ins like *, +, etc.)
if sym.context.is_some() { if sym.context.is_some() {
let fallback_sym = Symbol { name: sym.name.clone(), context: None }; let fallback_sym = Symbol { name: sym.name.clone(), context: None };
if let Some((idx, ty)) = globals.get(&fallback_sym) { if let Some(idx) = globals.get(&fallback_sym) {
return Ok((Address::Global(*idx), ty.clone())); return Ok(Address::Global(*idx));
} }
} }
Err(format!("Undefined variable '{}'", sym.name)) Err(format!("Undefined variable '{}'", sym.name))
} }
fn make_node(&self, identity: Identity, kind: BoundKind, ty: StaticType) -> Node<BoundKind, StaticType> { fn make_node(&self, identity: Identity, kind: BoundKind<()>) -> BoundNode {
Node { identity, kind, ty } Node { identity, kind, ty: () }
} }
} }
+42 -19
View File
@@ -2,15 +2,27 @@ use std::rc::Rc;
use crate::ast::types::{Value, StaticType, Identity}; use crate::ast::types::{Value, StaticType, Identity};
use crate::ast::nodes::Node; use crate::ast::nodes::Node;
#[derive(Debug, Clone, Copy, PartialEq)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Address { pub enum Address {
Local(u32), // Stack-Slot index (relative to frame base) Local(u32), // Stack-Slot index (relative to frame base)
Upvalue(u32), // Index in the closure's upvalue array Upvalue(u32), // Index in the closure's upvalue array
Global(u32), // Index in the global environment vector Global(u32), // Index in the global environment vector
} }
/// Trait for DSL-specific AST extensions (like Pipe, Series-Access, etc.)
pub trait BoundExtension<T>: std::fmt::Debug {
fn clone_box(&self) -> Box<dyn BoundExtension<T>>;
fn display_name(&self) -> String;
}
impl<T> Clone for Box<dyn BoundExtension<T>> {
fn clone(&self) -> Self {
self.clone_box()
}
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum BoundKind { pub enum BoundKind<T = ()> {
Nop, Nop,
Constant(Value), Constant(Value),
@@ -20,66 +32,76 @@ pub enum BoundKind {
// Variable Update (Assignment) // Variable Update (Assignment)
Set { Set {
addr: Address, addr: Address,
value: Box<Node<BoundKind, StaticType>>, value: Box<Node<BoundKind<T>, T>>,
}, },
// Variable Declaration (Local) // Variable Declaration (Local)
DefLocal { DefLocal {
slot: u32, slot: u32,
value: Box<Node<BoundKind, StaticType>>, value: Box<Node<BoundKind<T>, T>>,
captured_by: Vec<Identity>, captured_by: Vec<Identity>,
}, },
If { If {
cond: Box<Node<BoundKind, StaticType>>, cond: Box<Node<BoundKind<T>, T>>,
then_br: Box<Node<BoundKind, StaticType>>, then_br: Box<Node<BoundKind<T>, T>>,
else_br: Option<Box<Node<BoundKind, StaticType>>>, else_br: Option<Box<Node<BoundKind<T>, T>>>,
}, },
// Global Definition (Locals are implicit by stack position) // Global Definition (Locals are implicit by stack position)
DefGlobal { DefGlobal {
global_index: u32, global_index: u32,
value: Box<Node<BoundKind, StaticType>>, value: Box<Node<BoundKind<T>, T>>,
}, },
Lambda { Lambda {
param_count: u32, param_count: u32,
// The list of variables captured from enclosing scopes // The list of variables captured from enclosing scopes
upvalues: Vec<Address>, upvalues: Vec<Address>,
body: Rc<Node<BoundKind, StaticType>>, body: Rc<Node<BoundKind<T>, T>>,
}, },
Call { Call {
callee: Box<Node<BoundKind, StaticType>>, callee: Box<Node<BoundKind<T>, T>>,
args: Vec<Node<BoundKind, StaticType>>, args: Vec<Node<BoundKind<T>, T>>,
}, },
TailCall { TailCall {
callee: Box<Node<BoundKind, StaticType>>, callee: Box<Node<BoundKind<T>, T>>,
args: Vec<Node<BoundKind, StaticType>>, args: Vec<Node<BoundKind<T>, T>>,
}, },
Block { Block {
exprs: Vec<Node<BoundKind, StaticType>>, exprs: Vec<Node<BoundKind<T>, T>>,
}, },
// NEW
Tuple { Tuple {
elements: Vec<Node<BoundKind, StaticType>>, elements: Vec<Node<BoundKind<T>, T>>,
}, },
Map { Map {
entries: Vec<(Node<BoundKind, StaticType>, Node<BoundKind, StaticType>)>, entries: Vec<(Node<BoundKind<T>, T>, Node<BoundKind<T>, T>)>,
}, },
/// An expanded macro call, preserving the original call for debugging and UI. /// An expanded macro call, preserving the original call for debugging and UI.
Expansion { Expansion {
/// The original call from the untyped AST. /// The original call from the untyped AST.
original_call: Rc<Node<crate::ast::nodes::UntypedKind>>, original_call: Rc<Node<crate::ast::nodes::UntypedKind>>,
/// The result of binding the expanded AST. /// The result of binding the expanded AST.
bound_expanded: Box<Node<BoundKind, StaticType>>, bound_expanded: Box<Node<BoundKind<T>, T>>,
}, },
/// DSL-specific extension slot
Extension(Box<dyn BoundExtension<T>>),
} }
impl BoundKind { /// Type alias for a node that has been bound (addresses resolved) but not yet type-checked.
pub type BoundNode = Node<BoundKind<()>, ()>;
/// Type alias for a node that has been fully type-checked and is ready for execution.
pub type TypedNode = Node<BoundKind<StaticType>, StaticType>;
impl<T> BoundKind<T> {
pub fn display_name(&self) -> String { pub fn display_name(&self) -> String {
match self { match self {
BoundKind::Nop => "NOP".to_string(), BoundKind::Nop => "NOP".to_string(),
@@ -96,6 +118,7 @@ impl BoundKind {
BoundKind::Tuple { elements } => format!("TUPLE({})", elements.len()), BoundKind::Tuple { elements } => format!("TUPLE({})", elements.len()),
BoundKind::Map { entries } => format!("MAP({})", entries.len()), BoundKind::Map { entries } => format!("MAP({})", entries.len()),
BoundKind::Expansion { .. } => "EXPANSION".to_string(), BoundKind::Expansion { .. } => "EXPANSION".to_string(),
BoundKind::Extension(ext) => ext.display_name(),
} }
} }
} }
+9 -5
View File
@@ -1,6 +1,6 @@
use crate::ast::nodes::Node; use crate::ast::nodes::Node;
use crate::ast::compiler::bound_nodes::BoundKind; use crate::ast::compiler::bound_nodes::BoundKind;
use crate::ast::types::StaticType; use std::fmt::Debug;
/// Human-readable AST dumper for the bound AST. /// Human-readable AST dumper for the bound AST.
pub struct Dumper { pub struct Dumper {
@@ -10,7 +10,7 @@ pub struct Dumper {
impl Dumper { impl Dumper {
/// Produces a formatted string representation of the given bound AST node and its children. /// Produces a formatted string representation of the given bound AST node and its children.
pub fn dump(node: &Node<BoundKind, StaticType>) -> String { pub fn dump<T: Debug>(node: &Node<BoundKind<T>, T>) -> String {
let mut dumper = Self { let mut dumper = Self {
output: String::new(), output: String::new(),
indent: 0, indent: 0,
@@ -25,13 +25,13 @@ impl Dumper {
} }
} }
fn log(&mut self, label: &str, node: &Node<BoundKind, StaticType>) { fn log<T: Debug>(&mut self, label: &str, node: &Node<BoundKind<T>, T>) {
self.write_indent(); self.write_indent();
self.output.push_str(label); self.output.push_str(label);
self.output.push_str(&format!(" <Type: {:?}>\n", node.ty)); self.output.push_str(&format!(" <Metadata: {:?}>\n", node.ty));
} }
fn visit(&mut self, node: &Node<BoundKind, StaticType>) { fn visit<T: Debug>(&mut self, node: &Node<BoundKind<T>, T>) {
match &node.kind { match &node.kind {
BoundKind::Nop => self.log("Nop", node), BoundKind::Nop => self.log("Nop", node),
BoundKind::Constant(v) => self.log(&format!("Constant: {}", v), node), BoundKind::Constant(v) => self.log(&format!("Constant: {}", v), node),
@@ -156,6 +156,10 @@ impl Dumper {
self.visit(bound_expanded); self.visit(bound_expanded);
self.indent -= 1; self.indent -= 1;
} }
BoundKind::Extension(ext) => {
self.log(&ext.display_name(), node);
}
} }
} }
} }
+1 -1
View File
@@ -547,7 +547,7 @@ mod tests {
let expanded = expander.expand(untyped).unwrap(); let expanded = expander.expand(untyped).unwrap();
let mut global_names = HashMap::new(); 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 globals = Rc::new(RefCell::new(global_names));
let result = Binder::bind_root(globals, &expanded); let result = Binder::bind_root(globals, &expanded);
+2 -5
View File
@@ -1,19 +1,18 @@
use std::rc::Rc; use std::rc::Rc;
use crate::ast::nodes::Node; use crate::ast::nodes::Node;
use crate::ast::compiler::bound_nodes::BoundKind; use crate::ast::compiler::bound_nodes::BoundKind;
use crate::ast::types::StaticType;
pub struct TCO; pub struct TCO;
impl TCO { impl TCO {
pub fn optimize(node: Node<BoundKind, StaticType>) -> Node<BoundKind, StaticType> { pub fn optimize<T: Clone>(node: Node<BoundKind<T>, T>) -> Node<BoundKind<T>, T> {
// Top-level starts NOT in tail position usually, unless the script result is returned immediately // 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. // 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. // Let's assume standard execution context (not tail call) for the script root.
Self::transform(node, false) Self::transform(node, false)
} }
fn transform(node: Node<BoundKind, StaticType>, is_tail_position: bool) -> Node<BoundKind, StaticType> { fn transform<T: Clone>(node: Node<BoundKind<T>, T>, is_tail_position: bool) -> Node<BoundKind<T>, T> {
match node.kind { match node.kind {
BoundKind::Call { callee, args } => { BoundKind::Call { callee, args } => {
let new_callee = Box::new(Self::transform(*callee, false)); let new_callee = Box::new(Self::transform(*callee, false));
@@ -78,8 +77,6 @@ impl TCO {
BoundKind::Lambda { param_count, upvalues, body } => { BoundKind::Lambda { param_count, upvalues, body } => {
// The body of a lambda is implicitly in tail position when the lambda is called. // 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)); let new_body = Rc::new(Self::transform((*body).clone(), true));
Node { Node {
+15 -10
View File
@@ -2,13 +2,13 @@ use std::rc::Rc;
use std::cell::RefCell; use std::cell::RefCell;
use std::collections::HashMap; use std::collections::HashMap;
use std::any::Any; 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::nodes::Node;
use crate::ast::types::{Value, Object, StaticType}; use crate::ast::types::{Value, Object, StaticType};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Closure { pub struct Closure {
pub function_node: Rc<Node<BoundKind, StaticType>>, pub function_node: Rc<TypedNode>,
pub upvalues: Vec<Rc<RefCell<Value>>>, pub upvalues: Vec<Rc<RefCell<Value>>>,
} }
@@ -30,8 +30,8 @@ struct CallFrame {
/// Hook for observing VM execution (zero-cost when O::ACTIVE is false) /// Hook for observing VM execution (zero-cost when O::ACTIVE is false)
pub trait VMObserver { pub trait VMObserver {
const ACTIVE: bool = false; const ACTIVE: bool = false;
fn before_eval(&mut self, _vm: &VM, _node: &Node<BoundKind, StaticType>) {} fn before_eval(&mut self, _vm: &VM, _node: &TypedNode) {}
fn after_eval(&mut self, _vm: &VM, _node: &Node<BoundKind, StaticType>, _res: &Result<Value, String>) {} fn after_eval(&mut self, _vm: &VM, _node: &TypedNode, _res: &Result<Value, String>) {}
} }
/// Default observer that does nothing and should be optimized away /// Default observer that does nothing and should be optimized away
@@ -63,13 +63,13 @@ impl Default for TracingObserver {
impl VMObserver for TracingObserver { impl VMObserver for TracingObserver {
const ACTIVE: bool = true; const ACTIVE: bool = true;
fn before_eval(&mut self, _vm: &VM, node: &Node<BoundKind, StaticType>) { fn before_eval(&mut self, _vm: &VM, node: &TypedNode) {
let pad = self.pad(); let pad = self.pad();
self.logs.push(format!("{}{} [{}]: {{", pad, node.kind.display_name(), node.ty)); self.logs.push(format!("{}{} [{}]: {{", pad, node.kind.display_name(), node.ty));
self.indent += 1; self.indent += 1;
} }
fn after_eval(&mut self, vm: &VM, node: &Node<BoundKind, StaticType>, res: &Result<Value, String>) { fn after_eval(&mut self, vm: &VM, node: &TypedNode, res: &Result<Value, String>) {
self.indent = self.indent.saturating_sub(1); self.indent = self.indent.saturating_sub(1);
let pad = self.pad(); let pad = self.pad();
@@ -262,6 +262,11 @@ macro_rules! dispatch_eval {
BoundKind::Expansion { original_call: _, bound_expanded } => { BoundKind::Expansion { original_call: _, bound_expanded } => {
$self.$eval_method($($observer,)? 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<BoundKind, StaticType>) -> Result<Value, String> { pub fn run(&mut self, root: &TypedNode) -> Result<Value, String> {
self.stack.clear(); self.stack.clear();
self.frames.clear(); self.frames.clear();
@@ -290,7 +295,7 @@ impl VM {
result result
} }
pub fn run_with_observer<O: VMObserver>(&mut self, observer: &mut O, root: &Node<BoundKind, StaticType>) -> Result<Value, String> { pub fn run_with_observer<O: VMObserver>(&mut self, observer: &mut O, root: &TypedNode) -> Result<Value, String> {
self.stack.clear(); self.stack.clear();
self.frames.clear(); self.frames.clear();
@@ -307,12 +312,12 @@ impl VM {
/// The pure, original, zero-cost hot path. /// The pure, original, zero-cost hot path.
#[inline(always)] #[inline(always)]
fn eval(&mut self, node: &Node<BoundKind, StaticType>) -> Result<Value, String> { fn eval(&mut self, node: &TypedNode) -> Result<Value, String> {
dispatch_eval!(self, node, eval) dispatch_eval!(self, node, eval)
} }
/// The observed path for debugging. /// The observed path for debugging.
fn eval_observed<O: VMObserver>(&mut self, observer: &mut O, node: &Node<BoundKind, StaticType>) -> Result<Value, String> { fn eval_observed<O: VMObserver>(&mut self, observer: &mut O, node: &TypedNode) -> Result<Value, String> {
observer.before_eval(self, node); observer.before_eval(self, node);
// Wrap in a closure to ensure after_eval is called even on early returns (e.g. from ?) // Wrap in a closure to ensure after_eval is called even on early returns (e.g. from ?)
+4 -1
View File
@@ -80,9 +80,12 @@ mod tests {
let mut binder = Binder::new(globals.clone()); let mut binder = Binder::new(globals.clone());
let bound_ast = binder.bind(&ast).expect("Failed to bind"); 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 vm_globals = Rc::new(RefCell::new(Vec::new()));
let mut vm = VM::new(vm_globals); let mut vm = VM::new(vm_globals);
let result = vm.run(&bound_ast); let result = vm.run(&typed_ast);
match result { match result {
Ok(Value::Int(val)) => assert_eq!(val, 20, "Closure should modify outer variable"), Ok(Value::Int(val)) => assert_eq!(val, 20, "Closure should modify outer variable"),