Formatting

This commit is contained in:
Michael Schimmel
2026-02-22 02:35:06 +01:00
parent 2123f1d279
commit 329b885c4b
25 changed files with 8053 additions and 6400 deletions
+501 -412
View File
@@ -1,412 +1,501 @@
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, BoundNode};
use crate::ast::types::{Identity, StaticType};
#[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()
}
}
struct FunctionCompiler {
scope: CompilerScope,
upvalues: Vec<Address>,
}
impl FunctionCompiler {
fn new() -> Self {
Self {
scope: CompilerScope::new(),
upvalues: Vec::new(),
}
}
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());
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)
}
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(sym) => {
// Parameters are ONLY allowed if we are inside a function (Binder stack > 1)
if self.functions.len() <= 1 {
return Err(format!("Parameter '{}' is not allowed in root scope.", sym.name));
}
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
}))
},
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 { name, value } => {
// 1. Pre-declare name to support recursion
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)?
};
// 2. Bind Value (now 'name' is visible)
let val_node = self.bind(value)?;
// 3. Return Node
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(&node.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
}))
}
},
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 {
Err("Assignment target must be an identifier".to_string())
}
},
UntypedKind::Lambda { params, body } => {
let identity = node.identity.clone();
self.functions.push(FunctionCompiler::new());
// 1. Bind the parameter pattern/tuple
let params_bound = self.bind(params)?;
// 2. Bind the body
let body_bound = self.bind(body)?;
let compiled_fn = self.functions.pop().unwrap();
// 3. Static optimization: check if parameters are purely positional
let positional_count = match &params_bound.kind {
BoundKind::Tuple { elements } => {
let mut count = 0;
let mut all_params = true;
for e in elements {
if matches!(e.kind, BoundKind::Parameter { .. }) {
count += 1;
} else {
all_params = false;
break;
}
}
if all_params { Some(count) } else { None }
}
BoundKind::Parameter { .. } => Some(1),
_ => None,
};
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::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 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 -> [ DefLocal(x), DefLocal(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 {
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 {:?}", 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::DefLocal { captured_by, .. } = &x_decl.kind {
assert!(captured_by.is_empty(), "Variable 'x' should NOT have any capturers");
} else {
panic!("First expression should be DefLocal");
}
} 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"));
}
}
use crate::ast::compiler::bound_nodes::{Address, BoundKind, BoundNode};
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()
}
}
struct FunctionCompiler {
scope: CompilerScope,
upvalues: Vec<Address>,
}
impl FunctionCompiler {
fn new() -> Self {
Self {
scope: CompilerScope::new(),
upvalues: Vec::new(),
}
}
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());
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)
}
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(sym) => {
// Parameters are ONLY allowed if we are inside a function (Binder stack > 1)
if self.functions.len() <= 1 {
return Err(format!(
"Parameter '{}' is not allowed in root scope.",
sym.name
));
}
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,
},
))
}
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 { name, value } => {
// 1. Pre-declare name to support recursion
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)?
};
// 2. Bind Value (now 'name' is visible)
let val_node = self.bind(value)?;
// 3. Return Node
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(&node.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,
},
))
}
}
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 {
Err("Assignment target must be an identifier".to_string())
}
}
UntypedKind::Lambda { params, body } => {
let identity = node.identity.clone();
self.functions.push(FunctionCompiler::new());
// 1. Bind the parameter pattern/tuple
let params_bound = self.bind(params)?;
// 2. Bind the body
let body_bound = self.bind(body)?;
let compiled_fn = self.functions.pop().unwrap();
// 3. Static optimization: check if parameters are purely positional
let positional_count = match &params_bound.kind {
BoundKind::Tuple { elements } => {
let mut count = 0;
let mut all_params = true;
for e in elements {
if matches!(e.kind, BoundKind::Parameter { .. }) {
count += 1;
} else {
all_params = false;
break;
}
}
if all_params { Some(count) } else { None }
}
BoundKind::Parameter { .. } => Some(1),
_ => None,
};
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::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 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 -> [ DefLocal(x), DefLocal(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 {
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 {:?}",
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::DefLocal { captured_by, .. } = &x_decl.kind {
assert!(
captured_by.is_empty(),
"Variable 'x' should NOT have any capturers"
);
} else {
panic!("First expression should be DefLocal");
}
} 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"));
}
}
+253 -194
View File
@@ -1,194 +1,253 @@
use std::rc::Rc;
use crate::ast::types::{Value, StaticType, Identity};
use crate::ast::nodes::{Node, Symbol};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Address {
Local(u32), // Stack-Slot index (relative to frame base)
Upvalue(u32), // Index in the closure's upvalue array
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()
}
}
/// Type alias for a node that has been bound. Defaults to untyped (T = ()).
pub type BoundNode<T = ()> = Node<BoundKind<T>, T>;
/// Type alias for a node that has been fully type-checked.
pub type TypedNode = BoundNode<StaticType>;
#[derive(Debug, Clone)]
pub enum BoundKind<T = ()> {
Nop,
Constant(Value),
/// A positional parameter in a Lambda's parameter list.
Parameter {
name: Symbol,
slot: u32,
},
// Variable Access (Resolved)
Get {
addr: Address,
name: Symbol,
},
// Variable Update (Assignment)
Set {
addr: Address,
value: Box<BoundNode<T>>,
},
// Variable Declaration (Local)
DefLocal {
name: Symbol,
slot: u32,
value: Box<BoundNode<T>>,
captured_by: Vec<Identity>,
},
If {
cond: Box<BoundNode<T>>,
then_br: Box<BoundNode<T>>,
else_br: Option<Box<BoundNode<T>>>,
},
// Global Definition (Locals are implicit by stack position)
DefGlobal {
name: Symbol,
global_index: u32,
value: Box<BoundNode<T>>,
},
Lambda {
params: Rc<BoundNode<T>>,
// The list of variables captured from enclosing scopes
upvalues: Vec<Address>,
body: Rc<BoundNode<T>>,
/// Static optimization: number of positional parameters if the pattern is flat.
positional_count: Option<u32>,
},
Call {
callee: Box<BoundNode<T>>,
args: Box<BoundNode<T>>,
},
Block {
exprs: Vec<BoundNode<T>>,
},
Tuple {
elements: Vec<BoundNode<T>>,
},
Record {
fields: Vec<RecordField<T>>,
},
/// An expanded macro call, preserving the original call for debugging and UI.
Expansion {
/// The original call from the untyped AST.
original_call: Rc<Node<crate::ast::nodes::UntypedKind>>,
/// The result of binding the expanded AST.
bound_expanded: Box<BoundNode<T>>,
},
/// DSL-specific extension slot
Extension(Box<dyn BoundExtension<T>>),
}
impl<T> PartialEq for BoundKind<T>
where T: PartialEq {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(BoundKind::Nop, BoundKind::Nop) => true,
(BoundKind::Constant(a), BoundKind::Constant(b)) => a == b,
(BoundKind::Parameter { name: na, slot: sa }, BoundKind::Parameter { name: nb, slot: sb }) => {
na == nb && sa == sb
}
(BoundKind::Get { addr: aa, name: na }, BoundKind::Get { addr: ab, name: nb }) => {
aa == ab && na == nb
}
(BoundKind::Set { addr: aa, value: va }, BoundKind::Set { addr: ab, value: vb }) => {
aa == ab && va == vb
}
(BoundKind::DefLocal { name: na, slot: sa, value: va, captured_by: ca },
BoundKind::DefLocal { name: nb, slot: sb, value: vb, captured_by: cb }) => {
na == nb && sa == sb && va == vb && ca == cb
}
(BoundKind::If { cond: ca, then_br: ta, else_br: ea },
BoundKind::If { cond: cb, then_br: tb, else_br: eb }) => {
ca == cb && ta == tb && ea == eb
}
(BoundKind::DefGlobal { name: na, global_index: ga, value: va },
BoundKind::DefGlobal { name: nb, global_index: gb, value: vb }) => {
na == nb && ga == gb && va == vb
}
(BoundKind::Lambda { params: pa, upvalues: ua, body: ba, positional_count: pca },
BoundKind::Lambda { params: pb, upvalues: ub, body: bb, positional_count: pcb }) => {
pa == pb && ua == ub && ba == bb && pca == pcb
}
(BoundKind::Call { callee: ca, args: aa }, BoundKind::Call { callee: cb, args: ab }) => {
ca == cb && aa == ab
}
(BoundKind::Block { exprs: ea }, BoundKind::Block { exprs: eb }) => {
ea == eb
}
(BoundKind::Tuple { elements: ea }, BoundKind::Tuple { elements: eb }) => {
ea == eb
}
(BoundKind::Record { fields: fa }, BoundKind::Record { fields: fb }) => {
fa == fb
}
(BoundKind::Expansion { original_call: oa, bound_expanded: ba },
BoundKind::Expansion { original_call: ob, bound_expanded: bb }) => {
Rc::ptr_eq(oa, ob) && ba == bb
}
(BoundKind::Extension(_), BoundKind::Extension(_)) => false, // Currently no equality for extensions
_ => false,
}
}
}
/// A single field in a Record literal (Key-Value pair)
pub type RecordField<T> = (BoundNode<T>, BoundNode<T>);
impl<T> BoundKind<T> {
pub fn display_name(&self) -> String {
match self {
BoundKind::Nop => "NOP".to_string(),
BoundKind::Constant(v) => format!("CONST({})", v),
BoundKind::Parameter { name, slot } => format!("PARAM({}, Slot:{})", name.name, slot),
BoundKind::Get { addr, name } => format!("GET({}, {:?})", name.name, addr),
BoundKind::Set { addr, .. } => format!("SET({:?})", addr),
BoundKind::DefLocal { name, slot, .. } => format!("DEF_LOCAL({}, Slot:{})", name.name, slot),
BoundKind::If { .. } => "IF".to_string(),
BoundKind::DefGlobal { name, global_index, .. } => format!("DEF_GLOBAL({}, Idx:{})", name.name, global_index),
BoundKind::Lambda { params, upvalues, .. } => {
let p_str = match &params.kind {
BoundKind::Tuple { elements } => format!("p:{}", elements.len()),
_ => "p:1".to_string(),
};
format!("LAMBDA({}, Captures:{})", p_str, upvalues.len())
},
BoundKind::Call { .. } => "CALL".to_string(),
BoundKind::Block { .. } => "BLOCK".to_string(),
BoundKind::Tuple { elements } => format!("TUPLE({})", elements.len()),
BoundKind::Record { fields } => format!("RECORD({})", fields.len()),
BoundKind::Expansion { .. } => "EXPANSION".to_string(),
BoundKind::Extension(ext) => ext.display_name(),
}
}
}
use crate::ast::nodes::{Node, Symbol};
use crate::ast::types::{Identity, StaticType, Value};
use std::rc::Rc;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Address {
Local(u32), // Stack-Slot index (relative to frame base)
Upvalue(u32), // Index in the closure's upvalue array
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()
}
}
/// Type alias for a node that has been bound. Defaults to untyped (T = ()).
pub type BoundNode<T = ()> = Node<BoundKind<T>, T>;
/// Type alias for a node that has been fully type-checked.
pub type TypedNode = BoundNode<StaticType>;
#[derive(Debug, Clone)]
pub enum BoundKind<T = ()> {
Nop,
Constant(Value),
/// A positional parameter in a Lambda's parameter list.
Parameter {
name: Symbol,
slot: u32,
},
// Variable Access (Resolved)
Get {
addr: Address,
name: Symbol,
},
// Variable Update (Assignment)
Set {
addr: Address,
value: Box<BoundNode<T>>,
},
// Variable Declaration (Local)
DefLocal {
name: Symbol,
slot: u32,
value: Box<BoundNode<T>>,
captured_by: Vec<Identity>,
},
If {
cond: Box<BoundNode<T>>,
then_br: Box<BoundNode<T>>,
else_br: Option<Box<BoundNode<T>>>,
},
// Global Definition (Locals are implicit by stack position)
DefGlobal {
name: Symbol,
global_index: u32,
value: Box<BoundNode<T>>,
},
Lambda {
params: Rc<BoundNode<T>>,
// The list of variables captured from enclosing scopes
upvalues: Vec<Address>,
body: Rc<BoundNode<T>>,
/// Static optimization: number of positional parameters if the pattern is flat.
positional_count: Option<u32>,
},
Call {
callee: Box<BoundNode<T>>,
args: Box<BoundNode<T>>,
},
Block {
exprs: Vec<BoundNode<T>>,
},
Tuple {
elements: Vec<BoundNode<T>>,
},
Record {
fields: Vec<RecordField<T>>,
},
/// An expanded macro call, preserving the original call for debugging and UI.
Expansion {
/// The original call from the untyped AST.
original_call: Rc<Node<crate::ast::nodes::UntypedKind>>,
/// The result of binding the expanded AST.
bound_expanded: Box<BoundNode<T>>,
},
/// DSL-specific extension slot
Extension(Box<dyn BoundExtension<T>>),
}
impl<T> PartialEq for BoundKind<T>
where
T: PartialEq,
{
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(BoundKind::Nop, BoundKind::Nop) => true,
(BoundKind::Constant(a), BoundKind::Constant(b)) => a == b,
(
BoundKind::Parameter { name: na, slot: sa },
BoundKind::Parameter { name: nb, slot: sb },
) => na == nb && sa == sb,
(BoundKind::Get { addr: aa, name: na }, BoundKind::Get { addr: ab, name: nb }) => {
aa == ab && na == nb
}
(
BoundKind::Set {
addr: aa,
value: va,
},
BoundKind::Set {
addr: ab,
value: vb,
},
) => aa == ab && va == vb,
(
BoundKind::DefLocal {
name: na,
slot: sa,
value: va,
captured_by: ca,
},
BoundKind::DefLocal {
name: nb,
slot: sb,
value: vb,
captured_by: cb,
},
) => na == nb && sa == sb && va == vb && ca == cb,
(
BoundKind::If {
cond: ca,
then_br: ta,
else_br: ea,
},
BoundKind::If {
cond: cb,
then_br: tb,
else_br: eb,
},
) => ca == cb && ta == tb && ea == eb,
(
BoundKind::DefGlobal {
name: na,
global_index: ga,
value: va,
},
BoundKind::DefGlobal {
name: nb,
global_index: gb,
value: vb,
},
) => na == nb && ga == gb && va == vb,
(
BoundKind::Lambda {
params: pa,
upvalues: ua,
body: ba,
positional_count: pca,
},
BoundKind::Lambda {
params: pb,
upvalues: ub,
body: bb,
positional_count: pcb,
},
) => pa == pb && ua == ub && ba == bb && pca == pcb,
(
BoundKind::Call {
callee: ca,
args: aa,
},
BoundKind::Call {
callee: cb,
args: ab,
},
) => ca == cb && aa == ab,
(BoundKind::Block { exprs: ea }, BoundKind::Block { exprs: eb }) => ea == eb,
(BoundKind::Tuple { elements: ea }, BoundKind::Tuple { elements: eb }) => ea == eb,
(BoundKind::Record { fields: fa }, BoundKind::Record { fields: fb }) => fa == fb,
(
BoundKind::Expansion {
original_call: oa,
bound_expanded: ba,
},
BoundKind::Expansion {
original_call: ob,
bound_expanded: bb,
},
) => Rc::ptr_eq(oa, ob) && ba == bb,
(BoundKind::Extension(_), BoundKind::Extension(_)) => false, // Currently no equality for extensions
_ => false,
}
}
}
/// A single field in a Record literal (Key-Value pair)
pub type RecordField<T> = (BoundNode<T>, BoundNode<T>);
impl<T> BoundKind<T> {
pub fn display_name(&self) -> String {
match self {
BoundKind::Nop => "NOP".to_string(),
BoundKind::Constant(v) => format!("CONST({})", v),
BoundKind::Parameter { name, slot } => format!("PARAM({}, Slot:{})", name.name, slot),
BoundKind::Get { addr, name } => format!("GET({}, {:?})", name.name, addr),
BoundKind::Set { addr, .. } => format!("SET({:?})", addr),
BoundKind::DefLocal { name, slot, .. } => {
format!("DEF_LOCAL({}, Slot:{})", name.name, slot)
}
BoundKind::If { .. } => "IF".to_string(),
BoundKind::DefGlobal {
name, global_index, ..
} => format!("DEF_GLOBAL({}, Idx:{})", name.name, global_index),
BoundKind::Lambda {
params, upvalues, ..
} => {
let p_str = match &params.kind {
BoundKind::Tuple { elements } => format!("p:{}", elements.len()),
_ => "p:1".to_string(),
};
format!("LAMBDA({}, Captures:{})", p_str, upvalues.len())
}
BoundKind::Call { .. } => "CALL".to_string(),
BoundKind::Block { .. } => "BLOCK".to_string(),
BoundKind::Tuple { elements } => format!("TUPLE({})", elements.len()),
BoundKind::Record { fields } => format!("RECORD({})", fields.len()),
BoundKind::Expansion { .. } => "EXPANSION".to_string(),
BoundKind::Extension(ext) => ext.display_name(),
}
}
}
+236 -195
View File
@@ -1,195 +1,236 @@
use crate::ast::nodes::Node;
use crate::ast::compiler::bound_nodes::BoundKind;
use crate::ast::types::Value;
use crate::ast::vm::Closure;
use std::fmt::Debug;
/// Human-readable AST dumper for the bound AST.
pub struct Dumper {
output: String,
indent: usize,
}
impl Dumper {
/// Produces a formatted string representation of the given bound AST node and its children.
pub fn dump<T: Debug>(node: &Node<BoundKind<T>, T>) -> String {
let mut dumper = Self {
output: String::new(),
indent: 0,
};
dumper.visit(node);
dumper.output
}
fn write_indent(&mut self) {
for _ in 0..self.indent {
self.output.push_str(" ");
}
}
fn log<T: Debug>(&mut self, label: &str, node: &Node<BoundKind<T>, T>) {
self.write_indent();
self.output.push_str(label);
self.output.push_str(&format!(" <Metadata: {:?}>\n", node.ty));
}
fn visit<T: Debug>(&mut self, node: &Node<BoundKind<T>, T>) {
match &node.kind {
BoundKind::Nop => self.log("Nop", node),
BoundKind::Constant(v) => {
self.log(&format!("Constant: {}", v), node);
// Introspect Closure AST if possible
if let Value::Object(obj) = v
&& let Some(closure) = obj.as_any().downcast_ref::<Closure>()
{
self.indent += 1;
self.write_indent();
self.output.push_str("--- Specialized Body ---\n");
// We need to cast the inner TypedNode to the generic T required by visit.
// Since Dumper is generic over T, but Closure stores TypedNode (where T = StaticType),
// we can only fully dump if T is StaticType.
// However, we can hack it by creating a new Dumper for the inner AST string.
// We can't call self.visit because types mismatch if T != StaticType.
// So we just recursively dump to string and append.
let inner_dump = Dumper::dump(&closure.function_node);
for line in inner_dump.lines() {
self.write_indent();
self.output.push_str(line);
self.output.push('\n');
}
self.indent -= 1;
}
},
BoundKind::Get { addr, name } => self.log(&format!("Get: {} ({:?})", name.name, addr), node),
BoundKind::Set { addr, value } => {
self.log(&format!("Set: {:?}", addr), node);
self.indent += 1;
self.visit(value);
self.indent -= 1;
}
BoundKind::DefLocal { name, slot, value, captured_by } => {
let capture_info = if captured_by.is_empty() {
String::from("not captured")
} else {
format!("captured by {} lambdas", captured_by.len())
};
self.log(&format!("DefLocal (Name: '{}', Slot: {}, {})", name.name, slot, capture_info), node);
self.indent += 1;
if !captured_by.is_empty() {
for capturer in captured_by {
self.write_indent();
self.output.push_str(&format!("- Capturer: Lambda at line {}, col {}\n",
capturer.location.line, capturer.location.col));
}
}
self.visit(value);
self.indent -= 1;
}
BoundKind::DefGlobal { name, global_index, value } => {
self.log(&format!("DefGlobal (Name: '{}', Index: {})", name.name, global_index), node);
self.indent += 1;
self.visit(value);
self.indent -= 1;
}
BoundKind::If { cond, then_br, else_br } => {
self.log("If", node);
self.indent += 1;
self.write_indent();
self.output.push_str("Condition:\n");
self.visit(cond);
self.write_indent();
self.output.push_str("Then:\n");
self.visit(then_br);
if let Some(e) = else_br {
self.write_indent();
self.output.push_str("Else:\n");
self.visit(e);
}
self.indent -= 1;
}
BoundKind::Lambda { params, upvalues, body, .. } => {
self.log(&format!("Lambda (Upvalues: {})", upvalues.len()), node);
self.indent += 1;
self.write_indent();
self.output.push_str("Parameters:\n");
self.visit(params);
if !upvalues.is_empty() {
self.write_indent();
self.output.push_str(&format!("Upvalues: {:?}\n", upvalues));
}
self.visit(body);
self.indent -= 1;
}
BoundKind::Parameter { name, slot } => {
self.log(&format!("Parameter (Name: '{}', Slot: {})", name.name, slot), node);
}
BoundKind::Call { callee, args } => {
self.log("Call", node);
self.indent += 1;
self.write_indent();
self.output.push_str("Callee:\n");
self.visit(callee);
self.write_indent();
self.output.push_str("Arguments:\n");
self.visit(args);
self.indent -= 1;
}
BoundKind::Block { exprs } => {
self.log("Block", node);
self.indent += 1;
for expr in exprs {
self.visit(expr);
}
self.indent -= 1;
}
BoundKind::Tuple { elements } => {
self.log("Tuple", node);
self.indent += 1;
for el in elements {
self.visit(el);
}
self.indent -= 1;
}
BoundKind::Record { fields } => {
self.log("Record", node);
self.indent += 1;
for (k, v) in fields {
self.visit(k);
self.visit(v);
}
self.indent -= 1;
}
BoundKind::Expansion { original_call, bound_expanded } => {
self.log(&format!("Expansion (Original: {:?})", original_call.kind), node);
self.indent += 1;
self.visit(bound_expanded);
self.indent -= 1;
}
BoundKind::Extension(ext) => {
self.log(&ext.display_name(), node);
}
}
}
}
use crate::ast::compiler::bound_nodes::BoundKind;
use crate::ast::nodes::Node;
use crate::ast::types::Value;
use crate::ast::vm::Closure;
use std::fmt::Debug;
/// Human-readable AST dumper for the bound AST.
pub struct Dumper {
output: String,
indent: usize,
}
impl Dumper {
/// Produces a formatted string representation of the given bound AST node and its children.
pub fn dump<T: Debug>(node: &Node<BoundKind<T>, T>) -> String {
let mut dumper = Self {
output: String::new(),
indent: 0,
};
dumper.visit(node);
dumper.output
}
fn write_indent(&mut self) {
for _ in 0..self.indent {
self.output.push_str(" ");
}
}
fn log<T: Debug>(&mut self, label: &str, node: &Node<BoundKind<T>, T>) {
self.write_indent();
self.output.push_str(label);
self.output
.push_str(&format!(" <Metadata: {:?}>\n", node.ty));
}
fn visit<T: Debug>(&mut self, node: &Node<BoundKind<T>, T>) {
match &node.kind {
BoundKind::Nop => self.log("Nop", node),
BoundKind::Constant(v) => {
self.log(&format!("Constant: {}", v), node);
// Introspect Closure AST if possible
if let Value::Object(obj) = v
&& let Some(closure) = obj.as_any().downcast_ref::<Closure>()
{
self.indent += 1;
self.write_indent();
self.output.push_str("--- Specialized Body ---\n");
// We need to cast the inner TypedNode to the generic T required by visit.
// Since Dumper is generic over T, but Closure stores TypedNode (where T = StaticType),
// we can only fully dump if T is StaticType.
// However, we can hack it by creating a new Dumper for the inner AST string.
// We can't call self.visit because types mismatch if T != StaticType.
// So we just recursively dump to string and append.
let inner_dump = Dumper::dump(&closure.function_node);
for line in inner_dump.lines() {
self.write_indent();
self.output.push_str(line);
self.output.push('\n');
}
self.indent -= 1;
}
}
BoundKind::Get { addr, name } => {
self.log(&format!("Get: {} ({:?})", name.name, addr), node)
}
BoundKind::Set { addr, value } => {
self.log(&format!("Set: {:?}", addr), node);
self.indent += 1;
self.visit(value);
self.indent -= 1;
}
BoundKind::DefLocal {
name,
slot,
value,
captured_by,
} => {
let capture_info = if captured_by.is_empty() {
String::from("not captured")
} else {
format!("captured by {} lambdas", captured_by.len())
};
self.log(
&format!(
"DefLocal (Name: '{}', Slot: {}, {})",
name.name, slot, capture_info
),
node,
);
self.indent += 1;
if !captured_by.is_empty() {
for capturer in captured_by {
self.write_indent();
self.output.push_str(&format!(
"- Capturer: Lambda at line {}, col {}\n",
capturer.location.line, capturer.location.col
));
}
}
self.visit(value);
self.indent -= 1;
}
BoundKind::DefGlobal {
name,
global_index,
value,
} => {
self.log(
&format!("DefGlobal (Name: '{}', Index: {})", name.name, global_index),
node,
);
self.indent += 1;
self.visit(value);
self.indent -= 1;
}
BoundKind::If {
cond,
then_br,
else_br,
} => {
self.log("If", node);
self.indent += 1;
self.write_indent();
self.output.push_str("Condition:\n");
self.visit(cond);
self.write_indent();
self.output.push_str("Then:\n");
self.visit(then_br);
if let Some(e) = else_br {
self.write_indent();
self.output.push_str("Else:\n");
self.visit(e);
}
self.indent -= 1;
}
BoundKind::Lambda {
params,
upvalues,
body,
..
} => {
self.log(&format!("Lambda (Upvalues: {})", upvalues.len()), node);
self.indent += 1;
self.write_indent();
self.output.push_str("Parameters:\n");
self.visit(params);
if !upvalues.is_empty() {
self.write_indent();
self.output.push_str(&format!("Upvalues: {:?}\n", upvalues));
}
self.visit(body);
self.indent -= 1;
}
BoundKind::Parameter { name, slot } => {
self.log(
&format!("Parameter (Name: '{}', Slot: {})", name.name, slot),
node,
);
}
BoundKind::Call { callee, args } => {
self.log("Call", node);
self.indent += 1;
self.write_indent();
self.output.push_str("Callee:\n");
self.visit(callee);
self.write_indent();
self.output.push_str("Arguments:\n");
self.visit(args);
self.indent -= 1;
}
BoundKind::Block { exprs } => {
self.log("Block", node);
self.indent += 1;
for expr in exprs {
self.visit(expr);
}
self.indent -= 1;
}
BoundKind::Tuple { elements } => {
self.log("Tuple", node);
self.indent += 1;
for el in elements {
self.visit(el);
}
self.indent -= 1;
}
BoundKind::Record { fields } => {
self.log("Record", node);
self.indent += 1;
for (k, v) in fields {
self.visit(k);
self.visit(v);
}
self.indent -= 1;
}
BoundKind::Expansion {
original_call,
bound_expanded,
} => {
self.log(
&format!("Expansion (Original: {:?})", original_call.kind),
node,
);
self.indent += 1;
self.visit(bound_expanded);
self.indent -= 1;
}
BoundKind::Extension(ext) => {
self.log(&ext.display_name(), node);
}
}
}
}
+95 -85
View File
@@ -1,85 +1,95 @@
use std::collections::HashMap;
use crate::ast::compiler::bound_nodes::{BoundKind, Address, BoundNode};
/// A pass that collects all global function definitions (lambdas) into a registry.
/// This allows the Specializer to retrieve the original AST of a function for monomorphization.
pub struct LambdaCollector<'a, T> {
registry: &'a mut HashMap<u32, BoundNode<T>>,
}
impl<'a, T: Clone> LambdaCollector<'a, T> {
/// Performs a full traversal of the AST and populates the provided registry.
pub fn collect(node: &BoundNode<T>, registry: &'a mut HashMap<u32, BoundNode<T>>) {
let mut collector = Self { registry };
collector.visit(node);
}
fn visit(&mut self, node: &BoundNode<T>) {
match &node.kind {
BoundKind::Block { exprs } => {
for expr in exprs {
self.visit(expr);
}
}
BoundKind::DefGlobal { global_index, value, .. } => {
// Register the full Lambda node as the template.
if let BoundKind::Lambda { .. } = &value.kind {
self.registry.insert(*global_index, (*value).as_ref().clone());
}
self.visit(value);
}
BoundKind::Set { addr, value } => {
// Also track assignments to globals if they hold lambdas.
if let Address::Global(global_index) = addr
&& let BoundKind::Lambda { .. } = &value.kind
{
self.registry.insert(*global_index, (*value).as_ref().clone());
}
self.visit(value);
}
BoundKind::If { cond, then_br, else_br } => {
self.visit(cond);
self.visit(then_br);
if let Some(e) = else_br {
self.visit(e);
}
}
BoundKind::Lambda { params, body, .. } => {
self.visit(params);
self.visit(body);
}
BoundKind::DefLocal { value, .. } => {
self.visit(value);
}
BoundKind::Call { callee, args } => {
self.visit(callee);
self.visit(args);
}
BoundKind::Tuple { elements } => {
for el in elements {
self.visit(el);
}
}
BoundKind::Record { fields } => {
for (k, v) in fields {
self.visit(k);
self.visit(v);
}
}
BoundKind::Expansion { bound_expanded, .. } => {
self.visit(bound_expanded);
}
_ => {} // Leaf nodes
}
}
}
use crate::ast::compiler::bound_nodes::{Address, BoundKind, BoundNode};
use std::collections::HashMap;
/// A pass that collects all global function definitions (lambdas) into a registry.
/// This allows the Specializer to retrieve the original AST of a function for monomorphization.
pub struct LambdaCollector<'a, T> {
registry: &'a mut HashMap<u32, BoundNode<T>>,
}
impl<'a, T: Clone> LambdaCollector<'a, T> {
/// Performs a full traversal of the AST and populates the provided registry.
pub fn collect(node: &BoundNode<T>, registry: &'a mut HashMap<u32, BoundNode<T>>) {
let mut collector = Self { registry };
collector.visit(node);
}
fn visit(&mut self, node: &BoundNode<T>) {
match &node.kind {
BoundKind::Block { exprs } => {
for expr in exprs {
self.visit(expr);
}
}
BoundKind::DefGlobal {
global_index,
value,
..
} => {
// Register the full Lambda node as the template.
if let BoundKind::Lambda { .. } = &value.kind {
self.registry
.insert(*global_index, (*value).as_ref().clone());
}
self.visit(value);
}
BoundKind::Set { addr, value } => {
// Also track assignments to globals if they hold lambdas.
if let Address::Global(global_index) = addr
&& let BoundKind::Lambda { .. } = &value.kind
{
self.registry
.insert(*global_index, (*value).as_ref().clone());
}
self.visit(value);
}
BoundKind::If {
cond,
then_br,
else_br,
} => {
self.visit(cond);
self.visit(then_br);
if let Some(e) = else_br {
self.visit(e);
}
}
BoundKind::Lambda { params, body, .. } => {
self.visit(params);
self.visit(body);
}
BoundKind::DefLocal { value, .. } => {
self.visit(value);
}
BoundKind::Call { callee, args } => {
self.visit(callee);
self.visit(args);
}
BoundKind::Tuple { elements } => {
for el in elements {
self.visit(el);
}
}
BoundKind::Record { fields } => {
for (k, v) in fields {
self.visit(k);
self.visit(v);
}
}
BoundKind::Expansion { bound_expanded, .. } => {
self.visit(bound_expanded);
}
_ => {} // Leaf nodes
}
}
}
+765 -609
View File
File diff suppressed because it is too large Load Diff
+20 -20
View File
@@ -1,20 +1,20 @@
pub mod binder;
pub mod bound_nodes;
pub mod tco;
pub mod upvalues;
pub mod dumper;
pub mod macros;
pub mod type_checker;
pub mod specializer;
pub mod optimizer;
pub mod lambda_collector;
pub use binder::*;
pub use bound_nodes::*;
pub use tco::*;
pub use upvalues::*;
pub use dumper::*;
pub use macros::*;
pub use type_checker::*;
pub use specializer::*;
pub use optimizer::*;
pub mod binder;
pub mod bound_nodes;
pub mod dumper;
pub mod lambda_collector;
pub mod macros;
pub mod optimizer;
pub mod specializer;
pub mod tco;
pub mod type_checker;
pub mod upvalues;
pub use binder::*;
pub use bound_nodes::*;
pub use dumper::*;
pub use macros::*;
pub use optimizer::*;
pub use specializer::*;
pub use tco::*;
pub use type_checker::*;
pub use upvalues::*;
File diff suppressed because it is too large Load Diff
+313 -232
View File
@@ -1,232 +1,313 @@
use std::collections::HashMap;
use std::rc::Rc;
use std::cell::RefCell;
use crate::ast::types::{StaticType, Value, Signature};
use crate::ast::compiler::bound_nodes::{BoundKind, Address, TypedNode, BoundNode};
use crate::ast::nodes::Node;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct MonoCacheKey {
pub address: Address,
pub arg_types: Vec<StaticType>,
}
pub type CompileFunc = Rc<dyn Fn(BoundNode, &[StaticType]) -> Result<(Value, StaticType), String>>;
pub type RtlLookupFunc = Rc<dyn Fn(&str, &[StaticType]) -> Option<(Value, StaticType)>>;
pub trait FunctionRegistry {
fn resolve(&self, addr: Address) -> Option<BoundNode>;
}
pub type MonoCache = HashMap<MonoCacheKey, (Value, StaticType)>;
pub struct Specializer {
pub cache: Rc<RefCell<MonoCache>>,
registry: Option<Rc<dyn FunctionRegistry>>,
compiler: Option<CompileFunc>,
rtl_lookup: Option<RtlLookupFunc>,
}
impl Specializer {
pub fn new(
registry: Option<Rc<dyn FunctionRegistry>>,
compiler: Option<CompileFunc>,
rtl_lookup: Option<RtlLookupFunc>,
cache: Option<Rc<RefCell<MonoCache>>>,
) -> Self {
Self {
cache: cache.unwrap_or_else(|| Rc::new(RefCell::new(HashMap::new()))),
registry,
compiler,
rtl_lookup,
}
}
pub fn specialize(&self, node: TypedNode) -> TypedNode {
self.visit_node(node)
}
fn visit_node(&self, node: TypedNode) -> TypedNode {
let (new_kind, new_ty) = match node.kind {
BoundKind::Call { callee, args } => {
let (new_callee, new_args, ret_ty) = self.specialize_call_logic(*callee, *args, node.ty.clone());
(BoundKind::Call { callee: Box::new(new_callee), args: Box::new(new_args) }, ret_ty)
},
// Recursive traversal for other nodes
BoundKind::If { cond, then_br, else_br } => {
let cond = Box::new(self.visit_node(*cond));
let then_br = Box::new(self.visit_node(*then_br));
let else_br = else_br.map(|e| Box::new(self.visit_node(*e)));
(BoundKind::If { cond, then_br, else_br }, node.ty)
},
BoundKind::Block { exprs } => {
let exprs = exprs.into_iter().map(|e| self.visit_node(e)).collect();
(BoundKind::Block { exprs }, node.ty)
},
BoundKind::Lambda { params, upvalues, body, positional_count } => {
let params = Rc::new(self.visit_node(params.as_ref().clone()));
let body = Rc::new(self.visit_node((*body).clone()));
(BoundKind::Lambda { params, upvalues, body, positional_count }, node.ty)
},
BoundKind::DefLocal { name, slot, value, captured_by } => {
let value = Box::new(self.visit_node(*value));
(BoundKind::DefLocal { name, slot, value, captured_by }, node.ty)
},
BoundKind::DefGlobal { name, global_index, value } => {
let value = Box::new(self.visit_node(*value));
(BoundKind::DefGlobal { name, global_index, value }, node.ty)
},
BoundKind::Set { addr, value } => {
let value = Box::new(self.visit_node(*value));
(BoundKind::Set { addr, value }, node.ty)
},
BoundKind::Tuple { elements } => {
let elements = elements.into_iter().map(|e| self.visit_node(e)).collect();
(BoundKind::Tuple { elements }, node.ty)
},
BoundKind::Record { fields } => {
let fields = fields.into_iter().map(|(k, v)| (self.visit_node(k), self.visit_node(v))).collect();
(BoundKind::Record { fields }, node.ty)
},
BoundKind::Expansion { original_call, bound_expanded } => {
let bound_expanded = Box::new(self.visit_node(*bound_expanded));
(BoundKind::Expansion { original_call, bound_expanded }, node.ty)
},
// Leaf nodes or uninteresting nodes
k => (k, node.ty),
};
Node {
identity: node.identity,
kind: new_kind,
ty: new_ty,
}
}
fn specialize_call_logic(&self, callee: TypedNode, args: TypedNode, original_ty: StaticType) -> (TypedNode, TypedNode, StaticType) {
// 1. Specialize children first
let new_callee = self.visit_node(callee);
let new_args = self.visit_node(args);
// 2. Check if this call is a candidate (Callee is Get(Address))
let address = if let BoundKind::Get { addr, .. } = &new_callee.kind {
*addr
} else {
// Not a direct call to a named function/variable
return (new_callee, new_args, original_ty);
};
// 3. Check if all argument types are statically known
let arg_types: Vec<StaticType> = if let StaticType::Tuple(elements) = &new_args.ty {
elements.clone()
} else {
vec![new_args.ty.clone()]
};
if arg_types.iter().any(|t| matches!(t, StaticType::Any)) {
// Cannot specialize with unknown types
return (new_callee, new_args, original_ty);
}
// --- Optimization Candidate ---
let key = MonoCacheKey { address, arg_types: arg_types.clone() };
// 4. Check Cache
if let Some((val, ret_ty)) = self.cache.borrow().get(&key) {
// Cache Hit! Replace Callee with Constant(Function)
let specialized_callee = Node {
identity: new_callee.identity.clone(),
kind: BoundKind::Constant(val.clone()),
ty: StaticType::Function(Box::new(Signature {
params: StaticType::Tuple(arg_types),
ret: ret_ty.clone(),
})),
};
return (specialized_callee, new_args, ret_ty.clone());
}
// 5. Check RTL (Host Functions)
if let Some(rtl_lookup) = &self.rtl_lookup
&& let BoundKind::Get { name, .. } = &new_callee.kind
&& let Some((val, ret_ty)) = rtl_lookup(&name.name, &arg_types)
{
// Cache Hit (RTL)
self.cache.borrow_mut().insert(key.clone(), (val.clone(), ret_ty.clone()));
let specialized_callee = Node {
identity: new_callee.identity.clone(),
kind: BoundKind::Constant(val.clone()),
ty: StaticType::Function(Box::new(Signature {
params: StaticType::Tuple(arg_types),
ret: ret_ty.clone(),
})),
};
return (specialized_callee, new_args, ret_ty);
}
// 6. Resolve Function Definition
if let Some(func_node) = self.registry.as_ref().and_then(|r| r.resolve(address)) {
// Check constraints (no closures with state)
if let BoundKind::Lambda { upvalues, .. } = &func_node.kind {
if !upvalues.is_empty() {
return (new_callee, new_args, original_ty);
}
} else {
return (new_callee, new_args, original_ty);
}
// 7. Compile Specialization (User Code)
if let Some(compiler) = &self.compiler {
match compiler(func_node, &arg_types) {
Ok((compiled_val, ret_ty)) => {
let res_val: Value = compiled_val;
let res_ty: StaticType = ret_ty;
// Store in cache
self.cache.borrow_mut().insert(key, (res_val.clone(), res_ty.clone()));
// PERFORMANCE: Flatten the argument tuple to match the specialized signature.
let flat_elements = self.flatten_tuple(new_args.clone());
let flat_types = flat_elements.iter().map(|e| e.ty.clone()).collect();
let flattened_args = Node {
identity: new_args.identity.clone(),
kind: BoundKind::Tuple { elements: flat_elements },
ty: StaticType::Tuple(flat_types),
};
let specialized_callee = Node {
identity: new_callee.identity.clone(),
kind: BoundKind::Constant(res_val),
ty: StaticType::Function(Box::new(Signature {
params: flattened_args.ty.clone(),
ret: res_ty.clone(),
})),
};
return (specialized_callee, flattened_args, res_ty);
},
Err(_) => {
// Fallback on error
}
}
}
}
// Fallback: Dynamic Call
(new_callee, new_args, original_ty)
}
fn flatten_tuple(&self, node: TypedNode) -> Vec<TypedNode> {
match node.kind {
BoundKind::Tuple { elements } => {
let mut flat = Vec::new();
for el in elements {
flat.extend(self.flatten_tuple(el));
}
flat
}
_ => vec![node],
}
}
}
use crate::ast::compiler::bound_nodes::{Address, BoundKind, BoundNode, TypedNode};
use crate::ast::nodes::Node;
use crate::ast::types::{Signature, StaticType, Value};
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct MonoCacheKey {
pub address: Address,
pub arg_types: Vec<StaticType>,
}
pub type CompileFunc = Rc<dyn Fn(BoundNode, &[StaticType]) -> Result<(Value, StaticType), String>>;
pub type RtlLookupFunc = Rc<dyn Fn(&str, &[StaticType]) -> Option<(Value, StaticType)>>;
pub trait FunctionRegistry {
fn resolve(&self, addr: Address) -> Option<BoundNode>;
}
pub type MonoCache = HashMap<MonoCacheKey, (Value, StaticType)>;
pub struct Specializer {
pub cache: Rc<RefCell<MonoCache>>,
registry: Option<Rc<dyn FunctionRegistry>>,
compiler: Option<CompileFunc>,
rtl_lookup: Option<RtlLookupFunc>,
}
impl Specializer {
pub fn new(
registry: Option<Rc<dyn FunctionRegistry>>,
compiler: Option<CompileFunc>,
rtl_lookup: Option<RtlLookupFunc>,
cache: Option<Rc<RefCell<MonoCache>>>,
) -> Self {
Self {
cache: cache.unwrap_or_else(|| Rc::new(RefCell::new(HashMap::new()))),
registry,
compiler,
rtl_lookup,
}
}
pub fn specialize(&self, node: TypedNode) -> TypedNode {
self.visit_node(node)
}
fn visit_node(&self, node: TypedNode) -> TypedNode {
let (new_kind, new_ty) = match node.kind {
BoundKind::Call { callee, args } => {
let (new_callee, new_args, ret_ty) =
self.specialize_call_logic(*callee, *args, node.ty.clone());
(
BoundKind::Call {
callee: Box::new(new_callee),
args: Box::new(new_args),
},
ret_ty,
)
}
// Recursive traversal for other nodes
BoundKind::If {
cond,
then_br,
else_br,
} => {
let cond = Box::new(self.visit_node(*cond));
let then_br = Box::new(self.visit_node(*then_br));
let else_br = else_br.map(|e| Box::new(self.visit_node(*e)));
(
BoundKind::If {
cond,
then_br,
else_br,
},
node.ty,
)
}
BoundKind::Block { exprs } => {
let exprs = exprs.into_iter().map(|e| self.visit_node(e)).collect();
(BoundKind::Block { exprs }, node.ty)
}
BoundKind::Lambda {
params,
upvalues,
body,
positional_count,
} => {
let params = Rc::new(self.visit_node(params.as_ref().clone()));
let body = Rc::new(self.visit_node((*body).clone()));
(
BoundKind::Lambda {
params,
upvalues,
body,
positional_count,
},
node.ty,
)
}
BoundKind::DefLocal {
name,
slot,
value,
captured_by,
} => {
let value = Box::new(self.visit_node(*value));
(
BoundKind::DefLocal {
name,
slot,
value,
captured_by,
},
node.ty,
)
}
BoundKind::DefGlobal {
name,
global_index,
value,
} => {
let value = Box::new(self.visit_node(*value));
(
BoundKind::DefGlobal {
name,
global_index,
value,
},
node.ty,
)
}
BoundKind::Set { addr, value } => {
let value = Box::new(self.visit_node(*value));
(BoundKind::Set { addr, value }, node.ty)
}
BoundKind::Tuple { elements } => {
let elements = elements.into_iter().map(|e| self.visit_node(e)).collect();
(BoundKind::Tuple { elements }, node.ty)
}
BoundKind::Record { fields } => {
let fields = fields
.into_iter()
.map(|(k, v)| (self.visit_node(k), self.visit_node(v)))
.collect();
(BoundKind::Record { fields }, node.ty)
}
BoundKind::Expansion {
original_call,
bound_expanded,
} => {
let bound_expanded = Box::new(self.visit_node(*bound_expanded));
(
BoundKind::Expansion {
original_call,
bound_expanded,
},
node.ty,
)
}
// Leaf nodes or uninteresting nodes
k => (k, node.ty),
};
Node {
identity: node.identity,
kind: new_kind,
ty: new_ty,
}
}
fn specialize_call_logic(
&self,
callee: TypedNode,
args: TypedNode,
original_ty: StaticType,
) -> (TypedNode, TypedNode, StaticType) {
// 1. Specialize children first
let new_callee = self.visit_node(callee);
let new_args = self.visit_node(args);
// 2. Check if this call is a candidate (Callee is Get(Address))
let address = if let BoundKind::Get { addr, .. } = &new_callee.kind {
*addr
} else {
// Not a direct call to a named function/variable
return (new_callee, new_args, original_ty);
};
// 3. Check if all argument types are statically known
let arg_types: Vec<StaticType> = if let StaticType::Tuple(elements) = &new_args.ty {
elements.clone()
} else {
vec![new_args.ty.clone()]
};
if arg_types.iter().any(|t| matches!(t, StaticType::Any)) {
// Cannot specialize with unknown types
return (new_callee, new_args, original_ty);
}
// --- Optimization Candidate ---
let key = MonoCacheKey {
address,
arg_types: arg_types.clone(),
};
// 4. Check Cache
if let Some((val, ret_ty)) = self.cache.borrow().get(&key) {
// Cache Hit! Replace Callee with Constant(Function)
let specialized_callee = Node {
identity: new_callee.identity.clone(),
kind: BoundKind::Constant(val.clone()),
ty: StaticType::Function(Box::new(Signature {
params: StaticType::Tuple(arg_types),
ret: ret_ty.clone(),
})),
};
return (specialized_callee, new_args, ret_ty.clone());
}
// 5. Check RTL (Host Functions)
if let Some(rtl_lookup) = &self.rtl_lookup
&& let BoundKind::Get { name, .. } = &new_callee.kind
&& let Some((val, ret_ty)) = rtl_lookup(&name.name, &arg_types)
{
// Cache Hit (RTL)
self.cache
.borrow_mut()
.insert(key.clone(), (val.clone(), ret_ty.clone()));
let specialized_callee = Node {
identity: new_callee.identity.clone(),
kind: BoundKind::Constant(val.clone()),
ty: StaticType::Function(Box::new(Signature {
params: StaticType::Tuple(arg_types),
ret: ret_ty.clone(),
})),
};
return (specialized_callee, new_args, ret_ty);
}
// 6. Resolve Function Definition
if let Some(func_node) = self.registry.as_ref().and_then(|r| r.resolve(address)) {
// Check constraints (no closures with state)
if let BoundKind::Lambda { upvalues, .. } = &func_node.kind {
if !upvalues.is_empty() {
return (new_callee, new_args, original_ty);
}
} else {
return (new_callee, new_args, original_ty);
}
// 7. Compile Specialization (User Code)
if let Some(compiler) = &self.compiler {
match compiler(func_node, &arg_types) {
Ok((compiled_val, ret_ty)) => {
let res_val: Value = compiled_val;
let res_ty: StaticType = ret_ty;
// Store in cache
self.cache
.borrow_mut()
.insert(key, (res_val.clone(), res_ty.clone()));
// PERFORMANCE: Flatten the argument tuple to match the specialized signature.
let flat_elements = self.flatten_tuple(new_args.clone());
let flat_types = flat_elements.iter().map(|e| e.ty.clone()).collect();
let flattened_args = Node {
identity: new_args.identity.clone(),
kind: BoundKind::Tuple {
elements: flat_elements,
},
ty: StaticType::Tuple(flat_types),
};
let specialized_callee = Node {
identity: new_callee.identity.clone(),
kind: BoundKind::Constant(res_val),
ty: StaticType::Function(Box::new(Signature {
params: flattened_args.ty.clone(),
ret: res_ty.clone(),
})),
};
return (specialized_callee, flattened_args, res_ty);
}
Err(_) => {
// Fallback on error
}
}
}
}
// Fallback: Dynamic Call
(new_callee, new_args, original_ty)
}
fn flatten_tuple(&self, node: TypedNode) -> Vec<TypedNode> {
match node.kind {
BoundKind::Tuple { elements } => {
let mut flat = Vec::new();
for el in elements {
flat.extend(self.flatten_tuple(el));
}
flat
}
_ => vec![node],
}
}
}
+167 -131
View File
@@ -1,131 +1,167 @@
use std::rc::Rc;
use crate::ast::nodes::Node;
use crate::ast::compiler::bound_nodes::{BoundKind, TypedNode};
use crate::ast::types::StaticType;
use std::fmt::Debug;
#[derive(Clone)]
pub struct RuntimeMetadata {
pub ty: StaticType,
pub is_tail: bool,
/// The original, high-level typed node. Perfect for Debuggers and Optimizers.
pub original: Rc<TypedNode>,
}
impl Debug for RuntimeMetadata {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Metadata")
.field("ty", &self.ty)
.field("is_tail", &self.is_tail)
.finish()
}
}
/// The ExecNode is the AST used by the VM. It carries TCO flags and links to source.
pub type ExecNode = Node<BoundKind<RuntimeMetadata>, RuntimeMetadata>;
pub struct TCO;
impl TCO {
/// Lowers a TypedNode (Compiler-AST) to an ExecNode (VM-AST) and marks tail positions.
pub fn optimize(node: TypedNode) -> ExecNode {
Self::transform(Rc::new(node), true)
}
fn transform(node_rc: Rc<TypedNode>, is_tail_position: bool) -> ExecNode {
let node = &*node_rc;
let new_kind = match &node.kind {
BoundKind::Call { callee, args } => {
BoundKind::Call {
callee: Box::new(Self::transform(Rc::new((**callee).clone()), false)),
args: Box::new(Self::transform(Rc::new((**args).clone()), false)),
}
},
BoundKind::If { cond, then_br, else_br } => {
BoundKind::If {
cond: Box::new(Self::transform(Rc::new((**cond).clone()), false)),
then_br: Box::new(Self::transform(Rc::new((**then_br).clone()), is_tail_position)),
else_br: else_br.as_ref().map(|e| Box::new(Self::transform(Rc::new((**e).clone()), is_tail_position))),
}
},
BoundKind::Block { exprs } => {
if exprs.is_empty() {
BoundKind::Block { exprs: vec![] }
} else {
let last_idx = exprs.len() - 1;
let mut new_exprs = Vec::with_capacity(exprs.len());
for (i, expr) in exprs.iter().enumerate() {
let is_last = i == last_idx;
new_exprs.push(Self::transform(Rc::new(expr.clone()), is_tail_position && is_last));
}
BoundKind::Block { exprs: new_exprs }
}
},
BoundKind::Lambda { params, upvalues, body, positional_count } => {
BoundKind::Lambda {
params: Rc::new(Self::transform(params.clone(), false)),
upvalues: upvalues.clone(),
body: Rc::new(Self::transform(body.clone(), true)),
positional_count: *positional_count,
}
},
BoundKind::Set { addr, value } => {
BoundKind::Set { addr: *addr, value: Box::new(Self::transform(Rc::new((**value).clone()), false)) }
},
BoundKind::DefLocal { name, slot, value, captured_by } => {
BoundKind::DefLocal {
name: name.clone(),
slot: *slot,
value: Box::new(Self::transform(Rc::new((**value).clone()), false)),
captured_by: captured_by.clone()
}
},
BoundKind::DefGlobal { name, global_index, value } => {
BoundKind::DefGlobal {
name: name.clone(),
global_index: *global_index,
value: Box::new(Self::transform(Rc::new((**value).clone()), false))
}
},
BoundKind::Record { fields } => {
let new_fields = fields.iter().map(|(k, v)| {
(Self::transform(Rc::new(k.clone()), false), Self::transform(Rc::new(v.clone()), false))
}).collect();
BoundKind::Record { fields: new_fields }
},
BoundKind::Tuple { elements } => {
let new_elements = elements.iter().map(|e| Self::transform(Rc::new(e.clone()), false)).collect();
BoundKind::Tuple { elements: new_elements }
},
BoundKind::Parameter { name, slot } => BoundKind::Parameter { name: name.clone(), slot: *slot },
BoundKind::Constant(v) => BoundKind::Constant(v.clone()),
BoundKind::Get { addr, name } => BoundKind::Get { addr: *addr, name: name.clone() },
BoundKind::Nop => BoundKind::Nop,
BoundKind::Expansion { original_call, bound_expanded } => {
BoundKind::Expansion {
original_call: original_call.clone(),
bound_expanded: Box::new(Self::transform(Rc::new((**bound_expanded).clone()), is_tail_position))
}
}
BoundKind::Extension(_) => {
BoundKind::Nop
}
};
Node {
identity: node.identity.clone(),
kind: new_kind,
ty: RuntimeMetadata {
ty: node.ty.clone(),
is_tail: is_tail_position,
original: node_rc,
},
}
}
}
use crate::ast::compiler::bound_nodes::{BoundKind, TypedNode};
use crate::ast::nodes::Node;
use crate::ast::types::StaticType;
use std::rc::Rc;
use std::fmt::Debug;
#[derive(Clone)]
pub struct RuntimeMetadata {
pub ty: StaticType,
pub is_tail: bool,
/// The original, high-level typed node. Perfect for Debuggers and Optimizers.
pub original: Rc<TypedNode>,
}
impl Debug for RuntimeMetadata {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Metadata")
.field("ty", &self.ty)
.field("is_tail", &self.is_tail)
.finish()
}
}
/// The ExecNode is the AST used by the VM. It carries TCO flags and links to source.
pub type ExecNode = Node<BoundKind<RuntimeMetadata>, RuntimeMetadata>;
pub struct TCO;
impl TCO {
/// Lowers a TypedNode (Compiler-AST) to an ExecNode (VM-AST) and marks tail positions.
pub fn optimize(node: TypedNode) -> ExecNode {
Self::transform(Rc::new(node), true)
}
fn transform(node_rc: Rc<TypedNode>, is_tail_position: bool) -> ExecNode {
let node = &*node_rc;
let new_kind = match &node.kind {
BoundKind::Call { callee, args } => BoundKind::Call {
callee: Box::new(Self::transform(Rc::new((**callee).clone()), false)),
args: Box::new(Self::transform(Rc::new((**args).clone()), false)),
},
BoundKind::If {
cond,
then_br,
else_br,
} => BoundKind::If {
cond: Box::new(Self::transform(Rc::new((**cond).clone()), false)),
then_br: Box::new(Self::transform(
Rc::new((**then_br).clone()),
is_tail_position,
)),
else_br: else_br
.as_ref()
.map(|e| Box::new(Self::transform(Rc::new((**e).clone()), is_tail_position))),
},
BoundKind::Block { exprs } => {
if exprs.is_empty() {
BoundKind::Block { exprs: vec![] }
} else {
let last_idx = exprs.len() - 1;
let mut new_exprs = Vec::with_capacity(exprs.len());
for (i, expr) in exprs.iter().enumerate() {
let is_last = i == last_idx;
new_exprs.push(Self::transform(
Rc::new(expr.clone()),
is_tail_position && is_last,
));
}
BoundKind::Block { exprs: new_exprs }
}
}
BoundKind::Lambda {
params,
upvalues,
body,
positional_count,
} => BoundKind::Lambda {
params: Rc::new(Self::transform(params.clone(), false)),
upvalues: upvalues.clone(),
body: Rc::new(Self::transform(body.clone(), true)),
positional_count: *positional_count,
},
BoundKind::Set { addr, value } => BoundKind::Set {
addr: *addr,
value: Box::new(Self::transform(Rc::new((**value).clone()), false)),
},
BoundKind::DefLocal {
name,
slot,
value,
captured_by,
} => BoundKind::DefLocal {
name: name.clone(),
slot: *slot,
value: Box::new(Self::transform(Rc::new((**value).clone()), false)),
captured_by: captured_by.clone(),
},
BoundKind::DefGlobal {
name,
global_index,
value,
} => BoundKind::DefGlobal {
name: name.clone(),
global_index: *global_index,
value: Box::new(Self::transform(Rc::new((**value).clone()), false)),
},
BoundKind::Record { fields } => {
let new_fields = fields
.iter()
.map(|(k, v)| {
(
Self::transform(Rc::new(k.clone()), false),
Self::transform(Rc::new(v.clone()), false),
)
})
.collect();
BoundKind::Record { fields: new_fields }
}
BoundKind::Tuple { elements } => {
let new_elements = elements
.iter()
.map(|e| Self::transform(Rc::new(e.clone()), false))
.collect();
BoundKind::Tuple {
elements: new_elements,
}
}
BoundKind::Parameter { name, slot } => BoundKind::Parameter {
name: name.clone(),
slot: *slot,
},
BoundKind::Constant(v) => BoundKind::Constant(v.clone()),
BoundKind::Get { addr, name } => BoundKind::Get {
addr: *addr,
name: name.clone(),
},
BoundKind::Nop => BoundKind::Nop,
BoundKind::Expansion {
original_call,
bound_expanded,
} => BoundKind::Expansion {
original_call: original_call.clone(),
bound_expanded: Box::new(Self::transform(
Rc::new((**bound_expanded).clone()),
is_tail_position,
)),
},
BoundKind::Extension(_) => BoundKind::Nop,
};
Node {
identity: node.identity.clone(),
kind: new_kind,
ty: RuntimeMetadata {
ty: node.ty.clone(),
is_tail: is_tail_position,
original: node_rc,
},
}
}
}
File diff suppressed because it is too large Load Diff
+132 -121
View File
@@ -1,121 +1,132 @@
use std::collections::{HashMap, HashSet};
use crate::ast::nodes::{Node, UntypedKind, Symbol};
use crate::ast::types::Identity;
/// Analyzes the AST to find which lambdas capture which variable declarations.
/// Returns a map: Declaration Identity -> List of Lambda Identities that capture it.
pub struct UpvalueAnalyzer;
impl UpvalueAnalyzer {
pub fn analyze(root: &Node<UntypedKind>) -> HashMap<Identity, Vec<Identity>> {
let mut capture_map: HashMap<Identity, HashSet<Identity>> = HashMap::new();
let mut scopes = vec![HashMap::new()]; // Root scope
Self::visit(root, &mut scopes, &mut capture_map, None);
// Convert HashSet back to sorted Vec for the final result
capture_map.into_iter()
.map(|(decl, lambdas)| (decl, lambdas.into_iter().collect()))
.collect()
}
fn visit_params(node: &Node<UntypedKind>, scope: &mut HashMap<Symbol, Identity>) {
match &node.kind {
UntypedKind::Parameter(sym) => {
scope.insert(sym.clone(), node.identity.clone());
}
UntypedKind::Tuple { elements } => {
for el in elements {
Self::visit_params(el, scope);
}
}
_ => {} // Ignore other nodes in parameter patterns for now
}
}
fn visit(
node: &Node<UntypedKind>,
scopes: &mut Vec<HashMap<Symbol, Identity>>,
capture_map: &mut HashMap<Identity, HashSet<Identity>>,
current_lambda: Option<Identity>
) {
match &node.kind {
UntypedKind::Identifier(sym) => {
// Resolve name in scope stack (from inner to outer)
for (depth, scope) in scopes.iter().rev().enumerate() {
if let Some(decl_id) = scope.get(sym) {
if depth > 0 {
// Captured from an outer scope!
if let Some(lambda_id) = &current_lambda {
capture_map.entry(decl_id.clone())
.or_default()
.insert(lambda_id.clone());
}
}
break;
}
}
}
UntypedKind::Def { name, value } => {
Self::visit(value, scopes, capture_map, current_lambda.clone());
if let Some(current) = scopes.last_mut() {
current.insert(name.clone(), node.identity.clone());
}
}
UntypedKind::Lambda { params, body } => {
let mut new_scope = HashMap::new();
Self::visit_params(params, &mut new_scope);
scopes.push(new_scope);
// The current node is the lambda causing captures in its body
Self::visit(body, scopes, capture_map, Some(node.identity.clone()));
scopes.pop();
}
UntypedKind::MacroDecl { params, body, .. } => {
let mut new_scope = HashMap::new();
Self::visit_params(params, &mut new_scope);
scopes.push(new_scope);
Self::visit(body, scopes, capture_map, current_lambda);
scopes.pop();
}
UntypedKind::If { cond, then_br, else_br } => {
Self::visit(cond, scopes, capture_map, current_lambda.clone());
Self::visit(then_br, scopes, capture_map, current_lambda.clone());
if let Some(e) = else_br {
Self::visit(e, scopes, capture_map, current_lambda.clone());
}
}
UntypedKind::Assign { target, value } => {
Self::visit(target, scopes, capture_map, current_lambda.clone());
Self::visit(value, scopes, capture_map, current_lambda.clone());
}
UntypedKind::Call { callee, args } => {
Self::visit(callee, scopes, capture_map, current_lambda.clone());
Self::visit(args, scopes, capture_map, current_lambda.clone());
}
UntypedKind::Block { exprs } => {
for expr in exprs {
Self::visit(expr, scopes, capture_map, current_lambda.clone());
}
}
UntypedKind::Tuple { elements } => {
for el in elements {
Self::visit(el, scopes, capture_map, current_lambda.clone());
}
}
UntypedKind::Record { fields } => {
for (k, v) in fields {
Self::visit(k, scopes, capture_map, current_lambda.clone());
Self::visit(v, scopes, capture_map, current_lambda.clone());
}
}
UntypedKind::Expansion { call: _, expanded } => {
Self::visit(expanded, scopes, capture_map, current_lambda);
}
UntypedKind::Template(body) | UntypedKind::Placeholder(body) | UntypedKind::Splice(body) => {
Self::visit(body, scopes, capture_map, current_lambda);
}
UntypedKind::Nop | UntypedKind::Constant(_) | UntypedKind::Extension(_) | UntypedKind::Parameter(_) => {}
}
}
}
use crate::ast::nodes::{Node, Symbol, UntypedKind};
use crate::ast::types::Identity;
use std::collections::{HashMap, HashSet};
/// Analyzes the AST to find which lambdas capture which variable declarations.
/// Returns a map: Declaration Identity -> List of Lambda Identities that capture it.
pub struct UpvalueAnalyzer;
impl UpvalueAnalyzer {
pub fn analyze(root: &Node<UntypedKind>) -> HashMap<Identity, Vec<Identity>> {
let mut capture_map: HashMap<Identity, HashSet<Identity>> = HashMap::new();
let mut scopes = vec![HashMap::new()]; // Root scope
Self::visit(root, &mut scopes, &mut capture_map, None);
// Convert HashSet back to sorted Vec for the final result
capture_map
.into_iter()
.map(|(decl, lambdas)| (decl, lambdas.into_iter().collect()))
.collect()
}
fn visit_params(node: &Node<UntypedKind>, scope: &mut HashMap<Symbol, Identity>) {
match &node.kind {
UntypedKind::Parameter(sym) => {
scope.insert(sym.clone(), node.identity.clone());
}
UntypedKind::Tuple { elements } => {
for el in elements {
Self::visit_params(el, scope);
}
}
_ => {} // Ignore other nodes in parameter patterns for now
}
}
fn visit(
node: &Node<UntypedKind>,
scopes: &mut Vec<HashMap<Symbol, Identity>>,
capture_map: &mut HashMap<Identity, HashSet<Identity>>,
current_lambda: Option<Identity>,
) {
match &node.kind {
UntypedKind::Identifier(sym) => {
// Resolve name in scope stack (from inner to outer)
for (depth, scope) in scopes.iter().rev().enumerate() {
if let Some(decl_id) = scope.get(sym) {
if depth > 0 {
// Captured from an outer scope!
if let Some(lambda_id) = &current_lambda {
capture_map
.entry(decl_id.clone())
.or_default()
.insert(lambda_id.clone());
}
}
break;
}
}
}
UntypedKind::Def { name, value } => {
Self::visit(value, scopes, capture_map, current_lambda.clone());
if let Some(current) = scopes.last_mut() {
current.insert(name.clone(), node.identity.clone());
}
}
UntypedKind::Lambda { params, body } => {
let mut new_scope = HashMap::new();
Self::visit_params(params, &mut new_scope);
scopes.push(new_scope);
// The current node is the lambda causing captures in its body
Self::visit(body, scopes, capture_map, Some(node.identity.clone()));
scopes.pop();
}
UntypedKind::MacroDecl { params, body, .. } => {
let mut new_scope = HashMap::new();
Self::visit_params(params, &mut new_scope);
scopes.push(new_scope);
Self::visit(body, scopes, capture_map, current_lambda);
scopes.pop();
}
UntypedKind::If {
cond,
then_br,
else_br,
} => {
Self::visit(cond, scopes, capture_map, current_lambda.clone());
Self::visit(then_br, scopes, capture_map, current_lambda.clone());
if let Some(e) = else_br {
Self::visit(e, scopes, capture_map, current_lambda.clone());
}
}
UntypedKind::Assign { target, value } => {
Self::visit(target, scopes, capture_map, current_lambda.clone());
Self::visit(value, scopes, capture_map, current_lambda.clone());
}
UntypedKind::Call { callee, args } => {
Self::visit(callee, scopes, capture_map, current_lambda.clone());
Self::visit(args, scopes, capture_map, current_lambda.clone());
}
UntypedKind::Block { exprs } => {
for expr in exprs {
Self::visit(expr, scopes, capture_map, current_lambda.clone());
}
}
UntypedKind::Tuple { elements } => {
for el in elements {
Self::visit(el, scopes, capture_map, current_lambda.clone());
}
}
UntypedKind::Record { fields } => {
for (k, v) in fields {
Self::visit(k, scopes, capture_map, current_lambda.clone());
Self::visit(v, scopes, capture_map, current_lambda.clone());
}
}
UntypedKind::Expansion { call: _, expanded } => {
Self::visit(expanded, scopes, capture_map, current_lambda);
}
UntypedKind::Template(body)
| UntypedKind::Placeholder(body)
| UntypedKind::Splice(body) => {
Self::visit(body, scopes, capture_map, current_lambda);
}
UntypedKind::Nop
| UntypedKind::Constant(_)
| UntypedKind::Extension(_)
| UntypedKind::Parameter(_) => {}
}
}
}