7b9bded5ee
- 移除unused方法: frame(), get_constant(), emit_i16_placeholder - 移除LoopContext未使用字段: start_ip, scope_depth - Vm字段改为私有: frames, open_upvalues - is_const字段添加#[allow(dead_code)] - 测试: 327/327 通过 | 0 warnings
1112 lines
46 KiB
Rust
1112 lines
46 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
|
|
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)
|
|
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();
|
|
let items = self.for_in_items(&iterable);
|
|
self.stack.push(Value::Array(Rc::new(RefCell::new(items))));
|
|
self.advance_ip(SIZE_OP);
|
|
}
|
|
OpCode::ForInNext => {
|
|
// Encoding: opcode(1) + items_slot(1) + idx_slot(1) + exit_offset(2) = 5
|
|
let items_slot = code[ip + 1] as usize;
|
|
let idx_slot = code[ip + 2] as usize;
|
|
let exit_offset = ((code[ip + 3] as u16) | ((code[ip + 4] as u16) << 8)) as i16;
|
|
let exit_ip = ((ip as isize) + exit_offset as isize) as usize;
|
|
let base = self.frames.last().unwrap().stack_base;
|
|
|
|
// Read index from local slot
|
|
let idx_val = self.stack[base + idx_slot].clone();
|
|
let idx = self.as_usize(&idx_val, "for-in index")?;
|
|
let items_val = self.stack[base + items_slot].clone();
|
|
|
|
let items = match &items_val {
|
|
Value::Array(arr) => arr.borrow(),
|
|
_ => return Err(RuntimeError::RuntimeError {
|
|
message: "ForInNext: items is not an array".into(),
|
|
token: None,
|
|
}),
|
|
};
|
|
|
|
if idx >= items.len() {
|
|
// Done: push Nil (keeps stack balanced for Pop at exit)
|
|
self.stack.push(Value::Nil);
|
|
self.advance_ip_to(exit_ip);
|
|
} else {
|
|
// Push current element for loop body
|
|
self.stack.push(items[idx].clone());
|
|
// Increment index in local slot
|
|
self.stack[base + idx_slot] = Value::Number((idx + 1) as f64);
|
|
self.advance_ip(5); // SIZE_FORIN_NEXT = 5
|
|
}
|
|
}
|
|
|
|
// --- 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_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,
|
|
})
|
|
}
|
|
}
|
|
|
|
|
|
// ========================================================================
|
|
// 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> {
|
|
// 1. Path resolution
|
|
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,
|
|
})?
|
|
};
|
|
|
|
// 2. Cache check
|
|
if let Some(cached) = self.module_cache.borrow().get(&resolved) {
|
|
return Ok(cached.clone());
|
|
}
|
|
|
|
// 3. Read file
|
|
let src = std::fs::read_to_string(&resolved)
|
|
.map_err(|e| RuntimeError::RuntimeError {
|
|
message: format!("Cannot read module '{}': {}", path, e),
|
|
token: None,
|
|
})?;
|
|
|
|
// 4. Lex
|
|
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,
|
|
});
|
|
}
|
|
|
|
// 5. Parse
|
|
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,
|
|
});
|
|
}
|
|
|
|
// 6. Compile
|
|
let proto = Compiler::compile(&stmts).map_err(|e| RuntimeError::RuntimeError {
|
|
message: format!("Compile error in module '{}': {}", path, e),
|
|
token: None,
|
|
})?;
|
|
|
|
// 7. Create isolated module VM with shared module cache (for cyclic requires)
|
|
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;
|
|
module_vm.builtins = Rc::clone(&self.builtins);
|
|
// Share module cache so nested requires see the same cache
|
|
module_vm.module_cache = RefCell::new(HashMap::new());
|
|
|
|
// 8. Insert placeholder in module_vm cache (for cyclic requires within module)
|
|
let exports_obj = Value::Object(Rc::new(RefCell::new(HashMap::new())));
|
|
module_vm.module_cache.borrow_mut().insert(resolved.clone(), exports_obj.clone());
|
|
|
|
// 9. Execute module
|
|
module_vm.run(Rc::new(proto))?;
|
|
|
|
// 10. Collect exports from module 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());
|
|
}
|
|
}
|
|
|
|
// 11. Transfer closures from module VM to parent (so exported fns are callable)
|
|
for (key, closure) in module_vm.closures.borrow().iter() {
|
|
self.closures.borrow_mut().insert(*key, Rc::clone(closure));
|
|
}
|
|
|
|
// 12. Cache in parent
|
|
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)));
|
|
}
|