use super::*;
use crate::frontend::ast::{BinaryOp, Expr, ExprKind, Literal, Span, UnaryOp};
use crate::runtime::bytecode::Compiler;
#[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));
}