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::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<u32, String> {
|
||||
fn define(&mut self, sym: &Symbol) -> Result<u32, String> {
|
||||
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<Address>,
|
||||
}
|
||||
|
||||
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<FunctionCompiler>,
|
||||
// Globals mapping: Symbol -> (Index, Type)
|
||||
globals: Rc<RefCell<HashMap<Symbol, (u32, StaticType)>>>,
|
||||
// Globals mapping: Symbol -> Index
|
||||
globals: Rc<RefCell<HashMap<Symbol, u32>>>,
|
||||
// Map of Declaration Identity -> List of Lambda Identities that capture it
|
||||
capture_map: HashMap<Identity, Vec<Identity>>,
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
|
||||
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 {
|
||||
functions: Vec::new(),
|
||||
globals,
|
||||
@@ -86,52 +89,37 @@ impl 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 mut binder = Self::with_boxed(globals, captures);
|
||||
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 {
|
||||
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<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 {
|
||||
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<Address, String> {
|
||||
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<BoundKind, StaticType> {
|
||||
Node { identity, kind, ty }
|
||||
fn make_node(&self, identity: Identity, kind: BoundKind<()>) -> BoundNode {
|
||||
Node { identity, kind, ty: () }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user