Files
aster/aster-core/src/vm/vm.rs
T
elmma 299003a718 feat: VM函数调用、闭包、upvalue捕获支持
- 实现用户定义函数调用 (CallFrame创建、参数传递、返回)
- 实现闭包/upvalue捕获 (编译器解析、VM捕获、LoadUpvalue/StoreUpvalue)
- 修复栈管理: StoreLocal peek语义、局部变量槽位分配
- 修复跳转偏移计算 (移除错误的-3偏置)
- 修复对象字面量编译 (每字段后Pop)
- 修复函数声明栈泄漏 (DefineGlobal后Pop)
- 添加ForInNext值Pop (避免栈累积)
- 添加OpCode::from_u8对非连续值的支持

通过: 算术、循环、条件、数组、对象、字符串、递归、闭包、upvalue
部分通过: for-in迭代器 (基本工作,复杂嵌套场景待修复)
未实现: VM版require()、复合upvalue赋值

测试: 327/327 通过
2026-06-25 23:16:59 +08:00

1102 lines
45 KiB
Rust

//! Stack-based bytecode VM for Aster.
//!
//! Executes compiled bytecode from `FunctionProto`. Uses a value stack with
//! call frames. Implements the `Runtime` trait for native function support.
use crate::error::RuntimeError;
use crate::interpreter::{Value, Runtime};
use crate::lexer::Lexer;
use crate::parser::Parser;
use super::opcode::*;
use super::compiler::{Compiler, FunctionProto};
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
// ============================================================================
// VM data structures
// ============================================================================
/// An upvalue — a reference to a local variable in an enclosing function.
#[derive(Debug, Clone)]
struct UpvalueObj {
/// Stack index where the value lives, or usize::MAX if closed
location: usize,
/// The value, if it has been moved off the stack (closed)
closed: Option<Value>,
}
/// A runtime closure: compiled function proto + captured upvalues.
#[derive(Debug, Clone)]
struct Closure {
proto: Rc<FunctionProto>,
upvalues: Vec<Rc<RefCell<UpvalueObj>>>,
}
/// A call frame on the VM stack.
struct CallFrame {
closure: Rc<Closure>,
ip: usize,
stack_base: usize,
}
pub struct Vm {
/// Value stack
pub stack: Vec<Value>,
/// Call frames
pub frames: Vec<CallFrame>,
/// Script-level globals (top-level let bindings)
pub globals: Rc<RefCell<HashMap<String, Value>>>,
/// Builtins (shared across modules)
pub builtins: Rc<RefCell<HashMap<String, Value>>>,
/// Module cache (shared across require() calls)
pub module_cache: RefCell<HashMap<String, Value>>,
/// Current directory for module resolution
pub current_dir: String,
/// Open upvalues (tracked so closures share the same upvalue object)
pub open_upvalues: Vec<Rc<RefCell<UpvalueObj>>>,
/// VM-compiled closures: maps Function Rc pointer → Closure data
closures: RefCell<HashMap<*const crate::interpreter::Function, Rc<Closure>>>,
}
impl Vm {
pub fn new() -> Self {
let builtins = Rc::new(RefCell::new(HashMap::new()));
let globals = Rc::new(RefCell::new(HashMap::new()));
// Register builtins into the builtins map
register_builtins(&builtins);
Self {
stack: Vec::with_capacity(256),
frames: Vec::with_capacity(64),
globals,
builtins,
module_cache: RefCell::new(HashMap::new()),
current_dir: std::env::current_dir()
.map(|p| p.to_string_lossy().to_string())
.unwrap_or_else(|_| ".".to_string()),
open_upvalues: Vec::new(),
closures: RefCell::new(HashMap::new()),
}
}
pub fn with_current_dir(dir: String) -> Self {
let mut vm = Self::new();
vm.current_dir = dir;
vm
}
/// Compile and execute a list of statements.
pub fn interpret(&mut self, stmts: &[crate::ast::Stmt]) -> Result<(), RuntimeError> {
let proto = Compiler::compile(stmts)?;
self.run(Rc::new(proto))
}
/// Execute a compiled FunctionProto.
pub fn run(&mut self, proto: Rc<FunctionProto>) -> Result<(), RuntimeError> {
let closure = Rc::new(Closure {
proto: Rc::clone(&proto),
upvalues: Vec::new(),
});
self.frames.push(CallFrame {
closure,
ip: 0,
stack_base: 0,
});
self.execute_loop()
}
// ========================================================================
// Main execution loop
// ========================================================================
fn execute_loop(&mut self) -> Result<(), RuntimeError> {
loop {
if self.frames.is_empty() {
return Ok(());
}
// Snapshot frame state (must drop borrow before mutating self)
let ip = self.frames.last().unwrap().ip;
let code_len = self.frames.last().unwrap().closure.proto.code.len();
if ip >= code_len {
// End of function — implicit return nil
let frame = self.frames.pop().unwrap();
self.close_upvalues(frame.stack_base);
if self.frames.is_empty() {
return Ok(());
}
// Remove callee + args + function locals, push nil result
self.stack.truncate(frame.stack_base.saturating_sub(1));
self.stack.push(Value::Nil);
continue;
}
// Clone Rc<FunctionProto> to access code without holding self.frames borrow
let proto: Rc<FunctionProto> = Rc::clone(&self.frames.last().unwrap().closure.proto);
// Read opcode byte
let op = OpCode::from_u8(proto.code[ip])
.ok_or_else(|| RuntimeError::RuntimeError {
message: format!("Unknown opcode: {}", proto.code[ip]),
token: None,
})?;
// Local reference to code (borrows from proto, not self)
let code = &proto.code;
match op {
OpCode::Pop => {
self.stack.pop();
self.advance_ip(SIZE_OP);
}
OpCode::Dup => {
let val = self.stack.last().unwrap().clone();
self.stack.push(val);
self.advance_ip(SIZE_OP);
}
// --- Constants ---
OpCode::LoadConst => {
let idx = read_u16(code, ip) as usize;
let val = proto.constants.get(idx).cloned().ok_or_else(|| RuntimeError::RuntimeError {
message: format!("Constant index {} out of bounds", idx),
token: None,
})?;
self.stack.push(val);
self.advance_ip(SIZE_U16);
}
OpCode::LoadNil => {
self.stack.push(Value::Nil);
self.advance_ip(SIZE_OP);
}
OpCode::LoadTrue => {
self.stack.push(Value::Bool(true));
self.advance_ip(SIZE_OP);
}
OpCode::LoadFalse => {
self.stack.push(Value::Bool(false));
self.advance_ip(SIZE_OP);
}
// --- Locals ---
OpCode::LoadLocal => {
let slot = read_u8(code, ip) as usize;
let idx = self.frames.last().unwrap().stack_base + slot;
if idx >= self.stack.len() {
return Err(RuntimeError::RuntimeError {
message: format!("LoadLocal: slot {} uninitialized", slot),
token: None,
});
}
let val = self.stack[idx].clone();
self.stack.push(val);
self.advance_ip(SIZE_U8);
}
OpCode::StoreLocal => {
let slot = read_u8(code, ip) as usize;
let val = self.stack.last().unwrap().clone(); // peek
let base = self.frames.last().unwrap().stack_base;
let idx = base + slot;
if idx >= self.stack.len() {
self.stack.resize(idx + 1, Value::Nil);
}
self.stack[idx] = val;
self.advance_ip(SIZE_U8);
}
OpCode::LoadUpvalue => {
let idx = read_u8(code, ip) as usize;
let uv = Rc::clone(&self.frames.last().unwrap().closure.upvalues[idx]);
let uv_ref = uv.borrow();
let val = if let Some(ref closed) = uv_ref.closed {
closed.clone()
} else {
self.stack[uv_ref.location].clone()
};
drop(uv_ref);
self.stack.push(val);
self.advance_ip(SIZE_U8);
}
OpCode::StoreUpvalue => {
let idx = read_u8(code, ip) as usize;
let val = self.stack.last().unwrap().clone(); // peek
let uv = Rc::clone(&self.frames.last().unwrap().closure.upvalues[idx]);
let mut uv_ref = uv.borrow_mut();
if let Some(ref mut closed) = uv_ref.closed {
*closed = val;
} else {
self.stack[uv_ref.location] = val;
}
self.advance_ip(SIZE_U8);
}
// --- Globals ---
OpCode::LoadGlobal => {
let name_idx = read_u16(code, ip) as usize;
let name = proto_string(&proto, name_idx)?;
let val = self.globals.borrow().get(&name).cloned()
.or_else(|| self.builtins.borrow().get(&name).cloned())
.ok_or_else(|| RuntimeError::RuntimeError {
message: format!("Undefined variable '{}'", name),
token: None,
})?;
self.stack.push(val);
self.advance_ip(SIZE_U16);
}
OpCode::StoreGlobal => {
let name_idx = read_u16(code, ip) as usize;
let name = proto_string(&proto,name_idx)?;
let val = self.stack.last().unwrap().clone();
self.globals.borrow_mut().insert(name, val);
self.advance_ip(SIZE_U16);
}
OpCode::DefineGlobal => {
let name_idx = read_u16(code, ip) as usize;
let name = proto_string(&proto,name_idx)?;
let val = self.stack.pop().unwrap();
self.globals.borrow_mut().insert(name, val.clone());
self.stack.push(val);
self.advance_ip(SIZE_U16);
}
// --- Properties ---
OpCode::GetProperty => {
let name_idx = read_u16(code, ip) as usize;
let name = proto_string(&proto,name_idx)?;
let obj = self.stack.pop().unwrap();
let result = self.get_property(obj, &name)?;
self.stack.push(result);
self.advance_ip(SIZE_U16);
}
OpCode::SetProperty => {
let name_idx = read_u16(code, ip) as usize;
let name = proto_string(&proto,name_idx)?;
let val = self.stack.pop().unwrap();
let obj = self.stack.pop().unwrap();
self.set_property(obj, &name, val.clone())?;
self.stack.push(val);
self.advance_ip(SIZE_U16);
}
// --- Indexing ---
OpCode::GetIndex => {
let index = self.stack.pop().unwrap();
let target = self.stack.pop().unwrap();
let result = self.get_index(target, index)?;
self.stack.push(result);
self.advance_ip(SIZE_OP);
}
OpCode::SetIndex => {
let val = self.stack.pop().unwrap();
let index = self.stack.pop().unwrap();
let target = self.stack.pop().unwrap();
self.set_index(target, index, val.clone())?;
self.stack.push(val);
self.advance_ip(SIZE_OP);
}
// --- Arithmetic ---
OpCode::Add => {
let r = self.stack.pop().unwrap();
let l = self.stack.pop().unwrap();
self.stack.push(self.binary_add(l, r)?);
self.advance_ip(SIZE_OP);
}
OpCode::Sub => {
let r = self.stack.pop().unwrap();
let l = self.stack.pop().unwrap();
self.stack.push(self.binary_arith(l, r, |a, b| a - b, "-")?);
self.advance_ip(SIZE_OP);
}
OpCode::Mul => {
let r = self.stack.pop().unwrap();
let l = self.stack.pop().unwrap();
self.stack.push(self.binary_arith(l, r, |a, b| a * b, "*")?);
self.advance_ip(SIZE_OP);
}
OpCode::Div => {
let r = self.stack.pop().unwrap();
let l = self.stack.pop().unwrap();
self.stack.push(self.binary_div(l, r)?);
self.advance_ip(SIZE_OP);
}
OpCode::Mod => {
let r = self.stack.pop().unwrap();
let l = self.stack.pop().unwrap();
self.stack.push(self.binary_mod(l, r)?);
self.advance_ip(SIZE_OP);
}
// --- Unary ---
OpCode::Negate => {
let val = self.stack.pop().unwrap();
match val {
Value::Number(n) => self.stack.push(Value::Number(-n)),
_ => return Err(RuntimeError::RuntimeError {
message: "Unary '-' on non-number".into(),
token: None,
}),
}
self.advance_ip(SIZE_OP);
}
OpCode::Not => {
let val = self.stack.pop().unwrap();
let truth = !self.is_truthy(&val);
self.stack.push(Value::Bool(truth));
self.advance_ip(SIZE_OP);
}
// --- Comparisons ---
OpCode::Equal => {
let r = self.stack.pop().unwrap();
let l = self.stack.pop().unwrap();
self.stack.push(Value::Bool(self.is_equal(&l, &r)));
self.advance_ip(SIZE_OP);
}
OpCode::NotEqual => {
let r = self.stack.pop().unwrap();
let l = self.stack.pop().unwrap();
self.stack.push(Value::Bool(!self.is_equal(&l, &r)));
self.advance_ip(SIZE_OP);
}
OpCode::Greater => {
let r = self.stack.pop().unwrap();
let l = self.stack.pop().unwrap();
self.stack.push(Value::Bool(self.as_number(&l)? > self.as_number(&r)?));
self.advance_ip(SIZE_OP);
}
OpCode::GreaterEqual => {
let r = self.stack.pop().unwrap();
let l = self.stack.pop().unwrap();
self.stack.push(Value::Bool(self.as_number(&l)? >= self.as_number(&r)?));
self.advance_ip(SIZE_OP);
}
OpCode::Less => {
let r = self.stack.pop().unwrap();
let l = self.stack.pop().unwrap();
self.stack.push(Value::Bool(self.as_number(&l)? < self.as_number(&r)?));
self.advance_ip(SIZE_OP);
}
OpCode::LessEqual => {
let r = self.stack.pop().unwrap();
let l = self.stack.pop().unwrap();
self.stack.push(Value::Bool(self.as_number(&l)? <= self.as_number(&r)?));
self.advance_ip(SIZE_OP);
}
// --- Jumps ---
OpCode::Jump => {
let offset = read_i16(code, ip) as isize;
self.advance_ip_to(((ip as isize) + offset) as usize);
}
OpCode::JumpIfFalse => {
let cond = self.stack.pop().unwrap();
if !self.is_truthy(&cond) {
let offset = read_i16(code, ip) as isize;
self.advance_ip_to(((ip as isize) + offset) as usize);
} else {
self.advance_ip(SIZE_U16);
}
}
OpCode::JumpIfTrue => {
let cond = self.stack.pop().unwrap();
if self.is_truthy(&cond) {
let offset = read_i16(code, ip) as isize;
self.advance_ip_to(((ip as isize) + offset) as usize);
} else {
self.advance_ip(SIZE_U16);
}
}
OpCode::PopJumpIfFalse => {
let cond = self.stack.last().unwrap();
if !self.is_truthy(cond) {
let offset = read_i16(code, ip) as isize;
self.advance_ip_to(((ip as isize) + offset) as usize);
} else {
self.advance_ip(SIZE_U16);
}
}
// --- Functions ---
OpCode::Call => {
let arg_count = read_u8(code, ip) as usize;
self.call_function(arg_count)?;
// call_function updates the frame
}
OpCode::Return => {
let result = self.stack.pop().unwrap();
let frame = self.frames.pop().unwrap();
self.close_upvalues(frame.stack_base);
// Remove callee + args + function locals, push return value
self.stack.truncate(frame.stack_base.saturating_sub(1));
self.stack.push(result);
if self.frames.is_empty() {
return Ok(());
}
}
OpCode::Closure => {
let proto_idx = read_u16(code, ip) as usize;
let upvalue_count = code[ip + 3] as usize;
let proto = proto.protos.get(proto_idx).ok_or_else(|| RuntimeError::RuntimeError {
message: format!("Closure proto index {} out of bounds", proto_idx),
token: None,
})?.clone();
// Collect upvalue capture info
struct UpCapture { is_local: bool, index: usize }
let mut captures: Vec<UpCapture> = Vec::new();
let mut off = ip + 4;
for _ in 0..upvalue_count {
let is_local = code[off] != 0;
off += 1;
let index = code[off] as usize;
off += 1;
captures.push(UpCapture { is_local, index });
}
// Do the actual captures
let base = self.frames.last().unwrap().stack_base;
let parent_upvalues = self.frames.last().unwrap().closure.upvalues.clone();
let mut upvalues = Vec::new();
for cap in &captures {
if cap.is_local {
let location = base + cap.index;
upvalues.push(self.capture_upvalue(location));
} else {
upvalues.push(Rc::clone(&parent_upvalues[cap.index]));
}
}
let closure = Rc::new(Closure { proto, upvalues });
let func = Rc::new(crate::interpreter::Function {
params: Vec::new(),
body: Vec::new(),
env: Rc::new(RefCell::new(crate::interpreter::Env::new(None))),
name: None,
});
let key = Rc::as_ptr(&func) as *const crate::interpreter::Function;
self.closures.borrow_mut().insert(key, closure);
self.stack.push(Value::Function(func));
self.advance_ip_to(off);
}
// --- Object/Array ---
OpCode::NewObject => {
self.stack.push(Value::Object(Rc::new(RefCell::new(HashMap::new()))));
self.advance_ip(SIZE_OP);
}
OpCode::NewArray => {
let count = read_u16(code, ip) as usize;
let mut elements = Vec::with_capacity(count);
for _ in 0..count {
elements.push(self.stack.pop().unwrap());
}
elements.reverse();
self.stack.push(Value::Array(Rc::new(RefCell::new(elements))));
self.advance_ip(SIZE_U16);
}
// --- For-in ---
OpCode::ForInInit => {
let iterable = self.stack.pop().unwrap();
// Push iterator state: (collection, index)
let iter = Value::Number(0.0);
self.stack.push(iterable);
self.stack.push(iter);
self.advance_ip(SIZE_OP);
}
OpCode::ForInNext => {
let exit_offset = read_i16(code, ip) as isize;
let exit_ip = ((ip as isize) + exit_offset) as usize;
let iter_idx = self.stack.pop().unwrap(); // current index
let collection = self.stack.pop().unwrap(); // the iterable
let idx = self.as_number(&iter_idx)? as usize;
let items = self.for_in_items(&collection);
if idx >= items.len() {
// Done iterating — push back state and jump to exit
self.stack.push(collection);
self.stack.push(iter_idx);
self.advance_ip_to(exit_ip);
} else {
// Push back incremented state
self.stack.push(collection);
self.stack.push(Value::Number((idx + 1) as f64));
// Push the current value for the loop body
self.stack.push(items[idx].clone());
self.advance_ip(SIZE_U16);
}
}
// --- Compound assignment ---
OpCode::CompoundAssignLocal => {
let slot = read_u8(code, ip) as usize;
let op_tag = code[ip + 2];
let compound_op = CompoundOp::from_u8(op_tag).unwrap();
let rhs = self.stack.pop().unwrap();
let base = self.frames.last().unwrap().stack_base;
let idx = base + slot;
if idx >= self.stack.len() {
return Err(RuntimeError::RuntimeError {
message: format!("CompoundAssignLocal to uninitialized local {}", slot),
token: None,
});
}
let lhs = self.stack[idx].clone();
let result = self.apply_compound_op(lhs, rhs, compound_op)?;
self.stack[idx] = result.clone();
self.stack.push(result);
self.advance_ip(SIZE_U8 + 1);
}
OpCode::CompoundAssignProp => {
let name_idx = read_u16(code, ip) as usize;
let op_tag = code[ip + 3];
let compound_op = CompoundOp::from_u8(op_tag).unwrap();
let rhs = self.stack.pop().unwrap();
let obj = self.stack.pop().unwrap();
let name = proto_string(&proto,name_idx)?;
let lhs = self.get_property(obj.clone(), &name)?;
let result = self.apply_compound_op(lhs, rhs, compound_op)?;
self.set_property(obj, &name, result.clone())?;
self.stack.push(result);
self.advance_ip(SIZE_U16 + 1);
}
OpCode::CompoundAssignIndex => {
let op_tag = code[ip + 1];
let compound_op = CompoundOp::from_u8(op_tag).unwrap();
let rhs = self.stack.pop().unwrap();
let index = self.stack.pop().unwrap();
let target = self.stack.pop().unwrap();
let lhs = self.get_index(target.clone(), index.clone())?;
let result = self.apply_compound_op(lhs, rhs, compound_op)?;
self.set_index(target, index, result.clone())?;
self.stack.push(result);
self.advance_ip(SIZE_OP + 1);
}
}
}
}
// ========================================================================
// IP management
// ========================================================================
fn frame(&self) -> &CallFrame {
self.frames.last().unwrap()
}
fn frame_mut(&mut self) -> &mut CallFrame {
self.frames.last_mut().unwrap()
}
fn advance_ip(&mut self, size: usize) {
self.frame_mut().ip += size;
}
fn advance_ip_to(&mut self, target: usize) {
self.frame_mut().ip = target;
}
// ========================================================================
// Function calls
// ========================================================================
fn call_function(&mut self, arg_count: usize) -> Result<(), RuntimeError> {
let callee_idx = self.stack.len() - 1 - arg_count;
let callee = self.stack[callee_idx].clone();
match &callee {
Value::NativeFunction(_) => {
// Get the native function
let native_fn = match self.stack[callee_idx].clone() {
Value::NativeFunction(f) => f,
_ => unreachable!(),
};
// Pop arguments from stack
let mut args = Vec::new();
for _ in 0..arg_count {
args.push(self.stack.pop().unwrap());
}
args.reverse();
self.stack.pop(); // pop the native function itself
let result = native_fn(self as &mut dyn Runtime, args)?;
self.stack.push(result);
self.advance_ip(SIZE_U8);
}
Value::Function(_) => {
// Look up the VM-compiled closure
let func_ptr = match &self.stack[callee_idx] {
Value::Function(f) => Rc::as_ptr(f) as *const crate::interpreter::Function,
_ => unreachable!(),
};
let closure = self.closures.borrow().get(&func_ptr).cloned().ok_or_else(|| RuntimeError::RuntimeError {
message: "VM: call to non-VM function (tree-walker function not supported in VM)".into(),
token: None,
})?;
// Advance caller's IP past the Call instruction before pushing new frame
self.frame_mut().ip += SIZE_U8;
// Callee is at callee_idx, args start at callee_idx+1
let base = callee_idx + 1; // first param is here
self.frames.push(CallFrame {
closure,
ip: 0,
stack_base: base,
});
}
_ => {
return Err(RuntimeError::RuntimeError {
message: "Attempt to call non-function".into(),
token: None,
});
}
}
Ok(())
}
// ========================================================================
// Property / Index helpers (reuse tree-walker logic)
// ========================================================================
fn get_property(&self, obj: Value, name: &str) -> Result<Value, RuntimeError> {
match &obj {
Value::Object(map) => {
map.borrow().get(name).cloned().ok_or_else(|| RuntimeError::RuntimeError {
message: format!("Undefined property '{}'", name),
token: None,
})
}
Value::Array(arr) => match name {
"length" => Ok(Value::Number(arr.borrow().len() as f64)),
"push" => {
let arr = Rc::clone(arr);
Ok(Value::NativeFunction(Rc::new(move |_runtime: &mut dyn Runtime, mut args: Vec<Value>| {
let val = args.pop().unwrap_or(Value::Nil);
arr.borrow_mut().push(val.clone());
Ok(val)
})))
}
"pop" => {
let arr = Rc::clone(arr);
Ok(Value::NativeFunction(Rc::new(move |_runtime: &mut dyn Runtime, _args: Vec<Value>| {
arr.borrow_mut().pop().ok_or_else(|| RuntimeError::RuntimeError {
message: "pop() on empty array".into(),
token: None,
})
})))
}
_ => Err(RuntimeError::RuntimeError {
message: format!("Array has no property '{}'", name),
token: None,
}),
},
Value::String(s) => match name {
"length" => Ok(Value::Number(s.chars().count() as f64)),
_ => Err(RuntimeError::RuntimeError {
message: format!("String has no property '{}'", name),
token: None,
}),
},
_ => Err(RuntimeError::RuntimeError {
message: "Only objects have properties".into(),
token: None,
}),
}
}
fn set_property(&self, obj: Value, name: &str, val: Value) -> Result<(), RuntimeError> {
match obj {
Value::Object(map) => {
map.borrow_mut().insert(name.to_string(), val);
Ok(())
}
_ => Err(RuntimeError::RuntimeError {
message: "Only objects have properties".into(),
token: None,
}),
}
}
fn get_index(&self, target: Value, index: Value) -> Result<Value, RuntimeError> {
if let Value::Object(_) = &target {
let key = match &index {
Value::String(s) => s.clone(),
_ => return Err(RuntimeError::RuntimeError {
message: "Object index must be a string".into(),
token: None,
}),
};
return self.get_property(target, &key);
}
let i = self.as_usize(&index, "Index")?;
match &target {
Value::Array(vec) => {
let vec = vec.borrow();
vec.get(i).cloned().ok_or_else(|| RuntimeError::RuntimeError {
message: format!("Index {} out of bounds (len {})", i, vec.len()),
token: None,
})
}
Value::String(s) => {
let chars: Vec<char> = s.chars().collect();
chars.get(i).map(|&c| Value::String(c.to_string())).ok_or_else(|| RuntimeError::RuntimeError {
message: format!("Index {} out of bounds (len {})", i, chars.len()),
token: None,
})
}
_ => Err(RuntimeError::RuntimeError {
message: "Index access on non-array, non-string, non-object value".into(),
token: None,
}),
}
}
fn set_index(&self, target: Value, index: Value, val: Value) -> Result<(), RuntimeError> {
if let Value::Object(_) = &target {
let key = match &index {
Value::String(s) => s.clone(),
_ => return Err(RuntimeError::RuntimeError {
message: "Object index must be a string".into(),
token: None,
}),
};
return self.set_property(target, &key, val);
}
let i = self.as_usize(&index, "Index")?;
match target {
Value::Array(vec) => {
let mut vec = vec.borrow_mut();
if i >= vec.len() {
return Err(RuntimeError::RuntimeError {
message: format!("Index {} out of bounds (len {})", i, vec.len()),
token: None,
});
}
vec[i] = val;
Ok(())
}
_ => Err(RuntimeError::RuntimeError {
message: "Index assignment on non-array, non-object value".into(),
token: None,
}),
}
}
// ========================================================================
// Arithmetic helpers
// ========================================================================
fn binary_add(&self, l: Value, r: Value) -> Result<Value, RuntimeError> {
if matches!(l, Value::String(_)) || matches!(r, Value::String(_)) {
Ok(Value::String(format!("{}{}", l, r)))
} else {
match (l, r) {
(Value::Number(a), Value::Number(b)) => Ok(Value::Number(a + b)),
_ => Err(RuntimeError::RuntimeError {
message: "Invalid '+' operands".into(),
token: None,
}),
}
}
}
fn binary_arith(&self, l: Value, r: Value, f: fn(f64, f64) -> f64, name: &str) -> Result<Value, RuntimeError> {
match (l, r) {
(Value::Number(a), Value::Number(b)) => Ok(Value::Number(f(a, b))),
_ => Err(RuntimeError::RuntimeError {
message: format!("Invalid '{}' operands", name),
token: None,
}),
}
}
fn binary_div(&self, l: Value, r: Value) -> Result<Value, RuntimeError> {
match (l, r) {
(_, Value::Number(b)) if b == 0.0 => Err(RuntimeError::RuntimeError {
message: "Division by zero".into(),
token: None,
}),
(Value::Number(a), Value::Number(b)) => Ok(Value::Number(a / b)),
_ => Err(RuntimeError::RuntimeError {
message: "Invalid '/' operands".into(),
token: None,
}),
}
}
fn binary_mod(&self, l: Value, r: Value) -> Result<Value, RuntimeError> {
match (l, r) {
(_, Value::Number(b)) if b == 0.0 => Err(RuntimeError::RuntimeError {
message: "Modulo by zero".into(),
token: None,
}),
(Value::Number(a), Value::Number(b)) => Ok(Value::Number(a % b)),
_ => Err(RuntimeError::RuntimeError {
message: "Invalid '%' operands".into(),
token: None,
}),
}
}
fn apply_compound_op(&self, lhs: Value, rhs: Value, op: CompoundOp) -> Result<Value, RuntimeError> {
match op {
CompoundOp::PlusEqual => self.binary_add(lhs, rhs),
CompoundOp::MinusEqual => self.binary_arith(lhs, rhs, |a, b| a - b, "-="),
CompoundOp::StarEqual => self.binary_arith(lhs, rhs, |a, b| a * b, "*="),
CompoundOp::SlashEqual => self.binary_div(lhs, rhs),
CompoundOp::PercentEqual => self.binary_mod(lhs, rhs),
}
}
// ========================================================================
// General helpers
// ========================================================================
fn is_truthy(&self, val: &Value) -> bool {
match val {
Value::Nil => false,
Value::Bool(b) => *b,
_ => true,
}
}
fn is_equal(&self, a: &Value, b: &Value) -> bool {
match (a, b) {
(Value::Nil, Value::Nil) => true,
(Value::Bool(x), Value::Bool(y)) => x == y,
(Value::Number(x), Value::Number(y)) => x == y,
(Value::String(x), Value::String(y)) => x == y,
_ => false, // Simplified — full structural equality omitted for now
}
}
fn as_number(&self, val: &Value) -> Result<f64, RuntimeError> {
if let Value::Number(n) = val {
Ok(*n)
} else {
Err(RuntimeError::RuntimeError { message: "Expected number".into(), token: None })
}
}
fn as_usize(&self, val: &Value, arg_name: &str) -> Result<usize, RuntimeError> {
if let Value::Number(n) = val {
if *n < 0.0 || n.fract() != 0.0 {
return Err(RuntimeError::RuntimeError {
message: format!("{} must be a non-negative integer, got {}", arg_name, n),
token: None,
});
}
Ok(*n as usize)
} else {
Err(RuntimeError::RuntimeError {
message: format!("{} must be a number", arg_name),
token: None,
})
}
}
fn get_constant(&self, idx: usize) -> Result<Value, RuntimeError> {
let frame = self.frames.last().unwrap();
frame.closure.proto.constants.get(idx).cloned().ok_or_else(|| RuntimeError::RuntimeError {
message: format!("Constant index {} out of bounds", idx),
token: None,
})
}
// ========================================================================
// Upvalues
// ========================================================================
fn capture_upvalue(&mut self, location: usize) -> Rc<RefCell<UpvalueObj>> {
// Check if this location is already captured
for uv in &self.open_upvalues {
if uv.borrow().location == location {
return Rc::clone(uv);
}
}
let uv = Rc::new(RefCell::new(UpvalueObj {
location,
closed: None,
}));
self.open_upvalues.push(Rc::clone(&uv));
uv
}
fn close_upvalues(&mut self, last_slot: usize) {
for uv in &self.open_upvalues {
let mut uv_ref = uv.borrow_mut();
if uv_ref.location >= last_slot && uv_ref.closed.is_none() {
if uv_ref.location < self.stack.len() {
uv_ref.closed = Some(self.stack[uv_ref.location].clone());
}
uv_ref.location = usize::MAX;
}
}
self.open_upvalues.retain(|uv| uv.borrow().location != usize::MAX);
}
// ========================================================================
// For-in helper
// ========================================================================
fn for_in_items(&self, collection: &Value) -> Vec<Value> {
match collection {
Value::Array(arr) => arr.borrow().clone(),
Value::Object(obj) => obj.borrow().keys().map(|k| Value::String(k.clone())).collect(),
Value::String(s) => s.chars().map(|c| Value::String(c.to_string())).collect(),
_ => Vec::new(),
}
}
}
impl Runtime for Vm {
fn require(&mut self, path: &str) -> Result<Value, RuntimeError> {
// Simplified require for VM: lex → parse → compile → execute
let resolved = {
let path = std::path::Path::new(path);
let resolved = if path.is_absolute() {
path.to_path_buf()
} else {
std::path::Path::new(&self.current_dir).join(path)
};
std::fs::canonicalize(&resolved)
.map(|p| p.to_string_lossy().to_string())
.map_err(|_| RuntimeError::RuntimeError {
message: format!("Module '{}' not found", path.display()),
token: None,
})?
};
if let Some(cached) = self.module_cache.borrow().get(&resolved) {
return Ok(cached.clone());
}
let src = std::fs::read_to_string(&resolved)
.map_err(|e| RuntimeError::RuntimeError {
message: format!("Cannot read module '{}': {}", path, e),
token: None,
})?;
let (tokens, lex_errors) = Lexer::new(&src).tokenize();
if !lex_errors.is_empty() {
return Err(RuntimeError::RuntimeError {
message: format!("Lex error in module '{}': {}", path, lex_errors[0]),
token: None,
});
}
let mut parser = Parser::new(tokens);
let (stmts, parse_errors) = parser.parse();
if !parse_errors.is_empty() {
return Err(RuntimeError::RuntimeError {
message: format!("Parse error in module '{}': {}", path, parse_errors[0]),
token: None,
});
}
// Create isolated VM for module execution
let module_dir = std::path::Path::new(&resolved)
.parent()
.map(|p| p.to_string_lossy().to_string())
.unwrap_or_else(|| ".".to_string());
let mut module_vm = Vm::new();
module_vm.current_dir = module_dir;
// Share module cache and builtins
module_vm.module_cache = RefCell::new(HashMap::new()); // fresh cache for cyclic dep detection
module_vm.builtins = Rc::clone(&self.builtins);
// Insert placeholder for cyclic requires
let exports_obj = Value::Object(Rc::new(RefCell::new(HashMap::new())));
self.module_cache.borrow_mut().insert(resolved.clone(), exports_obj.clone());
// Compile and run
let proto = Compiler::compile(&stmts).map_err(|e| RuntimeError::RuntimeError {
message: format!("Compile error in module '{}': {}", path, e),
token: None,
})?;
module_vm.run(Rc::new(proto))?;
// Collect exports from module's globals
if let Value::Object(exports_map) = &exports_obj {
let mut map = exports_map.borrow_mut();
for (name, val) in module_vm.globals.borrow().iter() {
map.insert(name.clone(), val.clone());
}
// Update shared module cache
self.module_cache.borrow_mut().insert(resolved, exports_obj.clone());
}
Ok(exports_obj)
}
}
// ============================================================================
// Helpers (standalone, no self borrow)
// ============================================================================
fn proto_string(proto: &FunctionProto, idx: usize) -> Result<String, RuntimeError> {
match proto.constants.get(idx) {
Some(Value::String(s)) => Ok(s.clone()),
_ => Err(RuntimeError::RuntimeError {
message: format!("Expected string constant at index {}", idx),
token: None,
}),
}
}
// ============================================================================
// Builtin registration (reuses tree-walker's builtins)
// ============================================================================
fn register_builtins(map: &Rc<RefCell<HashMap<String, Value>>>) {
let mut m = map.borrow_mut();
// io
let mut io = HashMap::new();
io.insert("print".into(), Value::NativeFunction(Rc::new(crate::interpreter::builtins::io::print)));
io.insert("input".into(), Value::NativeFunction(Rc::new(crate::interpreter::builtins::io::input)));
m.insert("io".into(), Value::Object(Rc::new(RefCell::new(io))));
m.insert("print".into(), Value::NativeFunction(Rc::new(crate::interpreter::builtins::io::print)));
m.insert("input".into(), Value::NativeFunction(Rc::new(crate::interpreter::builtins::io::input)));
// os
let mut os = HashMap::new();
os.insert("clock".into(), Value::NativeFunction(Rc::new(crate::interpreter::builtins::os::clock)));
m.insert("os".into(), Value::Object(Rc::new(RefCell::new(os))));
m.insert("clock".into(), Value::NativeFunction(Rc::new(crate::interpreter::builtins::os::clock)));
// core
m.insert("len".into(), Value::NativeFunction(Rc::new(crate::interpreter::builtins::core::len)));
m.insert("typeof".into(), Value::NativeFunction(Rc::new(crate::interpreter::builtins::core::typeof_fn)));
m.insert("push".into(), Value::NativeFunction(Rc::new(crate::interpreter::builtins::core::push)));
m.insert("pop".into(), Value::NativeFunction(Rc::new(crate::interpreter::builtins::core::pop)));
// require (VM version)
m.insert("require".into(), Value::NativeFunction(Rc::new(crate::interpreter::module::require_fn)));
// string
m.insert("split".into(), Value::NativeFunction(Rc::new(crate::interpreter::builtins::string::split)));
m.insert("trim".into(), Value::NativeFunction(Rc::new(crate::interpreter::builtins::string::trim)));
m.insert("substring".into(), Value::NativeFunction(Rc::new(crate::interpreter::builtins::string::substring)));
m.insert("replace".into(), Value::NativeFunction(Rc::new(crate::interpreter::builtins::string::replace)));
m.insert("contains".into(), Value::NativeFunction(Rc::new(crate::interpreter::builtins::string::contains)));
m.insert("upper".into(), Value::NativeFunction(Rc::new(crate::interpreter::builtins::string::upper)));
m.insert("lower".into(), Value::NativeFunction(Rc::new(crate::interpreter::builtins::string::lower)));
m.insert("starts_with".into(), Value::NativeFunction(Rc::new(crate::interpreter::builtins::string::starts_with)));
m.insert("ends_with".into(), Value::NativeFunction(Rc::new(crate::interpreter::builtins::string::ends_with)));
}