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:
Michael Schimmel
2026-02-17 02:00:51 +01:00
parent 874a6f39a4
commit 7042206ab6
8 changed files with 595 additions and 29 deletions
+69
View File
@@ -0,0 +1,69 @@
use std::sync::{Arc, Mutex};
use std::collections::HashMap;
use crate::ast::types::Value;
use crate::ast::parser::Parser;
use crate::ast::compiler::binder::Binder;
use crate::ast::vm::VM;
pub struct Environment {
pub global_names: Arc<Mutex<HashMap<String, u32>>>,
pub global_values: Arc<Mutex<Vec<Value>>>,
}
impl Environment {
pub fn new() -> Self {
let env = Self {
global_names: Arc::new(Mutex::new(HashMap::new())),
global_values: Arc::new(Mutex::new(Vec::new())),
};
env.register_stdlib();
env
}
pub fn register_native(&self, name: &str, func: fn(Vec<Value>) -> Value) {
let mut names = self.global_names.lock().unwrap();
let mut values = self.global_values.lock().unwrap();
let idx = values.len() as u32;
names.insert(name.to_string(), idx);
values.push(Value::Function(Arc::new(move |args| func(args))));
}
fn register_stdlib(&self) {
// Simple math for testing
self.register_native("+", |args| {
let mut acc = 0.0;
for arg in args {
if let Value::Int(i) = arg { acc += i as f64; }
else if let Value::Float(f) = arg { acc += f; }
}
if acc.fract() == 0.0 { Value::Int(acc as i64) } else { Value::Float(acc) }
});
self.register_native(">", |args| {
if args.len() != 2 { return Value::Void; }
match (&args[0], &args[1]) {
(Value::Int(a), Value::Int(b)) => Value::Bool(a > b),
_ => Value::Bool(false),
}
});
}
pub fn run_script(&self, source: &str) -> Result<Value, String> {
// 1. Parse
let mut parser = Parser::new(source)?;
let untyped_ast = parser.parse_expression()?; // Or loop for script
// 2. Bind
let mut binder = Binder::new(self.global_names.clone());
let bound_ast = binder.bind(&untyped_ast)?;
// 3. Execute
// Note: VM needs to borrow globals, not own them exclusively?
// My VM impl takes Vec<Value>, but here we have Arc<Mutex<Vec>>.
// I need to update VM to accept shared globals.
let mut vm = VM::new(self.global_values.clone());
vm.run(&bound_ast)
}
}