feat: Implement AST binder and VM
Adds a new `Binder` struct that traverses the AST and resolves variable references to concrete `Address` types (Local, Upvalue, Global). This information is crucial for the Virtual Machine's execution phase. Introduces the `BoundKind` enum to represent the AST after binding. The `VM` is updated to handle `BoundKind` nodes and execute the bound AST. It now manages a call stack, local variables, and closures for function calls and upvalue capturing. The `Environment` struct is enhanced to manage global variables and provide a unified interface for parsing, binding, and executing scripts. It also includes basic standard library functions.
This commit is contained in:
@@ -0,0 +1,219 @@
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use crate::ast::nodes::{Node, UntypedKind};
|
||||
use crate::ast::compiler::bound_nodes::{BoundKind, Address};
|
||||
use crate::ast::types::{Value, Identity};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct CompilerScope {
|
||||
locals: HashMap<String, u32>,
|
||||
slot_count: u32,
|
||||
}
|
||||
|
||||
impl CompilerScope {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
locals: HashMap::new(),
|
||||
slot_count: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn define(&mut self, name: &str) -> u32 {
|
||||
let slot = self.slot_count;
|
||||
self.locals.insert(name.to_string(), slot);
|
||||
self.slot_count += 1;
|
||||
slot
|
||||
}
|
||||
|
||||
fn resolve(&self, name: &str) -> Option<u32> {
|
||||
self.locals.get(name).copied()
|
||||
}
|
||||
}
|
||||
|
||||
struct FunctionCompiler {
|
||||
scope: CompilerScope,
|
||||
// Upvalues capture variables from the *immediate* enclosing function.
|
||||
// The address stored here refers to a Local/Upvalue in the Parent Scope.
|
||||
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: Arc<Mutex<HashMap<String, u32>>>,
|
||||
}
|
||||
|
||||
impl Binder {
|
||||
pub fn new(globals: Arc<Mutex<HashMap<String, u32>>>) -> Self {
|
||||
let mut binder = Self {
|
||||
functions: Vec::new(),
|
||||
globals,
|
||||
};
|
||||
binder.functions.push(FunctionCompiler::new());
|
||||
binder
|
||||
}
|
||||
|
||||
pub fn bind(&mut self, node: &Node<UntypedKind>) -> Result<Node<BoundKind>, 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(name) => {
|
||||
let addr = self.resolve_variable(name)?;
|
||||
Ok(self.make_node(node.identity.clone(), BoundKind::Get(addr)))
|
||||
},
|
||||
|
||||
UntypedKind::If { cond, then_br, else_br } => {
|
||||
let cond = Box::new(self.bind(cond)?);
|
||||
let then_br = Box::new(self.bind(then_br)?);
|
||||
let else_br = match else_br {
|
||||
Some(e) => Some(Box::new(self.bind(e)?)),
|
||||
None => None,
|
||||
};
|
||||
Ok(self.make_node(node.identity.clone(), BoundKind::If { cond, then_br, else_br }))
|
||||
},
|
||||
|
||||
UntypedKind::Def { name, value } => {
|
||||
// Determine scope: Global (root) or Local (inside fn)?
|
||||
if self.functions.len() == 1 {
|
||||
// Global Definition
|
||||
let val_node = self.bind(value)?;
|
||||
let mut globals = self.globals.lock().unwrap();
|
||||
let idx = if let Some(&idx) = globals.get(name.as_ref()) {
|
||||
idx
|
||||
} else {
|
||||
let idx = globals.len() as u32;
|
||||
globals.insert(name.to_string(), idx);
|
||||
idx
|
||||
};
|
||||
Ok(self.make_node(node.identity.clone(), BoundKind::DefGlobal { global_index: idx, value: Box::new(val_node) }))
|
||||
} else {
|
||||
// Local Variable Definition
|
||||
// We bind the value *before* defining the name to avoid self-reference in init?
|
||||
// Usually yes: (def x 10) -> bind(10), define(x).
|
||||
let val_node = self.bind(value)?;
|
||||
let current_fn = self.functions.last_mut().unwrap();
|
||||
let slot = current_fn.scope.define(name);
|
||||
|
||||
// We treat local 'def' as a Set to the new slot
|
||||
Ok(self.make_node(node.identity.clone(), BoundKind::Set {
|
||||
addr: Address::Local(slot),
|
||||
value: Box::new(val_node)
|
||||
}))
|
||||
}
|
||||
},
|
||||
|
||||
UntypedKind::Assign { target, value } => {
|
||||
let val_node = self.bind(value)?;
|
||||
|
||||
if let UntypedKind::Identifier(name) = &target.kind {
|
||||
let addr = self.resolve_variable(name)?;
|
||||
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 } => {
|
||||
self.functions.push(FunctionCompiler::new());
|
||||
|
||||
{
|
||||
let current_fn = self.functions.last_mut().unwrap();
|
||||
for param in params {
|
||||
current_fn.scope.define(param);
|
||||
}
|
||||
}
|
||||
|
||||
let body_bound = self.bind(body)?;
|
||||
|
||||
let compiled_fn = self.functions.pop().unwrap();
|
||||
let upvalues = compiled_fn.upvalues;
|
||||
|
||||
Ok(self.make_node(node.identity.clone(), BoundKind::Lambda {
|
||||
param_count: params.len() as u32,
|
||||
upvalues,
|
||||
body: Arc::new(body_bound),
|
||||
}))
|
||||
},
|
||||
|
||||
UntypedKind::Call { callee, args } => {
|
||||
let callee = Box::new(self.bind(callee)?);
|
||||
let mut bound_args = Vec::new();
|
||||
for arg in args {
|
||||
bound_args.push(self.bind(arg)?);
|
||||
}
|
||||
Ok(self.make_node(node.identity.clone(), BoundKind::Call { callee, args: bound_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::Extension(_) => {
|
||||
Err("Custom extensions not supported in Binder yet".to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_variable(&mut self, name: &str) -> Result<Address, String> {
|
||||
let current_fn_idx = self.functions.len() - 1;
|
||||
|
||||
// 1. Try local in current function
|
||||
if let Some(slot) = self.functions[current_fn_idx].scope.resolve(name) {
|
||||
return Ok(Address::Local(slot));
|
||||
}
|
||||
|
||||
// 2. Try enclosing scopes (capture chain)
|
||||
let mut captured_addr: Option<Address> = None;
|
||||
|
||||
// Walk backwards from parent of current function up to root (0)
|
||||
// We look for where the variable is DEFINED.
|
||||
for i in (0..current_fn_idx).rev() {
|
||||
let func = &self.functions[i];
|
||||
if let Some(slot) = func.scope.resolve(name) {
|
||||
// Found definition! It's a Local variable in function 'i'.
|
||||
captured_addr = Some(Address::Local(slot));
|
||||
|
||||
// Now we must propagate this capture down through all functions from i+1 to current.
|
||||
let mut addr = captured_addr.unwrap();
|
||||
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.lock().unwrap();
|
||||
if let Some(&idx) = globals.get(name) {
|
||||
return Ok(Address::Global(idx));
|
||||
}
|
||||
|
||||
Err(format!("Undefined variable '{}'", name))
|
||||
}
|
||||
|
||||
fn make_node(&self, identity: Identity, kind: BoundKind) -> Node<BoundKind> {
|
||||
Node { identity, kind }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
use std::sync::Arc;
|
||||
use crate::ast::types::Value;
|
||||
use crate::ast::nodes::{Node, CustomNode};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
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
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum BoundKind {
|
||||
Nop,
|
||||
Constant(Value),
|
||||
|
||||
// Variable Access (Resolved)
|
||||
Get(Address),
|
||||
|
||||
// Variable Update (Resolved)
|
||||
Set {
|
||||
addr: Address,
|
||||
value: Box<Node<BoundKind>>,
|
||||
},
|
||||
|
||||
If {
|
||||
cond: Box<Node<BoundKind>>,
|
||||
then_br: Box<Node<BoundKind>>,
|
||||
else_br: Option<Box<Node<BoundKind>>>,
|
||||
},
|
||||
|
||||
// Global Definition (Locals are implicit by stack position)
|
||||
DefGlobal {
|
||||
global_index: u32,
|
||||
value: Box<Node<BoundKind>>,
|
||||
},
|
||||
|
||||
Lambda {
|
||||
param_count: u32,
|
||||
// The list of variables captured from enclosing scopes
|
||||
upvalues: Vec<Address>,
|
||||
body: Arc<Node<BoundKind>>,
|
||||
},
|
||||
|
||||
Call {
|
||||
callee: Box<Node<BoundKind>>,
|
||||
args: Vec<Node<BoundKind>>,
|
||||
},
|
||||
|
||||
Block {
|
||||
exprs: Vec<Node<BoundKind>>,
|
||||
},
|
||||
|
||||
// Extension points (need to be adapted for bound nodes if they use variables)
|
||||
// For now, we assume extensions are self-contained or handled dynamically.
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
pub mod binder;
|
||||
pub mod bound_nodes;
|
||||
|
||||
pub use binder::*;
|
||||
pub use bound_nodes::*;
|
||||
Reference in New Issue
Block a user