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:
+57
-135
@@ -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: () }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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
@@ -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 ?)
|
||||||
|
|||||||
@@ -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"),
|
||||||
|
|||||||
Reference in New Issue
Block a user