#![cfg(feature = "vm")]
#![allow(clippy::approx_constant)]
use ion_core::engine::Engine;
use ion_core::host_types::{HostEnumDef, HostStructDef, HostVariantDef};
use ion_core::value::Value;
fn vm_eval(src: &str) -> Value {
let mut engine = Engine::new();
engine.vm_eval(src).unwrap()
}
fn vm_eval_err(src: &str) -> String {
let mut engine = Engine::new();
engine.vm_eval(src).unwrap_err().message
}
#[test]
fn test_vm_int() {
assert_eq!(vm_eval("42"), Value::Int(42));
}
#[test]
fn test_vm_float() {
assert_eq!(vm_eval("3.14"), Value::Float(3.14));
}
#[test]
fn test_vm_bool() {
assert_eq!(vm_eval("true"), Value::Bool(true));
assert_eq!(vm_eval("false"), Value::Bool(false));
}
#[test]
fn test_vm_string() {
assert_eq!(vm_eval("\"hello\""), Value::Str("hello".to_string()));
}
#[test]
fn test_vm_unit() {
assert_eq!(vm_eval("()"), Value::Unit);
}
#[test]
fn test_vm_none() {
assert_eq!(vm_eval("None"), Value::Option(None));
}
#[test]
fn test_vm_add() {
assert_eq!(vm_eval("1 + 2"), Value::Int(3));
assert_eq!(vm_eval("1.5 + 2.5"), Value::Float(4.0));
}
#[test]
fn test_vm_sub() {
assert_eq!(vm_eval("10 - 3"), Value::Int(7));
}
#[test]
fn test_vm_mul() {
assert_eq!(vm_eval("4 * 5"), Value::Int(20));
}
#[test]
fn test_vm_div() {
assert_eq!(vm_eval("10 / 3"), Value::Int(3));
assert_eq!(vm_eval("10.0 / 3.0"), Value::Float(10.0 / 3.0));
}
#[test]
fn test_vm_mod() {
assert_eq!(vm_eval("10 % 3"), Value::Int(1));
}
#[test]
fn test_vm_neg() {
assert_eq!(vm_eval("-5"), Value::Int(-5));
}
#[test]
fn test_vm_complex_arithmetic() {
assert_eq!(vm_eval("(2 + 3) * 4 - 1"), Value::Int(19));
}
#[test]
fn test_vm_comparisons() {
assert_eq!(vm_eval("1 == 1"), Value::Bool(true));
assert_eq!(vm_eval("1 != 2"), Value::Bool(true));
assert_eq!(vm_eval("1 < 2"), Value::Bool(true));
assert_eq!(vm_eval("2 > 1"), Value::Bool(true));
assert_eq!(vm_eval("1 <= 1"), Value::Bool(true));
assert_eq!(vm_eval("1 >= 1"), Value::Bool(true));
}
#[test]
fn test_vm_not() {
assert_eq!(vm_eval("!true"), Value::Bool(false));
assert_eq!(vm_eval("!false"), Value::Bool(true));
}
#[test]
fn test_vm_and_or() {
assert_eq!(vm_eval("true && false"), Value::Bool(false));
assert_eq!(vm_eval("true || false"), Value::Bool(true));
assert_eq!(vm_eval("false || true"), Value::Bool(true));
}
#[test]
fn test_vm_short_circuit() {
assert_eq!(vm_eval("false && (1 / 0 == 0)"), Value::Bool(false));
}
#[test]
fn test_vm_let() {
assert_eq!(vm_eval("let x = 42; x"), Value::Int(42));
}
#[test]
fn test_vm_let_mut() {
assert_eq!(vm_eval("let mut x = 1; x = 2; x"), Value::Int(2));
}
#[test]
fn test_vm_compound_assign() {
assert_eq!(vm_eval("let mut x = 10; x += 5; x"), Value::Int(15));
assert_eq!(vm_eval("let mut x = 10; x -= 3; x"), Value::Int(7));
assert_eq!(vm_eval("let mut x = 10; x *= 2; x"), Value::Int(20));
assert_eq!(vm_eval("let mut x = 10; x /= 2; x"), Value::Int(5));
}
#[test]
fn test_vm_immutable_error() {
let err = vm_eval_err("let x = 1; x = 2; x");
assert!(err.contains("immutable"), "got: {}", err);
}
#[test]
fn test_vm_if() {
assert_eq!(vm_eval("if true { 1 } else { 2 }"), Value::Int(1));
assert_eq!(vm_eval("if false { 1 } else { 2 }"), Value::Int(2));
}
#[test]
fn test_vm_if_no_else() {
assert_eq!(vm_eval("if true { 42 }"), Value::Int(42));
assert_eq!(vm_eval("if false { 42 }"), Value::Unit);
}
#[test]
fn test_vm_block() {
assert_eq!(vm_eval("{ let x = 10; x + 5 }"), Value::Int(15));
}
#[test]
fn test_vm_while() {
assert_eq!(
vm_eval("let mut x = 0; while x < 5 { x += 1; } x"),
Value::Int(5)
);
}
#[test]
fn test_vm_for() {
assert_eq!(
vm_eval("let mut sum = 0; for x in [1, 2, 3] { sum += x; } sum"),
Value::Int(6)
);
}
#[test]
fn test_vm_continue_in_for() {
assert_eq!(
vm_eval(
"
let mut sum = 0;
for x in [1, 2, 3, 4, 5] {
if x == 3 { continue; }
sum += x;
}
sum
"
),
Value::Int(12)
);
}
#[test]
fn test_vm_continue_in_while() {
assert_eq!(
vm_eval(
"
let mut sum = 0;
let mut i = 0;
while i < 5 {
i += 1;
if i == 3 { continue; }
sum += i;
}
sum
"
),
Value::Int(12)
);
}
#[test]
fn test_vm_break_in_for() {
assert_eq!(
vm_eval(
"
let mut sum = 0;
for x in [1, 2, 3, 4, 5] {
if x == 4 { break; }
sum += x;
}
sum
"
),
Value::Int(6)
);
}
#[test]
fn test_vm_nested_continue() {
assert_eq!(
vm_eval(
"
let mut sum = 0;
for x in [1, 2, 3] {
for y in [10, 20, 30] {
if y == 20 { continue; }
sum += y;
}
sum += x;
}
sum
"
),
Value::Int(126)
);
}
#[test]
fn test_vm_fn_decl_and_call() {
assert_eq!(vm_eval("fn add(a, b) { a + b } add(3, 4)"), Value::Int(7));
}
#[test]
fn test_vm_recursive_fn() {
assert_eq!(
vm_eval(
"
fn fib(n) {
if n <= 1 { n } else { fib(n - 1) + fib(n - 2) }
}
fib(10)
"
),
Value::Int(55)
);
}
#[test]
fn test_vm_builtin_fn() {
assert_eq!(vm_eval("len([1, 2, 3])"), Value::Int(3));
assert_eq!(vm_eval("math::abs(-5)"), Value::Int(5));
}
#[test]
fn test_vm_list() {
assert_eq!(
vm_eval("[1, 2, 3]"),
Value::List(vec![Value::Int(1), Value::Int(2), Value::Int(3)])
);
}
#[test]
fn test_vm_tuple() {
assert_eq!(
vm_eval("(1, 2)"),
Value::Tuple(vec![Value::Int(1), Value::Int(2)])
);
}
#[test]
fn test_vm_dict() {
assert_eq!(vm_eval("let d = #{\"a\": 1}; d[\"a\"]"), Value::Int(1));
}
#[test]
fn test_vm_list_index() {
assert_eq!(vm_eval("[10, 20, 30][1]"), Value::Int(20));
}
#[test]
fn test_vm_negative_index() {
assert_eq!(vm_eval("[10, 20, 30][-1]"), Value::Int(30));
}
#[test]
fn test_vm_string_concat() {
assert_eq!(
vm_eval("\"hello\" + \" \" + \"world\""),
Value::Str("hello world".to_string())
);
}
#[test]
fn test_vm_fstring() {
assert_eq!(
vm_eval("let name = \"world\"; f\"hello {name}\""),
Value::Str("hello world".to_string())
);
}
#[test]
fn test_vm_list_methods() {
assert_eq!(vm_eval("[1, 2, 3].len()"), Value::Int(3));
assert_eq!(vm_eval("[1, 2, 3].contains(2)"), Value::Bool(true));
assert_eq!(vm_eval("[].is_empty()"), Value::Bool(true));
}
#[test]
fn test_vm_string_methods() {
assert_eq!(
vm_eval("\"hello\".to_upper()"),
Value::Str("HELLO".to_string())
);
assert_eq!(vm_eval("\" hi \".trim()"), Value::Str("hi".to_string()));
assert_eq!(vm_eval("\"hello\".len()"), Value::Int(5));
}
#[test]
fn test_vm_dict_methods() {
assert_eq!(vm_eval("#{\"a\": 1, \"b\": 2}.len()"), Value::Int(2));
assert_eq!(
vm_eval("#{\"a\": 1}.contains_key(\"a\")"),
Value::Bool(true)
);
}
#[test]
fn test_vm_some() {
assert_eq!(
vm_eval("Some(42)"),
Value::Option(Some(Box::new(Value::Int(42))))
);
}
#[test]
fn test_vm_option_methods() {
assert_eq!(vm_eval("Some(42).is_some()"), Value::Bool(true));
assert_eq!(vm_eval("None.is_none()"), Value::Bool(true));
assert_eq!(vm_eval("None.unwrap_or(0)"), Value::Int(0));
}
#[test]
fn test_vm_ok_err() {
assert_eq!(vm_eval("Ok(1).is_ok()"), Value::Bool(true));
assert_eq!(vm_eval("Err(\"bad\").is_err()"), Value::Bool(true));
}
#[test]
fn test_vm_try_operator() {
assert_eq!(
vm_eval(
"
fn get_val() { Some(42)? }
get_val()
"
),
Value::Int(42)
);
}
#[test]
fn test_vm_range() {
assert_eq!(
vm_eval("let mut sum = 0; for x in 1..4 { sum += x; } sum"),
Value::Int(6)
);
}
#[test]
fn test_vm_range_inclusive() {
assert_eq!(
vm_eval("let mut sum = 0; for x in 1..=3 { sum += x; } sum"),
Value::Int(6)
);
}
#[test]
fn test_vm_pipe() {
assert_eq!(vm_eval("-5 |> math::abs"), Value::Int(5));
}
#[test]
fn test_vm_scope_isolation() {
assert_eq!(vm_eval("let x = 1; { let x = 2; }; x"), Value::Int(1));
}
#[test]
fn test_vm_tuple_destructure() {
assert_eq!(vm_eval("let (a, b) = (10, 20); a + b"), Value::Int(30));
}
#[test]
fn test_vm_match_int() {
assert_eq!(
vm_eval("match 1 { 1 => \"one\", 2 => \"two\", _ => \"other\" }"),
Value::Str("one".to_string())
);
assert_eq!(
vm_eval("match 2 { 1 => \"one\", 2 => \"two\", _ => \"other\" }"),
Value::Str("two".to_string())
);
assert_eq!(
vm_eval("match 99 { 1 => \"one\", 2 => \"two\", _ => \"other\" }"),
Value::Str("other".to_string())
);
}
#[test]
fn test_vm_match_bool() {
assert_eq!(
vm_eval("match true { true => 1, false => 0 }"),
Value::Int(1)
);
assert_eq!(
vm_eval("match false { true => 1, false => 0 }"),
Value::Int(0)
);
}
#[test]
fn test_vm_match_string() {
assert_eq!(
vm_eval("match \"hi\" { \"hi\" => 1, \"bye\" => 2, _ => 0 }"),
Value::Int(1)
);
}
#[test]
fn test_vm_match_binding() {
assert_eq!(vm_eval("match 42 { x => x + 1 }"), Value::Int(43));
assert_eq!(
vm_eval("match 10 { 1 => \"one\", x => x * 2 }"),
Value::Int(20)
);
}
#[test]
fn test_vm_match_option() {
assert_eq!(
vm_eval("match Some(5) { Some(x) => x, None => 0 }"),
Value::Int(5)
);
assert_eq!(
vm_eval("match None { Some(x) => x, None => 0 }"),
Value::Int(0)
);
}
#[test]
fn test_vm_match_result() {
assert_eq!(
vm_eval("match Ok(10) { Ok(x) => x, Err(e) => 0 }"),
Value::Int(10)
);
assert_eq!(
vm_eval("match Err(\"fail\") { Ok(x) => 0, Err(e) => e }"),
Value::Str("fail".to_string())
);
}
#[test]
fn test_vm_match_tuple() {
assert_eq!(vm_eval("match (1, 2) { (a, b) => a + b }"), Value::Int(3));
assert_eq!(
vm_eval("match (1, 2) { (1, x) => x, _ => 0 }"),
Value::Int(2)
);
}
#[test]
fn test_vm_match_wildcard() {
assert_eq!(
vm_eval("match 42 { _ => \"any\" }"),
Value::Str("any".to_string())
);
}
#[test]
fn test_vm_lambda_basic() {
assert_eq!(vm_eval("let f = |x| x + 1; f(10)"), Value::Int(11));
}
#[test]
fn test_vm_lambda_multi_param() {
assert_eq!(vm_eval("let add = |a, b| a + b; add(3, 4)"), Value::Int(7));
}
#[test]
fn test_vm_lambda_closure_capture() {
assert_eq!(
vm_eval("let n = 10; let f = |x| x + n; f(5)"),
Value::Int(15)
);
}
#[test]
fn test_vm_lambda_in_list() {
assert_eq!(
vm_eval("let ops = [|x| x + 1, |x| x * 2]; ops[1](5)"),
Value::Int(10)
);
}
#[test]
fn test_vm_lambda_passed_to_fn() {
assert_eq!(
vm_eval("fn apply(f, x) { f(x) } apply(|x| x * 3, 7)"),
Value::Int(21)
);
}
#[test]
fn test_vm_list_comp_basic() {
assert_eq!(
vm_eval("[x * 2 for x in [1, 2, 3]]"),
Value::List(vec![Value::Int(2), Value::Int(4), Value::Int(6)])
);
}
#[test]
fn test_vm_list_comp_with_filter() {
assert_eq!(
vm_eval("[x for x in [1, 2, 3, 4, 5] if x > 2]"),
Value::List(vec![Value::Int(3), Value::Int(4), Value::Int(5)])
);
}
#[test]
fn test_vm_list_comp_transform_and_filter() {
assert_eq!(
vm_eval("[x * x for x in [1, 2, 3, 4] if x % 2 == 0]"),
Value::List(vec![Value::Int(4), Value::Int(16)])
);
}
#[test]
fn test_vm_list_comp_string() {
assert_eq!(
vm_eval("[c for c in \"abc\"]"),
Value::List(vec![
Value::Str("a".to_string()),
Value::Str("b".to_string()),
Value::Str("c".to_string())
])
);
}
#[test]
fn test_vm_dict_comp() {
let result = vm_eval("#{f\"{k}!\": v * 2 for (k, v) in #{\"a\": 1, \"b\": 2}}");
match result {
Value::Dict(map) => {
assert_eq!(map.get("a!"), Some(&Value::Int(2)));
assert_eq!(map.get("b!"), Some(&Value::Int(4)));
}
other => panic!("expected dict, got {:?}", other),
}
}
#[test]
fn test_vm_if_let_some() {
assert_eq!(
vm_eval("if let Some(x) = Some(42) { x } else { 0 }"),
Value::Int(42)
);
assert_eq!(
vm_eval("if let Some(x) = None { x } else { 0 }"),
Value::Int(0)
);
}
#[test]
fn test_vm_if_let_ok() {
assert_eq!(
vm_eval("if let Ok(x) = Ok(10) { x + 1 } else { 0 }"),
Value::Int(11)
);
assert_eq!(
vm_eval("if let Ok(x) = Err(\"bad\") { x } else { -1 }"),
Value::Int(-1)
);
}
#[test]
fn test_vm_if_let_no_else() {
assert_eq!(vm_eval("if let Some(x) = Some(5) { x }"), Value::Int(5));
assert_eq!(vm_eval("if let Some(x) = None { x }"), Value::Unit);
}
#[test]
fn test_vm_while_let() {
assert_eq!(
vm_eval(
r#"
let mut items = [Some(1), Some(2), Some(3), None];
let mut sum = 0;
let mut i = 0;
while let Some(x) = items[i] {
sum += x;
i += 1;
}
sum
"#
),
Value::Int(6)
);
}
#[test]
fn test_vm_list_index_assign() {
assert_eq!(
vm_eval("let mut a = [1, 2, 3]; a[0] = 10; a"),
Value::List(vec![Value::Int(10), Value::Int(2), Value::Int(3)])
);
}
#[test]
fn test_vm_list_index_compound_assign() {
assert_eq!(
vm_eval("let mut a = [1, 2, 3]; a[1] += 10; a"),
Value::List(vec![Value::Int(1), Value::Int(12), Value::Int(3)])
);
}
#[test]
fn test_vm_dict_field_assign() {
let result = vm_eval("let mut d = #{\"x\": 1}; d.x = 42; d.x");
assert_eq!(result, Value::Int(42));
}
#[test]
fn test_vm_dict_index_assign() {
let result = vm_eval("let mut d = #{\"a\": 1, \"b\": 2}; d[\"a\"] = 99; d[\"a\"]");
assert_eq!(result, Value::Int(99));
}
#[test]
fn test_vm_dict_field_compound_assign() {
let result = vm_eval("let mut d = #{\"x\": 10}; d.x += 5; d.x");
assert_eq!(result, Value::Int(15));
}
#[test]
fn test_vm_list_map() {
assert_eq!(
vm_eval("[1, 2, 3].map(|x| x * 2)"),
Value::List(vec![Value::Int(2), Value::Int(4), Value::Int(6)])
);
}
#[test]
fn test_vm_list_filter() {
assert_eq!(
vm_eval("[1, 2, 3, 4, 5].filter(|x| x > 2)"),
Value::List(vec![Value::Int(3), Value::Int(4), Value::Int(5)])
);
}
#[test]
fn test_vm_list_fold() {
assert_eq!(
vm_eval("[1, 2, 3, 4].fold(0, |acc, x| acc + x)"),
Value::Int(10)
);
}
#[test]
fn test_vm_list_flat_map() {
assert_eq!(
vm_eval("[1, 2, 3].flat_map(|x| [x, x * 10])"),
Value::List(vec![
Value::Int(1),
Value::Int(10),
Value::Int(2),
Value::Int(20),
Value::Int(3),
Value::Int(30)
])
);
}
#[test]
fn test_vm_list_any_all() {
assert_eq!(vm_eval("[1, 2, 3].any(|x| x > 2)"), Value::Bool(true));
assert_eq!(vm_eval("[1, 2, 3].any(|x| x > 5)"), Value::Bool(false));
assert_eq!(vm_eval("[1, 2, 3].all(|x| x > 0)"), Value::Bool(true));
assert_eq!(vm_eval("[1, 2, 3].all(|x| x > 2)"), Value::Bool(false));
}
#[test]
fn test_vm_option_map() {
assert_eq!(
vm_eval("Some(5).map(|x| x * 2)"),
Value::Option(Some(Box::new(Value::Int(10))))
);
assert_eq!(vm_eval("None.map(|x| x * 2)"), Value::Option(None));
}
#[test]
fn test_vm_option_and_then() {
assert_eq!(
vm_eval("Some(5).and_then(|x| Some(x + 1))"),
Value::Option(Some(Box::new(Value::Int(6))))
);
assert_eq!(
vm_eval("None.and_then(|x| Some(x + 1))"),
Value::Option(None)
);
}
#[test]
fn test_vm_result_map() {
assert_eq!(
vm_eval("Ok(10).map(|x| x + 1)"),
Value::Result(Ok(Box::new(Value::Int(11))))
);
}
#[test]
fn test_vm_result_map_err() {
assert_eq!(
vm_eval("Err(\"bad\").map_err(|e| f\"error: {e}\")"),
Value::Result(Err(Box::new(Value::Str("error: bad".to_string()))))
);
}
#[test]
fn test_vm_match_list() {
assert_eq!(
vm_eval("match [1, 2, 3] { [a, b, c] => a + b + c, _ => 0 }"),
Value::Int(6)
);
}
#[test]
fn test_vm_match_list_rest() {
assert_eq!(
vm_eval("match [1, 2, 3, 4] { [first, ...rest] => first, _ => 0 }"),
Value::Int(1)
);
}
#[test]
fn test_vm_match_list_rest_binding() {
assert_eq!(
vm_eval("match [1, 2, 3, 4] { [h, ...t] => t }"),
Value::List(vec![Value::Int(2), Value::Int(3), Value::Int(4)])
);
}
#[test]
fn test_vm_match_list_empty() {
assert_eq!(
vm_eval("match [] { [] => \"empty\", _ => \"nonempty\" }"),
Value::Str("empty".to_string())
);
assert_eq!(
vm_eval("match [1] { [] => \"empty\", _ => \"nonempty\" }"),
Value::Str("nonempty".to_string())
);
}
#[test]
fn test_vm_match_list_length_mismatch() {
assert_eq!(
vm_eval("match [1, 2] { [a, b, c] => 0, [a, b] => a + b, _ => -1 }"),
Value::Int(3)
);
}
#[test]
fn test_vm_let_list_destructure() {
assert_eq!(vm_eval("let [a, b, c] = [10, 20, 30]; b"), Value::Int(20));
}
#[test]
fn test_vm_let_list_rest() {
assert_eq!(
vm_eval("let [h, ...t] = [1, 2, 3, 4]; t"),
Value::List(vec![Value::Int(2), Value::Int(3), Value::Int(4)])
);
}
#[test]
fn test_vm_for_list_destructure() {
assert_eq!(
vm_eval("let mut sum = 0; for [a, b] in [[1, 2], [3, 4]] { sum += a + b; } sum"),
Value::Int(10)
);
}
#[test]
fn test_vm_many_variable_accesses() {
assert_eq!(
vm_eval(
r#"
let mut x = 0;
let mut y = 0;
let mut z = 0;
for i in 0..100 {
x += 1;
y += 2;
z += 3;
}
x + y + z
"#
),
Value::Int(600)
);
}
#[test]
fn test_vm_nested_scopes_many_vars() {
assert_eq!(
vm_eval(
r#"
let a = 1;
let b = 2;
let result = {
let a = 10;
let c = 3;
a + b + c
};
result + a
"#
),
Value::Int(16)
);
}
#[test]
fn test_vm_bitwise() {
assert_eq!(vm_eval("12 & 10"), Value::Int(8));
assert_eq!(vm_eval("12 | 10"), Value::Int(14));
assert_eq!(vm_eval("12 ^ 10"), Value::Int(6));
assert_eq!(vm_eval("1 << 4"), Value::Int(16));
assert_eq!(vm_eval("16 >> 2"), Value::Int(4));
}
#[test]
fn test_vm_list_first_last() {
assert_eq!(
vm_eval("[1, 2, 3].first()"),
Value::Option(Some(Box::new(Value::Int(1))))
);
assert_eq!(
vm_eval("[1, 2, 3].last()"),
Value::Option(Some(Box::new(Value::Int(3))))
);
assert_eq!(vm_eval("[].first()"), Value::Option(None));
assert_eq!(vm_eval("[].last()"), Value::Option(None));
}
#[test]
fn test_vm_list_sort() {
assert_eq!(
vm_eval("[3, 1, 2].sort()"),
Value::List(vec![Value::Int(1), Value::Int(2), Value::Int(3)])
);
assert_eq!(
vm_eval(r#"["banana", "apple", "cherry"].sort()"#),
Value::List(vec![
Value::Str("apple".into()),
Value::Str("banana".into()),
Value::Str("cherry".into())
])
);
}
#[test]
fn test_vm_list_flatten() {
assert_eq!(
vm_eval("[[1, 2], [3, 4], [5]].flatten()"),
Value::List(vec![
Value::Int(1),
Value::Int(2),
Value::Int(3),
Value::Int(4),
Value::Int(5)
])
);
assert_eq!(
vm_eval("[[1], 2, [3]].flatten()"),
Value::List(vec![Value::Int(1), Value::Int(2), Value::Int(3)])
);
}
#[test]
fn test_vm_list_zip() {
assert_eq!(
vm_eval("[1, 2, 3].zip([\"a\", \"b\", \"c\"])"),
Value::List(vec![
Value::Tuple(vec![Value::Int(1), Value::Str("a".into())]),
Value::Tuple(vec![Value::Int(2), Value::Str("b".into())]),
Value::Tuple(vec![Value::Int(3), Value::Str("c".into())]),
])
);
}
#[test]
fn test_vm_dict_entries() {
let result = vm_eval(r#"let d = #{"a": 1}; d.entries()"#);
assert_eq!(
result,
Value::List(vec![Value::Tuple(vec![
Value::Str("a".into()),
Value::Int(1)
])])
);
}
#[test]
fn test_vm_dict_insert() {
let result = vm_eval(r#"let d = #{"a": 1}; d.insert("b", 2)"#);
if let Value::Dict(map) = result {
assert_eq!(map.len(), 2);
assert_eq!(map.get("b"), Some(&Value::Int(2)));
} else {
panic!("expected dict");
}
}
#[test]
fn test_vm_dict_remove() {
let result = vm_eval(r#"let d = #{"a": 1, "b": 2}; d.remove("a")"#);
if let Value::Dict(map) = result {
assert_eq!(map.len(), 1);
assert!(map.get("a").is_none());
assert_eq!(map.get("b"), Some(&Value::Int(2)));
} else {
panic!("expected dict");
}
}
#[test]
fn test_vm_dict_merge() {
let result = vm_eval(r#"let a = #{"x": 1}; let b = #{"y": 2}; a.merge(b)"#);
if let Value::Dict(map) = result {
assert_eq!(map.len(), 2);
assert_eq!(map.get("x"), Some(&Value::Int(1)));
assert_eq!(map.get("y"), Some(&Value::Int(2)));
} else {
panic!("expected dict");
}
}
#[test]
fn test_vm_fn_cache_recursive() {
assert_eq!(
vm_eval(
r#"
fn fib(n) { if n <= 1 { n } else { fib(n - 1) + fib(n - 2) } }
fib(10)
"#
),
Value::Int(55)
);
}
#[test]
fn test_vm_fn_cache_repeated_calls() {
assert_eq!(
vm_eval(
r#"
fn double(x) { x * 2 }
let a = double(1);
let b = double(2);
let c = double(3);
a + b + c
"#
),
Value::Int(12)
);
}
#[test]
fn test_vm_error_has_col_info() {
use ion_core::error::IonError;
let mut engine = Engine::new();
let err: IonError = engine.vm_eval("1 / 0").unwrap_err();
assert_eq!(err.message, "division by zero");
assert_eq!(err.line, 1);
assert!(err.col > 0, "expected non-zero column, got {}", err.col);
}
#[test]
fn test_vm_error_field_access_col() {
use ion_core::error::IonError;
let mut engine = Engine::new();
let err: IonError = engine.vm_eval("let x = 42; x.foo").unwrap_err();
assert_eq!(err.line, 1);
assert!(
err.col > 0,
"expected non-zero column for field error, got {}",
err.col
);
}
#[test]
fn test_vm_tail_call_deep_recursion() {
assert_eq!(
vm_eval(
r#"
fn countdown(n) {
if n <= 0 { 0 } else { countdown(n - 1) }
}
countdown(10000)
"#
),
Value::Int(0)
);
}
#[test]
fn test_vm_tail_call_accumulator() {
assert_eq!(
vm_eval(
r#"
fn sum_acc(n, acc) {
if n <= 0 { acc } else { sum_acc(n - 1, acc + n) }
}
sum_acc(10000, 0)
"#
),
Value::Int(50005000)
);
}
#[test]
fn test_vm_dict_spread_basic() {
let val = vm_eval(
r#"
let base = #{ "a": 1, "b": 2 };
#{ ...base, "c": 3 }
"#,
);
if let Value::Dict(map) = val {
assert_eq!(map.len(), 3);
assert_eq!(map["a"], Value::Int(1));
assert_eq!(map["b"], Value::Int(2));
assert_eq!(map["c"], Value::Int(3));
} else {
panic!("expected dict");
}
}
#[test]
fn test_vm_dict_spread_override() {
let val = vm_eval(
r#"
let base = #{ "a": 1, "b": 2 };
#{ ...base, "b": 99 }
"#,
);
if let Value::Dict(map) = val {
assert_eq!(map["a"], Value::Int(1));
assert_eq!(map["b"], Value::Int(99));
} else {
panic!("expected dict");
}
}
#[test]
fn test_vm_dict_spread_multiple() {
let val = vm_eval(
r#"
let a = #{ "x": 1 };
let b = #{ "y": 2 };
#{ ...a, ...b, "z": 3 }
"#,
);
if let Value::Dict(map) = val {
assert_eq!(map.len(), 3);
assert_eq!(map["x"], Value::Int(1));
assert_eq!(map["y"], Value::Int(2));
assert_eq!(map["z"], Value::Int(3));
} else {
panic!("expected dict");
}
}
#[test]
fn test_vm_dict_spread_non_dict_error() {
let err = vm_eval_err(r#"#{ ...[1, 2, 3] }"#);
assert!(err.contains("spread requires a dict"), "got: {}", err);
}
#[test]
fn test_vm_const_fold_arithmetic() {
assert_eq!(vm_eval("2 + 3"), Value::Int(5));
assert_eq!(vm_eval("10 - 3"), Value::Int(7));
assert_eq!(vm_eval("4 * 5"), Value::Int(20));
assert_eq!(vm_eval("10 / 3"), Value::Int(3));
assert_eq!(vm_eval("10 % 3"), Value::Int(1));
}
#[test]
fn test_vm_const_fold_float() {
assert_eq!(vm_eval("1.5 + 2.5"), Value::Float(4.0));
assert_eq!(vm_eval("2 + 1.5"), Value::Float(3.5));
assert_eq!(vm_eval("1.5 * 2"), Value::Float(3.0));
}
#[test]
fn test_vm_const_fold_string_concat() {
assert_eq!(
vm_eval(r#""hello" + " world""#),
Value::Str("hello world".into())
);
}
#[test]
fn test_vm_const_fold_comparison() {
assert_eq!(vm_eval("3 > 2"), Value::Bool(true));
assert_eq!(vm_eval("1 == 2"), Value::Bool(false));
assert_eq!(vm_eval("5 <= 5"), Value::Bool(true));
}
#[test]
fn test_vm_const_fold_bool_logic() {
assert_eq!(vm_eval("true && false"), Value::Bool(false));
assert_eq!(vm_eval("true || false"), Value::Bool(true));
}
#[test]
fn test_vm_const_fold_unary() {
assert_eq!(vm_eval("-42"), Value::Int(-42));
assert_eq!(vm_eval("!true"), Value::Bool(false));
assert_eq!(vm_eval("-3.14"), Value::Float(-3.14));
}
#[test]
fn test_vm_const_fold_nested() {
assert_eq!(vm_eval("(2 + 3) * 4"), Value::Int(20));
}
fn vm_engine_with_types() -> Engine {
let mut engine = Engine::new();
engine.register_struct(HostStructDef {
name: "Config".into(),
fields: vec!["host".into(), "port".into(), "debug".into()],
});
engine.register_enum(HostEnumDef {
name: "Color".into(),
variants: vec![
HostVariantDef {
name: "Red".into(),
arity: 0,
},
HostVariantDef {
name: "Custom".into(),
arity: 3,
},
],
});
engine
}
#[test]
fn test_vm_host_struct_construct() {
let mut engine = vm_engine_with_types();
let val = engine
.vm_eval(r#"Config { host: "localhost", port: 8080, debug: true }"#)
.unwrap();
if let Value::HostStruct { type_name, fields } = &val {
assert_eq!(type_name, "Config");
assert_eq!(fields["host"], Value::Str("localhost".into()));
assert_eq!(fields["port"], Value::Int(8080));
assert_eq!(fields["debug"], Value::Bool(true));
} else {
panic!("expected HostStruct, got: {:?}", val);
}
}
#[test]
fn test_vm_host_struct_field_access() {
let mut engine = vm_engine_with_types();
let val = engine
.vm_eval(
r#"
let c = Config { host: "localhost", port: 8080, debug: false };
c.port
"#,
)
.unwrap();
assert_eq!(val, Value::Int(8080));
}
#[test]
fn test_vm_host_struct_spread() {
let mut engine = vm_engine_with_types();
let val = engine
.vm_eval(
r#"
let base = Config { host: "localhost", port: 8080, debug: false };
let updated = Config { ...base, debug: true };
updated.debug
"#,
)
.unwrap();
assert_eq!(val, Value::Bool(true));
}
#[test]
fn test_vm_host_enum_variant() {
let mut engine = vm_engine_with_types();
let val = engine.vm_eval("Color::Red").unwrap();
if let Value::HostEnum {
enum_name,
variant,
data,
} = &val
{
assert_eq!(enum_name, "Color");
assert_eq!(variant, "Red");
assert!(data.is_empty());
} else {
panic!("expected HostEnum, got: {:?}", val);
}
}
#[test]
fn test_vm_dead_code_after_return() {
assert_eq!(
vm_eval(
r#"
fn foo() {
return 42;
let x = 100;
x + 1
}
foo()
"#
),
Value::Int(42)
);
}
#[test]
fn test_vm_dead_code_after_break() {
assert_eq!(
vm_eval(
r#"
let mut sum = 0;
for i in [1, 2, 3, 4, 5] {
if i > 3 {
break;
sum = sum + 999;
}
sum = sum + i;
}
sum
"#
),
Value::Int(6)
);
}
#[test]
fn test_vm_host_enum_variant_with_data() {
let mut engine = vm_engine_with_types();
let val = engine.vm_eval("Color::Custom(255, 128, 0)").unwrap();
if let Value::HostEnum {
enum_name,
variant,
data,
} = &val
{
assert_eq!(enum_name, "Color");
assert_eq!(variant, "Custom");
assert_eq!(data, &vec![Value::Int(255), Value::Int(128), Value::Int(0)]);
} else {
panic!("expected HostEnum, got: {:?}", val);
}
}
#[test]
fn test_vm_host_enum_pattern_match_with_payload() {
let mut engine = vm_engine_with_types();
let val = engine
.vm_eval(
r#"
let c = Color::Custom(255, 128, 0);
match c {
Color::Red => "red",
Color::Custom(r, g, b) => f"rgb({r},{g},{b})",
_ => "other",
}
"#,
)
.unwrap();
assert_eq!(val, Value::Str("rgb(255,128,0)".into()));
}
#[test]
fn test_vm_host_enum_pattern_match_unit_variant() {
let mut engine = vm_engine_with_types();
let val = engine
.vm_eval(
r#"
let c = Color::Red;
match c {
Color::Red => "red",
Color::Custom(_, _, _) => "custom",
_ => "other",
}
"#,
)
.unwrap();
assert_eq!(val, Value::Str("red".into()));
}
#[test]
fn test_vm_host_struct_let_destructure() {
let mut engine = vm_engine_with_types();
let val = engine
.vm_eval(
r#"
let Config { host, port } = Config { host: "localhost", port: 8080, debug: true };
f"{host}:{port}"
"#,
)
.unwrap();
assert_eq!(val, Value::Str("localhost:8080".into()));
}
#[test]
fn test_vm_host_enum_let_destructure() {
let mut engine = vm_engine_with_types();
let val = engine
.vm_eval(
r#"
let Color::Custom(r, g, b) = Color::Custom(255, 128, 0);
r + g + b
"#,
)
.unwrap();
assert_eq!(val, Value::Int(383));
}
#[test]
fn test_vm_let_pattern_mismatch_errors() {
let mut engine = vm_engine_with_types();
let err = engine
.vm_eval(
r#"
let Color::Custom(r, g, b) = Color::Red;
r
"#,
)
.unwrap_err();
assert!(
err.message.contains("non-exhaustive match"),
"unexpected error: {}",
err.message
);
}
#[test]
fn test_vm_for_host_struct_destructure_checks_pattern() {
let mut engine = vm_engine_with_types();
let val = engine
.vm_eval(
r#"
let mut sum = 0;
for Config { port } in [
Config { host: "a", port: 20, debug: false },
Config { host: "b", port: 22, debug: true },
] {
sum += port;
}
sum
"#,
)
.unwrap();
assert_eq!(val, Value::Int(42));
}
#[test]
fn test_vm_for_pattern_mismatch_errors() {
let err = vm_eval_err(
r#"
for (a, b) in [(1, 2), 3] {
a + b;
}
"#,
);
assert!(
err.contains("non-exhaustive match"),
"unexpected error: {}",
err
);
}
#[test]
fn test_vm_list_comp_pattern_mismatch_errors() {
let err = vm_eval_err("[a for (a, b) in [(1, 2), 3]]");
assert!(
err.contains("non-exhaustive match"),
"unexpected error: {}",
err
);
}
#[test]
fn test_vm_peephole_double_not() {
assert_eq!(vm_eval("!!true"), Value::Bool(true));
assert_eq!(vm_eval("!!false"), Value::Bool(false));
assert_eq!(vm_eval("let x = 5; !!(x > 3)"), Value::Bool(true));
}
#[test]
fn test_vm_peephole_double_neg() {
assert_eq!(vm_eval("--5"), Value::Int(5));
assert_eq!(vm_eval("let x = 42; --x"), Value::Int(42));
}
#[test]
fn test_vm_peephole_if_else_correctness() {
assert_eq!(vm_eval("if true { 1 } else { 2 }"), Value::Int(1));
assert_eq!(vm_eval("if false { 1 } else { 2 }"), Value::Int(2));
assert_eq!(
vm_eval("let x = if true { 10 } else { 20 }; x"),
Value::Int(10)
);
}
#[test]
fn test_vm_peephole_match_correctness() {
assert_eq!(
vm_eval("match Some(5) { Some(v) => v * 2, None => 0 }"),
Value::Int(10)
);
assert_eq!(
vm_eval("match None { Some(v) => v, None => 99 }"),
Value::Int(99)
);
assert_eq!(
vm_eval("match Ok(7) { Ok(v) => v, Err(e) => 0 }"),
Value::Int(7)
);
}
#[test]
fn test_vm_bytes_literal() {
assert_eq!(vm_eval(r#"b"hello""#), Value::Bytes(b"hello".to_vec()));
assert_eq!(vm_eval(r#"b"\x48\x49""#), Value::Bytes(vec![0x48, 0x49]));
assert_eq!(vm_eval(r#"b"""#), Value::Bytes(vec![]));
}
#[test]
fn test_vm_bytes_index() {
assert_eq!(vm_eval(r#"b"hello"[0]"#), Value::Int(104));
assert_eq!(vm_eval(r#"b"hello"[-1]"#), Value::Int(111));
}
#[test]
fn test_vm_bytes_methods() {
assert_eq!(vm_eval(r#"b"hello".len()"#), Value::Int(5));
assert_eq!(vm_eval(r#"b"".is_empty()"#), Value::Bool(true));
assert_eq!(vm_eval(r#"b"hi".is_empty()"#), Value::Bool(false));
assert_eq!(vm_eval(r#"b"hello".contains(104)"#), Value::Bool(true));
assert_eq!(vm_eval(r#"b"hello".contains(0)"#), Value::Bool(false));
assert_eq!(
vm_eval(r#"b"hello".to_hex()"#),
Value::Str("68656c6c6f".to_string())
);
assert_eq!(
vm_eval(r#"b"hi".to_str()"#),
Value::Result(Ok(Box::new(Value::Str("hi".to_string()))))
);
}
#[test]
fn test_vm_bytes_slice() {
assert_eq!(vm_eval(r#"b"hello"[1..3]"#), Value::Bytes(vec![101, 108]));
}
#[test]
fn test_vm_bytes_to_list() {
assert_eq!(
vm_eval(r#"b"AB".to_list()"#),
Value::List(vec![Value::Int(65), Value::Int(66)])
);
}
#[test]
fn test_vm_bytes_find() {
assert_eq!(
vm_eval(r#"b"hello".find(108)"#),
Value::Option(Some(Box::new(Value::Int(2))))
);
assert_eq!(vm_eval(r#"b"hello".find(0)"#), Value::Option(None));
}
#[test]
fn test_vm_bytes_reverse() {
assert_eq!(
vm_eval(r#"b"abc".reverse()"#),
Value::Bytes(vec![99, 98, 97])
);
}
#[test]
fn test_vm_bytes_push() {
assert_eq!(
vm_eval(r#"b"hi".push(33)"#),
Value::Bytes(vec![104, 105, 33])
);
}
#[test]
fn test_vm_sort_by() {
assert_eq!(
vm_eval("[3, 1, 2].sort_by(|a, b| a - b)"),
Value::List(vec![Value::Int(1), Value::Int(2), Value::Int(3)])
);
assert_eq!(
vm_eval("[3, 1, 2].sort_by(|a, b| b - a)"),
Value::List(vec![Value::Int(3), Value::Int(2), Value::Int(1)])
);
}
#[test]
fn test_vm_clamp() {
assert_eq!(vm_eval("math::clamp(5, 0, 3)"), Value::Int(3));
assert_eq!(vm_eval("math::clamp(-1, 0, 10)"), Value::Int(0));
assert_eq!(vm_eval("math::clamp(5, 0, 10)"), Value::Int(5));
assert_eq!(vm_eval("math::clamp(1.5, 0.0, 1.0)"), Value::Float(1.0));
}
#[test]
fn test_vm_try_catch_no_error() {
assert_eq!(vm_eval("try { 42 } catch e { e }"), Value::Int(42));
}
#[test]
fn test_vm_try_catch_with_error() {
assert_eq!(
vm_eval(r#"try { assert(false, "boom"); 1 } catch e { e }"#),
Value::Str("boom".to_string())
);
}
#[test]
fn test_vm_try_catch_division_by_zero() {
assert_eq!(
vm_eval(r#"try { 1 / 0 } catch e { "caught" }"#),
Value::Str("caught".to_string())
);
}
#[test]
fn test_vm_try_catch_nested() {
assert_eq!(
vm_eval(
r#"
try {
try { assert(false, "inner"); 1 } catch e { e }
} catch e { "outer" }
"#
),
Value::Str("inner".to_string())
);
}
#[test]
fn test_vm_try_catch_as_expression() {
assert_eq!(
vm_eval(r#"let x = try { 10 } catch e { 0 }; x + 1"#),
Value::Int(11)
);
}
#[test]
fn test_vm_named_args_basic() {
assert_eq!(
vm_eval(
r#"fn greet(name, greeting) { f"{greeting} {name}" } greet(greeting: "hi", name: "world")"#
),
Value::Str("hi world".to_string())
);
}
#[test]
fn test_vm_named_args_with_default() {
assert_eq!(
vm_eval(
r#"fn greet(name, greeting = "hello") { f"{greeting} {name}" } greet(name: "world")"#
),
Value::Str("hello world".to_string())
);
}
#[test]
fn test_vm_named_args_mixed() {
assert_eq!(
vm_eval(r#"fn add(a, b) { a + b } add(1, b: 2)"#),
Value::Int(3)
);
}