//! 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, } /// A runtime closure: compiled function proto + captured upvalues. #[derive(Debug, Clone)] struct Closure { proto: Rc, upvalues: Vec>>, } /// A call frame on the VM stack. struct CallFrame { closure: Rc, ip: usize, stack_base: usize, } pub struct Vm { /// Value stack pub stack: Vec, /// Call frames pub frames: Vec, /// Script-level globals (top-level let bindings) pub globals: Rc>>, /// Builtins (shared across modules) pub builtins: Rc>>, /// Module cache (shared across require() calls) pub module_cache: RefCell>, /// Current directory for module resolution pub current_dir: String, /// Open upvalues (tracked so closures share the same upvalue object) pub open_upvalues: Vec>>, /// VM-compiled closures: maps Function Rc pointer → Closure data closures: RefCell>>, } 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) -> 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 to access code without holding self.frames borrow let proto: Rc = 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 = 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 { 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| { 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| { 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 { 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 = 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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> { // 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 { 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 { // 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 { 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>>) { 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))); }