Refactor: Use generic Define bound kind

This commit consolidates `DefLocal` and `DefGlobal` into a single
`Define` bound kind. This simplifies the AST and makes it more
consistent.

It also introduces `DeclarationKind` to differentiate between variable
and parameter definitions.
This commit is contained in:
Michael Schimmel
2026-02-25 10:45:36 +01:00
parent f995aaf81b
commit c256a8a992
11 changed files with 280 additions and 464 deletions
+76 -84
View File
@@ -1,4 +1,4 @@
use crate::ast::compiler::bound_nodes::{Address, BoundKind, BoundNode};
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;
@@ -110,6 +110,26 @@ impl Binder {
binder.bind(node)
}
fn declare_variable(
&mut self,
name: &Symbol,
_kind: crate::ast::compiler::bound_nodes::DeclarationKind,
) -> Result<Address, String> {
if self.functions.len() == 1 {
let mut globals = self.globals.borrow_mut();
if globals.contains_key(name) {
return Err(format!("Global variable '{}' is already defined.", name.name));
}
let idx = globals.len() as u32;
globals.insert(name.clone(), idx);
Ok(Address::Global(idx))
} else {
let current_fn = self.functions.last_mut().unwrap();
let slot = current_fn.scope.define(name)?;
Ok(Address::Local(slot))
}
}
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)),
@@ -157,55 +177,33 @@ impl Binder {
UntypedKind::Def { target, value } => {
// Special case: Single identifier (to support recursion)
if let UntypedKind::Parameter(ref name) = target.kind {
let slot_or_idx = if self.functions.len() == 1 {
let mut globals = self.globals.borrow_mut();
if globals.contains_key(name) {
return Err(format!(
"Global variable '{}' is already defined.",
name.name
));
}
let idx = globals.len() as u32;
globals.insert(name.clone(), idx);
idx
} else {
let current_fn = self.functions.last_mut().unwrap();
current_fn.scope.define(name)?
};
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();
if self.functions.len() == 1 {
Ok(self.make_node(
node.identity.clone(),
BoundKind::DefGlobal {
name: name.clone(),
global_index: slot_or_idx,
value: Box::new(val_node),
},
))
} else {
let captured_by = self
.capture_map
.get(&target.identity)
.cloned()
.unwrap_or_default();
Ok(self.make_node(
node.identity.clone(),
BoundKind::DefLocal {
name: name.clone(),
slot: slot_or_idx,
value: Box::new(val_node),
captured_by,
},
))
}
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)?;
let target_node = self.bind_pattern(target, DeclarationKind::Variable)?;
Ok(self.make_node(
node.identity.clone(),
@@ -246,7 +244,7 @@ impl Binder {
self.functions.push(FunctionCompiler::new());
// 1. Bind the parameter pattern/tuple
let params_bound = self.bind_pattern(params)?;
let params_bound = self.bind_pattern(params, DeclarationKind::Parameter)?;
// 2. Bind the body
let body_bound = self.bind(body)?;
@@ -256,7 +254,10 @@ impl Binder {
// 3. Static optimization: count total parameters needed in flat argument list
fn count_params(node: &BoundNode) -> Option<u32> {
match &node.kind {
BoundKind::Parameter { .. } => Some(1),
BoundKind::Define {
kind: DeclarationKind::Parameter,
..
} => Some(1),
BoundKind::Tuple { elements } => {
let mut total = 0;
for e in elements {
@@ -411,46 +412,35 @@ impl Binder {
Err(format!("Undefined variable '{}'", sym.name))
}
fn bind_pattern(&mut self, node: &Node<UntypedKind>) -> Result<BoundNode, String> {
fn bind_pattern(
&mut self,
node: &Node<UntypedKind>,
kind: DeclarationKind,
) -> Result<BoundNode, String> {
match &node.kind {
UntypedKind::Parameter(sym) => {
if self.functions.len() == 1 {
// Global Definition in pattern
let mut globals = self.globals.borrow_mut();
if globals.contains_key(sym) {
return Err(format!(
"Global variable '{}' is already defined.",
sym.name
));
}
let idx = globals.len() as u32;
globals.insert(sym.clone(), idx);
// For Global Destructuring, we return a DefGlobal with Nop value as it's a pattern part
Ok(self.make_node(
node.identity.clone(),
BoundKind::DefGlobal {
name: sym.clone(),
global_index: idx,
value: Box::new(self.make_node(node.identity.clone(), BoundKind::Nop)),
},
))
} else {
// Local Parameter/Definition in pattern
let current_fn = self.functions.last_mut().unwrap();
let slot = current_fn.scope.define(sym)?;
Ok(self.make_node(
node.identity.clone(),
BoundKind::Parameter {
name: sym.clone(),
slot,
},
))
}
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)?);
bound_elems.push(self.bind_pattern(e, kind)?);
}
Ok(self.make_node(
node.identity.clone(),
@@ -518,18 +508,19 @@ mod tests {
let globals = Rc::new(RefCell::new(HashMap::new()));
let bound = Binder::bind_root(globals, &untyped).unwrap();
// Structure: Lambda -> Block -> [ DefLocal(x), DefLocal(f), Get(x) ]
// 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::DefLocal { captured_by, .. } = &x_decl.kind {
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 DefLocal, got {:?}",
"First expression in block should be Define, got {:?}",
x_decl.kind
);
}
@@ -553,13 +544,14 @@ mod tests {
if let BoundKind::Lambda { body, .. } = &bound.kind {
if let BoundKind::Block { exprs } = &body.kind {
let x_decl = &exprs[0];
if let BoundKind::DefLocal { captured_by, .. } = &x_decl.kind {
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 DefLocal");
panic!("First expression should be Define");
}
} else {
panic!("Lambda body should be a Block");