#![cfg(feature = "vm")]
#![allow(clippy::approx_constant)]
use ion_core::engine::Engine;
use ion_core::value::Value;
fn assert_both(src: &str) {
let mut tw = Engine::new();
let mut vm = Engine::new();
let tw_result = tw.eval(src);
let vm_result = vm.vm_eval(src);
match (tw_result, vm_result) {
(Ok(tw_val), Ok(vm_val)) => {
assert_eq!(tw_val, vm_val, "divergence on: {}", src);
}
(Err(tw_err), Err(vm_err)) => {
let _ = (tw_err, vm_err);
}
(Ok(tw_val), Err(vm_err)) => {
panic!(
"tree-walk OK ({:?}) but VM errored: {} | src: {}",
tw_val, vm_err.message, src
);
}
(Err(tw_err), Ok(vm_val)) => {
panic!(
"tree-walk errored ({}) but VM OK: {:?} | src: {}",
tw_err.message, vm_val, src
);
}
}
}
fn assert_both_eq(src: &str, expected: Value) {
let mut tw = Engine::new();
let mut vm = Engine::new();
let tw_val = tw.eval(src).unwrap();
let vm_val = vm.vm_eval(src).unwrap();
assert_eq!(tw_val, expected, "tree-walk mismatch: {}", src);
assert_eq!(vm_val, expected, "VM mismatch: {}", src);
}
#[test]
fn cross_int_literals() {
assert_both_eq("42", Value::Int(42));
assert_both_eq("-1", Value::Int(-1));
assert_both_eq("0", Value::Int(0));
}
#[test]
fn cross_float_literals() {
assert_both_eq("3.14", Value::Float(3.14));
assert_both_eq("-0.5", Value::Float(-0.5));
}
#[test]
fn cross_bool_literals() {
assert_both_eq("true", Value::Bool(true));
assert_both_eq("false", Value::Bool(false));
}
#[test]
fn cross_string_literals() {
assert_both_eq(r#""hello""#, Value::Str("hello".to_string()));
assert_both_eq(r#""""#, Value::Str(String::new()));
}
#[test]
fn cross_unit() {
assert_both_eq("()", Value::Unit);
}
#[test]
fn cross_arithmetic() {
assert_both_eq("2 + 3", Value::Int(5));
assert_both_eq("10 - 4", Value::Int(6));
assert_both_eq("3 * 7", Value::Int(21));
assert_both_eq("15 / 3", Value::Int(5));
assert_both_eq("17 % 5", Value::Int(2));
assert_both_eq("2.0 + 3.0", Value::Float(5.0));
}
#[test]
fn cross_unary() {
assert_both_eq("-5", Value::Int(-5));
assert_both_eq("!true", Value::Bool(false));
assert_both_eq("!false", Value::Bool(true));
}
#[test]
fn cross_comparison() {
assert_both_eq("1 < 2", Value::Bool(true));
assert_both_eq("2 > 1", Value::Bool(true));
assert_both_eq("1 == 1", Value::Bool(true));
assert_both_eq("1 != 2", Value::Bool(true));
assert_both_eq("3 <= 3", Value::Bool(true));
assert_both_eq("4 >= 5", Value::Bool(false));
}
#[test]
fn cross_logic() {
assert_both_eq("true && true", Value::Bool(true));
assert_both_eq("true && false", Value::Bool(false));
assert_both_eq("false || true", Value::Bool(true));
assert_both_eq("false || false", Value::Bool(false));
}
#[test]
fn cross_let_bindings() {
assert_both_eq("let x = 10; x", Value::Int(10));
assert_both_eq("let mut x = 1; x = 2; x", Value::Int(2));
assert_both_eq("let mut x = 0; x += 5; x", Value::Int(5));
}
#[test]
fn cross_if_else() {
assert_both_eq("if true { 1 } else { 2 }", Value::Int(1));
assert_both_eq("if false { 1 } else { 2 }", Value::Int(2));
assert_both_eq("if 1 > 0 { 10 } else { 20 }", Value::Int(10));
}
#[test]
fn cross_if_chain() {
assert_both_eq(
"let x = 5; if x < 0 { -1 } else if x == 0 { 0 } else { 1 }",
Value::Int(1),
);
}
#[test]
fn cross_while_loop() {
assert_both_eq("let mut x = 0; while x < 5 { x += 1; } x", Value::Int(5));
}
#[test]
fn cross_for_loop() {
assert_both_eq(
"let mut sum = 0; for x in [1, 2, 3] { sum += x; } sum",
Value::Int(6),
);
}
#[test]
fn cross_for_continue() {
assert_both_eq(
"let mut sum = 0; for x in [1, 2, 3, 4, 5] { if x == 3 { continue; } sum += x; } sum",
Value::Int(12),
);
}
#[test]
fn cross_for_break() {
assert_both_eq(
"let mut sum = 0; for x in [1, 2, 3, 4, 5] { if x == 4 { break; } sum += x; } sum",
Value::Int(6),
);
}
#[test]
fn cross_fn_basic() {
assert_both_eq("fn add(a, b) { a + b } add(3, 4)", Value::Int(7));
}
#[test]
fn cross_fn_recursive() {
assert_both_eq(
"fn fib(n) { if n <= 1 { n } else { fib(n - 1) + fib(n - 2) } } fib(6)",
Value::Int(8),
);
}
#[test]
fn cross_fn_default_params() {
assert_both_eq(
r#"fn greet(name = "world") { name } greet()"#,
Value::Str("world".to_string()),
);
}
#[test]
fn cross_fn_missing_required_arg_errors() {
assert_both("fn f(a, b) { [a, b] } f(1)");
}
#[test]
fn cross_fn_default_uses_outer_binding() {
assert_both_eq(
r#"let suffix = "!"; fn greet(name, mark = suffix) { name + mark } greet("ion")"#,
Value::Str("ion!".to_string()),
);
}
#[test]
fn cross_fn_default_uses_prior_param() {
assert_both_eq("fn f(a, b = a) { b } f(7)", Value::Int(7));
}
#[test]
fn cross_fn_named_default_uses_outer_binding() {
assert_both_eq(
r#"let suffix = "!"; fn greet(name, mark = suffix) { name + mark } greet(name: "ion")"#,
Value::Str("ion!".to_string()),
);
}
#[test]
fn cross_closure() {
assert_both_eq("let x = 10; fn add_x(y) { x + y } add_x(5)", Value::Int(15));
}
#[test]
fn cross_lambda() {
assert_both_eq("let double = |x| x * 2; double(5)", Value::Int(10));
}
#[test]
fn cross_list() {
assert_both_eq(
"[1, 2, 3]",
Value::List(vec![Value::Int(1), Value::Int(2), Value::Int(3)]),
);
assert_both_eq("[1, 2, 3].len()", Value::Int(3));
}
#[test]
fn cross_tuple() {
assert_both_eq(
"(1, 2, 3)",
Value::Tuple(vec![Value::Int(1), Value::Int(2), Value::Int(3)]),
);
}
#[test]
fn cross_dict() {
assert_both("let d = #{a: 1, b: 2}; d.a");
}
#[test]
fn cross_index() {
assert_both_eq("[10, 20, 30][1]", Value::Int(20));
}
#[test]
fn cross_field_access() {
assert_both_eq(r#"#{"name": "ion"}.name"#, Value::Str("ion".to_string()));
}
#[test]
fn cross_option() {
assert_both_eq("Some(42)", Value::Option(Some(Box::new(Value::Int(42)))));
assert_both_eq("None", Value::Option(None));
}
#[test]
fn cross_result() {
assert_both_eq("Ok(1)", Value::Result(Ok(Box::new(Value::Int(1)))));
}
#[test]
fn cross_try_ok() {
assert_both_eq("fn f() { let x = Ok(42); x? } f()", Value::Int(42));
}
#[test]
fn cross_try_some() {
assert_both_eq("fn f() { let x = Some(10); x? } f()", Value::Int(10));
}
#[test]
fn cross_try_err_propagates() {
assert_both(r#"fn f() { let x = Err("fail"); x? } f()"#);
}
#[test]
fn cross_try_none_propagates() {
assert_both("fn f() { let x = None; x? } f()");
}
#[test]
fn cross_match_literal() {
assert_both_eq("match 5 { 5 => 10, _ => 0 }", Value::Int(10));
assert_both_eq("match 3 { 5 => 10, _ => 0 }", Value::Int(0));
}
#[test]
fn cross_match_option() {
assert_both_eq(
"match Some(5) { Some(v) => v * 2, None => 0 }",
Value::Int(10),
);
assert_both_eq("match None { Some(v) => v, None => 99 }", Value::Int(99));
}
#[test]
fn cross_match_result() {
assert_both_eq("match Ok(7) { Ok(v) => v, Err(e) => 0 }", Value::Int(7));
assert_both_eq(
r#"match Err("fail") { Ok(v) => 0, Err(e) => e }"#,
Value::Str("fail".to_string()),
);
}
#[test]
fn cross_match_tuple() {
assert_both_eq("match (1, 2) { (a, b) => a + b, _ => 0 }", Value::Int(3));
}
#[test]
fn cross_match_list() {
assert_both_eq(
"match [1, 2, 3] { [a, b, c] => a + b + c, _ => 0 }",
Value::Int(6),
);
assert_both_eq("match [] { [] => 1, _ => 0 }", Value::Int(1));
}
#[test]
fn cross_fstring() {
assert_both_eq(
r#"let x = 42; f"val={x}""#,
Value::Str("val=42".to_string()),
);
}
#[test]
fn cross_string_methods() {
assert_both_eq(r#""hello".len()"#, Value::Int(5));
assert_both_eq(r#""hello".contains("ell")"#, Value::Bool(true));
assert_both_eq(r#""hello".starts_with("hel")"#, Value::Bool(true));
assert_both_eq(r#""hello".ends_with("llo")"#, Value::Bool(true));
assert_both_eq(r#"" hi ".trim()"#, Value::Str("hi".to_string()));
assert_both_eq(r#"" hi ".trim_start()"#, Value::Str("hi ".to_string()));
assert_both_eq(r#"" hi ".trim_end()"#, Value::Str(" hi".to_string()));
assert_both_eq(r#""hi".to_upper()"#, Value::Str("HI".to_string()));
assert_both_eq(r#""HI".to_lower()"#, Value::Str("hi".to_string()));
assert_both_eq(r#""ab".repeat(3)"#, Value::Str("ababab".to_string()));
assert_both_eq(r#""hello".reverse()"#, Value::Str("olleh".to_string()));
assert_both_eq(r#""hello".is_empty()"#, Value::Bool(false));
assert_both_eq(r#""".is_empty()"#, Value::Bool(true));
}
#[test]
fn cross_string_find() {
assert_both_eq(
r#""hello".find("ell")"#,
Value::Option(Some(Box::new(Value::Int(1)))),
);
assert_both_eq(r#""hello".find("xyz")"#, Value::Option(None));
}
#[test]
fn cross_string_split_replace() {
assert_both_eq(
r#""a,b,c".split(",")"#,
Value::List(vec![
Value::Str("a".to_string()),
Value::Str("b".to_string()),
Value::Str("c".to_string()),
]),
);
assert_both_eq(
r#""hello".replace("l", "r")"#,
Value::Str("herro".to_string()),
);
}
#[test]
fn cross_string_chars() {
assert_both_eq(
r#""hi".chars()"#,
Value::List(vec![
Value::Str("h".to_string()),
Value::Str("i".to_string()),
]),
);
}
#[test]
fn cross_string_slice() {
assert_both_eq(r#""hello".slice(1, 4)"#, Value::Str("ell".to_string()));
}
#[test]
fn cross_pipe() {
assert_both_eq("fn double(x) { x * 2 } 5 |> double", Value::Int(10));
}
#[test]
fn cross_bitwise() {
assert_both_eq("255 & 15", Value::Int(15));
assert_both_eq("15 | 240", Value::Int(255));
assert_both_eq("255 ^ 15", Value::Int(240));
assert_both_eq("1 << 4", Value::Int(16));
assert_both_eq("16 >> 2", Value::Int(4));
}
#[test]
fn cross_if_let() {
assert_both_eq(
"let x = Some(10); if let Some(v) = x { v + 1 } else { 0 }",
Value::Int(11),
);
assert_both_eq(
"let x = None; if let Some(v) = x { v } else { 99 }",
Value::Int(99),
);
}
#[test]
fn cross_list_comprehension() {
assert_both_eq(
"[x * 2 for x in [1, 2, 3]]",
Value::List(vec![Value::Int(2), Value::Int(4), Value::Int(6)]),
);
}
#[test]
fn cross_filtered_comprehension() {
assert_both_eq(
"[x for x in [1, 2, 3, 4, 5] if x % 2 == 0]",
Value::List(vec![Value::Int(2), Value::Int(4)]),
);
}
#[test]
fn cross_list_assign() {
assert_both_eq(
"let mut a = [1, 2, 3]; a[0] = 10; a",
Value::List(vec![Value::Int(10), Value::Int(2), Value::Int(3)]),
);
}
#[test]
fn cross_dict_assign() {
assert_both("let mut d = #{x: 1}; d.x = 2; d.x");
}
#[test]
fn cross_block_scope() {
assert_both_eq(
"let x = 1; let y = { let x = 2; x + 10 }; x + y",
Value::Int(13),
);
}
#[test]
fn cross_fibonacci() {
assert_both_eq(
"fn fib(n) { if n <= 1 { n } else { fib(n - 1) + fib(n - 2) } } fib(6)",
Value::Int(8),
);
}
#[test]
fn cross_nested_closures() {
assert_both_eq(
"fn make_adder(x) { |y| x + y } let add5 = make_adder(5); add5(10)",
Value::Int(15),
);
}
#[test]
fn cross_map_filter() {
assert_both_eq(
"[1, 2, 3, 4, 5].filter(|x| x > 2).map(|x| x * 10)",
Value::List(vec![Value::Int(30), Value::Int(40), Value::Int(50)]),
);
}
#[test]
fn cross_complex_match() {
assert_both_eq(
r#"
fn classify(n) {
match n % 3 {
0 => "fizz",
1 => "one",
_ => "other",
}
}
classify(9)
"#,
Value::Str("fizz".to_string()),
);
}
#[test]
fn cross_nested_loops() {
assert_both_eq(
r#"
let mut sum = 0;
for i in [1, 2, 3] {
for j in [10, 20] {
sum += i * j;
}
}
sum
"#,
Value::Int(180),
);
}
#[test]
fn cross_dict_methods() {
assert_both_eq(r#"#{"a": 1, "b": 2}.len()"#, Value::Int(2));
assert_both_eq(r#"#{"a": 1, "b": 2}.contains_key("a")"#, Value::Bool(true));
assert_both_eq(r#"#{"a": 1, "b": 2}.contains_key("c")"#, Value::Bool(false));
assert_both_eq(r#"#{"a": 1}.is_empty()"#, Value::Bool(false));
assert_both_eq(r#"#{}.is_empty()"#, Value::Bool(true));
}
#[test]
fn cross_dict_shorthand_keys() {
assert_both_eq("#{a: 1, b: 2}.len()", Value::Int(2));
assert_both(r#"#{a: 1}.a"#);
}
#[test]
fn cross_list_methods() {
assert_both_eq(
"[3, 1, 2].sort()",
Value::List(vec![Value::Int(1), Value::Int(2), Value::Int(3)]),
);
assert_both_eq(
"[1, 2, 3].reverse()",
Value::List(vec![Value::Int(3), Value::Int(2), Value::Int(1)]),
);
assert_both_eq("[1, 2, 3].contains(2)", Value::Bool(true));
assert_both_eq("[1, 2, 3].contains(5)", Value::Bool(false));
assert_both_eq("[1, 2, 3].is_empty()", Value::Bool(false));
assert_both_eq("[].is_empty()", Value::Bool(true));
assert_both_eq(r#"[1, 2, 3].join(",")"#, Value::Str("1,2,3".to_string()));
}
#[test]
fn cross_list_first_last() {
assert_both_eq(
"[1, 2, 3].first()",
Value::Option(Some(Box::new(Value::Int(1)))),
);
assert_both_eq(
"[1, 2, 3].last()",
Value::Option(Some(Box::new(Value::Int(3)))),
);
assert_both_eq("[].first()", Value::Option(None));
}
#[test]
fn cross_list_closure_methods() {
assert_both_eq(
"[1, 2, 3].map(|x| x * 2)",
Value::List(vec![Value::Int(2), Value::Int(4), Value::Int(6)]),
);
assert_both_eq(
"[1, 2, 3, 4].filter(|x| x > 2)",
Value::List(vec![Value::Int(3), Value::Int(4)]),
);
assert_both_eq("[1, 2, 3].fold(0, |acc, x| acc + x)", Value::Int(6));
assert_both_eq("[1, 2, 3].any(|x| x > 2)", Value::Bool(true));
assert_both_eq("[1, 2, 3].any(|x| x > 5)", Value::Bool(false));
assert_both_eq("[1, 2, 3].all(|x| x > 0)", Value::Bool(true));
assert_both_eq("[1, 2, 3].all(|x| x > 1)", Value::Bool(false));
}
#[test]
fn cross_list_flatten_zip() {
assert_both_eq(
"[[1, 2], [3, 4]].flatten()",
Value::List(vec![
Value::Int(1),
Value::Int(2),
Value::Int(3),
Value::Int(4),
]),
);
assert_both_eq(
"[1, 2].zip([3, 4])",
Value::List(vec![
Value::Tuple(vec![Value::Int(1), Value::Int(3)]),
Value::Tuple(vec![Value::Int(2), Value::Int(4)]),
]),
);
}
#[test]
fn cross_list_enumerate() {
assert_both_eq(
"[10, 20].enumerate()",
Value::List(vec![
Value::Tuple(vec![Value::Int(0), Value::Int(10)]),
Value::Tuple(vec![Value::Int(1), Value::Int(20)]),
]),
);
}
#[test]
fn cross_dict_get() {
assert_both_eq(
r#"#{"a": 1}.get("a")"#,
Value::Option(Some(Box::new(Value::Int(1)))),
);
assert_both_eq(r#"#{"a": 1}.get("b")"#, Value::Option(None));
}
#[test]
fn cross_dict_keys_values() {
assert_both_eq(
r#"#{"a": 1, "b": 2}.keys()"#,
Value::List(vec![
Value::Str("a".to_string()),
Value::Str("b".to_string()),
]),
);
assert_both_eq(
r#"#{"a": 1, "b": 2}.values()"#,
Value::List(vec![Value::Int(1), Value::Int(2)]),
);
}
#[test]
fn cross_dict_entries() {
assert_both_eq(
r#"#{"a": 1}.entries()"#,
Value::List(vec![Value::Tuple(vec![
Value::Str("a".to_string()),
Value::Int(1),
])]),
);
}
#[test]
fn cross_math_builtins() {
assert_both_eq("math::abs(-5)", Value::Int(5));
assert_both_eq("math::abs(3.5)", Value::Float(3.5));
assert_both_eq("math::min(3, 7)", Value::Int(3));
assert_both_eq("math::max(3, 7)", Value::Int(7));
assert_both_eq("math::floor(3.7)", Value::Float(3.0));
assert_both_eq("math::ceil(3.2)", Value::Float(4.0));
assert_both_eq("math::round(3.5)", Value::Float(4.0));
assert_both_eq("math::sqrt(16.0)", Value::Float(4.0));
assert_both_eq("math::pow(2, 10)", Value::Int(1024));
}
#[test]
fn cross_type_of() {
assert_both_eq(r#"type_of(42)"#, Value::Str("int".to_string()));
assert_both_eq(r#"type_of("hello")"#, Value::Str("string".to_string()));
assert_both_eq(r#"type_of(true)"#, Value::Str("bool".to_string()));
assert_both_eq(r#"type_of([])"#, Value::Str("list".to_string()));
assert_both_eq(r#"type_of(#{})"#, Value::Str("dict".to_string()));
}
#[test]
fn cross_flat_map() {
assert_both_eq(
"[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 cross_string_index() {
assert_both_eq(r#""hello"[0]"#, Value::Str("h".to_string()));
assert_both_eq(r#""hello"[4]"#, Value::Str("o".to_string()));
assert_both_eq(r#""hello"[-1]"#, Value::Str("o".to_string()));
}
#[test]
fn cross_tuple_methods() {
assert_both_eq("(1, 2, 3).len()", Value::Int(3));
assert_both_eq("(1, 2, 3).contains(2)", Value::Bool(true));
assert_both_eq("(1, 2, 3).contains(9)", Value::Bool(false));
assert_both_eq(
"(10, 20).to_list()",
Value::List(vec![Value::Int(10), Value::Int(20)]),
);
}
#[test]
fn cross_triple_string() {
assert_both_eq("\"\"\"hello\"\"\"", Value::Str("hello".to_string()));
}
#[test]
fn cross_string_multiply() {
assert_both_eq(r#""ha" * 3"#, Value::Str("hahaha".to_string()));
assert_both_eq(r#"3 * "ab""#, Value::Str("ababab".to_string()));
}
#[test]
fn cross_range_iteration() {
assert_both_eq("let mut s = 0; for i in 0..5 { s += i; } s", Value::Int(10));
assert_both_eq("let mut s = 0; for i in 1..=3 { s += i; } s", Value::Int(6));
}
#[test]
fn cross_multiline_lambda() {
assert_both_eq("let f = |x| { let y = x * 2; y + 1 }; f(5)", Value::Int(11));
}
#[test]
fn cross_string_slice_char() {
assert_both_eq(r#""hello"[1..3]"#, Value::Str("el".to_string()));
assert_both_eq(r#""hello"[..2]"#, Value::Str("he".to_string()));
assert_both_eq(r#""hello"[3..]"#, Value::Str("lo".to_string()));
assert_both_eq(r#""hello"[1..=3]"#, Value::Str("ell".to_string()));
}
#[test]
fn cross_try_top_level() {
assert_both_eq(
r#"let x = Err("oops"); x?"#,
Value::Result(Err(Box::new(Value::Str("oops".to_string())))),
);
assert_both_eq("let x = None; x?", Value::Option(None));
assert_both_eq("let x = Ok(42); x?", Value::Int(42));
assert_both_eq("let x = Some(10); x?", Value::Int(10));
}
#[test]
fn cross_string_negative_index_unicode() {
assert_both_eq(r#""héllo"[-1]"#, Value::Str("o".to_string()));
assert_both_eq(r#""héllo"[-2]"#, Value::Str("l".to_string()));
assert_both_eq(r#""héllo"[0]"#, Value::Str("h".to_string()));
assert_both_eq(r#""héllo"[1]"#, Value::Str("é".to_string()));
}
#[test]
fn cross_string_slice_unicode() {
assert_both_eq(r#""héllo".slice(1, 3)"#, Value::Str("él".to_string()));
assert_both_eq(r#""hello".slice(0, 3)"#, Value::Str("hel".to_string()));
}
#[test]
fn cross_string_find_char_offset() {
assert_both_eq(
r#""héllo".find("l")"#,
Value::Option(Some(Box::new(Value::Int(2)))),
);
assert_both_eq(r#""hello".find("z")"#, Value::Option(None));
}
#[test]
fn cross_sort_homogeneous() {
assert_both_eq(
r#"[3, 1, 2].sort()"#,
Value::List(vec![Value::Int(1), Value::Int(2), Value::Int(3)]),
);
assert_both_eq(r#"[].sort()"#, Value::List(vec![]));
}
#[test]
fn cross_sort_by() {
assert_both_eq(
"[3, 1, 2].sort_by(|a, b| a - b)",
Value::List(vec![Value::Int(1), Value::Int(2), Value::Int(3)]),
);
assert_both_eq(
"[3, 1, 2].sort_by(|a, b| b - a)",
Value::List(vec![Value::Int(3), Value::Int(2), Value::Int(1)]),
);
}
#[test]
fn cross_clamp() {
assert_both_eq("math::clamp(5, 0, 3)", Value::Int(3));
assert_both_eq("math::clamp(-1, 0, 10)", Value::Int(0));
assert_both_eq("math::clamp(5, 0, 10)", Value::Int(5));
}
#[test]
fn cross_bytes_basic() {
assert_both_eq(r#"b"hello".len()"#, Value::Int(5));
assert_both_eq(r#"b"hello"[0]"#, Value::Int(104));
assert_both_eq(r#"b"hello"[-1]"#, Value::Int(111));
assert_both_eq(r#"b"hello".to_hex()"#, Value::Str("68656c6c6f".to_string()));
}
#[test]
fn cross_dict_map() {
assert_both(r#"#{a: 1, b: 2}.map(|k, v| v * 10)"#);
}
#[test]
fn cross_dict_filter() {
assert_both(r#"#{a: 1, b: 2, c: 3}.filter(|k, v| v > 1)"#);
}
#[test]
fn cross_unicode_escape() {
assert_both_eq(r#""\u{48}\u{49}""#, Value::Str("HI".to_string()));
assert_both_eq(r#""\u{E9}""#, Value::Str("é".to_string()));
}
#[test]
fn cross_string_contains_int() {
assert_both_eq(r#""hello".contains(104)"#, Value::Bool(true));
assert_both_eq(r#""hello".contains(122)"#, Value::Bool(false));
}
#[test]
fn cross_to_string() {
assert_both_eq(r#"let x = 42; x.to_string()"#, Value::Str("42".to_string()));
assert_both_eq(r#"true.to_string()"#, Value::Str("true".to_string()));
assert_both_eq(r#"None.to_string()"#, Value::Str("None".to_string()));
}
#[test]
fn cross_dict_zip() {
assert_both(r#"#{a: 1, b: 2}.zip(#{a: 10, b: 20})"#);
}
#[test]
fn cross_join_builtin() {
assert_both_eq(
r#"string::join(["a", "b"], ",")"#,
Value::Str("a,b".to_string()),
);
}
#[test]
fn cross_string_bytes() {
assert_both_eq(
r#""AB".bytes()"#,
Value::List(vec![Value::Int(65), Value::Int(66)]),
);
}
#[test]
fn cross_enumerate_string() {
assert_both_eq(
r#"enumerate("ab")"#,
Value::List(vec![
Value::Tuple(vec![Value::Int(0), Value::Str("a".to_string())]),
Value::Tuple(vec![Value::Int(1), Value::Str("b".to_string())]),
]),
);
}
#[test]
fn cross_list_index() {
assert_both_eq(
r#"[10, 20, 30].index(20)"#,
Value::Option(Some(Box::new(Value::Int(1)))),
);
assert_both_eq(r#"[10, 20, 30].index(99)"#, Value::Option(None));
}
#[test]
fn cross_list_count() {
assert_both_eq(r#"[1, 2, 1, 3, 1].count(1)"#, Value::Int(3));
}
#[test]
fn cross_list_slice() {
assert_both_eq(
r#"[1, 2, 3, 4, 5].slice(1, 3)"#,
Value::List(vec![Value::Int(2), Value::Int(3)]),
);
}
#[test]
fn cross_list_dedup() {
assert_both_eq(
r#"[1, 1, 2, 2, 3].dedup()"#,
Value::List(vec![Value::Int(1), Value::Int(2), Value::Int(3)]),
);
}
#[test]
fn cross_string_pad_start() {
assert_both_eq(r#""42".pad_start(5, "0")"#, Value::Str("00042".to_string()));
}
#[test]
fn cross_string_pad_end() {
assert_both_eq(r#""42".pad_end(5, "0")"#, Value::Str("42000".to_string()));
}
#[test]
fn cross_let_destructure_tuple() {
assert_both_eq(r#"let (a, b) = (10, 20); a + b"#, Value::Int(30));
}
#[test]
fn cross_let_destructure_list() {
assert_both_eq(r#"let [a, b] = [10, 20]; a + b"#, Value::Int(30));
}
#[test]
fn cross_list_unique() {
assert_both_eq(
r#"[1, 2, 1, 3, 2].unique()"#,
Value::List(vec![Value::Int(1), Value::Int(2), Value::Int(3)]),
);
}
#[test]
fn cross_list_min() {
assert_both_eq(
r#"[3, 1, 2].min()"#,
Value::Option(Some(Box::new(Value::Int(1)))),
);
assert_both_eq(r#"[].min()"#, Value::Option(None));
}
#[test]
fn cross_list_max() {
assert_both_eq(
r#"[3, 1, 2].max()"#,
Value::Option(Some(Box::new(Value::Int(3)))),
);
}
#[test]
fn cross_dict_update() {
assert_both(r#"#{a: 1, b: 2}.update(#{b: 20, c: 30})"#);
}
#[test]
fn cross_string_char_len() {
assert_both_eq(r#""héllo".char_len()"#, Value::Int(5));
}
#[test]
fn cross_list_sum() {
assert_both_eq(r#"[1, 2, 3].sum()"#, Value::Int(6));
assert_both_eq(r#"[1, 2.5, 3].sum()"#, Value::Float(6.5));
}
#[test]
fn cross_list_window() {
assert_both_eq(
r#"[1, 2, 3].window(2)"#,
Value::List(vec![
Value::List(vec![Value::Int(1), Value::Int(2)]),
Value::List(vec![Value::Int(2), Value::Int(3)]),
]),
);
}
#[test]
fn cross_string_strip_prefix() {
assert_both_eq(
r#""hello world".strip_prefix("hello ")"#,
Value::Str("world".to_string()),
);
}
#[test]
fn cross_string_strip_suffix() {
assert_both_eq(
r#""hello.ion".strip_suffix(".ion")"#,
Value::Str("hello".to_string()),
);
}
#[test]
fn cross_dict_keys_of() {
assert_both_eq(
r#"#{a: 1, b: 2, c: 1}.keys_of(1)"#,
Value::List(vec![
Value::Str("a".to_string()),
Value::Str("c".to_string()),
]),
);
}
#[test]
fn cross_try_catch_no_error() {
assert_both_eq("try { 42 } catch e { e }", Value::Int(42));
}
#[test]
fn cross_try_catch_with_error() {
assert_both_eq(
r#"try { assert(false, "boom"); 1 } catch e { e }"#,
Value::Str("boom".to_string()),
);
}
#[test]
fn cross_try_catch_division_by_zero() {
assert_both_eq(
r#"try { 1 / 0 } catch e { "caught" }"#,
Value::Str("caught".to_string()),
);
}
#[test]
fn cross_try_catch_as_expression() {
assert_both_eq("let x = try { 10 } catch e { 0 }; x + 1", Value::Int(11));
}
#[test]
fn cross_named_args() {
assert_both_eq(
r#"fn greet(name, greeting) { f"{greeting} {name}" } greet(greeting: "hi", name: "world")"#,
Value::Str("hi world".to_string()),
);
}
#[test]
fn cross_named_args_with_default() {
assert_both_eq(
r#"fn greet(name, greeting = "hello") { f"{greeting} {name}" } greet(name: "world")"#,
Value::Str("hello world".to_string()),
);
}
#[test]
fn cross_json_encode_decode() {
assert_both_eq(
r#"json::decode(json::encode(#{a: 1, b: "two"}))"#,
Value::Dict(indexmap::indexmap! {
"a".to_string() => Value::Int(1),
"b".to_string() => Value::Str("two".to_string()),
}),
);
}
#[test]
fn cross_assert_no_error() {
assert_both_eq("assert(true); 42", Value::Int(42));
}
#[test]
fn cross_assert_eq_pass() {
assert_both_eq("assert_eq(1 + 1, 2); true", Value::Bool(true));
}
#[test]
fn cross_loop_as_expr() {
assert_both_eq(
"let mut i = 0; let x = loop { i = i + 1; if i >= 5 { break i; } }; x",
Value::Int(5),
);
}
#[test]
fn cross_enumerate_list() {
assert_both_eq(
"enumerate([10, 20])",
Value::List(vec![
Value::Tuple(vec![Value::Int(0), Value::Int(10)]),
Value::Tuple(vec![Value::Int(1), Value::Int(20)]),
]),
);
}
#[test]
fn cross_range() {
assert_both_eq(
"let mut s = 0; for i in range(1, 4) { s = s + i; } s",
Value::Int(6),
);
}
#[test]
fn cross_option_methods() {
assert_both_eq("Some(42).unwrap()", Value::Int(42));
assert_both_eq("None.unwrap_or(99)", Value::Int(99));
}
#[test]
fn cross_result_methods() {
assert_both_eq("Ok(42).unwrap()", Value::Int(42));
assert_both_eq(r#"Err("bad").unwrap_or(0)"#, Value::Int(0));
}
#[cfg(feature = "msgpack")]
#[test]
fn cross_msgpack_int() {
assert_both_eq(
"json::msgpack_decode(json::msgpack_encode(42))",
Value::Int(42),
);
}
#[cfg(feature = "msgpack")]
#[test]
fn cross_msgpack_string() {
assert_both_eq(
r#"json::msgpack_decode(json::msgpack_encode("hello"))"#,
Value::Str("hello".to_string()),
);
}
#[cfg(feature = "msgpack")]
#[test]
fn cross_msgpack_bytes() {
assert_both_eq(
r#"json::msgpack_decode(json::msgpack_encode(b"\xde\xad"))"#,
Value::Bytes(vec![0xde, 0xad]),
);
}
#[cfg(feature = "msgpack")]
#[test]
fn msgpack_bytes_use_binary_family() {
let encoded = Value::Bytes(vec![0xde, 0xad])
.to_msgpack()
.expect("encode bytes");
assert_eq!(encoded, vec![0xc4, 0x02, 0xde, 0xad]);
assert_eq!(
Value::from_msgpack(&encoded).expect("decode bytes"),
Value::Bytes(vec![0xde, 0xad])
);
}
#[cfg(feature = "msgpack")]
#[test]
fn cross_msgpack_list() {
assert_both_eq(
"json::msgpack_decode(json::msgpack_encode([1, 2, 3]))",
Value::List(vec![Value::Int(1), Value::Int(2), Value::Int(3)]),
);
}
#[cfg(feature = "msgpack")]
#[test]
fn cross_msgpack_dict() {
assert_both_eq(
r#"let d = #{a: 1}; json::msgpack_decode(json::msgpack_encode(d))"#,
Value::Dict(indexmap::indexmap! { "a".to_string() => Value::Int(1) }),
);
}
#[test]
fn cross_list_chunk() {
assert_both_eq(
"[1, 2, 3, 4].chunk(2)",
Value::List(vec![
Value::List(vec![Value::Int(1), Value::Int(2)]),
Value::List(vec![Value::Int(3), Value::Int(4)]),
]),
);
}
#[test]
fn cross_list_reduce() {
assert_both_eq("[1, 2, 3].reduce(|a, b| a + b)", Value::Int(6));
}
#[test]
fn cross_list_spread() {
assert_both_eq(
"let a = [1, 2]; [...a, 3]",
Value::List(vec![Value::Int(1), Value::Int(2), Value::Int(3)]),
);
}
#[test]
fn cross_set_basic() {
assert_both_eq("set([1, 2, 2]).len()", Value::Int(2));
}
#[test]
fn cross_set_union() {
assert_both_eq("set([1, 2]).union(set([2, 3])).len()", Value::Int(3));
}
#[test]
fn cross_type_ann() {
assert_both_eq("let x: int = 42; x", Value::Int(42));
}
#[test]
fn cross_type_ann_list() {
assert_both_eq("let xs: list = [1, 2]; xs.len()", Value::Int(2));
}
#[test]
fn cross_type_ann_float() {
assert_both_eq("let x: float = 3.14; x", Value::Float(3.14));
}
#[test]
fn cross_type_ann_option() {
assert_both_eq(
"let x: Option<int> = Some(42); x",
Value::Option(Some(Box::new(Value::Int(42)))),
);
}
#[test]
fn cross_type_ann_result() {
assert_both_eq(
"let x: Result<int, string> = Ok(1); x",
Value::Result(Ok(Box::new(Value::Int(1)))),
);
}
#[test]
fn cross_type_ann_generic_list() {
assert_both_eq(r#"let xs: list<int> = ["a"]; xs.len()"#, Value::Int(1));
}
#[test]
fn cross_type_ann_dict() {
assert_both_eq(r#"let d: dict = #{"a": 1}; d["a"]"#, Value::Int(1));
}
#[test]
fn cross_type_ann_fn() {
assert_both_eq("let f: fn = |x| x + 1; f(2)", Value::Int(3));
}
#[test]
fn cross_type_ann_any() {
assert_both_eq("let x: any = 42; x", Value::Int(42));
}
#[test]
fn cross_type_ann_mismatch() {
assert_both("let x: int = true;");
}
#[test]
fn cross_cell_basic() {
assert_both_eq("let c = cell(0); c.get()", Value::Int(0));
assert_both_eq("let c = cell(0); c.set(42); c.get()", Value::Int(42));
}
#[test]
fn cross_cell_update() {
assert_both_eq(
"let c = cell(0); c.update(|x| x + 1); c.get()",
Value::Int(1),
);
}
#[test]
fn cross_cell_counter_closure() {
assert_both_eq(
r#"
let count = cell(0);
let inc = || { count.update(|x| x + 1) };
inc();
inc();
inc();
count.get()
"#,
Value::Int(3),
);
}
#[test]
fn cross_cell_shared_closures() {
assert_both_eq(
r#"
let state = cell(0);
let inc = || { state.update(|x| x + 1) };
let get = || { state.get() };
inc();
inc();
get()
"#,
Value::Int(2),
);
}
#[test]
fn cross_cell_factory() {
assert_both_eq(
r#"
fn make_counter() {
let count = cell(0);
let inc = || { count.update(|x| x + 1) };
let get = || { count.get() };
(inc, get)
}
let (inc, get) = make_counter();
inc();
inc();
get()
"#,
Value::Int(2),
);
}
#[test]
fn cross_cell_accumulator() {
assert_both_eq(
r#"
let acc = cell([]);
let add = |item| { acc.update(|xs| xs.push(item)) };
add(1);
add(2);
acc.get()
"#,
Value::List(vec![Value::Int(1), Value::Int(2)]),
);
}
#[test]
fn cross_cell_type_of() {
assert_both_eq("type_of(cell(0))", Value::Str("cell".to_string()));
}
#[test]
fn cross_cell_update_returns_value() {
assert_both_eq("let c = cell(10); c.update(|x| x * 2)", Value::Int(20));
}
#[test]
fn cross_set_intersection() {
assert_both_eq(
"set([1, 2, 3]).intersection(set([2, 3, 4])).len()",
Value::Int(2),
);
}
#[test]
fn cross_set_difference() {
assert_both_eq(
"set([1, 2, 3]).difference(set([2, 3, 4])).len()",
Value::Int(1),
);
}
#[test]
fn cross_set_contains() {
assert_both_eq("set([1, 2, 3]).contains(2)", Value::Bool(true));
assert_both_eq("set([1, 2, 3]).contains(5)", Value::Bool(false));
}
#[test]
fn cross_set_add_remove() {
assert_both("let mut s = set([1, 2]); s.add(3); s.len()");
assert_both("let mut s = set([1, 2, 3]); s.remove(2); s.len()");
}
#[test]
fn cross_set_add_duplicate() {
assert_both("let mut s = set([1, 2]); s.add(2); s.len()");
}
#[test]
fn cross_set_to_list() {
assert_both_eq("set([3, 1, 2]).to_list().len()", Value::Int(3));
}
#[test]
fn cross_set_is_empty() {
assert_both_eq("set([]).is_empty()", Value::Bool(true));
assert_both_eq("set([1]).is_empty()", Value::Bool(false));
}
#[test]
fn cross_spread_multiple() {
assert_both_eq(
"let a = [1, 2]; let b = [3, 4]; [...a, ...b]",
Value::List(vec![
Value::Int(1),
Value::Int(2),
Value::Int(3),
Value::Int(4),
]),
);
}
#[test]
fn cross_spread_with_elements() {
assert_both_eq(
"let a = [2, 3]; [1, ...a, 4]",
Value::List(vec![
Value::Int(1),
Value::Int(2),
Value::Int(3),
Value::Int(4),
]),
);
}
#[test]
fn cross_spread_empty() {
assert_both_eq(
"let a = []; [1, ...a, 2]",
Value::List(vec![Value::Int(1), Value::Int(2)]),
);
}
#[test]
fn cross_pipe_chain() {
assert_both_eq(
"fn double(x) { x * 2 } fn inc(x) { x + 1 } 5 |> double |> inc",
Value::Int(11),
);
}
#[test]
fn cross_range_in_list() {
assert_both_eq(
"let mut r = []; for i in 0..3 { r = r.push(i); } r",
Value::List(vec![Value::Int(0), Value::Int(1), Value::Int(2)]),
);
}
#[test]
fn cross_range_inclusive_boundary() {
assert_both_eq(
"let mut r = []; for i in 1..=1 { r = r.push(i); } r",
Value::List(vec![Value::Int(1)]),
);
}
#[test]
fn cross_match_nested_option() {
assert_both_eq(
"match Some(Some(42)) { Some(Some(v)) => v, _ => 0 }",
Value::Int(42),
);
}
#[test]
fn cross_match_string() {
assert_both_eq(
r#"match "hello" { "hello" => 1, "world" => 2, _ => 0 }"#,
Value::Int(1),
);
}
#[test]
fn cross_match_bool() {
assert_both_eq("match true { true => 1, false => 0 }", Value::Int(1));
}
#[test]
fn cross_match_in_function() {
assert_both_eq(
r#"
fn describe(opt) {
match opt {
Some(v) => f"has {v}",
None => "empty",
}
}
describe(Some(42))
"#,
Value::Str("has 42".to_string()),
);
}
#[test]
fn cross_list_push() {
assert_both_eq(
"let a = [1, 2].push(3); a",
Value::List(vec![Value::Int(1), Value::Int(2), Value::Int(3)]),
);
}
#[test]
fn cross_list_pop() {
assert_both("let a = [1, 2, 3].pop(); a");
}
#[test]
fn cross_list_negative_index() {
assert_both_eq("[10, 20, 30][-1]", Value::Int(30));
assert_both_eq("[10, 20, 30][-2]", Value::Int(20));
}
#[test]
fn cross_list_find() {
assert_both("[1, 2, 3, 4].find(|x| x > 2)");
assert_both("[1, 2, 3].find(|x| x > 5)");
}
#[test]
fn cross_string_to_int() {
assert_both(r#""42".to_int()"#);
}
#[test]
fn cross_string_to_float() {
assert_both(r#""3.14".to_float()"#);
}
#[test]
fn cross_option_map() {
assert_both_eq(
"Some(5).map(|x| x * 2)",
Value::Option(Some(Box::new(Value::Int(10)))),
);
assert_both_eq("None.map(|x| x * 2)", Value::Option(None));
}
#[test]
fn cross_option_is_some_none() {
assert_both_eq("Some(1).is_some()", Value::Bool(true));
assert_both_eq("Some(1).is_none()", Value::Bool(false));
assert_both_eq("None.is_some()", Value::Bool(false));
assert_both_eq("None.is_none()", Value::Bool(true));
}
#[test]
fn cross_result_is_ok_err() {
assert_both_eq("Ok(1).is_ok()", Value::Bool(true));
assert_both_eq("Ok(1).is_err()", Value::Bool(false));
assert_both_eq(r#"Err("x").is_ok()"#, Value::Bool(false));
assert_both_eq(r#"Err("x").is_err()"#, Value::Bool(true));
}
#[test]
fn cross_result_map() {
assert_both_eq(
"Ok(5).map(|x| x + 1)",
Value::Result(Ok(Box::new(Value::Int(6)))),
);
assert_both_eq(
r#"Err("bad").map(|x| x + 1)"#,
Value::Result(Err(Box::new(Value::Str("bad".to_string())))),
);
}
#[test]
fn cross_for_in_tuple_destructure() {
assert_both_eq(
"let mut sum = 0; for (i, v) in [(0, 10), (1, 20)] { sum += v; } sum",
Value::Int(30),
);
}
#[test]
fn cross_for_in_enumerate_destructure() {
assert_both_eq(
r#"let mut s = ""; for (i, c) in "ab".chars().enumerate() { s = s + c; } s"#,
Value::Str("ab".to_string()),
);
}
#[test]
fn cross_while_let() {
assert_both_eq(
r#"
let mut items = [Some(1), Some(2), None];
let mut sum = 0;
let mut i = 0;
while i < items.len() {
if let Some(v) = items[i] { sum += v; }
i += 1;
}
sum
"#,
Value::Int(3),
);
}
#[test]
fn cross_nested_dict_access() {
assert_both_eq(r#"let d = #{a: #{b: 42}}; d.a.b"#, Value::Int(42));
}
#[test]
fn cross_nested_list_index() {
assert_both_eq("[[1, 2], [3, 4]][1][0]", Value::Int(3));
}
#[test]
fn cross_closure_captures_loop_var() {
assert_both_eq(
r#"
let mut fns = [];
for i in [1, 2, 3] {
fns = fns.push(|x| x + i);
}
fns[0](10)
"#,
Value::Int(11),
);
}
#[test]
fn cross_closure_returning_closure() {
assert_both_eq(
"fn make(x) { |y| |z| x + y + z } make(1)(2)(3)",
Value::Int(6),
);
}
#[test]
fn cross_compound_assign() {
assert_both("let mut x = 10; x -= 3; x");
assert_both("let mut x = 4; x *= 5; x");
assert_both("let mut x = 20; x /= 4; x");
assert_both("let mut x = 17; x %= 5; x");
}
#[test]
fn cross_int_to_float() {
assert_both_eq("int(3.7)", Value::Int(3));
assert_both_eq("float(42)", Value::Float(42.0));
}
#[test]
fn cross_string_to_int_builtin() {
assert_both_eq(r#"int("123")"#, Value::Int(123));
assert_both_eq(r#"float("3.14")"#, Value::Float(3.14));
}
#[test]
fn cross_accumulator_pattern() {
assert_both_eq(
r#"
let mut result = [];
for i in 0..5 {
if i % 2 == 0 {
result = result.push(i * i);
}
}
result
"#,
Value::List(vec![Value::Int(0), Value::Int(4), Value::Int(16)]),
);
}
#[test]
fn cross_nested_match_in_loop() {
assert_both(
r#"
let items = [Some(1), None, Some(3)];
let mut sum = 0;
for item in items {
match item {
Some(v) => { sum += v; }
None => {}
}
}
sum
"#,
);
}
#[test]
fn cross_functional_pipeline() {
assert_both_eq(
"[1, 2, 3, 4, 5].filter(|x| x % 2 == 1).map(|x| x * x).reduce(|a, b| a + b)",
Value::Int(35),
);
}
#[test]
fn cross_dict_comprehension() {
assert_both(r#"#{x: x * x for x in [1, 2, 3]}"#);
}
#[test]
fn cross_early_return() {
assert_both_eq(
r#"
fn find_first_even(lst) {
for x in lst {
if x % 2 == 0 { return x; }
}
-1
}
find_first_even([1, 3, 4, 6])
"#,
Value::Int(4),
);
}
#[test]
fn cross_recursive_sum() {
assert_both_eq(
r#"
fn sum(lst) {
if lst.len() == 0 { 0 }
else { lst[0] + sum(lst.slice(1, lst.len())) }
}
sum([1, 2, 3, 4, 5])
"#,
Value::Int(15),
);
}
#[test]
fn cross_shift_left() {
assert_both_eq("1 << 10", Value::Int(1024));
assert_both_eq("3 << 4", Value::Int(48));
}
#[test]
fn cross_shift_right() {
assert_both_eq("1024 >> 5", Value::Int(32));
assert_both_eq("255 >> 4", Value::Int(15));
}
#[test]
fn cross_shift_out_of_range() {
assert_both("1 << 64");
assert_both("1 >> -1");
assert_both("1 << -1");
}
#[test]
fn cross_match_non_exhaustive() {
assert_both(r#"match 42 { 1 => "one", 2 => "two" }"#);
}
#[test]
fn cross_math_module() {
assert_both_eq("math::abs(-5)", Value::Int(5));
assert_both_eq("math::max(3, 7)", Value::Int(7));
assert_both_eq("math::min(3, 7)", Value::Int(3));
}
#[test]
fn cross_math_constants() {
assert_both("math::PI");
assert_both("math::E");
}
#[test]
fn cross_string_join() {
assert_both_eq(
r#"string::join(["a", "b", "c"], ", ")"#,
Value::Str("a, b, c".to_string()),
);
}
#[test]
fn cross_window() {
assert_both_eq(
"[1, 2, 3, 4].window(2)",
Value::List(vec![
Value::List(vec![Value::Int(1), Value::Int(2)]),
Value::List(vec![Value::Int(2), Value::Int(3)]),
Value::List(vec![Value::Int(3), Value::Int(4)]),
]),
);
}
#[test]
fn cross_window_zero_error() {
assert_both("[1, 2, 3].window(0)");
}