use super::*;
use crate::frontend::ast::{Expr, ExprKind, Literal, MatchArm, Pattern, Span};
use crate::runtime::bytecode::compiler::BytecodeChunk;
use std::sync::Arc;
fn make_span() -> Span {
Span { start: 0, end: 0 }
}
fn make_expr(kind: ExprKind) -> Arc<Expr> {
Arc::new(Expr {
kind,
span: make_span(),
attributes: Vec::new(),
leading_comments: vec![],
trailing_comment: None,
})
}
fn make_int_expr(v: i64) -> Arc<Expr> {
make_expr(ExprKind::Literal(Literal::Integer(v, None)))
}
#[test]
fn test_vm_call_closure() {
let body = make_expr(ExprKind::Binary {
left: Box::new(Expr {
kind: ExprKind::Identifier("x".to_string()),
span: make_span(),
attributes: Vec::new(),
leading_comments: vec![],
trailing_comment: None,
}),
op: crate::frontend::ast::BinaryOp::Add,
right: Box::new(Expr {
kind: ExprKind::Literal(Literal::Integer(10, None)),
span: make_span(),
attributes: Vec::new(),
leading_comments: vec![],
trailing_comment: None,
}),
});
let closure = Value::Closure {
params: vec![("x".to_string(), None)],
body,
env: std::rc::Rc::new(std::cell::RefCell::new(std::collections::HashMap::new())),
};
let mut chunk = BytecodeChunk::new("test_call".to_string());
chunk.constants.push(Value::Integer(5)); chunk.constants.push(Value::from_array(vec![
Value::Integer(0), Value::Integer(1), ])); chunk.register_count = 4;
chunk.emit(Instruction::abx(OpCode::Const, 1, 0), 1);
chunk.emit(Instruction::abx(OpCode::Call, 2, 1), 2);
chunk.emit(Instruction::abc(OpCode::Move, 0, 2, 0), 3);
let mut vm = VM::new();
vm.registers[0] = closure;
let result = vm.execute(&chunk);
assert!(result.is_ok(), "Call should succeed: {:?}", result.err());
assert_eq!(result.unwrap(), Value::Integer(15));
}
#[test]
fn test_vm_call_non_function_error() {
let mut chunk = BytecodeChunk::new("test_call_err".to_string());
chunk.constants.push(Value::Integer(42)); chunk.constants.push(Value::from_array(vec![
Value::Integer(0), ])); chunk.register_count = 4;
chunk.emit(Instruction::abx(OpCode::Const, 0, 0), 1);
chunk.emit(Instruction::abx(OpCode::Call, 1, 1), 2);
let mut vm = VM::new();
let result = vm.execute(&chunk);
assert!(result.is_err());
assert!(result.unwrap_err().contains("non-function"));
}
#[test]
fn test_vm_call_wrong_arg_count_error() {
let body = make_int_expr(1);
let closure = Value::Closure {
params: vec![("a".to_string(), None), ("b".to_string(), None)],
body,
env: std::rc::Rc::new(std::cell::RefCell::new(std::collections::HashMap::new())),
};
let mut chunk = BytecodeChunk::new("test_call_args".to_string());
chunk.constants.push(Value::from_array(vec![
Value::Integer(0), Value::Integer(1), ]));
chunk.register_count = 4;
chunk.emit(Instruction::abx(OpCode::Call, 2, 0), 1);
let mut vm = VM::new();
vm.registers[0] = closure;
vm.registers[1] = Value::Integer(1);
let result = vm.execute(&chunk);
assert!(result.is_err());
assert!(result.unwrap_err().contains("expects 2 arguments"));
}
#[test]
fn test_vm_call_empty_call_info_error() {
let mut chunk = BytecodeChunk::new("test_call_empty".to_string());
chunk.constants.push(Value::from_array(vec![])); chunk.register_count = 4;
chunk.emit(Instruction::abx(OpCode::Call, 0, 0), 1);
let mut vm = VM::new();
let result = vm.execute(&chunk);
assert!(result.is_err());
assert!(result.unwrap_err().contains("empty"));
}
#[test]
fn test_vm_call_non_array_call_info_error() {
let mut chunk = BytecodeChunk::new("test_call_nonarr".to_string());
chunk.constants.push(Value::from_string("bad".to_string())); chunk.register_count = 4;
chunk.emit(Instruction::abx(OpCode::Call, 0, 0), 1);
let mut vm = VM::new();
let result = vm.execute(&chunk);
assert!(result.is_err());
assert!(result.unwrap_err().contains("array"));
}
#[test]
fn test_vm_for_loop() {
let body = make_expr(ExprKind::Identifier("x".to_string()));
let mut chunk = BytecodeChunk::new("test_for".to_string());
chunk.constants.push(Value::from_array(vec![
Value::Integer(1),
Value::Integer(2),
Value::Integer(3),
]));
chunk.constants.push(Value::from_array(vec![
Value::Integer(1), Value::from_string("x".to_string()), Value::Integer(0), ]));
chunk.loop_bodies.push(body);
chunk.register_count = 4;
chunk.emit(Instruction::abx(OpCode::Const, 1, 0), 1);
chunk.emit(Instruction::abx(OpCode::For, 0, 1), 2);
let mut vm = VM::new();
let result = vm.execute(&chunk);
assert!(result.is_ok(), "For loop should succeed: {:?}", result.err());
assert_eq!(result.unwrap(), Value::Integer(3));
}
#[test]
fn test_vm_for_non_array_iterator_error() {
let body = make_int_expr(0);
let mut chunk = BytecodeChunk::new("test_for_err".to_string());
chunk.constants.push(Value::Integer(42)); chunk.constants.push(Value::from_array(vec![
Value::Integer(1),
Value::from_string("x".to_string()),
Value::Integer(0),
]));
chunk.loop_bodies.push(body);
chunk.register_count = 4;
chunk.emit(Instruction::abx(OpCode::Const, 1, 0), 1);
chunk.emit(Instruction::abx(OpCode::For, 0, 1), 2);
let mut vm = VM::new();
let result = vm.execute(&chunk);
assert!(result.is_err());
assert!(result.unwrap_err().contains("array"));
}
#[test]
fn test_vm_for_loop_info_not_array_error() {
let mut chunk = BytecodeChunk::new("test_for_info".to_string());
chunk.constants.push(Value::from_string("bad".to_string()));
chunk.register_count = 4;
chunk.emit(Instruction::abx(OpCode::For, 0, 0), 1);
let mut vm = VM::new();
let result = vm.execute(&chunk);
assert!(result.is_err());
}
#[test]
fn test_vm_for_loop_info_too_short_error() {
let mut chunk = BytecodeChunk::new("test_for_short".to_string());
chunk.constants.push(Value::from_array(vec![Value::Integer(0)])); chunk.register_count = 4;
chunk.emit(Instruction::abx(OpCode::For, 0, 0), 1);
let mut vm = VM::new();
let result = vm.execute(&chunk);
assert!(result.is_err());
}
#[test]
fn test_vm_for_loop_with_locals_map() {
let body = make_expr(ExprKind::Binary {
left: Box::new(Expr {
kind: ExprKind::Identifier("sum".to_string()),
span: make_span(),
attributes: Vec::new(),
leading_comments: vec![],
trailing_comment: None,
}),
op: crate::frontend::ast::BinaryOp::Add,
right: Box::new(Expr {
kind: ExprKind::Identifier("x".to_string()),
span: make_span(),
attributes: Vec::new(),
leading_comments: vec![],
trailing_comment: None,
}),
});
let mut chunk = BytecodeChunk::new("test_for_locals".to_string());
chunk.constants.push(Value::from_array(vec![
Value::Integer(1),
Value::Integer(2),
]));
chunk.constants.push(Value::from_array(vec![
Value::Integer(1),
Value::from_string("x".to_string()),
Value::Integer(0),
]));
chunk.loop_bodies.push(body);
chunk.locals_map.insert("sum".to_string(), 2);
chunk.register_count = 4;
chunk.emit(Instruction::abx(OpCode::Const, 1, 0), 1);
chunk.emit(Instruction::abx(OpCode::For, 0, 1), 2);
let mut vm = VM::new();
vm.registers[2] = Value::Integer(0); let result = vm.execute(&chunk);
assert!(result.is_ok(), "For with locals should work: {:?}", result.err());
}
#[test]
fn test_vm_method_call() {
let receiver = make_expr(ExprKind::List(vec![
Expr {
kind: ExprKind::Literal(Literal::Integer(1, None)),
span: make_span(),
attributes: Vec::new(),
leading_comments: vec![],
trailing_comment: None,
},
Expr {
kind: ExprKind::Literal(Literal::Integer(2, None)),
span: make_span(),
attributes: Vec::new(),
leading_comments: vec![],
trailing_comment: None,
},
]));
let mut chunk = BytecodeChunk::new("test_method".to_string());
chunk.constants.push(Value::Integer(0));
chunk.method_calls.push((receiver, "length".to_string(), vec![]));
chunk.register_count = 4;
chunk.emit(Instruction::abx(OpCode::MethodCall, 0, 0), 1);
let mut vm = VM::new();
let result = vm.execute(&chunk);
assert!(result.is_ok(), "MethodCall should succeed: {:?}", result.err());
assert_eq!(result.unwrap(), Value::Integer(2));
}
#[test]
fn test_vm_method_call_non_int_index_error() {
let mut chunk = BytecodeChunk::new("test_method_err".to_string());
chunk.constants.push(Value::from_string("bad".to_string())); chunk.register_count = 4;
chunk.emit(Instruction::abx(OpCode::MethodCall, 0, 0), 1);
let mut vm = VM::new();
let result = vm.execute(&chunk);
assert!(result.is_err());
assert!(result.unwrap_err().contains("integer"));
}
#[test]
fn test_vm_method_call_out_of_bounds_error() {
let mut chunk = BytecodeChunk::new("test_method_oob".to_string());
chunk.constants.push(Value::Integer(99)); chunk.register_count = 4;
chunk.emit(Instruction::abx(OpCode::MethodCall, 0, 0), 1);
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_match_expr() {
let match_expr = make_int_expr(42);
let arm = MatchArm {
pattern: Pattern::Wildcard,
guard: None,
body: Box::new(Expr {
kind: ExprKind::Literal(Literal::Integer(99, None)),
span: make_span(),
attributes: Vec::new(),
leading_comments: vec![],
trailing_comment: None,
}),
span: make_span(),
};
let mut chunk = BytecodeChunk::new("test_match".to_string());
chunk.constants.push(Value::Integer(0)); chunk.match_exprs.push((match_expr, vec![arm]));
chunk.register_count = 4;
chunk.emit(Instruction::abx(OpCode::Match, 0, 0), 1);
let mut vm = VM::new();
let result = vm.execute(&chunk);
assert!(result.is_ok(), "Match should succeed: {:?}", result.err());
assert_eq!(result.unwrap(), Value::Integer(99));
}
#[test]
fn test_vm_match_non_int_index_error() {
let mut chunk = BytecodeChunk::new("test_match_err".to_string());
chunk.constants.push(Value::from_string("bad".to_string()));
chunk.register_count = 4;
chunk.emit(Instruction::abx(OpCode::Match, 0, 0), 1);
let mut vm = VM::new();
let result = vm.execute(&chunk);
assert!(result.is_err());
}
#[test]
fn test_vm_match_out_of_bounds_error() {
let mut chunk = BytecodeChunk::new("test_match_oob".to_string());
chunk.constants.push(Value::Integer(5)); chunk.register_count = 4;
chunk.emit(Instruction::abx(OpCode::Match, 0, 0), 1);
let mut vm = VM::new();
let result = vm.execute(&chunk);
assert!(result.is_err());
}
#[test]
fn test_vm_new_closure() {
let body = make_int_expr(42);
let params = vec![("x".to_string(), None)];
let mut chunk = BytecodeChunk::new("test_closure".to_string());
chunk.constants.push(Value::Integer(0)); chunk.closures.push((params, body));
chunk.register_count = 4;
chunk.emit(Instruction::abx(OpCode::NewClosure, 0, 0), 1);
let mut vm = VM::new();
let result = vm.execute(&chunk);
assert!(result.is_ok(), "NewClosure should succeed: {:?}", result.err());
match result.unwrap() {
Value::Closure { params, .. } => {
assert_eq!(params.len(), 1);
assert_eq!(params[0].0, "x");
}
other => panic!("Expected Closure, got {:?}", other),
}
}
#[test]
fn test_vm_new_closure_non_int_index_error() {
let mut chunk = BytecodeChunk::new("test_closure_err".to_string());
chunk.constants.push(Value::from_string("bad".to_string()));
chunk.register_count = 4;
chunk.emit(Instruction::abx(OpCode::NewClosure, 0, 0), 1);
let mut vm = VM::new();
let result = vm.execute(&chunk);
assert!(result.is_err());
}
#[test]
fn test_vm_new_closure_out_of_bounds_error() {
let mut chunk = BytecodeChunk::new("test_closure_oob".to_string());
chunk.constants.push(Value::Integer(99));
chunk.register_count = 4;
chunk.emit(Instruction::abx(OpCode::NewClosure, 0, 0), 1);
let mut vm = VM::new();
let result = vm.execute(&chunk);
assert!(result.is_err());
}
#[test]
fn test_vm_new_closure_with_locals_map() {
let body = make_expr(ExprKind::Identifier("y".to_string()));
let params = vec![];
let mut chunk = BytecodeChunk::new("test_closure_locals".to_string());
chunk.constants.push(Value::Integer(0));
chunk.closures.push((params, body));
chunk.locals_map.insert("y".to_string(), 1);
chunk.register_count = 4;
chunk.emit(Instruction::abx(OpCode::NewClosure, 0, 0), 1);
let mut vm = VM::new();
vm.registers[1] = Value::Integer(77);
let result = vm.execute(&chunk);
assert!(result.is_ok());
}
#[test]
fn test_vm_for_loop_body_idx_not_int_error() {
let mut chunk = BytecodeChunk::new("test_for_body_err".to_string());
chunk.constants.push(Value::from_array(vec![
Value::Integer(1),
Value::from_string("x".to_string()),
Value::from_string("bad".to_string()), ]));
chunk.register_count = 4;
chunk.emit(Instruction::abx(OpCode::For, 0, 0), 1);
let mut vm = VM::new();
let result = vm.execute(&chunk);
assert!(result.is_err());
}
#[test]
fn test_vm_for_loop_var_name_not_string_error() {
let body = make_int_expr(0);
let mut chunk = BytecodeChunk::new("test_for_var_err".to_string());
chunk.constants.push(Value::from_array(vec![
Value::Integer(1),
Value::Integer(999), Value::Integer(0),
]));
chunk.loop_bodies.push(body);
chunk.register_count = 4;
chunk.emit(Instruction::abx(OpCode::Const, 1, 0), 1);
chunk.emit(Instruction::abx(OpCode::For, 0, 0), 2);
let mut vm = VM::new();
vm.registers[1] = Value::from_array(vec![Value::Integer(1)]);
let result = vm.execute(&chunk);
assert!(result.is_err());
}
#[test]
fn test_vm_for_iter_reg_not_int_error() {
let mut chunk = BytecodeChunk::new("test_for_iter_err".to_string());
chunk.constants.push(Value::from_array(vec![
Value::from_string("bad".to_string()), Value::from_string("x".to_string()),
Value::Integer(0),
]));
chunk.register_count = 4;
chunk.emit(Instruction::abx(OpCode::For, 0, 0), 1);
let mut vm = VM::new();
let result = vm.execute(&chunk);
assert!(result.is_err());
}
#[test]
fn test_vm_call_with_closure_env() {
let body = make_expr(ExprKind::Identifier("y".to_string()));
let mut env = std::collections::HashMap::new();
env.insert("y".to_string(), Value::Integer(100));
let closure = Value::Closure {
params: vec![],
body,
env: std::rc::Rc::new(std::cell::RefCell::new(env)),
};
let mut chunk = BytecodeChunk::new("test_call_env".to_string());
chunk.constants.push(Value::from_array(vec![
Value::Integer(0), ])); chunk.register_count = 4;
chunk.emit(Instruction::abx(OpCode::Call, 1, 0), 1);
chunk.emit(Instruction::abc(OpCode::Move, 0, 1, 0), 2);
let mut vm = VM::new();
vm.registers[0] = closure;
let result = vm.execute(&chunk);
assert!(result.is_ok(), "Call with env should work: {:?}", result.err());
assert_eq!(result.unwrap(), Value::Integer(100));
}
#[test]
fn test_vm_match_with_literal_pattern() {
let match_expr = make_int_expr(1);
let arm1 = MatchArm {
pattern: Pattern::Literal(Literal::Integer(1, None)),
guard: None,
body: Box::new(Expr {
kind: ExprKind::Literal(Literal::Integer(10, None)),
span: make_span(),
attributes: Vec::new(),
leading_comments: vec![],
trailing_comment: None,
}),
span: make_span(),
};
let arm2 = MatchArm {
pattern: Pattern::Wildcard,
guard: None,
body: Box::new(Expr {
kind: ExprKind::Literal(Literal::Integer(20, None)),
span: make_span(),
attributes: Vec::new(),
leading_comments: vec![],
trailing_comment: None,
}),
span: make_span(),
};
let mut chunk = BytecodeChunk::new("test_match_lit".to_string());
chunk.constants.push(Value::Integer(0));
chunk.match_exprs.push((match_expr, vec![arm1, arm2]));
chunk.register_count = 4;
chunk.emit(Instruction::abx(OpCode::Match, 0, 0), 1);
let mut vm = VM::new();
let result = vm.execute(&chunk);
assert!(result.is_ok());
assert_eq!(result.unwrap(), Value::Integer(10));
}
#[test]
fn test_vm_method_call_with_locals_sync() {
let receiver = make_expr(ExprKind::List(vec![
Expr {
kind: ExprKind::Literal(Literal::Integer(5, None)),
span: make_span(),
attributes: Vec::new(),
leading_comments: vec![],
trailing_comment: None,
},
]));
let mut chunk = BytecodeChunk::new("test_method_locals".to_string());
chunk.constants.push(Value::Integer(0));
chunk.method_calls.push((receiver, "length".to_string(), vec![]));
chunk.locals_map.insert("result".to_string(), 1);
chunk.register_count = 4;
chunk.emit(Instruction::abx(OpCode::MethodCall, 0, 0), 1);
let mut vm = VM::new();
vm.registers[1] = Value::Integer(0);
let result = vm.execute(&chunk);
assert!(result.is_ok());
assert_eq!(result.unwrap(), Value::Integer(1));
}
#[test]
fn test_vm_neg_float() {
let mut chunk = BytecodeChunk::new("test_neg_f".to_string());
chunk.constants.push(Value::Float(3.14));
chunk.register_count = 4;
chunk.emit(Instruction::abx(OpCode::Const, 1, 0), 1);
chunk.emit(Instruction::abc(OpCode::Neg, 0, 1, 0), 2);
let mut vm = VM::new();
let result = vm.execute(&chunk);
assert!(result.is_ok());
assert_eq!(result.unwrap(), Value::Float(-3.14));
}
#[test]
fn test_vm_bitnot_integer() {
let mut chunk = BytecodeChunk::new("test_bitnot".to_string());
chunk.constants.push(Value::Integer(0));
chunk.register_count = 4;
chunk.emit(Instruction::abx(OpCode::Const, 1, 0), 1);
chunk.emit(Instruction::abc(OpCode::BitNot, 0, 1, 0), 2);
let mut vm = VM::new();
let result = vm.execute(&chunk);
assert!(result.is_ok());
assert_eq!(result.unwrap(), Value::Integer(!0i64));
}
#[test]
fn test_vm_load_field_tuple_index() {
let mut chunk = BytecodeChunk::new("test_tuple_idx".to_string());
chunk.constants.push(Value::from_string("1".to_string())); chunk.register_count = 4;
chunk.emit(Instruction::abc(OpCode::LoadField, 0, 1, 0), 1);
let mut vm = VM::new();
vm.registers[1] = Value::Tuple(Arc::from(vec![Value::Integer(10), Value::Integer(20)].as_slice()));
let result = vm.execute(&chunk);
assert!(result.is_ok());
assert_eq!(result.unwrap(), Value::Integer(20));
}
#[test]
fn test_vm_load_field_tuple_out_of_bounds() {
let mut chunk = BytecodeChunk::new("test_tuple_oob".to_string());
chunk.constants.push(Value::from_string("5".to_string()));
chunk.register_count = 4;
chunk.emit(Instruction::abc(OpCode::LoadField, 0, 1, 0), 1);
let mut vm = VM::new();
vm.registers[1] = Value::Tuple(Arc::from(vec![Value::Integer(10)].as_slice()));
let result = vm.execute(&chunk);
assert!(result.is_err());
assert!(result.unwrap_err().contains("out of bounds"));
}
#[test]
fn test_vm_for_empty_array() {
let body = make_int_expr(99);
let mut chunk = BytecodeChunk::new("test_for_empty".to_string());
chunk.constants.push(Value::from_array(vec![])); chunk.constants.push(Value::from_array(vec![
Value::Integer(1),
Value::from_string("x".to_string()),
Value::Integer(0),
]));
chunk.loop_bodies.push(body);
chunk.register_count = 4;
chunk.emit(Instruction::abx(OpCode::Const, 1, 0), 1);
chunk.emit(Instruction::abx(OpCode::For, 0, 1), 2);
let mut vm = VM::new();
let result = vm.execute(&chunk);
assert!(result.is_ok());
assert_eq!(result.unwrap(), Value::Nil);
}