use fusevm::chunk::ChunkBuilder;
use fusevm::op::Op;
use fusevm::value::Value;
use fusevm::vm::{VMResult, VM};
fn run(b: ChunkBuilder) -> Value {
match VM::new(b.build()).run() {
VMResult::Ok(v) => v,
other => panic!("unexpected result: {:?}", other),
}
}
fn run_emit(ops: Vec<Op>) -> Value {
let mut b = ChunkBuilder::new();
for op in ops {
b.emit(op, 1);
}
run(b)
}
#[test]
fn dup_duplicates_top() {
assert_eq!(
run_emit(vec![Op::LoadInt(7), Op::Dup, Op::Add]),
Value::Int(14)
);
}
#[test]
fn dup2_duplicates_top_two() {
assert_eq!(
run_emit(vec![Op::LoadInt(1), Op::LoadInt(2), Op::Dup2, Op::Add]),
Value::Int(3)
);
}
#[test]
fn swap_exchanges_top_two() {
assert_eq!(
run_emit(vec![Op::LoadInt(10), Op::LoadInt(3), Op::Swap, Op::Sub]),
Value::Int(-7)
);
}
#[test]
fn rot_rotates_top_three() {
let v = run_emit(vec![
Op::LoadInt(1),
Op::LoadInt(2),
Op::LoadInt(3),
Op::Rot,
]);
assert_ne!(v, Value::Int(3));
}
#[test]
fn pop_discards_top() {
assert_eq!(
run_emit(vec![Op::LoadInt(1), Op::LoadInt(2), Op::Pop]),
Value::Int(1)
);
}
#[test]
fn load_true_false_undef() {
assert_eq!(run_emit(vec![Op::LoadTrue]), Value::Bool(true));
assert_eq!(run_emit(vec![Op::LoadFalse]), Value::Bool(false));
assert_eq!(run_emit(vec![Op::LoadUndef]), Value::Undef);
}
#[test]
fn load_float_pushes_float() {
let v = run_emit(vec![Op::LoadFloat(2.5)]);
assert_eq!(v, Value::Float(2.5));
}
#[test]
fn load_const_resolves_pool_index() {
let mut b = ChunkBuilder::new();
let k = b.add_constant(Value::str("hello"));
b.emit(Op::LoadConst(k), 1);
assert_eq!(run(b), Value::str("hello"));
}
#[test]
fn nop_is_pass_through() {
assert_eq!(
run_emit(vec![Op::LoadInt(5), Op::Nop, Op::Nop]),
Value::Int(5)
);
}
#[test]
fn pow_produces_float() {
let v = run_emit(vec![Op::LoadInt(2), Op::LoadInt(10), Op::Pow]);
match v {
Value::Float(f) => assert!((f - 1024.0).abs() < 1e-9),
_ => panic!("expected float"),
}
}
#[test]
fn pow_zero_to_zero_is_one() {
let v = run_emit(vec![Op::LoadInt(0), Op::LoadInt(0), Op::Pow]);
if let Value::Float(f) = v {
assert_eq!(f, 1.0);
} else {
panic!("expected float")
}
}
#[test]
fn pow_negative_base_fractional_exp_is_nan() {
let v = run_emit(vec![Op::LoadInt(-1), Op::LoadFloat(0.5), Op::Pow]);
if let Value::Float(f) = v {
assert!(f.is_nan());
} else {
panic!("expected float")
}
}
#[test]
fn negate_int_uses_wrapping() {
let v = run_emit(vec![Op::LoadInt(i64::MIN), Op::Negate]);
assert_eq!(v, Value::Int(i64::MIN));
}
#[test]
fn negate_non_int_falls_to_float() {
let v = run_emit(vec![Op::LoadFloat(3.5), Op::Negate]);
assert_eq!(v, Value::Float(-3.5));
}
#[test]
fn inc_int_wraps_at_max() {
let v = run_emit(vec![Op::LoadInt(i64::MAX), Op::Inc]);
assert_eq!(v, Value::Int(i64::MIN));
}
#[test]
fn dec_int_wraps_at_min() {
let v = run_emit(vec![Op::LoadInt(i64::MIN), Op::Dec]);
assert_eq!(v, Value::Int(i64::MAX));
}
#[test]
fn inc_non_int_coerces_then_increments() {
let v = run_emit(vec![Op::LoadFloat(1.7), Op::Inc]);
assert_eq!(v, Value::Int(2));
}
#[test]
fn add_with_mixed_int_float_promotes() {
let v = run_emit(vec![Op::LoadInt(1), Op::LoadFloat(2.5), Op::Add]);
assert_eq!(v, Value::Float(3.5));
}
#[test]
fn mod_with_zero_does_not_crash() {
let _ = run_emit(vec![Op::LoadInt(5), Op::LoadInt(0), Op::Mod]);
}
#[test]
fn div_with_zero_does_not_crash() {
let _ = run_emit(vec![Op::LoadInt(5), Op::LoadInt(0), Op::Div]);
}
#[test]
fn concat_str_str() {
let mut b = ChunkBuilder::new();
let a = b.add_constant(Value::str("foo"));
let c = b.add_constant(Value::str("bar"));
b.emit(Op::LoadConst(a), 1);
b.emit(Op::LoadConst(c), 1);
b.emit(Op::Concat, 1);
assert_eq!(run(b), Value::str("foobar"));
}
#[test]
fn concat_int_and_str_stringifies() {
let mut b = ChunkBuilder::new();
b.emit(Op::LoadInt(42), 1);
let s = b.add_constant(Value::str("x"));
b.emit(Op::LoadConst(s), 1);
b.emit(Op::Concat, 1);
assert_eq!(run(b), Value::str("42x"));
}
#[test]
fn string_repeat_basic() {
let mut b = ChunkBuilder::new();
let s = b.add_constant(Value::str("ab"));
b.emit(Op::LoadConst(s), 1);
b.emit(Op::LoadInt(3), 1);
b.emit(Op::StringRepeat, 1);
assert_eq!(run(b), Value::str("ababab"));
}
#[test]
fn string_repeat_zero_yields_empty() {
let mut b = ChunkBuilder::new();
let s = b.add_constant(Value::str("abc"));
b.emit(Op::LoadConst(s), 1);
b.emit(Op::LoadInt(0), 1);
b.emit(Op::StringRepeat, 1);
assert_eq!(run(b), Value::str(""));
}
#[test]
fn string_repeat_negative_clamps_to_zero() {
let mut b = ChunkBuilder::new();
let s = b.add_constant(Value::str("abc"));
b.emit(Op::LoadConst(s), 1);
b.emit(Op::LoadInt(-5), 1);
b.emit(Op::StringRepeat, 1);
assert_eq!(run(b), Value::str(""));
}
#[test]
fn string_len_for_ascii_bytes() {
let mut b = ChunkBuilder::new();
let s = b.add_constant(Value::str("hello"));
b.emit(Op::LoadConst(s), 1);
b.emit(Op::StringLen, 1);
assert_eq!(run(b), Value::Int(5));
}
#[test]
fn string_len_for_unicode_returns_byte_len() {
let mut b = ChunkBuilder::new();
let s = b.add_constant(Value::str("héllo"));
b.emit(Op::LoadConst(s), 1);
b.emit(Op::StringLen, 1);
assert_eq!(run(b), Value::Int(6));
}
#[test]
fn spaceship_int_lt() {
assert_eq!(
run_emit(vec![Op::LoadInt(1), Op::LoadInt(2), Op::Spaceship]),
Value::Int(-1)
);
}
#[test]
fn spaceship_int_eq() {
assert_eq!(
run_emit(vec![Op::LoadInt(5), Op::LoadInt(5), Op::Spaceship]),
Value::Int(0)
);
}
#[test]
fn spaceship_int_gt() {
assert_eq!(
run_emit(vec![Op::LoadInt(9), Op::LoadInt(3), Op::Spaceship]),
Value::Int(1)
);
}
#[test]
fn spaceship_mixed_uses_float() {
assert_eq!(
run_emit(vec![Op::LoadFloat(2.5), Op::LoadInt(2), Op::Spaceship]),
Value::Int(1)
);
}
#[test]
fn strcmp_lex_order() {
let mut b = ChunkBuilder::new();
let a = b.add_constant(Value::str("apple"));
let z = b.add_constant(Value::str("banana"));
b.emit(Op::LoadConst(a), 1);
b.emit(Op::LoadConst(z), 1);
b.emit(Op::StrCmp, 1);
let v = run(b);
if let Value::Int(n) = v {
assert!(n < 0);
} else {
panic!("expected int")
}
}
#[test]
fn strcmp_equal() {
let mut b = ChunkBuilder::new();
let a = b.add_constant(Value::str("same"));
let z = b.add_constant(Value::str("same"));
b.emit(Op::LoadConst(a), 1);
b.emit(Op::LoadConst(z), 1);
b.emit(Op::StrCmp, 1);
assert_eq!(run(b), Value::Int(0));
}
fn cmp(a: &str, op: Op, b: &str) -> Value {
let mut bld = ChunkBuilder::new();
let ai = bld.add_constant(Value::str(a));
let bi = bld.add_constant(Value::str(b));
bld.emit(Op::LoadConst(ai), 1);
bld.emit(Op::LoadConst(bi), 1);
bld.emit(op, 1);
run(bld)
}
#[test]
fn str_eq_true_for_identical() {
assert_eq!(cmp("abc", Op::StrEq, "abc"), Value::Bool(true));
assert_eq!(cmp("abc", Op::StrEq, "abd"), Value::Bool(false));
}
#[test]
fn str_ne_inverts_eq() {
assert_eq!(cmp("abc", Op::StrNe, "abc"), Value::Bool(false));
assert_eq!(cmp("abc", Op::StrNe, "xyz"), Value::Bool(true));
}
#[test]
fn str_lt_gt_le_ge_are_consistent() {
assert_eq!(cmp("a", Op::StrLt, "b"), Value::Bool(true));
assert_eq!(cmp("b", Op::StrLt, "a"), Value::Bool(false));
assert_eq!(cmp("b", Op::StrGt, "a"), Value::Bool(true));
assert_eq!(cmp("a", Op::StrLe, "a"), Value::Bool(true));
assert_eq!(cmp("a", Op::StrGe, "a"), Value::Bool(true));
}
#[test]
fn log_not_inverts_truthiness() {
assert_eq!(run_emit(vec![Op::LoadInt(0), Op::LogNot]), Value::Bool(true));
assert_eq!(
run_emit(vec![Op::LoadInt(7), Op::LogNot]),
Value::Bool(false)
);
}
#[test]
fn log_and_full_evaluation() {
assert_eq!(
run_emit(vec![Op::LoadInt(1), Op::LoadInt(2), Op::LogAnd]),
Value::Bool(true)
);
assert_eq!(
run_emit(vec![Op::LoadInt(0), Op::LoadInt(2), Op::LogAnd]),
Value::Bool(false)
);
}
#[test]
fn log_or_full_evaluation() {
assert_eq!(
run_emit(vec![Op::LoadInt(0), Op::LoadInt(0), Op::LogOr]),
Value::Bool(false)
);
assert_eq!(
run_emit(vec![Op::LoadInt(0), Op::LoadInt(1), Op::LogOr]),
Value::Bool(true)
);
}
#[test]
fn bit_not_inverts_bits() {
assert_eq!(run_emit(vec![Op::LoadInt(0), Op::BitNot]), Value::Int(-1));
assert_eq!(run_emit(vec![Op::LoadInt(-1), Op::BitNot]), Value::Int(0));
}
#[test]
fn shl_and_shr() {
assert_eq!(
run_emit(vec![Op::LoadInt(1), Op::LoadInt(4), Op::Shl]),
Value::Int(16)
);
assert_eq!(
run_emit(vec![Op::LoadInt(64), Op::LoadInt(2), Op::Shr]),
Value::Int(16)
);
}
#[test]
fn range_inclusive_produces_array() {
let v = run_emit(vec![Op::LoadInt(1), Op::LoadInt(5), Op::Range]);
assert_eq!(
v,
Value::Array(vec![
Value::Int(1),
Value::Int(2),
Value::Int(3),
Value::Int(4),
Value::Int(5)
])
);
}
#[test]
fn range_empty_when_from_gt_to() {
let v = run_emit(vec![Op::LoadInt(5), Op::LoadInt(1), Op::Range]);
assert_eq!(v, Value::Array(vec![]));
}
#[test]
fn range_single_when_from_eq_to() {
let v = run_emit(vec![Op::LoadInt(3), Op::LoadInt(3), Op::Range]);
assert_eq!(v, Value::Array(vec![Value::Int(3)]));
}
#[test]
fn range_step_positive() {
let v = run_emit(vec![
Op::LoadInt(0),
Op::LoadInt(10),
Op::LoadInt(3),
Op::RangeStep,
]);
assert_eq!(
v,
Value::Array(vec![Value::Int(0), Value::Int(3), Value::Int(6), Value::Int(9)])
);
}
#[test]
fn range_step_negative_descends() {
let v = run_emit(vec![
Op::LoadInt(10),
Op::LoadInt(0),
Op::LoadInt(-2),
Op::RangeStep,
]);
assert_eq!(
v,
Value::Array(vec![
Value::Int(10),
Value::Int(8),
Value::Int(6),
Value::Int(4),
Value::Int(2),
Value::Int(0)
])
);
}
#[test]
fn range_step_zero_yields_empty() {
let v = run_emit(vec![
Op::LoadInt(0),
Op::LoadInt(10),
Op::LoadInt(0),
Op::RangeStep,
]);
assert_eq!(v, Value::Array(vec![]));
}
#[test]
fn make_array_collects_n_values() {
let v = run_emit(vec![
Op::LoadInt(1),
Op::LoadInt(2),
Op::LoadInt(3),
Op::MakeArray(3),
]);
assert_eq!(
v,
Value::Array(vec![Value::Int(1), Value::Int(2), Value::Int(3)])
);
}
#[test]
fn make_array_zero_yields_empty() {
let v = run_emit(vec![Op::MakeArray(0)]);
assert_eq!(v, Value::Array(vec![]));
}
#[test]
fn make_hash_collects_n_pairs() {
let mut b = ChunkBuilder::new();
let k = b.add_constant(Value::str("k"));
b.emit(Op::LoadConst(k), 1);
b.emit(Op::LoadInt(7), 1);
b.emit(Op::MakeHash(2), 1);
let v = run(b);
if let Value::Hash(h) = v {
assert_eq!(h.get("k"), Some(&Value::Int(7)));
assert_eq!(h.len(), 1);
} else {
panic!("expected hash")
}
}
#[test]
fn set_status_then_get_status() {
let v = run_emit(vec![Op::LoadInt(42), Op::SetStatus, Op::GetStatus]);
assert_eq!(v, Value::Status(42));
}
#[test]
fn get_status_initial_is_zero() {
let v = run_emit(vec![Op::GetStatus]);
assert_eq!(v, Value::Status(0));
}
#[test]
fn string_len_for_int_uses_to_str_then_len() {
assert_eq!(
run_emit(vec![Op::LoadInt(12345), Op::StringLen]),
Value::Int(5)
);
}
#[test]
fn vm_reset_clears_stack_and_runs_new_chunk() {
let mut b1 = ChunkBuilder::new();
b1.emit(Op::LoadInt(1), 1);
b1.emit(Op::LoadInt(2), 1);
b1.emit(Op::Add, 1);
let mut vm = VM::new(b1.build());
assert!(matches!(vm.run(), VMResult::Ok(Value::Int(3))));
let mut b2 = ChunkBuilder::new();
b2.emit(Op::LoadInt(10), 1);
b2.emit(Op::LoadInt(20), 1);
b2.emit(Op::Mul, 1);
vm.reset(b2.build());
assert!(matches!(vm.run(), VMResult::Ok(Value::Int(200))));
}
#[test]
fn push_pop_peek_on_vm() {
let mut b = ChunkBuilder::new();
b.emit(Op::Nop, 1);
let mut vm = VM::new(b.build());
vm.push(Value::Int(42));
assert_eq!(*vm.peek(), Value::Int(42));
assert_eq!(vm.pop(), Value::Int(42));
}