use super::compiler::BytecodeChunk;
use super::instruction::Instruction;
use super::opcode::OpCode;
use crate::frontend::ast::Expr;
use crate::runtime::{Interpreter, Value};
use std::collections::HashMap;
use std::sync::Arc;
const MAX_REGISTERS: usize = 32;
#[derive(Debug)]
struct CallFrame<'a> {
chunk: &'a BytecodeChunk,
pc: usize,
base_register: u8,
}
impl<'a> CallFrame<'a> {
fn new(chunk: &'a BytecodeChunk) -> Self {
Self {
chunk,
pc: 0,
base_register: 0,
}
}
#[inline]
fn fetch_instruction(&self) -> Option<Instruction> {
self.chunk.instructions.get(self.pc).copied()
}
#[inline]
fn advance_pc(&mut self) {
self.pc += 1;
}
#[inline]
fn jump(&mut self, offset: i16) {
let target = (self.pc as i32) + i32::from(offset);
self.pc = target as usize;
}
}
#[derive(Debug)]
pub struct VM {
registers: [Value; MAX_REGISTERS],
call_stack: Vec<CallFrame<'static>>,
globals: HashMap<String, Value>,
interpreter: Interpreter,
}
impl VM {
pub fn new() -> Self {
Self {
registers: std::array::from_fn(|_| Value::Nil),
call_stack: Vec::new(),
globals: HashMap::new(),
interpreter: Interpreter::new(),
}
}
#[allow(unsafe_code)] pub fn execute(&mut self, chunk: &BytecodeChunk) -> Result<Value, String> {
let chunk_ref: &'static BytecodeChunk = unsafe { std::mem::transmute(chunk) };
self.call_stack.push(CallFrame::new(chunk_ref));
while let Some(frame) = self.call_stack.last_mut() {
let instruction = if let Some(instr) = frame.fetch_instruction() {
instr
} else {
self.call_stack.pop();
continue;
};
let opcode = OpCode::from_u8(instruction.opcode())
.ok_or_else(|| format!("Invalid opcode: {}", instruction.opcode()))?;
self.execute_instruction(opcode, instruction)?;
if let Some(frame) = self.call_stack.last_mut() {
frame.advance_pc();
}
}
Ok(self.registers[0].clone())
}
#[inline]
fn execute_instruction(
&mut self,
opcode: OpCode,
instruction: Instruction,
) -> Result<(), String> {
match opcode {
OpCode::Const => {
let dest = instruction.get_a() as usize;
let const_idx = instruction.get_bx() as usize;
let frame = self.call_stack.last().ok_or("No active call frame")?;
let value = frame
.chunk
.constants
.get(const_idx)
.ok_or_else(|| format!("Constant index out of bounds: {const_idx}"))?;
self.registers[dest] = value.clone();
Ok(())
}
OpCode::Move => {
let dest = instruction.get_a() as usize;
let src = instruction.get_b() as usize;
self.registers[dest] = self.registers[src].clone();
Ok(())
}
OpCode::LoadField => {
let dest = instruction.get_a() as usize;
let object_reg = instruction.get_b() as usize;
let field_idx = instruction.get_c() as usize;
let frame = self.call_stack.last().ok_or("No active call frame")?;
let field_value = frame
.chunk
.constants
.get(field_idx)
.ok_or_else(|| format!("Constant index out of bounds: {field_idx}"))?;
let field_name = match field_value {
Value::String(s) => s.as_ref(),
_ => return Err("Field name must be a string".to_string()),
};
let object = &self.registers[object_reg];
let result = match object {
Value::Object(ref map) => map
.get(field_name)
.cloned()
.ok_or_else(|| format!("Field '{field_name}' not found in object")),
Value::Struct {
ref fields,
ref name,
} => fields
.get(field_name)
.cloned()
.ok_or_else(|| format!("Field '{field_name}' not found in struct {name}")),
Value::Class {
ref fields,
ref class_name,
..
} => {
let fields_read = fields
.read()
.expect("RwLock poisoned: class fields lock is corrupted");
fields_read.get(field_name).cloned().ok_or_else(|| {
format!("Field '{field_name}' not found in class {class_name}")
})
}
Value::Tuple(ref elements) => {
field_name
.parse::<usize>()
.ok()
.and_then(|idx| elements.get(idx).cloned())
.ok_or_else(|| format!("Tuple index '{field_name}' out of bounds"))
}
_ => Err(format!(
"Cannot access field '{}' on type {}",
field_name,
object.type_name()
)),
}?;
self.registers[dest] = result;
Ok(())
}
OpCode::LoadIndex => {
let dest = instruction.get_a() as usize;
let object_reg = instruction.get_b() as usize;
let index_reg = instruction.get_c() as usize;
let object = &self.registers[object_reg];
let index = &self.registers[index_reg];
let result = match (object, index) {
(Value::Array(arr), Value::Integer(i)) => {
let idx = if *i < 0 {
let len = arr.len() as i64;
(len + i) as usize
} else {
*i as usize
};
arr.get(idx).cloned().ok_or_else(|| {
format!(
"Index {} out of bounds for array of length {}",
i,
arr.len()
)
})
}
(Value::String(s), Value::Integer(i)) => {
let chars: Vec<char> = s.chars().collect();
let idx = if *i < 0 {
let len = chars.len() as i64;
(len + i) as usize
} else {
*i as usize
};
chars
.get(idx)
.map(|c| Value::from_string(c.to_string()))
.ok_or_else(|| {
format!(
"Index {} out of bounds for string of length {}",
i,
chars.len()
)
})
}
_ => Err(format!(
"Cannot index {} with {}",
object.type_name(),
index.type_name()
)),
}?;
self.registers[dest] = result;
Ok(())
}
OpCode::NewArray => {
let dest = instruction.get_a() as usize;
let element_regs_idx = instruction.get_bx() as usize;
let frame = self.call_stack.last().ok_or("No active call frame")?;
let element_regs = frame
.chunk
.array_element_regs
.get(element_regs_idx)
.ok_or_else(|| {
format!("Array element regs index out of bounds: {element_regs_idx}")
})?;
let mut elements = Vec::with_capacity(element_regs.len());
for &elem_reg in element_regs {
let elem_reg = elem_reg as usize;
if elem_reg >= self.registers.len() {
return Err(format!("Element register {elem_reg} out of bounds"));
}
elements.push(self.registers[elem_reg].clone());
}
let array = Value::from_array(elements);
self.registers[dest] = array;
Ok(())
}
OpCode::NewTuple => {
let dest = instruction.get_a() as usize;
let element_regs_idx = instruction.get_bx() as usize;
let frame = self.call_stack.last().ok_or("No active call frame")?;
let element_regs = frame
.chunk
.array_element_regs
.get(element_regs_idx)
.ok_or_else(|| {
format!("Tuple element regs index out of bounds: {element_regs_idx}")
})?;
let mut elements = Vec::with_capacity(element_regs.len());
for &elem_reg in element_regs {
let elem_reg = elem_reg as usize;
if elem_reg >= self.registers.len() {
return Err(format!("Element register {elem_reg} out of bounds"));
}
elements.push(self.registers[elem_reg].clone());
}
let tuple = Value::Tuple(Arc::from(elements.as_slice()));
self.registers[dest] = tuple;
Ok(())
}
OpCode::NewObject => {
let dest = instruction.get_a() as usize;
let field_data_idx = instruction.get_bx() as usize;
let frame = self.call_stack.last().ok_or("No active call frame")?;
let field_data =
frame
.chunk
.object_fields
.get(field_data_idx)
.ok_or_else(|| {
format!("Object field data index out of bounds: {field_data_idx}")
})?;
let mut object_map = std::collections::HashMap::new();
for (key, value_reg) in field_data {
let value_reg = *value_reg as usize;
if value_reg >= self.registers.len() {
return Err(format!("Value register {value_reg} out of bounds"));
}
object_map.insert(key.clone(), self.registers[value_reg].clone());
}
let object = Value::Object(Arc::new(object_map));
self.registers[dest] = object;
Ok(())
}
OpCode::Add => self.binary_op(instruction, super::super::interpreter::Value::add),
OpCode::Sub => self.binary_op(instruction, super::super::interpreter::Value::subtract),
OpCode::Mul => self.binary_op(instruction, super::super::interpreter::Value::multiply),
OpCode::Div => self.binary_op(instruction, super::super::interpreter::Value::divide),
OpCode::Mod => self.binary_op(instruction, super::super::interpreter::Value::modulo),
OpCode::Neg => self.unary_op(instruction, |v| match v {
Value::Integer(i) => Ok(Value::Integer(-i)),
Value::Float(f) => Ok(Value::Float(-f)),
_ => Err(format!("Cannot negate {}", v.type_name())),
}),
OpCode::Not => self.unary_op(instruction, |v| Ok(Value::Bool(!v.is_truthy()))),
OpCode::BitNot => self.unary_op(instruction, |v| match v {
Value::Integer(i) => Ok(Value::Integer(!i)),
_ => Err(format!("Cannot apply bitwise NOT to {}", v.type_name())),
}),
OpCode::Equal => self.binary_op(instruction, |a, b| Ok(Value::Bool(a == b))),
OpCode::NotEqual => self.binary_op(instruction, |a, b| Ok(Value::Bool(a != b))),
OpCode::Less => {
self.comparison_op(instruction, super::super::interpreter::Value::less_than)
}
OpCode::LessEqual => {
self.comparison_op(instruction, super::super::interpreter::Value::less_equal)
}
OpCode::Greater => {
self.comparison_op(instruction, super::super::interpreter::Value::greater_than)
}
OpCode::GreaterEqual => {
self.comparison_op(instruction, super::super::interpreter::Value::greater_equal)
}
OpCode::And => self.logical_op(instruction, |a, b| a && b),
OpCode::Or => self.logical_op(instruction, |a, b| a || b),
OpCode::Jump => {
let offset = instruction.get_sbx();
if let Some(frame) = self.call_stack.last_mut() {
frame.jump(offset);
}
Ok(())
}
OpCode::JumpIfFalse => {
let condition = instruction.get_a() as usize;
let offset = instruction.get_sbx();
let is_false =
matches!(&self.registers[condition], Value::Bool(false) | Value::Nil);
if is_false {
if let Some(frame) = self.call_stack.last_mut() {
frame.jump(offset);
}
}
Ok(())
}
OpCode::JumpIfTrue => {
let condition = instruction.get_a() as usize;
let offset = instruction.get_sbx();
let is_true = match &self.registers[condition] {
Value::Bool(true) => true,
Value::Bool(false) | Value::Nil => false,
_ => true, };
if is_true {
if let Some(frame) = self.call_stack.last_mut() {
frame.jump(offset);
}
}
Ok(())
}
OpCode::Call => {
let result_reg = instruction.get_a() as usize;
let call_info_idx = instruction.get_bx() as usize;
let frame = self.call_stack.last().ok_or("No active call frame")?;
let call_info_value = frame
.chunk
.constants
.get(call_info_idx)
.ok_or_else(|| format!("Constant index out of bounds: {call_info_idx}"))?;
let call_info: Vec<usize> = match call_info_value {
Value::Array(arr) => arr
.iter()
.map(|v| match v {
Value::Integer(i) => Ok(*i as usize),
_ => Err("Call info element must be an integer".to_string()),
})
.collect::<Result<Vec<_>, _>>()?,
_ => return Err("Call info must be an array".to_string()),
};
if call_info.is_empty() {
return Err("Call info array is empty".to_string());
}
let func_reg = call_info[0];
let arg_regs = &call_info[1..];
let func_value = self.registers[func_reg].clone();
let (params, body, env) = match func_value {
Value::Closure { params, body, env } => (params, body, env),
_ => {
return Err(format!(
"Cannot call non-function value: {}",
func_value.type_name()
))
}
};
if arg_regs.len() != params.len() {
return Err(format!(
"Function expects {} arguments, got {}",
params.len(),
arg_regs.len()
));
}
let mut args: Vec<Value> = Vec::new();
for &arg_reg in arg_regs {
args.push(self.registers[arg_reg].clone());
}
self.interpreter.push_scope();
let captured_vars: Vec<(String, Value)> = env
.borrow()
.iter()
.map(|(k, v)| (k.clone(), v.clone()))
.collect();
for (name, value) in captured_vars {
self.interpreter.set_variable(&name, value);
}
for ((param_name, _default_value), arg) in params.iter().zip(args.iter()) {
self.interpreter.set_variable(param_name, arg.clone());
}
let result = self
.interpreter
.eval_expr(&body)
.map_err(|e| format!("Function call error: {e}"))?;
self.interpreter.pop_scope();
self.registers[result_reg] = result;
Ok(())
}
OpCode::For => {
let result_reg = instruction.get_a() as usize;
let loop_info_idx = instruction.get_bx() as usize;
let frame = self.call_stack.last().ok_or("No active call frame")?;
let loop_info_value = frame
.chunk
.constants
.get(loop_info_idx)
.ok_or_else(|| format!("Constant index out of bounds: {loop_info_idx}"))?;
let loop_info: Vec<i64> = match loop_info_value {
Value::Array(arr) => {
let mut info = Vec::new();
if arr.len() < 3 {
return Err("Loop info array must have at least 3 elements".to_string());
}
if let Value::Integer(iter_reg) = arr[0] {
info.push(iter_reg);
} else {
return Err("Loop info[0] must be an integer".to_string());
}
if let Value::Integer(body_idx) = arr[2] {
info.push(body_idx);
} else {
return Err("Loop info[2] must be an integer".to_string());
}
info
}
_ => return Err("Loop info must be an array".to_string()),
};
let iter_reg = loop_info[0] as usize;
let body_idx = loop_info[1] as usize;
let var_name = match &loop_info_value {
Value::Array(arr) if arr.len() >= 2 => match &arr[1] {
Value::String(s) => s.as_ref().to_string(),
_ => return Err("Loop var name must be a string".to_string()),
},
_ => return Err("Loop info must be an array".to_string()),
};
let iter_value = self.registers[iter_reg].clone();
let iter_array = match iter_value {
Value::Array(arr) => arr,
_ => {
return Err(format!(
"For-loop iterator must be an array, got {}",
iter_value.type_name()
))
}
};
let body = frame
.chunk
.loop_bodies
.get(body_idx)
.ok_or_else(|| format!("Loop body index out of bounds: {body_idx}"))?
.clone();
for (name, reg_idx) in &frame.chunk.locals_map {
let value = self.registers[*reg_idx as usize].clone();
self.interpreter.set_variable(name, value);
}
let mut last_result = Value::Nil;
for elem in iter_array.iter() {
self.interpreter.push_scope();
self.interpreter.set_variable(&var_name, elem.clone());
last_result = self
.interpreter
.eval_expr(&body)
.map_err(|e| format!("For-loop body error: {e}"))?;
self.interpreter.pop_scope();
for (name, reg_idx) in &frame.chunk.locals_map {
if let Some(value) = self.interpreter.get_variable(name) {
self.registers[*reg_idx as usize] = value;
}
}
}
self.registers[result_reg] = last_result;
Ok(())
}
OpCode::MethodCall => {
let result_reg = instruction.get_a() as usize;
let method_call_idx_const = instruction.get_bx() as usize;
let frame = self.call_stack.last().ok_or("No active call frame")?;
let method_call_idx_value = frame
.chunk
.constants
.get(method_call_idx_const)
.ok_or_else(|| {
format!("Constant index out of bounds: {method_call_idx_const}")
})?;
let method_call_idx = match method_call_idx_value {
Value::Integer(idx) => *idx as usize,
_ => return Err("Method call index must be an integer".to_string()),
};
let (receiver, method, args) = frame
.chunk
.method_calls
.get(method_call_idx)
.ok_or_else(|| format!("Method call index out of bounds: {method_call_idx}"))?;
for (name, reg_idx) in &frame.chunk.locals_map {
let value = self.registers[*reg_idx as usize].clone();
self.interpreter.set_variable(name, value);
}
let args_exprs: Vec<Expr> = args.iter().map(|arc| (**arc).clone()).collect();
let result = self
.interpreter
.eval_method_call(receiver, method, &args_exprs)
.map_err(|e| format!("Method call error: {e}"))?;
for (name, reg_idx) in &frame.chunk.locals_map {
if let Some(value) = self.interpreter.get_variable(name) {
self.registers[*reg_idx as usize] = value;
}
}
self.registers[result_reg] = result;
Ok(())
}
OpCode::Match => {
let result_reg = instruction.get_a() as usize;
let match_idx_const = instruction.get_bx() as usize;
let frame = self.call_stack.last().ok_or("No active call frame")?;
let match_idx_value =
frame.chunk.constants.get(match_idx_const).ok_or_else(|| {
format!("Constant index out of bounds: {match_idx_const}")
})?;
let match_idx = match match_idx_value {
Value::Integer(idx) => *idx as usize,
_ => return Err("Match index must be an integer".to_string()),
};
let (expr, arms) = frame
.chunk
.match_exprs
.get(match_idx)
.ok_or_else(|| format!("Match index out of bounds: {match_idx}"))?;
for (name, reg_idx) in &frame.chunk.locals_map {
let value = self.registers[*reg_idx as usize].clone();
self.interpreter.set_variable(name, value);
}
let result = self
.interpreter
.eval_match(expr, arms)
.map_err(|e| format!("Match expression error: {e}"))?;
for (name, reg_idx) in &frame.chunk.locals_map {
if let Some(value) = self.interpreter.get_variable(name) {
self.registers[*reg_idx as usize] = value;
}
}
self.registers[result_reg] = result;
Ok(())
}
OpCode::NewClosure => {
let result_reg = instruction.get_a() as usize;
let closure_idx_const = instruction.get_bx() as usize;
let frame = self.call_stack.last().ok_or("No active call frame")?;
let closure_idx_value = frame
.chunk
.constants
.get(closure_idx_const)
.ok_or_else(|| format!("Constant index out of bounds: {closure_idx_const}"))?;
let closure_idx = match closure_idx_value {
Value::Integer(idx) => *idx as usize,
_ => return Err("Closure index must be an integer".to_string()),
};
let (params, body) = frame
.chunk
.closures
.get(closure_idx)
.ok_or_else(|| format!("Closure index out of bounds: {closure_idx}"))?;
for (name, reg_idx) in &frame.chunk.locals_map {
let value = self.registers[*reg_idx as usize].clone();
self.interpreter.set_variable(name, value);
}
let env = self.interpreter.current_env().clone();
let closure = Value::Closure {
params: params.clone(),
body: body.clone(),
env,
};
self.registers[result_reg] = closure;
Ok(())
}
OpCode::Return => {
let return_reg = instruction.get_a() as usize;
let return_value = self.registers[return_reg].clone();
self.call_stack.pop();
self.registers[0] = return_value;
Ok(())
}
OpCode::LoadGlobal => {
let dest = instruction.get_a() as usize;
let name_idx = instruction.get_bx() as usize;
let frame = self.call_stack.last().ok_or("No active call frame")?;
let name_value = frame
.chunk
.constants
.get(name_idx)
.ok_or_else(|| format!("Constant index out of bounds: {name_idx}"))?;
let name = match name_value {
Value::String(s) => s.as_ref(),
_ => return Err("Global name must be a string".to_string()),
};
let value = self
.globals
.get(name)
.ok_or_else(|| format!("Undefined global variable: {name}"))?;
self.registers[dest] = value.clone();
Ok(())
}
OpCode::StoreGlobal => {
let src = instruction.get_a() as usize;
let name_idx = instruction.get_bx() as usize;
let frame = self.call_stack.last().ok_or("No active call frame")?;
let name_value = frame
.chunk
.constants
.get(name_idx)
.ok_or_else(|| format!("Constant index out of bounds: {name_idx}"))?;
let name = match name_value {
Value::String(s) => s.to_string(),
_ => return Err("Global name must be a string".to_string()),
};
self.globals.insert(name, self.registers[src].clone());
Ok(())
}
_ => Err(format!("Unsupported opcode: {opcode:?}")),
}
}
#[inline]
fn binary_op<F>(&mut self, instruction: Instruction, op: F) -> Result<(), String>
where
F: FnOnce(&Value, &Value) -> Result<Value, String>,
{
let dest = instruction.get_a() as usize;
let left = instruction.get_b() as usize;
let right = instruction.get_c() as usize;
let result = op(&self.registers[left], &self.registers[right])?;
self.registers[dest] = result;
Ok(())
}
#[inline]
fn unary_op<F>(&mut self, instruction: Instruction, op: F) -> Result<(), String>
where
F: FnOnce(&Value) -> Result<Value, String>,
{
let dest = instruction.get_a() as usize;
let operand = instruction.get_b() as usize;
let result = op(&self.registers[operand])?;
self.registers[dest] = result;
Ok(())
}
#[inline]
fn comparison_op<F>(&mut self, instruction: Instruction, op: F) -> Result<(), String>
where
F: FnOnce(&Value, &Value) -> bool,
{
let dest = instruction.get_a() as usize;
let left = instruction.get_b() as usize;
let right = instruction.get_c() as usize;
let result = op(&self.registers[left], &self.registers[right]);
self.registers[dest] = Value::Bool(result);
Ok(())
}
#[inline]
fn logical_op<F>(&mut self, instruction: Instruction, op: F) -> Result<(), String>
where
F: FnOnce(bool, bool) -> bool,
{
let dest = instruction.get_a() as usize;
let left = instruction.get_b() as usize;
let right = instruction.get_c() as usize;
let left_bool = self.registers[left].is_truthy();
let right_bool = self.registers[right].is_truthy();
let result = op(left_bool, right_bool);
self.registers[dest] = Value::Bool(result);
Ok(())
}
}
impl Default for VM {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::frontend::ast::{BinaryOp, Expr, ExprKind, Literal, Span, UnaryOp};
use crate::runtime::bytecode::Compiler;
#[test]
fn test_vm_execute_integer_literal() {
let mut compiler = Compiler::new("test".to_string());
let expr = Expr::new(
ExprKind::Literal(Literal::Integer(42, None)),
Span::default(),
);
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm
.execute(&chunk)
.expect("vm.execute should succeed in test");
assert_eq!(result, Value::Integer(42));
}
#[test]
fn test_vm_execute_addition() {
let mut compiler = Compiler::new("test".to_string());
let left = Expr::new(
ExprKind::Literal(Literal::Integer(10, None)),
Span::default(),
);
let right = Expr::new(
ExprKind::Literal(Literal::Integer(32, None)),
Span::default(),
);
let expr = Expr::new(
ExprKind::Binary {
op: BinaryOp::Add,
left: Box::new(left),
right: Box::new(right),
},
Span::default(),
);
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm
.execute(&chunk)
.expect("vm.execute should succeed in test");
assert_eq!(result, Value::Integer(42));
}
#[test]
fn test_vm_execute_multiplication() {
let mut compiler = Compiler::new("test".to_string());
let left = Expr::new(
ExprKind::Literal(Literal::Integer(6, None)),
Span::default(),
);
let right = Expr::new(
ExprKind::Literal(Literal::Integer(7, None)),
Span::default(),
);
let expr = Expr::new(
ExprKind::Binary {
op: BinaryOp::Multiply,
left: Box::new(left),
right: Box::new(right),
},
Span::default(),
);
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm
.execute(&chunk)
.expect("vm.execute should succeed in test");
assert_eq!(result, Value::Integer(42));
}
#[test]
fn test_vm_execute_comparison() {
let mut compiler = Compiler::new("test".to_string());
let left = Expr::new(
ExprKind::Literal(Literal::Integer(10, None)),
Span::default(),
);
let right = Expr::new(
ExprKind::Literal(Literal::Integer(20, None)),
Span::default(),
);
let expr = Expr::new(
ExprKind::Binary {
op: BinaryOp::Less,
left: Box::new(left),
right: Box::new(right),
},
Span::default(),
);
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm
.execute(&chunk)
.expect("vm.execute should succeed in test");
assert_eq!(result, Value::Bool(true));
}
#[test]
fn test_vm_execute_if_true_branch() {
let mut compiler = Compiler::new("test".to_string());
let condition = Expr::new(ExprKind::Literal(Literal::Bool(true)), Span::default());
let then_branch = Expr::new(
ExprKind::Literal(Literal::Integer(42, None)),
Span::default(),
);
let else_branch = Expr::new(
ExprKind::Literal(Literal::Integer(0, None)),
Span::default(),
);
let expr = Expr::new(
ExprKind::If {
condition: Box::new(condition),
then_branch: Box::new(then_branch),
else_branch: Some(Box::new(else_branch)),
},
Span::default(),
);
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm
.execute(&chunk)
.expect("vm.execute should succeed in test");
assert_eq!(result, Value::Integer(42));
}
#[test]
fn test_vm_execute_if_false_branch() {
let mut compiler = Compiler::new("test".to_string());
let condition = Expr::new(ExprKind::Literal(Literal::Bool(false)), Span::default());
let then_branch = Expr::new(
ExprKind::Literal(Literal::Integer(42, None)),
Span::default(),
);
let else_branch = Expr::new(
ExprKind::Literal(Literal::Integer(100, None)),
Span::default(),
);
let expr = Expr::new(
ExprKind::If {
condition: Box::new(condition),
then_branch: Box::new(then_branch),
else_branch: Some(Box::new(else_branch)),
},
Span::default(),
);
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm
.execute(&chunk)
.expect("vm.execute should succeed in test");
assert_eq!(result, Value::Integer(100));
}
#[test]
fn test_vm_execute_block() {
let mut compiler = Compiler::new("test".to_string());
let exprs = vec![
Expr::new(
ExprKind::Literal(Literal::Integer(1, None)),
Span::default(),
),
Expr::new(
ExprKind::Literal(Literal::Integer(2, None)),
Span::default(),
),
Expr::new(
ExprKind::Literal(Literal::Integer(3, None)),
Span::default(),
),
];
let block = Expr::new(ExprKind::Block(exprs), Span::default());
compiler
.compile_expr(&block)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm
.execute(&chunk)
.expect("vm.execute should succeed in test");
assert_eq!(result, Value::Integer(3));
}
#[test]
fn test_callframe_new_initialization() {
let mut compiler = Compiler::new("test".to_string());
let expr = Expr::new(
ExprKind::Literal(Literal::Integer(42, None)),
Span::default(),
);
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let frame = CallFrame::new(&chunk);
assert_eq!(frame.pc, 0, "PC should initialize to 0");
assert_eq!(
frame.base_register, 0,
"Base register should initialize to 0"
);
}
#[test]
fn test_callframe_fetch_instruction_valid() {
let mut compiler = Compiler::new("test".to_string());
let expr = Expr::new(
ExprKind::Literal(Literal::Integer(42, None)),
Span::default(),
);
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let frame = CallFrame::new(&chunk);
let instruction = frame.fetch_instruction();
assert!(instruction.is_some(), "Should fetch instruction at PC 0");
assert_eq!(
instruction
.expect("instruction should be Some (verified by assert)")
.opcode(),
OpCode::Const as u8,
"First instruction should be Const (load constant)"
);
}
#[test]
fn test_callframe_fetch_instruction_out_of_bounds() {
let mut compiler = Compiler::new("test".to_string());
let expr = Expr::new(
ExprKind::Literal(Literal::Integer(42, None)),
Span::default(),
);
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut frame = CallFrame::new(&chunk);
frame.pc = chunk.instructions.len() + 10;
let instruction = frame.fetch_instruction();
assert!(
instruction.is_none(),
"Should return None for out-of-bounds PC"
);
}
#[test]
fn test_callframe_advance_pc() {
let mut compiler = Compiler::new("test".to_string());
let expr = Expr::new(
ExprKind::Literal(Literal::Integer(42, None)),
Span::default(),
);
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut frame = CallFrame::new(&chunk);
assert_eq!(frame.pc, 0);
frame.advance_pc();
assert_eq!(frame.pc, 1, "PC should increment by 1");
frame.advance_pc();
assert_eq!(frame.pc, 2, "PC should increment again");
}
#[test]
fn test_callframe_jump_positive_offset() {
let mut compiler = Compiler::new("test".to_string());
let expr = Expr::new(
ExprKind::Literal(Literal::Integer(42, None)),
Span::default(),
);
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut frame = CallFrame::new(&chunk);
frame.pc = 5;
frame.jump(10);
assert_eq!(frame.pc, 15, "PC should jump forward by offset");
}
#[test]
fn test_callframe_jump_negative_offset() {
let mut compiler = Compiler::new("test".to_string());
let expr = Expr::new(
ExprKind::Literal(Literal::Integer(42, None)),
Span::default(),
);
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut frame = CallFrame::new(&chunk);
frame.pc = 10;
frame.jump(-5);
assert_eq!(frame.pc, 5, "PC should jump backward by offset");
}
#[test]
fn test_callframe_jump_zero_offset() {
let mut compiler = Compiler::new("test".to_string());
let expr = Expr::new(
ExprKind::Literal(Literal::Integer(42, None)),
Span::default(),
);
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut frame = CallFrame::new(&chunk);
frame.pc = 7;
frame.jump(0);
assert_eq!(frame.pc, 7, "PC should remain unchanged with zero offset");
}
#[test]
fn test_vm_new_initialization() {
let vm = VM::new();
for (idx, reg) in vm.registers.iter().enumerate() {
assert_eq!(*reg, Value::Nil, "Register {idx} should initialize to Nil");
}
assert!(vm.call_stack.is_empty(), "Call stack should be empty");
assert!(vm.globals.is_empty(), "Globals should be empty");
}
#[test]
fn test_vm_register_count() {
let vm = VM::new();
assert_eq!(
vm.registers.len(),
MAX_REGISTERS,
"VM should have exactly {MAX_REGISTERS} registers"
);
}
#[test]
fn test_vm_execute_empty_bytecode() {
let compiler = Compiler::new("test".to_string());
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm
.execute(&chunk)
.expect("vm.execute should succeed in test");
assert_eq!(result, Value::Nil, "Empty bytecode should return Nil");
}
#[test]
fn test_vm_multiple_sequential_executions() {
let mut vm = VM::new();
let mut compiler1 = Compiler::new("test1".to_string());
let left1 = Expr::new(
ExprKind::Literal(Literal::Integer(10, None)),
Span::default(),
);
let right1 = Expr::new(
ExprKind::Literal(Literal::Integer(20, None)),
Span::default(),
);
let expr1 = Expr::new(
ExprKind::Binary {
op: BinaryOp::Add,
left: Box::new(left1),
right: Box::new(right1),
},
Span::default(),
);
compiler1
.compile_expr(&expr1)
.expect("compile_expr should succeed in test");
let chunk1 = compiler1.finalize();
let result1 = vm
.execute(&chunk1)
.expect("vm.execute should succeed in test");
assert_eq!(result1, Value::Integer(30));
let mut compiler2 = Compiler::new("test2".to_string());
let left2 = Expr::new(
ExprKind::Literal(Literal::Integer(5, None)),
Span::default(),
);
let right2 = Expr::new(
ExprKind::Literal(Literal::Integer(6, None)),
Span::default(),
);
let expr2 = Expr::new(
ExprKind::Binary {
op: BinaryOp::Multiply,
left: Box::new(left2),
right: Box::new(right2),
},
Span::default(),
);
compiler2
.compile_expr(&expr2)
.expect("compile_expr should succeed in test");
let chunk2 = compiler2.finalize();
let result2 = vm
.execute(&chunk2)
.expect("vm.execute should succeed in test");
assert_eq!(result2, Value::Integer(30));
}
#[test]
fn test_vm_register_isolation_between_executions() {
let mut vm = VM::new();
let mut compiler1 = Compiler::new("test1".to_string());
let expr1 = Expr::new(
ExprKind::Literal(Literal::Integer(42, None)),
Span::default(),
);
compiler1
.compile_expr(&expr1)
.expect("compile_expr should succeed in test");
let chunk1 = compiler1.finalize();
let result1 = vm
.execute(&chunk1)
.expect("vm.execute should succeed in test");
assert_eq!(result1, Value::Integer(42));
let mut compiler2 = Compiler::new("test2".to_string());
let expr2 = Expr::new(
ExprKind::Literal(Literal::Integer(100, None)),
Span::default(),
);
compiler2
.compile_expr(&expr2)
.expect("compile_expr should succeed in test");
let chunk2 = compiler2.finalize();
let result2 = vm
.execute(&chunk2)
.expect("vm.execute should succeed in test");
assert_eq!(
result2,
Value::Integer(100),
"Second execution should overwrite register 0"
);
}
#[test]
fn test_vm_opcode_subtraction() {
let mut compiler = Compiler::new("test".to_string());
let left = Expr::new(
ExprKind::Literal(Literal::Integer(50, None)),
Span::default(),
);
let right = Expr::new(
ExprKind::Literal(Literal::Integer(8, None)),
Span::default(),
);
let expr = Expr::new(
ExprKind::Binary {
op: BinaryOp::Subtract,
left: Box::new(left),
right: Box::new(right),
},
Span::default(),
);
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm
.execute(&chunk)
.expect("vm.execute should succeed in test");
assert_eq!(result, Value::Integer(42));
}
#[test]
fn test_vm_opcode_division() {
let mut compiler = Compiler::new("test".to_string());
let left = Expr::new(
ExprKind::Literal(Literal::Integer(84, None)),
Span::default(),
);
let right = Expr::new(
ExprKind::Literal(Literal::Integer(2, None)),
Span::default(),
);
let expr = Expr::new(
ExprKind::Binary {
op: BinaryOp::Divide,
left: Box::new(left),
right: Box::new(right),
},
Span::default(),
);
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm
.execute(&chunk)
.expect("vm.execute should succeed in test");
assert_eq!(result, Value::Integer(42));
}
#[test]
fn test_vm_opcode_modulo() {
let mut compiler = Compiler::new("test".to_string());
let left = Expr::new(
ExprKind::Literal(Literal::Integer(100, None)),
Span::default(),
);
let right = Expr::new(
ExprKind::Literal(Literal::Integer(58, None)),
Span::default(),
);
let expr = Expr::new(
ExprKind::Binary {
op: BinaryOp::Modulo,
left: Box::new(left),
right: Box::new(right),
},
Span::default(),
);
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm
.execute(&chunk)
.expect("vm.execute should succeed in test");
assert_eq!(result, Value::Integer(42));
}
#[test]
fn test_vm_opcode_negation_integer() {
let mut compiler = Compiler::new("test".to_string());
let inner = Expr::new(
ExprKind::Unary {
op: crate::frontend::ast::UnaryOp::Negate,
operand: Box::new(Expr::new(
ExprKind::Literal(Literal::Integer(42, None)),
Span::default(),
)),
},
Span::default(),
);
let expr = Expr::new(
ExprKind::Unary {
op: crate::frontend::ast::UnaryOp::Negate,
operand: Box::new(inner),
},
Span::default(),
);
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm
.execute(&chunk)
.expect("vm.execute should succeed in test");
assert_eq!(result, Value::Integer(42));
}
#[test]
fn test_vm_opcode_negation_float() {
let mut compiler = Compiler::new("test".to_string());
let expr = Expr::new(
ExprKind::Unary {
op: crate::frontend::ast::UnaryOp::Negate,
operand: Box::new(Expr::new(
ExprKind::Literal(Literal::Float(std::f64::consts::PI)),
Span::default(),
)),
},
Span::default(),
);
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm
.execute(&chunk)
.expect("vm.execute should succeed in test");
assert_eq!(result, Value::Float(-std::f64::consts::PI));
}
#[test]
fn test_vm_opcode_logical_not_true() {
let mut compiler = Compiler::new("test".to_string());
let expr = Expr::new(
ExprKind::Unary {
op: crate::frontend::ast::UnaryOp::Not,
operand: Box::new(Expr::new(
ExprKind::Literal(Literal::Bool(true)),
Span::default(),
)),
},
Span::default(),
);
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm
.execute(&chunk)
.expect("vm.execute should succeed in test");
assert_eq!(result, Value::Bool(false));
}
#[test]
fn test_vm_opcode_logical_not_false() {
let mut compiler = Compiler::new("test".to_string());
let expr = Expr::new(
ExprKind::Unary {
op: crate::frontend::ast::UnaryOp::Not,
operand: Box::new(Expr::new(
ExprKind::Literal(Literal::Bool(false)),
Span::default(),
)),
},
Span::default(),
);
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm
.execute(&chunk)
.expect("vm.execute should succeed in test");
assert_eq!(result, Value::Bool(true));
}
#[test]
fn test_vm_opcode_equal_true() {
let mut compiler = Compiler::new("test".to_string());
let left = Expr::new(
ExprKind::Literal(Literal::Integer(42, None)),
Span::default(),
);
let right = Expr::new(
ExprKind::Literal(Literal::Integer(42, None)),
Span::default(),
);
let expr = Expr::new(
ExprKind::Binary {
op: BinaryOp::Equal,
left: Box::new(left),
right: Box::new(right),
},
Span::default(),
);
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm
.execute(&chunk)
.expect("vm.execute should succeed in test");
assert_eq!(result, Value::Bool(true));
}
#[test]
fn test_vm_opcode_equal_false() {
let mut compiler = Compiler::new("test".to_string());
let left = Expr::new(
ExprKind::Literal(Literal::Integer(42, None)),
Span::default(),
);
let right = Expr::new(
ExprKind::Literal(Literal::Integer(100, None)),
Span::default(),
);
let expr = Expr::new(
ExprKind::Binary {
op: BinaryOp::Equal,
left: Box::new(left),
right: Box::new(right),
},
Span::default(),
);
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm
.execute(&chunk)
.expect("vm.execute should succeed in test");
assert_eq!(result, Value::Bool(false));
}
#[test]
fn test_vm_opcode_not_equal() {
let mut compiler = Compiler::new("test".to_string());
let left = Expr::new(
ExprKind::Literal(Literal::Integer(42, None)),
Span::default(),
);
let right = Expr::new(
ExprKind::Literal(Literal::Integer(100, None)),
Span::default(),
);
let expr = Expr::new(
ExprKind::Binary {
op: BinaryOp::NotEqual,
left: Box::new(left),
right: Box::new(right),
},
Span::default(),
);
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm
.execute(&chunk)
.expect("vm.execute should succeed in test");
assert_eq!(result, Value::Bool(true));
}
#[test]
fn test_vm_opcode_less_equal() {
let mut compiler = Compiler::new("test".to_string());
let left = Expr::new(
ExprKind::Literal(Literal::Integer(42, None)),
Span::default(),
);
let right = Expr::new(
ExprKind::Literal(Literal::Integer(42, None)),
Span::default(),
);
let expr = Expr::new(
ExprKind::Binary {
op: BinaryOp::LessEqual,
left: Box::new(left),
right: Box::new(right),
},
Span::default(),
);
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm
.execute(&chunk)
.expect("vm.execute should succeed in test");
assert_eq!(result, Value::Bool(true));
}
#[test]
fn test_vm_opcode_greater() {
let mut compiler = Compiler::new("test".to_string());
let left = Expr::new(
ExprKind::Literal(Literal::Integer(100, None)),
Span::default(),
);
let right = Expr::new(
ExprKind::Literal(Literal::Integer(42, None)),
Span::default(),
);
let expr = Expr::new(
ExprKind::Binary {
op: BinaryOp::Greater,
left: Box::new(left),
right: Box::new(right),
},
Span::default(),
);
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm
.execute(&chunk)
.expect("vm.execute should succeed in test");
assert_eq!(result, Value::Bool(true));
}
#[test]
fn test_vm_opcode_greater_equal() {
let mut compiler = Compiler::new("test".to_string());
let left = Expr::new(
ExprKind::Literal(Literal::Integer(42, None)),
Span::default(),
);
let right = Expr::new(
ExprKind::Literal(Literal::Integer(42, None)),
Span::default(),
);
let expr = Expr::new(
ExprKind::Binary {
op: BinaryOp::GreaterEqual,
left: Box::new(left),
right: Box::new(right),
},
Span::default(),
);
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm
.execute(&chunk)
.expect("vm.execute should succeed in test");
assert_eq!(result, Value::Bool(true));
}
#[test]
fn test_vm_opcode_logical_and_true() {
let mut compiler = Compiler::new("test".to_string());
let left = Expr::new(ExprKind::Literal(Literal::Bool(true)), Span::default());
let right = Expr::new(ExprKind::Literal(Literal::Bool(true)), Span::default());
let expr = Expr::new(
ExprKind::Binary {
op: BinaryOp::And,
left: Box::new(left),
right: Box::new(right),
},
Span::default(),
);
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm
.execute(&chunk)
.expect("vm.execute should succeed in test");
assert_eq!(result, Value::Bool(true));
}
#[test]
fn test_vm_opcode_logical_and_false() {
let mut compiler = Compiler::new("test".to_string());
let left = Expr::new(ExprKind::Literal(Literal::Bool(true)), Span::default());
let right = Expr::new(ExprKind::Literal(Literal::Bool(false)), Span::default());
let expr = Expr::new(
ExprKind::Binary {
op: BinaryOp::And,
left: Box::new(left),
right: Box::new(right),
},
Span::default(),
);
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm
.execute(&chunk)
.expect("vm.execute should succeed in test");
assert_eq!(result, Value::Bool(false));
}
#[test]
fn test_vm_opcode_logical_or_true() {
let mut compiler = Compiler::new("test".to_string());
let left = Expr::new(ExprKind::Literal(Literal::Bool(false)), Span::default());
let right = Expr::new(ExprKind::Literal(Literal::Bool(true)), Span::default());
let expr = Expr::new(
ExprKind::Binary {
op: BinaryOp::Or,
left: Box::new(left),
right: Box::new(right),
},
Span::default(),
);
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm
.execute(&chunk)
.expect("vm.execute should succeed in test");
assert_eq!(result, Value::Bool(true));
}
#[test]
fn test_vm_opcode_logical_or_false() {
let mut compiler = Compiler::new("test".to_string());
let left = Expr::new(ExprKind::Literal(Literal::Bool(false)), Span::default());
let right = Expr::new(ExprKind::Literal(Literal::Bool(false)), Span::default());
let expr = Expr::new(
ExprKind::Binary {
op: BinaryOp::Or,
left: Box::new(left),
right: Box::new(right),
},
Span::default(),
);
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm
.execute(&chunk)
.expect("vm.execute should succeed in test");
assert_eq!(result, Value::Bool(false));
}
#[test]
fn test_vm_opcode_array_literal() {
let mut compiler = Compiler::new("test".to_string());
let elements = vec![
Expr::new(
ExprKind::Literal(Literal::Integer(1, None)),
Span::default(),
),
Expr::new(
ExprKind::Literal(Literal::Integer(2, None)),
Span::default(),
),
Expr::new(
ExprKind::Literal(Literal::Integer(3, None)),
Span::default(),
),
];
let expr = Expr::new(ExprKind::List(elements), Span::default());
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm
.execute(&chunk)
.expect("vm.execute should succeed in test");
match result {
Value::Array(arr) => {
assert_eq!(arr.len(), 3);
assert_eq!(arr[0], Value::Integer(1));
assert_eq!(arr[1], Value::Integer(2));
assert_eq!(arr[2], Value::Integer(3));
}
_ => panic!("Expected array, got {result:?}"),
}
}
#[test]
fn test_vm_opcode_array_empty() {
let mut compiler = Compiler::new("test".to_string());
let expr = Expr::new(ExprKind::List(vec![]), Span::default());
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm
.execute(&chunk)
.expect("vm.execute should succeed in test");
match result {
Value::Array(arr) => assert_eq!(arr.len(), 0),
_ => panic!("Expected array, got {result:?}"),
}
}
#[test]
fn test_vm_opcode_tuple_literal() {
let mut compiler = Compiler::new("test".to_string());
let elements = vec![
Expr::new(
ExprKind::Literal(Literal::Integer(42, None)),
Span::default(),
),
Expr::new(ExprKind::Literal(Literal::Bool(true)), Span::default()),
Expr::new(
ExprKind::Literal(Literal::String("hello".to_string())),
Span::default(),
),
];
let expr = Expr::new(ExprKind::Tuple(elements), Span::default());
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm
.execute(&chunk)
.expect("vm.execute should succeed in test");
match result {
Value::Tuple(tuple) => {
assert_eq!(tuple.len(), 3);
assert_eq!(tuple[0], Value::Integer(42));
assert_eq!(tuple[1], Value::Bool(true));
assert_eq!(tuple[2], Value::from_string("hello".to_string()));
}
_ => panic!("Expected tuple, got {result:?}"),
}
}
#[test]
fn test_vm_opcode_object_literal() {
let mut compiler = Compiler::new("test".to_string());
use crate::frontend::ast::ObjectField;
let fields = vec![
ObjectField::KeyValue {
key: "x".to_string(),
value: Expr::new(
ExprKind::Literal(Literal::Integer(10, None)),
Span::default(),
),
},
ObjectField::KeyValue {
key: "y".to_string(),
value: Expr::new(
ExprKind::Literal(Literal::Integer(20, None)),
Span::default(),
),
},
];
let expr = Expr::new(ExprKind::ObjectLiteral { fields }, Span::default());
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm
.execute(&chunk)
.expect("vm.execute should succeed in test");
match result {
Value::Object(obj) => {
assert_eq!(obj.get("x"), Some(&Value::Integer(10)));
assert_eq!(obj.get("y"), Some(&Value::Integer(20)));
}
_ => panic!("Expected object, got {result:?}"),
}
}
#[test]
fn test_vm_opcode_array_index_access() {
let mut compiler = Compiler::new("test".to_string());
let array = Expr::new(
ExprKind::List(vec![
Expr::new(
ExprKind::Literal(Literal::Integer(10, None)),
Span::default(),
),
Expr::new(
ExprKind::Literal(Literal::Integer(20, None)),
Span::default(),
),
Expr::new(
ExprKind::Literal(Literal::Integer(30, None)),
Span::default(),
),
]),
Span::default(),
);
let index = Expr::new(
ExprKind::Literal(Literal::Integer(1, None)),
Span::default(),
);
let expr = Expr::new(
ExprKind::IndexAccess {
object: Box::new(array),
index: Box::new(index),
},
Span::default(),
);
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm
.execute(&chunk)
.expect("vm.execute should succeed in test");
assert_eq!(result, Value::Integer(20));
}
#[test]
fn test_vm_opcode_array_index_negative() {
let mut compiler = Compiler::new("test".to_string());
let array = Expr::new(
ExprKind::List(vec![
Expr::new(
ExprKind::Literal(Literal::Integer(10, None)),
Span::default(),
),
Expr::new(
ExprKind::Literal(Literal::Integer(20, None)),
Span::default(),
),
Expr::new(
ExprKind::Literal(Literal::Integer(30, None)),
Span::default(),
),
]),
Span::default(),
);
let index = Expr::new(
ExprKind::Literal(Literal::Integer(-1, None)),
Span::default(),
);
let expr = Expr::new(
ExprKind::IndexAccess {
object: Box::new(array),
index: Box::new(index),
},
Span::default(),
);
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm
.execute(&chunk)
.expect("vm.execute should succeed in test");
assert_eq!(result, Value::Integer(30));
}
#[test]
fn test_vm_opcode_string_index_access() {
let mut compiler = Compiler::new("test".to_string());
let string = Expr::new(
ExprKind::Literal(Literal::String("hello".to_string())),
Span::default(),
);
let index = Expr::new(
ExprKind::Literal(Literal::Integer(1, None)),
Span::default(),
);
let expr = Expr::new(
ExprKind::IndexAccess {
object: Box::new(string),
index: Box::new(index),
},
Span::default(),
);
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm
.execute(&chunk)
.expect("vm.execute should succeed in test");
assert_eq!(result, Value::from_string("e".to_string()));
}
#[test]
fn test_vm_opcode_object_field_access() {
let mut compiler = Compiler::new("test".to_string());
use crate::frontend::ast::ObjectField;
let fields = vec![ObjectField::KeyValue {
key: "x".to_string(),
value: Expr::new(
ExprKind::Literal(Literal::Integer(42, None)),
Span::default(),
),
}];
let object = Expr::new(ExprKind::ObjectLiteral { fields }, Span::default());
let expr = Expr::new(
ExprKind::FieldAccess {
object: Box::new(object),
field: "x".to_string(),
},
Span::default(),
);
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm
.execute(&chunk)
.expect("vm.execute should succeed in test");
assert_eq!(result, Value::Integer(42));
}
#[test]
fn test_vm_opcode_tuple_field_access() {
let mut compiler = Compiler::new("test".to_string());
let tuple = Expr::new(
ExprKind::Tuple(vec![
Expr::new(
ExprKind::Literal(Literal::Integer(100, None)),
Span::default(),
),
Expr::new(
ExprKind::Literal(Literal::Integer(200, None)),
Span::default(),
),
]),
Span::default(),
);
let expr = Expr::new(
ExprKind::FieldAccess {
object: Box::new(tuple),
field: "0".to_string(),
},
Span::default(),
);
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm
.execute(&chunk)
.expect("vm.execute should succeed in test");
assert_eq!(result, Value::Integer(100));
}
#[test]
fn test_vm_opcode_nested_array_access() {
let mut compiler = Compiler::new("test".to_string());
let inner1 = Expr::new(
ExprKind::List(vec![
Expr::new(
ExprKind::Literal(Literal::Integer(1, None)),
Span::default(),
),
Expr::new(
ExprKind::Literal(Literal::Integer(2, None)),
Span::default(),
),
]),
Span::default(),
);
let inner2 = Expr::new(
ExprKind::List(vec![
Expr::new(
ExprKind::Literal(Literal::Integer(3, None)),
Span::default(),
),
Expr::new(
ExprKind::Literal(Literal::Integer(4, None)),
Span::default(),
),
]),
Span::default(),
);
let outer = Expr::new(ExprKind::List(vec![inner1, inner2]), Span::default());
let first_index = Expr::new(
ExprKind::IndexAccess {
object: Box::new(outer),
index: Box::new(Expr::new(
ExprKind::Literal(Literal::Integer(1, None)),
Span::default(),
)),
},
Span::default(),
);
let expr = Expr::new(
ExprKind::IndexAccess {
object: Box::new(first_index),
index: Box::new(Expr::new(
ExprKind::Literal(Literal::Integer(0, None)),
Span::default(),
)),
},
Span::default(),
);
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm
.execute(&chunk)
.expect("vm.execute should succeed in test");
assert_eq!(result, Value::Integer(3));
}
#[test]
fn test_vm_opcode_if_else_both_branches() {
let mut compiler = Compiler::new("test".to_string());
let condition = Expr::new(ExprKind::Literal(Literal::Bool(false)), Span::default());
let then_branch = Expr::new(
ExprKind::Literal(Literal::Integer(10, None)),
Span::default(),
);
let else_branch = Expr::new(
ExprKind::Literal(Literal::Integer(20, None)),
Span::default(),
);
let expr = Expr::new(
ExprKind::If {
condition: Box::new(condition),
then_branch: Box::new(then_branch),
else_branch: Some(Box::new(else_branch)),
},
Span::default(),
);
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm
.execute(&chunk)
.expect("vm.execute should succeed in test");
assert_eq!(result, Value::Integer(20)); }
#[test]
fn test_vm_opcode_if_without_else_true() {
let mut compiler = Compiler::new("test".to_string());
let condition = Expr::new(ExprKind::Literal(Literal::Bool(true)), Span::default());
let then_branch = Expr::new(
ExprKind::Literal(Literal::Integer(42, None)),
Span::default(),
);
let expr = Expr::new(
ExprKind::If {
condition: Box::new(condition),
then_branch: Box::new(then_branch),
else_branch: None,
},
Span::default(),
);
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm
.execute(&chunk)
.expect("vm.execute should succeed in test");
assert_eq!(result, Value::Integer(42));
}
#[test]
fn test_vm_opcode_if_without_else_false() {
let mut compiler = Compiler::new("test".to_string());
let condition = Expr::new(ExprKind::Literal(Literal::Bool(false)), Span::default());
let then_branch = Expr::new(
ExprKind::Literal(Literal::Integer(42, None)),
Span::default(),
);
let expr = Expr::new(
ExprKind::If {
condition: Box::new(condition),
then_branch: Box::new(then_branch),
else_branch: None,
},
Span::default(),
);
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm
.execute(&chunk)
.expect("vm.execute should succeed in test");
assert_eq!(result, Value::Nil);
}
#[test]
fn test_vm_opcode_truthy_nonzero_integer() {
let mut compiler = Compiler::new("test".to_string());
let condition = Expr::new(
ExprKind::Literal(Literal::Integer(42, None)),
Span::default(),
);
let then_branch = Expr::new(
ExprKind::Literal(Literal::Integer(100, None)),
Span::default(),
);
let else_branch = Expr::new(
ExprKind::Literal(Literal::Integer(200, None)),
Span::default(),
);
let expr = Expr::new(
ExprKind::If {
condition: Box::new(condition),
then_branch: Box::new(then_branch),
else_branch: Some(Box::new(else_branch)),
},
Span::default(),
);
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm
.execute(&chunk)
.expect("vm.execute should succeed in test");
assert_eq!(result, Value::Integer(100)); }
#[test]
fn test_vm_opcode_array_index_out_of_bounds() {
let mut compiler = Compiler::new("test".to_string());
let array = Expr::new(
ExprKind::List(vec![
Expr::new(
ExprKind::Literal(Literal::Integer(10, None)),
Span::default(),
),
Expr::new(
ExprKind::Literal(Literal::Integer(20, None)),
Span::default(),
),
]),
Span::default(),
);
let expr = Expr::new(
ExprKind::IndexAccess {
object: Box::new(array),
index: Box::new(Expr::new(
ExprKind::Literal(Literal::Integer(5, None)),
Span::default(),
)),
},
Span::default(),
);
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm.execute(&chunk);
assert!(result.is_err());
assert!(result.unwrap_err().contains("out of bounds"));
}
#[test]
fn test_vm_opcode_string_index_out_of_bounds() {
let mut compiler = Compiler::new("test".to_string());
let string = Expr::new(
ExprKind::Literal(Literal::String("hello".to_string())),
Span::default(),
);
let expr = Expr::new(
ExprKind::IndexAccess {
object: Box::new(string),
index: Box::new(Expr::new(
ExprKind::Literal(Literal::Integer(10, None)),
Span::default(),
)),
},
Span::default(),
);
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm.execute(&chunk);
assert!(result.is_err());
assert!(result.unwrap_err().contains("out of bounds"));
}
#[test]
fn test_vm_opcode_field_access_missing_field() {
let mut compiler = Compiler::new("test".to_string());
use crate::frontend::ast::ObjectField;
let object = Expr::new(
ExprKind::ObjectLiteral {
fields: vec![ObjectField::KeyValue {
key: "x".to_string(),
value: Expr::new(
ExprKind::Literal(Literal::Integer(10, None)),
Span::default(),
),
}],
},
Span::default(),
);
let expr = Expr::new(
ExprKind::FieldAccess {
object: Box::new(object),
field: "y".to_string(),
},
Span::default(),
);
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm.execute(&chunk);
assert!(result.is_err());
assert!(result.unwrap_err().contains("not found"));
}
#[test]
fn test_vm_opcode_tuple_index_out_of_bounds() {
let mut compiler = Compiler::new("test".to_string());
let tuple = Expr::new(
ExprKind::Tuple(vec![
Expr::new(
ExprKind::Literal(Literal::Integer(100, None)),
Span::default(),
),
Expr::new(
ExprKind::Literal(Literal::Integer(200, None)),
Span::default(),
),
]),
Span::default(),
);
let expr = Expr::new(
ExprKind::FieldAccess {
object: Box::new(tuple),
field: "5".to_string(),
},
Span::default(),
);
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm.execute(&chunk);
assert!(result.is_err());
assert!(result.unwrap_err().contains("out of bounds"));
}
#[test]
#[ignore = "VM doesn't implement divide-by-zero error handling yet - panics instead of returning error"]
fn test_vm_opcode_division_by_zero_integer() {
let mut compiler = Compiler::new("test".to_string());
let expr = Expr::new(
ExprKind::Binary {
left: Box::new(Expr::new(
ExprKind::Literal(Literal::Integer(10, None)),
Span::default(),
)),
op: BinaryOp::Divide,
right: Box::new(Expr::new(
ExprKind::Literal(Literal::Integer(0, None)),
Span::default(),
)),
},
Span::default(),
);
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm.execute(&chunk);
assert!(result.is_err());
let err_msg = result.unwrap_err();
assert!(err_msg.contains("division by zero") || err_msg.contains("divide by zero"));
}
#[test]
#[ignore = "VM doesn't implement modulo-by-zero error handling yet - panics instead of returning error"]
fn test_vm_opcode_modulo_by_zero() {
let mut compiler = Compiler::new("test".to_string());
let expr = Expr::new(
ExprKind::Binary {
left: Box::new(Expr::new(
ExprKind::Literal(Literal::Integer(10, None)),
Span::default(),
)),
op: BinaryOp::Modulo,
right: Box::new(Expr::new(
ExprKind::Literal(Literal::Integer(0, None)),
Span::default(),
)),
},
Span::default(),
);
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm.execute(&chunk);
assert!(result.is_err());
let err_msg = result.unwrap_err();
assert!(err_msg.contains("modulo by zero") || err_msg.contains("divide by zero"));
}
#[test]
fn test_vm_opcode_bitnot_on_float_error() {
let mut compiler = Compiler::new("test".to_string());
let expr = Expr::new(
ExprKind::Unary {
op: UnaryOp::BitwiseNot,
operand: Box::new(Expr::new(
ExprKind::Literal(Literal::Float(std::f64::consts::PI)),
Span::default(),
)),
},
Span::default(),
);
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm.execute(&chunk);
assert!(result.is_err());
let err_msg = result.unwrap_err();
assert!(err_msg.contains("bitwise NOT") || err_msg.contains("Float"));
}
#[test]
fn test_vm_opcode_negate_string_error() {
let mut compiler = Compiler::new("test".to_string());
let expr = Expr::new(
ExprKind::Unary {
op: UnaryOp::Negate,
operand: Box::new(Expr::new(
ExprKind::Literal(Literal::String("hello".to_string())),
Span::default(),
)),
},
Span::default(),
);
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm.execute(&chunk);
assert!(result.is_err());
let err_msg = result.unwrap_err();
assert!(err_msg.contains("negate") || err_msg.contains("String"));
}
#[test]
fn test_vm_opcode_complex_arithmetic() {
let mut compiler = Compiler::new("test".to_string());
let add = Expr::new(
ExprKind::Binary {
left: Box::new(Expr::new(
ExprKind::Literal(Literal::Integer(10, None)),
Span::default(),
)),
op: BinaryOp::Add,
right: Box::new(Expr::new(
ExprKind::Literal(Literal::Integer(20, None)),
Span::default(),
)),
},
Span::default(),
);
let mul = Expr::new(
ExprKind::Binary {
left: Box::new(add),
op: BinaryOp::Multiply,
right: Box::new(Expr::new(
ExprKind::Literal(Literal::Integer(3, None)),
Span::default(),
)),
},
Span::default(),
);
let expr = Expr::new(
ExprKind::Binary {
left: Box::new(mul),
op: BinaryOp::Subtract,
right: Box::new(Expr::new(
ExprKind::Literal(Literal::Integer(5, None)),
Span::default(),
)),
},
Span::default(),
);
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm
.execute(&chunk)
.expect("vm.execute should succeed in test");
assert_eq!(result, Value::Integer(85)); }
#[test]
fn test_vm_opcode_complex_boolean_logic() {
let mut compiler = Compiler::new("test".to_string());
let and1 = Expr::new(
ExprKind::Binary {
left: Box::new(Expr::new(
ExprKind::Literal(Literal::Bool(true)),
Span::default(),
)),
op: BinaryOp::And,
right: Box::new(Expr::new(
ExprKind::Literal(Literal::Bool(false)),
Span::default(),
)),
},
Span::default(),
);
let and2 = Expr::new(
ExprKind::Binary {
left: Box::new(Expr::new(
ExprKind::Literal(Literal::Bool(true)),
Span::default(),
)),
op: BinaryOp::And,
right: Box::new(Expr::new(
ExprKind::Literal(Literal::Bool(true)),
Span::default(),
)),
},
Span::default(),
);
let expr = Expr::new(
ExprKind::Binary {
left: Box::new(and1),
op: BinaryOp::Or,
right: Box::new(and2),
},
Span::default(),
);
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm
.execute(&chunk)
.expect("vm.execute should succeed in test");
assert_eq!(result, Value::Bool(true)); }
#[test]
fn test_vm_opcode_float_arithmetic() {
let mut compiler = Compiler::new("test".to_string());
let mul = Expr::new(
ExprKind::Binary {
left: Box::new(Expr::new(
ExprKind::Literal(Literal::Float(3.5)),
Span::default(),
)),
op: BinaryOp::Multiply,
right: Box::new(Expr::new(
ExprKind::Literal(Literal::Float(2.0)),
Span::default(),
)),
},
Span::default(),
);
let expr = Expr::new(
ExprKind::Binary {
left: Box::new(mul),
op: BinaryOp::Add,
right: Box::new(Expr::new(
ExprKind::Literal(Literal::Float(1.5)),
Span::default(),
)),
},
Span::default(),
);
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm
.execute(&chunk)
.expect("vm.execute should succeed in test");
match result {
Value::Float(f) => assert!((f - 8.5).abs() < 0.001), _ => panic!("Expected Float, got {result:?}"),
}
}
#[test]
fn test_vm_opcode_comparison_chain() {
let mut compiler = Compiler::new("test".to_string());
let cmp1 = Expr::new(
ExprKind::Binary {
left: Box::new(Expr::new(
ExprKind::Literal(Literal::Integer(5, None)),
Span::default(),
)),
op: BinaryOp::Greater,
right: Box::new(Expr::new(
ExprKind::Literal(Literal::Integer(3, None)),
Span::default(),
)),
},
Span::default(),
);
let cmp2 = Expr::new(
ExprKind::Binary {
left: Box::new(Expr::new(
ExprKind::Literal(Literal::Integer(3, None)),
Span::default(),
)),
op: BinaryOp::Greater,
right: Box::new(Expr::new(
ExprKind::Literal(Literal::Integer(1, None)),
Span::default(),
)),
},
Span::default(),
);
let expr = Expr::new(
ExprKind::Binary {
left: Box::new(cmp1),
op: BinaryOp::And,
right: Box::new(cmp2),
},
Span::default(),
);
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm
.execute(&chunk)
.expect("vm.execute should succeed in test");
assert_eq!(result, Value::Bool(true)); }
#[test]
fn test_vm_opcode_nil_truthy() {
let mut compiler = Compiler::new("test".to_string());
let condition = Expr::new(ExprKind::Literal(Literal::Null), Span::default());
let then_branch = Expr::new(
ExprKind::Literal(Literal::Integer(10, None)),
Span::default(),
);
let else_branch = Expr::new(
ExprKind::Literal(Literal::Integer(20, None)),
Span::default(),
);
let expr = Expr::new(
ExprKind::If {
condition: Box::new(condition),
then_branch: Box::new(then_branch),
else_branch: Some(Box::new(else_branch)),
},
Span::default(),
);
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm
.execute(&chunk)
.expect("vm.execute should succeed in test");
assert_eq!(result, Value::Integer(20)); }
#[test]
fn test_vm_opcode_double_negation() {
let mut compiler = Compiler::new("test".to_string());
let inner = Expr::new(
ExprKind::Unary {
op: UnaryOp::Negate,
operand: Box::new(Expr::new(
ExprKind::Literal(Literal::Integer(42, None)),
Span::default(),
)),
},
Span::default(),
);
let expr = Expr::new(
ExprKind::Unary {
op: UnaryOp::Negate,
operand: Box::new(inner),
},
Span::default(),
);
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm
.execute(&chunk)
.expect("vm.execute should succeed in test");
assert_eq!(result, Value::Integer(42)); }
#[test]
fn test_vm_default_trait() {
let vm = VM::default();
assert!(vm.call_stack.is_empty());
assert!(vm.globals.is_empty());
for reg in vm.registers.iter() {
assert_eq!(*reg, Value::Nil);
}
}
#[test]
fn test_vm_opcode_bitnot_integer() {
let mut compiler = Compiler::new("test".to_string());
let expr = Expr::new(
ExprKind::Unary {
op: UnaryOp::BitwiseNot,
operand: Box::new(Expr::new(
ExprKind::Literal(Literal::Integer(5, None)),
Span::default(),
)),
},
Span::default(),
);
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm
.execute(&chunk)
.expect("vm.execute should succeed in test");
assert_eq!(result, Value::Integer(!5)); }
#[test]
fn test_vm_opcode_string_negative_index() {
let mut compiler = Compiler::new("test".to_string());
let string = Expr::new(
ExprKind::Literal(Literal::String("hello".to_string())),
Span::default(),
);
let index = Expr::new(
ExprKind::Literal(Literal::Integer(-1, None)),
Span::default(),
);
let expr = Expr::new(
ExprKind::IndexAccess {
object: Box::new(string),
index: Box::new(index),
},
Span::default(),
);
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm
.execute(&chunk)
.expect("vm.execute should succeed in test");
assert_eq!(result, Value::from_string("o".to_string()));
}
#[test]
fn test_vm_opcode_string_negative_index_second_last() {
let mut compiler = Compiler::new("test".to_string());
let string = Expr::new(
ExprKind::Literal(Literal::String("hello".to_string())),
Span::default(),
);
let index = Expr::new(
ExprKind::Literal(Literal::Integer(-2, None)),
Span::default(),
);
let expr = Expr::new(
ExprKind::IndexAccess {
object: Box::new(string),
index: Box::new(index),
},
Span::default(),
);
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm
.execute(&chunk)
.expect("vm.execute should succeed in test");
assert_eq!(result, Value::from_string("l".to_string()));
}
#[test]
fn test_vm_opcode_array_negative_index_second_last() {
let mut compiler = Compiler::new("test".to_string());
let array = Expr::new(
ExprKind::List(vec![
Expr::new(
ExprKind::Literal(Literal::Integer(10, None)),
Span::default(),
),
Expr::new(
ExprKind::Literal(Literal::Integer(20, None)),
Span::default(),
),
Expr::new(
ExprKind::Literal(Literal::Integer(30, None)),
Span::default(),
),
]),
Span::default(),
);
let index = Expr::new(
ExprKind::Literal(Literal::Integer(-2, None)),
Span::default(),
);
let expr = Expr::new(
ExprKind::IndexAccess {
object: Box::new(array),
index: Box::new(index),
},
Span::default(),
);
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm
.execute(&chunk)
.expect("vm.execute should succeed in test");
assert_eq!(result, Value::Integer(20));
}
#[test]
fn test_vm_opcode_jump_if_true_with_truthy_value() {
let mut compiler = Compiler::new("test".to_string());
let condition = Expr::new(
ExprKind::Binary {
op: BinaryOp::Greater,
left: Box::new(Expr::new(
ExprKind::Literal(Literal::Integer(5, None)),
Span::default(),
)),
right: Box::new(Expr::new(
ExprKind::Literal(Literal::Integer(3, None)),
Span::default(),
)),
},
Span::default(),
);
let then_branch = Expr::new(
ExprKind::Literal(Literal::Integer(100, None)),
Span::default(),
);
let else_branch = Expr::new(
ExprKind::Literal(Literal::Integer(200, None)),
Span::default(),
);
let expr = Expr::new(
ExprKind::If {
condition: Box::new(condition),
then_branch: Box::new(then_branch),
else_branch: Some(Box::new(else_branch)),
},
Span::default(),
);
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm
.execute(&chunk)
.expect("vm.execute should succeed in test");
assert_eq!(result, Value::Integer(100));
}
#[test]
fn test_vm_opcode_jump_if_true_with_false_comparison() {
let mut compiler = Compiler::new("test".to_string());
let condition = Expr::new(
ExprKind::Binary {
op: BinaryOp::Greater,
left: Box::new(Expr::new(
ExprKind::Literal(Literal::Integer(3, None)),
Span::default(),
)),
right: Box::new(Expr::new(
ExprKind::Literal(Literal::Integer(5, None)),
Span::default(),
)),
},
Span::default(),
);
let then_branch = Expr::new(
ExprKind::Literal(Literal::Integer(100, None)),
Span::default(),
);
let else_branch = Expr::new(
ExprKind::Literal(Literal::Integer(200, None)),
Span::default(),
);
let expr = Expr::new(
ExprKind::If {
condition: Box::new(condition),
then_branch: Box::new(then_branch),
else_branch: Some(Box::new(else_branch)),
},
Span::default(),
);
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm
.execute(&chunk)
.expect("vm.execute should succeed in test");
assert_eq!(result, Value::Integer(200));
}
#[test]
fn test_vm_opcode_nested_block() {
let mut compiler = Compiler::new("test".to_string());
let inner = Expr::new(
ExprKind::Block(vec![Expr::new(
ExprKind::Literal(Literal::Integer(42, None)),
Span::default(),
)]),
Span::default(),
);
let middle = Expr::new(ExprKind::Block(vec![inner]), Span::default());
let outer = Expr::new(ExprKind::Block(vec![middle]), Span::default());
compiler
.compile_expr(&outer)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm
.execute(&chunk)
.expect("vm.execute should succeed in test");
assert_eq!(result, Value::Integer(42));
}
#[test]
fn test_vm_opcode_empty_block() {
let mut compiler = Compiler::new("test".to_string());
let expr = Expr::new(ExprKind::Block(vec![]), Span::default());
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm
.execute(&chunk)
.expect("vm.execute should succeed in test");
assert_eq!(result, Value::Nil);
}
#[test]
fn test_vm_opcode_nested_if() {
let mut compiler = Compiler::new("test".to_string());
let inner_condition = Expr::new(ExprKind::Literal(Literal::Bool(false)), Span::default());
let inner_then = Expr::new(
ExprKind::Literal(Literal::Integer(1, None)),
Span::default(),
);
let inner_else = Expr::new(
ExprKind::Literal(Literal::Integer(2, None)),
Span::default(),
);
let inner_if = Expr::new(
ExprKind::If {
condition: Box::new(inner_condition),
then_branch: Box::new(inner_then),
else_branch: Some(Box::new(inner_else)),
},
Span::default(),
);
let outer_condition = Expr::new(ExprKind::Literal(Literal::Bool(true)), Span::default());
let outer_else = Expr::new(
ExprKind::Literal(Literal::Integer(3, None)),
Span::default(),
);
let expr = Expr::new(
ExprKind::If {
condition: Box::new(outer_condition),
then_branch: Box::new(inner_if),
else_branch: Some(Box::new(outer_else)),
},
Span::default(),
);
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm
.execute(&chunk)
.expect("vm.execute should succeed in test");
assert_eq!(result, Value::Integer(2)); }
#[test]
fn test_vm_opcode_logical_not_truthy_integer() {
let mut compiler = Compiler::new("test".to_string());
let expr = Expr::new(
ExprKind::Unary {
op: UnaryOp::Not,
operand: Box::new(Expr::new(
ExprKind::Literal(Literal::Integer(42, None)),
Span::default(),
)),
},
Span::default(),
);
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm
.execute(&chunk)
.expect("vm.execute should succeed in test");
assert_eq!(result, Value::Bool(false));
}
#[test]
fn test_vm_opcode_logical_not_nil() {
let mut compiler = Compiler::new("test".to_string());
let expr = Expr::new(
ExprKind::Unary {
op: UnaryOp::Not,
operand: Box::new(Expr::new(ExprKind::Literal(Literal::Null), Span::default())),
},
Span::default(),
);
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm
.execute(&chunk)
.expect("vm.execute should succeed in test");
assert_eq!(result, Value::Bool(true));
}
#[test]
fn test_vm_opcode_logical_and_with_truthy() {
let mut compiler = Compiler::new("test".to_string());
let expr = Expr::new(
ExprKind::Binary {
op: BinaryOp::And,
left: Box::new(Expr::new(
ExprKind::Literal(Literal::Integer(1, None)),
Span::default(),
)),
right: Box::new(Expr::new(
ExprKind::Literal(Literal::Integer(2, None)),
Span::default(),
)),
},
Span::default(),
);
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm
.execute(&chunk)
.expect("vm.execute should succeed in test");
assert_eq!(result, Value::Bool(true));
}
#[test]
fn test_vm_opcode_logical_or_with_truthy() {
let mut compiler = Compiler::new("test".to_string());
let expr = Expr::new(
ExprKind::Binary {
op: BinaryOp::Or,
left: Box::new(Expr::new(ExprKind::Literal(Literal::Null), Span::default())),
right: Box::new(Expr::new(
ExprKind::Literal(Literal::Integer(1, None)),
Span::default(),
)),
},
Span::default(),
);
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm
.execute(&chunk)
.expect("vm.execute should succeed in test");
assert_eq!(result, Value::Bool(true));
}
#[test]
fn test_vm_opcode_array_with_mixed_types() {
let mut compiler = Compiler::new("test".to_string());
let elements = vec![
Expr::new(
ExprKind::Literal(Literal::Integer(1, None)),
Span::default(),
),
Expr::new(ExprKind::Literal(Literal::Bool(true)), Span::default()),
Expr::new(
ExprKind::Literal(Literal::String("hello".to_string())),
Span::default(),
),
Expr::new(ExprKind::Literal(Literal::Null), Span::default()),
];
let expr = Expr::new(ExprKind::List(elements), Span::default());
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm
.execute(&chunk)
.expect("vm.execute should succeed in test");
match result {
Value::Array(arr) => {
assert_eq!(arr.len(), 4);
assert_eq!(arr[0], Value::Integer(1));
assert_eq!(arr[1], Value::Bool(true));
assert_eq!(arr[2], Value::from_string("hello".to_string()));
assert_eq!(arr[3], Value::Nil);
}
_ => panic!("Expected array, got {result:?}"),
}
}
#[test]
fn test_vm_opcode_empty_tuple() {
let mut compiler = Compiler::new("test".to_string());
let expr = Expr::new(ExprKind::Tuple(vec![]), Span::default());
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm
.execute(&chunk)
.expect("vm.execute should succeed in test");
match result {
Value::Tuple(tuple) => assert_eq!(tuple.len(), 0),
_ => panic!("Expected tuple, got {result:?}"),
}
}
#[test]
fn test_vm_opcode_single_element_tuple() {
let mut compiler = Compiler::new("test".to_string());
let expr = Expr::new(
ExprKind::Tuple(vec![Expr::new(
ExprKind::Literal(Literal::Integer(42, None)),
Span::default(),
)]),
Span::default(),
);
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm
.execute(&chunk)
.expect("vm.execute should succeed in test");
match result {
Value::Tuple(tuple) => {
assert_eq!(tuple.len(), 1);
assert_eq!(tuple[0], Value::Integer(42));
}
_ => panic!("Expected tuple, got {result:?}"),
}
}
#[test]
fn test_vm_opcode_tuple_second_element_access() {
let mut compiler = Compiler::new("test".to_string());
let tuple = Expr::new(
ExprKind::Tuple(vec![
Expr::new(
ExprKind::Literal(Literal::Integer(10, None)),
Span::default(),
),
Expr::new(
ExprKind::Literal(Literal::Integer(20, None)),
Span::default(),
),
Expr::new(
ExprKind::Literal(Literal::Integer(30, None)),
Span::default(),
),
]),
Span::default(),
);
let expr = Expr::new(
ExprKind::FieldAccess {
object: Box::new(tuple),
field: "1".to_string(),
},
Span::default(),
);
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm
.execute(&chunk)
.expect("vm.execute should succeed in test");
assert_eq!(result, Value::Integer(20));
}
#[test]
fn test_vm_opcode_string_comparison() {
let mut compiler = Compiler::new("test".to_string());
let expr = Expr::new(
ExprKind::Binary {
op: BinaryOp::Equal,
left: Box::new(Expr::new(
ExprKind::Literal(Literal::String("abc".to_string())),
Span::default(),
)),
right: Box::new(Expr::new(
ExprKind::Literal(Literal::String("abc".to_string())),
Span::default(),
)),
},
Span::default(),
);
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm
.execute(&chunk)
.expect("vm.execute should succeed in test");
assert_eq!(result, Value::Bool(true));
}
#[test]
fn test_vm_opcode_string_inequality() {
let mut compiler = Compiler::new("test".to_string());
let expr = Expr::new(
ExprKind::Binary {
op: BinaryOp::NotEqual,
left: Box::new(Expr::new(
ExprKind::Literal(Literal::String("abc".to_string())),
Span::default(),
)),
right: Box::new(Expr::new(
ExprKind::Literal(Literal::String("def".to_string())),
Span::default(),
)),
},
Span::default(),
);
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm
.execute(&chunk)
.expect("vm.execute should succeed in test");
assert_eq!(result, Value::Bool(true));
}
#[test]
fn test_vm_opcode_float_division() {
let mut compiler = Compiler::new("test".to_string());
let expr = Expr::new(
ExprKind::Binary {
op: BinaryOp::Divide,
left: Box::new(Expr::new(
ExprKind::Literal(Literal::Float(7.5)),
Span::default(),
)),
right: Box::new(Expr::new(
ExprKind::Literal(Literal::Float(2.5)),
Span::default(),
)),
},
Span::default(),
);
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm
.execute(&chunk)
.expect("vm.execute should succeed in test");
match result {
Value::Float(f) => assert!((f - 3.0).abs() < 0.001),
_ => panic!("Expected Float, got {result:?}"),
}
}
#[test]
fn test_vm_opcode_float_modulo() {
let mut compiler = Compiler::new("test".to_string());
let expr = Expr::new(
ExprKind::Binary {
op: BinaryOp::Modulo,
left: Box::new(Expr::new(
ExprKind::Literal(Literal::Float(7.5)),
Span::default(),
)),
right: Box::new(Expr::new(
ExprKind::Literal(Literal::Float(2.0)),
Span::default(),
)),
},
Span::default(),
);
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm
.execute(&chunk)
.expect("vm.execute should succeed in test");
match result {
Value::Float(f) => assert!((f - 1.5).abs() < 0.001),
_ => panic!("Expected Float, got {result:?}"),
}
}
#[test]
fn test_vm_opcode_float_subtraction() {
let mut compiler = Compiler::new("test".to_string());
let expr = Expr::new(
ExprKind::Binary {
op: BinaryOp::Subtract,
left: Box::new(Expr::new(
ExprKind::Literal(Literal::Float(10.5)),
Span::default(),
)),
right: Box::new(Expr::new(
ExprKind::Literal(Literal::Float(3.5)),
Span::default(),
)),
},
Span::default(),
);
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm
.execute(&chunk)
.expect("vm.execute should succeed in test");
match result {
Value::Float(f) => assert!((f - 7.0).abs() < 0.001),
_ => panic!("Expected Float, got {result:?}"),
}
}
#[test]
fn test_vm_opcode_object_with_multiple_fields() {
let mut compiler = Compiler::new("test".to_string());
use crate::frontend::ast::ObjectField;
let fields = vec![
ObjectField::KeyValue {
key: "a".to_string(),
value: Expr::new(
ExprKind::Literal(Literal::Integer(1, None)),
Span::default(),
),
},
ObjectField::KeyValue {
key: "b".to_string(),
value: Expr::new(
ExprKind::Literal(Literal::Integer(2, None)),
Span::default(),
),
},
ObjectField::KeyValue {
key: "c".to_string(),
value: Expr::new(
ExprKind::Literal(Literal::Integer(3, None)),
Span::default(),
),
},
];
let expr = Expr::new(ExprKind::ObjectLiteral { fields }, Span::default());
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm
.execute(&chunk)
.expect("vm.execute should succeed in test");
match result {
Value::Object(obj) => {
assert_eq!(obj.len(), 3);
assert_eq!(obj.get("a"), Some(&Value::Integer(1)));
assert_eq!(obj.get("b"), Some(&Value::Integer(2)));
assert_eq!(obj.get("c"), Some(&Value::Integer(3)));
}
_ => panic!("Expected object, got {result:?}"),
}
}
#[test]
fn test_vm_opcode_empty_object() {
let mut compiler = Compiler::new("test".to_string());
let expr = Expr::new(ExprKind::ObjectLiteral { fields: vec![] }, Span::default());
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm
.execute(&chunk)
.expect("vm.execute should succeed in test");
match result {
Value::Object(obj) => assert!(obj.is_empty()),
_ => panic!("Expected object, got {result:?}"),
}
}
#[test]
fn test_vm_opcode_less_than_false() {
let mut compiler = Compiler::new("test".to_string());
let expr = Expr::new(
ExprKind::Binary {
op: BinaryOp::Less,
left: Box::new(Expr::new(
ExprKind::Literal(Literal::Integer(20, None)),
Span::default(),
)),
right: Box::new(Expr::new(
ExprKind::Literal(Literal::Integer(10, None)),
Span::default(),
)),
},
Span::default(),
);
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm
.execute(&chunk)
.expect("vm.execute should succeed in test");
assert_eq!(result, Value::Bool(false));
}
#[test]
fn test_vm_opcode_greater_than_false() {
let mut compiler = Compiler::new("test".to_string());
let expr = Expr::new(
ExprKind::Binary {
op: BinaryOp::Greater,
left: Box::new(Expr::new(
ExprKind::Literal(Literal::Integer(10, None)),
Span::default(),
)),
right: Box::new(Expr::new(
ExprKind::Literal(Literal::Integer(20, None)),
Span::default(),
)),
},
Span::default(),
);
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm
.execute(&chunk)
.expect("vm.execute should succeed in test");
assert_eq!(result, Value::Bool(false));
}
#[test]
fn test_vm_opcode_less_equal_strict_less() {
let mut compiler = Compiler::new("test".to_string());
let expr = Expr::new(
ExprKind::Binary {
op: BinaryOp::LessEqual,
left: Box::new(Expr::new(
ExprKind::Literal(Literal::Integer(10, None)),
Span::default(),
)),
right: Box::new(Expr::new(
ExprKind::Literal(Literal::Integer(20, None)),
Span::default(),
)),
},
Span::default(),
);
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm
.execute(&chunk)
.expect("vm.execute should succeed in test");
assert_eq!(result, Value::Bool(true));
}
#[test]
fn test_vm_opcode_greater_equal_strict_greater() {
let mut compiler = Compiler::new("test".to_string());
let expr = Expr::new(
ExprKind::Binary {
op: BinaryOp::GreaterEqual,
left: Box::new(Expr::new(
ExprKind::Literal(Literal::Integer(20, None)),
Span::default(),
)),
right: Box::new(Expr::new(
ExprKind::Literal(Literal::Integer(10, None)),
Span::default(),
)),
},
Span::default(),
);
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm
.execute(&chunk)
.expect("vm.execute should succeed in test");
assert_eq!(result, Value::Bool(true));
}
#[test]
fn test_vm_opcode_less_equal_false() {
let mut compiler = Compiler::new("test".to_string());
let expr = Expr::new(
ExprKind::Binary {
op: BinaryOp::LessEqual,
left: Box::new(Expr::new(
ExprKind::Literal(Literal::Integer(20, None)),
Span::default(),
)),
right: Box::new(Expr::new(
ExprKind::Literal(Literal::Integer(10, None)),
Span::default(),
)),
},
Span::default(),
);
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm
.execute(&chunk)
.expect("vm.execute should succeed in test");
assert_eq!(result, Value::Bool(false));
}
#[test]
fn test_vm_opcode_greater_equal_false() {
let mut compiler = Compiler::new("test".to_string());
let expr = Expr::new(
ExprKind::Binary {
op: BinaryOp::GreaterEqual,
left: Box::new(Expr::new(
ExprKind::Literal(Literal::Integer(10, None)),
Span::default(),
)),
right: Box::new(Expr::new(
ExprKind::Literal(Literal::Integer(20, None)),
Span::default(),
)),
},
Span::default(),
);
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm
.execute(&chunk)
.expect("vm.execute should succeed in test");
assert_eq!(result, Value::Bool(false));
}
#[test]
fn test_vm_opcode_not_equal_same_values() {
let mut compiler = Compiler::new("test".to_string());
let expr = Expr::new(
ExprKind::Binary {
op: BinaryOp::NotEqual,
left: Box::new(Expr::new(
ExprKind::Literal(Literal::Integer(42, None)),
Span::default(),
)),
right: Box::new(Expr::new(
ExprKind::Literal(Literal::Integer(42, None)),
Span::default(),
)),
},
Span::default(),
);
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm
.execute(&chunk)
.expect("vm.execute should succeed in test");
assert_eq!(result, Value::Bool(false));
}
#[test]
fn test_vm_opcode_many_constants() {
let mut compiler = Compiler::new("test".to_string());
let expr1 = Expr::new(
ExprKind::Literal(Literal::Integer(1, None)),
Span::default(),
);
let expr2 = Expr::new(
ExprKind::Literal(Literal::Integer(2, None)),
Span::default(),
);
let add1 = Expr::new(
ExprKind::Binary {
op: BinaryOp::Add,
left: Box::new(expr1),
right: Box::new(expr2),
},
Span::default(),
);
let expr3 = Expr::new(
ExprKind::Literal(Literal::Integer(3, None)),
Span::default(),
);
let add2 = Expr::new(
ExprKind::Binary {
op: BinaryOp::Add,
left: Box::new(add1),
right: Box::new(expr3),
},
Span::default(),
);
let expr4 = Expr::new(
ExprKind::Literal(Literal::Integer(4, None)),
Span::default(),
);
let add3 = Expr::new(
ExprKind::Binary {
op: BinaryOp::Add,
left: Box::new(add2),
right: Box::new(expr4),
},
Span::default(),
);
let expr5 = Expr::new(
ExprKind::Literal(Literal::Integer(5, None)),
Span::default(),
);
let expr = Expr::new(
ExprKind::Binary {
op: BinaryOp::Add,
left: Box::new(add3),
right: Box::new(expr5),
},
Span::default(),
);
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm
.execute(&chunk)
.expect("vm.execute should succeed in test");
assert_eq!(result, Value::Integer(15)); }
#[test]
fn test_vm_opcode_bool_literal_false() {
let mut compiler = Compiler::new("test".to_string());
let expr = Expr::new(ExprKind::Literal(Literal::Bool(false)), Span::default());
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm
.execute(&chunk)
.expect("vm.execute should succeed in test");
assert_eq!(result, Value::Bool(false));
}
#[test]
fn test_vm_opcode_bool_literal_true() {
let mut compiler = Compiler::new("test".to_string());
let expr = Expr::new(ExprKind::Literal(Literal::Bool(true)), Span::default());
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm
.execute(&chunk)
.expect("vm.execute should succeed in test");
assert_eq!(result, Value::Bool(true));
}
#[test]
fn test_vm_opcode_nil_literal() {
let mut compiler = Compiler::new("test".to_string());
let expr = Expr::new(ExprKind::Literal(Literal::Null), Span::default());
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm
.execute(&chunk)
.expect("vm.execute should succeed in test");
assert_eq!(result, Value::Nil);
}
#[test]
fn test_vm_opcode_string_literal() {
let mut compiler = Compiler::new("test".to_string());
let expr = Expr::new(
ExprKind::Literal(Literal::String("hello world".to_string())),
Span::default(),
);
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm
.execute(&chunk)
.expect("vm.execute should succeed in test");
assert_eq!(result, Value::from_string("hello world".to_string()));
}
#[test]
fn test_vm_opcode_float_literal() {
let mut compiler = Compiler::new("test".to_string());
let expr = Expr::new(
ExprKind::Literal(Literal::Float(std::f64::consts::PI)),
Span::default(),
);
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm
.execute(&chunk)
.expect("vm.execute should succeed in test");
match result {
Value::Float(f) => assert!((f - std::f64::consts::PI).abs() < 0.00001),
_ => panic!("Expected Float, got {result:?}"),
}
}
#[test]
fn test_vm_opcode_negative_integer() {
let mut compiler = Compiler::new("test".to_string());
let expr = Expr::new(
ExprKind::Unary {
op: UnaryOp::Negate,
operand: Box::new(Expr::new(
ExprKind::Literal(Literal::Integer(42, None)),
Span::default(),
)),
},
Span::default(),
);
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm
.execute(&chunk)
.expect("vm.execute should succeed in test");
assert_eq!(result, Value::Integer(-42));
}
#[test]
fn test_vm_opcode_string_concatenation() {
let mut compiler = Compiler::new("test".to_string());
let hello = Expr::new(
ExprKind::Literal(Literal::String("hello".to_string())),
Span::default(),
);
let space = Expr::new(
ExprKind::Literal(Literal::String(" ".to_string())),
Span::default(),
);
let world = Expr::new(
ExprKind::Literal(Literal::String("world".to_string())),
Span::default(),
);
let first_concat = Expr::new(
ExprKind::Binary {
op: BinaryOp::Add,
left: Box::new(hello),
right: Box::new(space),
},
Span::default(),
);
let expr = Expr::new(
ExprKind::Binary {
op: BinaryOp::Add,
left: Box::new(first_concat),
right: Box::new(world),
},
Span::default(),
);
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm
.execute(&chunk)
.expect("vm.execute should succeed in test");
assert_eq!(result, Value::from_string("hello world".to_string()));
}
#[test]
fn test_vm_opcode_deeply_nested_arithmetic() {
let mut compiler = Compiler::new("test".to_string());
let one = Expr::new(
ExprKind::Literal(Literal::Integer(1, None)),
Span::default(),
);
let two = Expr::new(
ExprKind::Literal(Literal::Integer(2, None)),
Span::default(),
);
let three = Expr::new(
ExprKind::Literal(Literal::Integer(3, None)),
Span::default(),
);
let four = Expr::new(
ExprKind::Literal(Literal::Integer(4, None)),
Span::default(),
);
let add1 = Expr::new(
ExprKind::Binary {
op: BinaryOp::Add,
left: Box::new(one),
right: Box::new(two),
},
Span::default(),
);
let add2 = Expr::new(
ExprKind::Binary {
op: BinaryOp::Add,
left: Box::new(add1),
right: Box::new(three),
},
Span::default(),
);
let expr = Expr::new(
ExprKind::Binary {
op: BinaryOp::Add,
left: Box::new(add2),
right: Box::new(four),
},
Span::default(),
);
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm
.execute(&chunk)
.expect("vm.execute should succeed in test");
assert_eq!(result, Value::Integer(10)); }
#[test]
fn test_vm_opcode_zero_integer() {
let mut compiler = Compiler::new("test".to_string());
let expr = Expr::new(
ExprKind::Literal(Literal::Integer(0, None)),
Span::default(),
);
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm
.execute(&chunk)
.expect("vm.execute should succeed in test");
assert_eq!(result, Value::Integer(0));
}
#[test]
fn test_vm_opcode_large_integer() {
let mut compiler = Compiler::new("test".to_string());
let expr = Expr::new(
ExprKind::Literal(Literal::Integer(i64::MAX - 1, None)),
Span::default(),
);
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm
.execute(&chunk)
.expect("vm.execute should succeed in test");
assert_eq!(result, Value::Integer(i64::MAX - 1));
}
#[test]
fn test_vm_opcode_negative_large_integer() {
let mut compiler = Compiler::new("test".to_string());
let expr = Expr::new(
ExprKind::Literal(Literal::Integer(i64::MIN + 1, None)),
Span::default(),
);
compiler
.compile_expr(&expr)
.expect("compile_expr should succeed in test");
let chunk = compiler.finalize();
let mut vm = VM::new();
let result = vm
.execute(&chunk)
.expect("vm.execute should succeed in test");
assert_eq!(result, Value::Integer(i64::MIN + 1));
}
}