Files
RustAst/src/ast/compiler/binder.rs
T
Michael Schimmel 82daf03522 Refactor function compiler scope handling
Introduce a `ScopeKind` enum to distinguish between root and local
scopes.
This allows the `FunctionCompiler` to correctly handle global variable
declarations in the root scope and local variables in other scopes.
The `define_variable` method is introduced to encapsulate this logic.
2026-02-25 11:19:08 +01:00

625 lines
21 KiB
Rust

use crate::ast::compiler::bound_nodes::{Address, BoundKind, BoundNode, DeclarationKind};
use crate::ast::nodes::{Node, Symbol, UntypedKind};
use crate::ast::types::{Identity, StaticType};
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
#[derive(Debug, Clone)]
struct LocalInfo {
slot: u32,
// 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)]
struct CompilerScope {
locals: HashMap<Symbol, LocalInfo>,
slot_count: u32,
}
impl CompilerScope {
fn new() -> Self {
Self {
locals: HashMap::new(),
slot_count: 0,
}
}
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: StaticType::Any,
},
);
self.slot_count += 1;
Ok(slot)
}
fn resolve(&self, sym: &Symbol) -> Option<LocalInfo> {
self.locals.get(sym).cloned()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum ScopeKind {
Root,
Local,
}
struct FunctionCompiler {
scope: CompilerScope,
upvalues: Vec<Address>,
kind: ScopeKind,
}
impl FunctionCompiler {
fn new(kind: ScopeKind) -> Self {
Self {
scope: CompilerScope::new(),
upvalues: Vec::new(),
kind,
}
}
fn define_variable(
&mut self,
name: &Symbol,
globals: &Rc<RefCell<HashMap<Symbol, u32>>>,
) -> Result<Address, String> {
match self.kind {
ScopeKind::Root => {
let mut globals_map = globals.borrow_mut();
if globals_map.contains_key(name) {
return Err(format!(
"Global variable '{}' is already defined.",
name.name
));
}
let idx = globals_map.len() as u32;
globals_map.insert(name.clone(), idx);
Ok(Address::Global(idx))
}
ScopeKind::Local => {
let slot = self.scope.define(name)?;
Ok(Address::Local(slot))
}
}
}
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);
idx
}
}
pub struct Binder {
functions: Vec<FunctionCompiler>,
// 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>>>) -> Self {
Self::with_boxed(globals, HashMap::new())
}
fn with_boxed(
globals: Rc<RefCell<HashMap<Symbol, u32>>>,
captures: HashMap<Identity, Vec<Identity>>,
) -> Self {
let mut binder = Self {
functions: Vec::new(),
globals,
capture_map: captures,
};
binder
.functions
.push(FunctionCompiler::new(ScopeKind::Root));
binder
}
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)
}
fn declare_variable(
&mut self,
name: &Symbol,
_kind: crate::ast::compiler::bound_nodes::DeclarationKind,
) -> Result<Address, String> {
let current_fn = self.functions.last_mut().unwrap();
current_fn.define_variable(name, &self.globals)
}
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)),
UntypedKind::Constant(v) => {
Ok(self.make_node(node.identity.clone(), BoundKind::Constant(v.clone())))
}
UntypedKind::Identifier(sym) => {
let addr = self.resolve_variable(sym)?;
Ok(self.make_node(
node.identity.clone(),
BoundKind::Get {
addr,
name: sym.clone(),
},
))
}
UntypedKind::Parameter(_) => {
Err("Unexpected 'Parameter' node in general binder context. This should be handled via 'bind_pattern'.".to_string())
}
UntypedKind::If {
cond,
then_br,
else_br,
} => {
let cond = self.bind(cond)?;
let then_br = self.bind(then_br)?;
let mut else_br_bound = None;
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,
},
))
}
UntypedKind::Def { target, value } => {
// Special case: Single identifier (to support recursion)
if let UntypedKind::Parameter(ref name) = target.kind {
let addr = self.declare_variable(
name,
crate::ast::compiler::bound_nodes::DeclarationKind::Variable,
)?;
let val_node = self.bind(value)?;
let captured_by = self
.capture_map
.get(&target.identity)
.cloned()
.unwrap_or_default();
Ok(self.make_node(
node.identity.clone(),
BoundKind::Define {
name: name.clone(),
addr,
kind: crate::ast::compiler::bound_nodes::DeclarationKind::Variable,
value: Box::new(val_node),
captured_by,
},
))
} else {
// Complex Destructuring Pattern
// NOTE: Destructuring definitions are NOT recursive by default
// (the variables are only available AFTER the definition)
let val_node = self.bind(value)?;
let target_node = self.bind_pattern(target, DeclarationKind::Variable)?;
Ok(self.make_node(
node.identity.clone(),
BoundKind::Destructure {
pattern: Box::new(target_node),
value: Box::new(val_node),
},
))
}
}
UntypedKind::Assign { target, value } => {
let val_node = self.bind(value)?;
if let UntypedKind::Identifier(sym) = &target.kind {
let addr = self.resolve_variable(sym)?;
Ok(self.make_node(
node.identity.clone(),
BoundKind::Set {
addr,
value: Box::new(val_node),
},
))
} else {
let target_node = self.bind_assign_pattern(target)?;
Ok(self.make_node(
node.identity.clone(),
BoundKind::Destructure {
pattern: Box::new(target_node),
value: Box::new(val_node),
},
))
}
}
UntypedKind::Lambda { params, body } => {
let identity = node.identity.clone();
self.functions.push(FunctionCompiler::new(ScopeKind::Local));
// 1. Bind the parameter pattern/tuple
let params_bound = self.bind_pattern(params, DeclarationKind::Parameter)?;
// 2. Bind the body
let body_bound = self.bind(body)?;
let compiled_fn = self.functions.pop().unwrap();
// 3. Static optimization: count total parameters needed in flat argument list
fn count_params(node: &BoundNode) -> Option<u32> {
match &node.kind {
BoundKind::Define {
kind: DeclarationKind::Parameter,
..
} => Some(1),
BoundKind::Tuple { elements } => {
let mut total = 0;
for e in elements {
total += count_params(e)?;
}
Some(total)
}
BoundKind::Nop => Some(0),
_ => None,
}
}
let positional_count = count_params(&params_bound);
Ok(self.make_node(
identity,
BoundKind::Lambda {
params: Rc::new(params_bound),
upvalues: compiled_fn.upvalues,
body: Rc::new(body_bound),
positional_count,
},
))
}
UntypedKind::Call { callee, args } => {
let callee = self.bind(callee)?;
let args = self.bind(args)?;
Ok(self.make_node(
node.identity.clone(),
BoundKind::Call {
callee: Box::new(callee),
args: Box::new(args),
},
))
}
UntypedKind::Again { args } => {
if self.functions.len() <= 1 {
return Err("'again' is only allowed inside a function or lambda.".to_string());
}
let args = self.bind(args)?;
Ok(self.make_node(
node.identity.clone(),
BoundKind::Again {
args: Box::new(args),
},
))
}
UntypedKind::Block { exprs } => {
let mut bound_exprs = Vec::new();
for expr in exprs {
bound_exprs.push(self.bind(expr)?);
}
Ok(self.make_node(
node.identity.clone(),
BoundKind::Block { exprs: bound_exprs },
))
}
UntypedKind::Tuple { elements } => {
let mut bound_elems = Vec::new();
for e in elements {
bound_elems.push(self.bind(e)?);
}
Ok(self.make_node(
node.identity.clone(),
BoundKind::Tuple {
elements: bound_elems,
},
))
}
UntypedKind::Record { fields } => {
let mut bound_fields = Vec::new();
for (k, v) in fields {
bound_fields.push((self.bind(k)?, self.bind(v)?));
}
Ok(self.make_node(
node.identity.clone(),
BoundKind::Record {
fields: bound_fields,
},
))
}
UntypedKind::Expansion { call, expanded } => {
let bound_expanded = self.bind(expanded)?;
Ok(self.make_node(
node.identity.clone(),
BoundKind::Expansion {
original_call: Rc::from(call.as_ref().clone()),
bound_expanded: Box::new(bound_expanded),
},
))
}
UntypedKind::Template(_)
| UntypedKind::Placeholder(_)
| UntypedKind::Splice(_)
| UntypedKind::MacroDecl { .. } => Err(format!(
"Macro construct {:?} found in Binder. Macros must be expanded before binding.",
node.kind
)),
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, 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));
}
// 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);
for k in (i + 1)..=current_fn_idx {
addr = Address::Upvalue(self.functions[k].add_upvalue(addr));
}
return Ok(addr);
}
}
// 3. Try Global
let globals = self.globals.borrow();
if let Some(idx) = globals.get(sym) {
return Ok(Address::Global(*idx));
}
// 4. Global Fallback
if sym.context.is_some() {
let fallback_sym = Symbol {
name: sym.name.clone(),
context: None,
};
if let Some(idx) = globals.get(&fallback_sym) {
return Ok(Address::Global(*idx));
}
}
Err(format!("Undefined variable '{}'", sym.name))
}
fn bind_pattern(
&mut self,
node: &Node<UntypedKind>,
kind: DeclarationKind,
) -> Result<BoundNode, String> {
match &node.kind {
UntypedKind::Parameter(sym) => {
let addr = self.declare_variable(sym, kind)?;
let captured_by = self
.capture_map
.get(&node.identity)
.cloned()
.unwrap_or_default();
Ok(self.make_node(
node.identity.clone(),
BoundKind::Define {
name: sym.clone(),
addr,
kind,
value: Box::new(self.make_node(node.identity.clone(), BoundKind::Nop)),
captured_by,
},
))
}
UntypedKind::Tuple { elements } => {
let mut bound_elems = Vec::new();
for e in elements {
bound_elems.push(self.bind_pattern(e, kind)?);
}
Ok(self.make_node(
node.identity.clone(),
BoundKind::Tuple {
elements: bound_elems,
},
))
}
_ => Err(format!("Invalid node in pattern: {:?}", node.kind)),
}
}
fn bind_assign_pattern(&mut self, node: &Node<UntypedKind>) -> Result<BoundNode, String> {
match &node.kind {
UntypedKind::Identifier(sym) => {
let addr = self.resolve_variable(sym)?;
Ok(self.make_node(
node.identity.clone(),
BoundKind::Set {
addr,
value: Box::new(self.make_node(node.identity.clone(), BoundKind::Nop)),
},
))
}
UntypedKind::Tuple { elements } => {
let mut bound_elems = Vec::new();
for e in elements {
bound_elems.push(self.bind_assign_pattern(e)?);
}
Ok(self.make_node(
node.identity.clone(),
BoundKind::Tuple {
elements: bound_elems,
},
))
}
_ => Err(format!(
"Invalid node in assignment pattern: {:?}",
node.kind
)),
}
}
fn make_node(&self, identity: Identity, kind: BoundKind<()>) -> BoundNode {
Node {
identity,
kind,
ty: (),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ast::parser::Parser;
#[test]
fn test_upvalue_capture_sets_is_boxed() {
// Wrap in a lambda to ensure 'x' is a local variable, not a global
let source = "(fn [] (do (def x 10) (def f (fn [] x)) x))";
let mut parser = Parser::new(source).unwrap();
let untyped = parser.parse_expression().unwrap();
let globals = Rc::new(RefCell::new(HashMap::new()));
let bound = Binder::bind_root(globals, &untyped).unwrap();
// Structure: Lambda -> Block -> [ Define(x), Define(f), Get(x) ]
if let BoundKind::Lambda { body, .. } = &bound.kind {
if let BoundKind::Block { exprs } = &body.kind {
let x_decl = &exprs[0];
if let BoundKind::Define {
captured_by, addr, ..
} = &x_decl.kind
{
assert!(matches!(addr, Address::Local(_)));
assert!(
!captured_by.is_empty(),
"Variable 'x' should have capturers because it is used in lambda 'f'"
);
} else {
panic!(
"First expression in block should be Define, got {:?}",
x_decl.kind
);
}
} else {
panic!("Lambda body should be a Block, got {:?}", body.kind);
}
} else {
panic!("Root should be a Lambda, got {:?}", bound.kind);
}
}
#[test]
fn test_no_capture_not_boxed() {
let source = "(fn [] (do (def x 10) x))";
let mut parser = Parser::new(source).unwrap();
let untyped = parser.parse_expression().unwrap();
let globals = Rc::new(RefCell::new(HashMap::new()));
let bound = Binder::bind_root(globals, &untyped).unwrap();
if let BoundKind::Lambda { body, .. } = &bound.kind {
if let BoundKind::Block { exprs } = &body.kind {
let x_decl = &exprs[0];
if let BoundKind::Define {
captured_by, addr, ..
} = &x_decl.kind
{
assert!(matches!(addr, Address::Local(_)));
assert!(
captured_by.is_empty(),
"Variable 'x' should NOT have any capturers"
);
} else {
panic!("First expression should be Define");
}
} else {
panic!("Lambda body should be a Block");
}
} else {
panic!("Root should be a Lambda");
}
}
#[test]
fn test_redefinition_error() {
let source = "(do (def x 1) (def x 2))";
let mut parser = Parser::new(source).unwrap();
let untyped = parser.parse_expression().unwrap();
let globals = Rc::new(RefCell::new(HashMap::new()));
let result = Binder::bind_root(globals, &untyped);
assert!(result.is_err());
assert!(result.unwrap_err().contains("already defined"));
}
#[test]
fn test_repro_global_redefinition() {
let globals = Rc::new(RefCell::new(HashMap::new()));
// First run: defines 'x'
let source1 = "(def x 1)";
let untyped1 = Parser::new(source1).unwrap().parse_expression().unwrap();
assert!(Binder::bind_root(globals.clone(), &untyped1).is_ok());
// Second run: attempts to redefine 'x' in the same global environment
let source2 = "(def x 2)";
let untyped2 = Parser::new(source2).unwrap().parse_expression().unwrap();
let result = Binder::bind_root(globals.clone(), &untyped2);
assert!(result.is_err());
assert!(result.unwrap_err().contains("already defined"));
}
}