Files
dexpr/tests/integration_tests.rs
2026-04-06 02:51:42 +03:00

1337 lines
48 KiB
Rust

use dexpr::{ast::value::Value, compiler::Compiler, parser, vm::VM};
use indexmap::IndexMap;
use rust_decimal::prelude::ToPrimitive;
use rust_decimal_macros::dec;
use smol_str::SmolStr;
use std::rc::Rc;
/// Helper to run code and get the value of "result" variable
fn run_and_get_result(code: &str) -> Value {
let ast = parser::program(code).expect("Failed to parse");
let mut compiler = Compiler::new();
let bytecode = compiler.compile(ast).expect("Failed to compile");
let mut vm = VM::new(&bytecode);
vm.execute().expect("Failed to execute");
vm.get_global("result").expect("result not found").clone()
}
/// Helper to run code and return the expression result (last expression value)
fn run_expr(code: &str) -> Value {
let ast = parser::program(code).expect("Failed to parse");
let mut compiler = Compiler::new();
let bytecode = compiler.compile(ast).expect("Failed to compile");
let mut vm = VM::new(&bytecode);
vm.execute().expect("Failed to execute")
}
/// Helper to run code with globals and get "result" variable
fn run_and_get_result_with_globals(code: &str, globals: Vec<(&str, Value)>) -> Value {
let ast = parser::program(code).expect("Failed to parse");
let mut compiler = Compiler::new();
let bytecode = compiler.compile(ast).expect("Failed to compile");
let mut vm = VM::new(&bytecode);
for (name, value) in globals {
vm.set_global(name, value);
}
vm.execute().expect("Failed to execute");
vm.get_global("result").expect("result not found").clone()
}
/// Helper to run code with globals and return expression result
fn run_expr_with_globals(code: &str, globals: Vec<(&str, Value)>) -> Value {
let ast = parser::program(code).expect("Failed to parse");
let mut compiler = Compiler::new();
let bytecode = compiler.compile(ast).expect("Failed to compile");
let mut vm = VM::new(&bytecode);
for (name, value) in globals {
vm.set_global(name, value);
}
vm.execute().expect("Failed to execute")
}
// ==================== MODULO OPERATOR TESTS ====================
#[test]
fn test_modulo_basic() {
let result = run_and_get_result("result = 10 % 3");
assert_eq!(result, Value::Number(dec!(1)));
}
#[test]
fn test_modulo_zero_remainder() {
let result = run_and_get_result("result = 12 % 4");
assert_eq!(result, Value::Number(dec!(0)));
}
#[test]
fn test_modulo_with_decimals() {
let result = run_and_get_result("result = 10.5 % 3");
assert_eq!(result, Value::Number(dec!(1.5)));
}
#[test]
fn test_modulo_negative() {
// Rust remainder semantics: -7 % 3 = -1
let result = run_and_get_result("result = -7 % 3");
assert_eq!(result, Value::Number(dec!(-1)));
}
#[test]
fn test_modulo_precedence() {
// % should have same precedence as * and /
// 10 + 7 % 3 = 10 + 1 = 11
let result = run_and_get_result("result = 10 + 7 % 3");
assert_eq!(result, Value::Number(dec!(11)));
}
// ==================== POWER OPERATOR TESTS ====================
#[test]
fn test_power_basic() {
let result = run_and_get_result("result = 2 ** 10");
assert_eq!(result, Value::Number(dec!(1024)));
}
#[test]
fn test_power_square() {
let result = run_and_get_result("result = 5 ** 2");
assert_eq!(result, Value::Number(dec!(25)));
}
#[test]
fn test_power_cube() {
let result = run_and_get_result("result = 3 ** 3");
assert_eq!(result, Value::Number(dec!(27)));
}
#[test]
fn test_power_zero_exponent() {
let result = run_and_get_result("result = 5 ** 0");
assert_eq!(result, Value::Number(dec!(1)));
}
#[test]
fn test_power_one_exponent() {
let result = run_and_get_result("result = 7 ** 1");
assert_eq!(result, Value::Number(dec!(7)));
}
#[test]
fn test_power_right_associative() {
// 2 ** 3 ** 2 should be 2 ** (3 ** 2) = 2 ** 9 = 512 (right-associative)
let result = run_and_get_result("result = 2 ** 3 ** 2");
assert_eq!(result, Value::Number(dec!(512)));
}
#[test]
fn test_power_precedence() {
// ** should have higher precedence than * and /
// 2 * 3 ** 2 = 2 * 9 = 18
let result = run_and_get_result("result = 2 * 3 ** 2");
assert_eq!(result, Value::Number(dec!(18)));
}
#[test]
fn test_power_in_expression() {
// (2 + 3) ** 2 = 5 ** 2 = 25
let result = run_and_get_result("result = (2 + 3) ** 2");
assert_eq!(result, Value::Number(dec!(25)));
}
// ==================== COMBINED OPERATOR TESTS ====================
#[test]
fn test_modulo_and_power_together() {
// 2 ** 4 % 5 = 16 % 5 = 1
let result = run_and_get_result("result = 2 ** 4 % 5");
assert_eq!(result, Value::Number(dec!(1)));
}
#[test]
fn test_complex_expression_with_new_ops() {
// (3 ** 2 + 4 ** 2) % 10 = (9 + 16) % 10 = 25 % 10 = 5
let result = run_and_get_result("result = (3 ** 2 + 4 ** 2) % 10");
assert_eq!(result, Value::Number(dec!(5)));
}
// ==================== COMPOUND ASSIGNMENT TESTS ====================
#[test]
fn test_compound_add() {
let result = run_and_get_result("x = 10\nx += 5\nresult = x");
assert_eq!(result, Value::Number(dec!(15)));
}
#[test]
fn test_compound_sub() {
let result = run_and_get_result("x = 20\nx -= 7\nresult = x");
assert_eq!(result, Value::Number(dec!(13)));
}
#[test]
fn test_compound_mul() {
let result = run_and_get_result("x = 3\nx *= 4\nresult = x");
assert_eq!(result, Value::Number(dec!(12)));
}
#[test]
fn test_compound_div() {
let result = run_and_get_result("x = 100\nx /= 4\nresult = x");
assert_eq!(result, Value::Number(dec!(25)));
}
#[test]
fn test_compound_mod() {
let result = run_and_get_result("x = 17\nx %= 5\nresult = x");
assert_eq!(result, Value::Number(dec!(2)));
}
#[test]
fn test_compound_multiple_operations() {
let result = run_and_get_result(
"x = 10\nx += 5\nx *= 2\nx -= 10\nresult = x",
);
assert_eq!(result, Value::Number(dec!(20)));
}
#[test]
fn test_compound_with_expression() {
let result = run_and_get_result("x = 10\nx += 3 * 2\nresult = x");
assert_eq!(result, Value::Number(dec!(16)));
}
#[test]
fn test_compound_chained() {
let result = run_and_get_result(
"a = 5\nb = 10\na += 1\nb -= 2\nresult = a + b",
);
assert_eq!(result, Value::Number(dec!(14)));
}
// ==================== STRING METHOD TESTS ====================
#[test]
fn test_string_trim() {
let result = run_and_get_result(r#"result = " hello ".trim()"#);
assert_eq!(result, Value::String("hello".into()));
}
#[test]
fn test_string_trim_start() {
let result = run_and_get_result(r#"result = " hello ".trimStart()"#);
assert_eq!(result, Value::String("hello ".into()));
}
#[test]
fn test_string_trim_end() {
let result = run_and_get_result(r#"result = " hello ".trimEnd()"#);
assert_eq!(result, Value::String(" hello".into()));
}
#[test]
fn test_string_upper() {
let result = run_and_get_result(r#"result = "hello".upper()"#);
assert_eq!(result, Value::String("HELLO".into()));
}
#[test]
fn test_string_lower() {
let result = run_and_get_result(r#"result = "HELLO".lower()"#);
assert_eq!(result, Value::String("hello".into()));
}
#[test]
fn test_string_replace() {
let result = run_and_get_result(r#"result = "hello".replace("l", "x")"#);
assert_eq!(result, Value::String("hexxo".into()));
}
#[test]
fn test_string_contains() {
let result = run_and_get_result(r#"result = "hello".contains("ell")"#);
assert_eq!(result, Value::Boolean(true));
}
#[test]
fn test_string_contains_false() {
let result = run_and_get_result(r#"result = "hello".contains("xyz")"#);
assert_eq!(result, Value::Boolean(false));
}
#[test]
fn test_string_starts_with() {
let result = run_and_get_result(r#"result = "hello".startsWith("hel")"#);
assert_eq!(result, Value::Boolean(true));
}
#[test]
fn test_string_ends_with() {
let result = run_and_get_result(r#"result = "hello".endsWith("llo")"#);
assert_eq!(result, Value::Boolean(true));
}
#[test]
fn test_string_length() {
let result = run_and_get_result(r#"result = "hello".length()"#);
assert_eq!(result, Value::Number(dec!(5)));
}
#[test]
fn test_string_char_at() {
let result = run_and_get_result(r#"result = "hello".charAt(1)"#);
assert_eq!(result, Value::String("e".into()));
}
#[test]
fn test_string_char_at_out_of_bounds() {
let result = run_and_get_result(r#"result = "hello".charAt(10)"#);
assert_eq!(result, Value::Null);
}
#[test]
fn test_string_substring() {
let result = run_and_get_result(r#"result = "hello".substring(1, 4)"#);
assert_eq!(result, Value::String("ell".into()));
}
#[test]
fn test_string_substring_to_end() {
let result = run_and_get_result(r#"result = "hello".substring(2)"#);
assert_eq!(result, Value::String("llo".into()));
}
#[test]
fn test_string_split() {
let result = run_and_get_result(r#"
parts = "a,b,c".split(",")
result = parts.length()
"#);
assert_eq!(result, Value::Number(dec!(3)));
}
// ==================== STRING LIST METHOD TESTS ====================
#[test]
fn test_stringlist_first() {
let result = run_and_get_result(r#"
parts = "a,b,c".split(",")
result = parts.first()
"#);
assert_eq!(result, Value::String("a".into()));
}
#[test]
fn test_stringlist_last() {
let result = run_and_get_result(r#"
parts = "a,b,c".split(",")
result = parts.last()
"#);
assert_eq!(result, Value::String("c".into()));
}
#[test]
fn test_stringlist_get() {
let result = run_and_get_result(r#"
parts = "a,b,c".split(",")
result = parts.get(1)
"#);
assert_eq!(result, Value::String("b".into()));
}
#[test]
fn test_stringlist_get_out_of_bounds() {
let result = run_and_get_result(r#"
parts = "a,b,c".split(",")
result = parts.get(10)
"#);
assert_eq!(result, Value::Null);
}
#[test]
fn test_stringlist_join() {
let result = run_and_get_result(r#"
parts = "a,b,c".split(",")
result = parts.join("-")
"#);
assert_eq!(result, Value::String("a-b-c".into()));
}
#[test]
fn test_stringlist_join_no_delimiter() {
let result = run_and_get_result(r#"
parts = "a,b,c".split(",")
result = parts.join()
"#);
assert_eq!(result, Value::String("abc".into()));
}
// ==================== EXPRESSION RETURN VALUE TESTS ====================
#[test]
fn test_expr_return_single_expression() {
// Single-line expression should return its value
let result = run_expr("42 + 8");
assert_eq!(result, Value::Number(dec!(50)));
}
#[test]
fn test_expr_return_formula() {
// Formula-style expression
let result = run_expr_with_globals(
"toplamTutar * kdv / 100",
vec![
("toplamTutar", Value::Number(dec!(1000))),
("kdv", Value::Number(dec!(18))),
],
);
assert_eq!(result, Value::Number(dec!(180)));
}
#[test]
fn test_expr_return_last_expression() {
// Multi-line: last expression statement wins
let result = run_expr("x = 10\ny = 20\nx + y");
assert_eq!(result, Value::Number(dec!(30)));
}
#[test]
fn test_expr_return_assignment_then_expr() {
let result = run_expr("result = 42\nresult");
assert_eq!(result, Value::Number(dec!(42)));
}
#[test]
fn test_expr_return_null_when_only_assignments() {
// Only assignments, no expression statement → Null
let result = run_expr("x = 10\ny = 20");
assert_eq!(result, Value::Null);
}
#[test]
fn test_expr_return_string() {
let result = run_expr("\"hello\" + \" world\"");
assert_eq!(result, Value::String("hello world".into()));
}
#[test]
fn test_expr_return_boolean() {
let result = run_expr("10 > 5");
assert_eq!(result, Value::Boolean(true));
}
// ==================== EXTERNAL FUNCTION TESTS ====================
#[test]
fn test_external_function_basic() {
let ast = parser::program("result = add(3, 4)").expect("parse");
let mut compiler = Compiler::new();
let bytecode = compiler.compile(ast).expect("compile");
let mut vm = VM::new(&bytecode);
vm.register_function("add", |args| {
match (&args[0], &args[1]) {
(Value::Number(a), Value::Number(b)) => Ok(Value::Number(a + b)),
_ => Err("expected numbers".to_string()),
}
});
vm.execute().expect("execute");
assert_eq!(vm.get_global("result").unwrap(), &Value::Number(dec!(7)));
}
#[test]
fn test_external_function_no_args() {
let ast = parser::program("pi()").expect("parse");
let mut compiler = Compiler::new();
let bytecode = compiler.compile(ast).expect("compile");
let mut vm = VM::new(&bytecode);
vm.register_function("pi", |_args| {
Ok(Value::Number(dec!(3.14159)))
});
let result = vm.execute().expect("execute");
assert_eq!(result, Value::Number(dec!(3.14159)));
}
#[test]
fn test_external_function_with_globals() {
let code = "getExchangeRate('USD')";
let ast = parser::program(code).expect("parse");
let mut compiler = Compiler::new();
let bytecode = compiler.compile(ast).expect("compile");
let mut vm = VM::new(&bytecode);
vm.register_function("getExchangeRate", |args| {
match &args[0] {
Value::String(currency) if currency.as_str() == "USD" => Ok(Value::Number(dec!(34.5))),
Value::String(currency) if currency.as_str() == "EUR" => Ok(Value::Number(dec!(37.2))),
_ => Err("unknown currency".to_string()),
}
});
let result = vm.execute().expect("execute");
assert_eq!(result, Value::Number(dec!(34.5)));
}
#[test]
fn test_external_function_undefined_error() {
let ast = parser::program("unknownFn()").expect("parse");
let mut compiler = Compiler::new();
let bytecode = compiler.compile(ast).expect("compile");
let mut vm = VM::new(&bytecode);
let err = vm.execute().unwrap_err();
assert!(err.to_string().contains("Undefined function: unknownFn"));
}
#[test]
fn test_external_function_in_formula() {
// Realistic pricing formula: base * exchangeRate * (1 + commission/100)
let code = "rate = getRate('USD')\nbase * rate * (1 + commission / 100)";
let ast = parser::program(code).expect("parse");
let mut compiler = Compiler::new();
let bytecode = compiler.compile(ast).expect("compile");
let mut vm = VM::new(&bytecode);
vm.set_global("base", Value::Number(dec!(100)));
vm.set_global("commission", Value::Number(dec!(5)));
vm.register_function("getRate", |args| {
match &args[0] {
Value::String(s) if s.as_str() == "USD" => Ok(Value::Number(dec!(34))),
_ => Err("unknown".to_string()),
}
});
let result = vm.execute().expect("execute");
assert_eq!(result, Value::Number(dec!(3570)));
}
// ==================== IN KEYWORD TESTS ====================
#[test]
fn test_in_string_in_stringlist() {
let result = run_and_get_result(r#"
categories = "finans,teknoloji,saglik".split(",")
result = "finans" in categories
"#);
assert_eq!(result, Value::Boolean(true));
}
#[test]
fn test_in_string_not_in_stringlist() {
let result = run_and_get_result(r#"
categories = "finans,teknoloji,saglik".split(",")
result = "spor" in categories
"#);
assert_eq!(result, Value::Boolean(false));
}
#[test]
fn test_in_string_in_string_substring() {
let result = run_expr("\"hello\" in \"hello world\"");
assert_eq!(result, Value::Boolean(true));
}
#[test]
fn test_in_string_not_in_string() {
let result = run_expr("\"xyz\" in \"hello world\"");
assert_eq!(result, Value::Boolean(false));
}
#[test]
fn test_in_with_if_statement() {
let result = run_and_get_result(r#"
categories = "finans,teknoloji".split(",")
if "finans" in categories then
result = "found"
else
result = "not found"
end
"#);
assert_eq!(result, Value::String("found".into()));
}
// ==================== LIST METHOD EXPANSION TESTS ====================
// StringList methods
#[test]
fn test_stringlist_contains() {
let result = run_and_get_result(r#"
parts = "a,b,c".split(",")
result = parts.contains("b")
"#);
assert_eq!(result, Value::Boolean(true));
}
#[test]
fn test_stringlist_contains_missing() {
let result = run_and_get_result(r#"
parts = "a,b,c".split(",")
result = parts.contains("z")
"#);
assert_eq!(result, Value::Boolean(false));
}
#[test]
fn test_stringlist_indexof() {
let result = run_and_get_result(r#"
parts = "a,b,c".split(",")
result = parts.indexOf("b")
"#);
assert_eq!(result, Value::Number(dec!(1)));
}
#[test]
fn test_stringlist_indexof_not_found() {
let result = run_and_get_result(r#"
parts = "a,b,c".split(",")
result = parts.indexOf("z")
"#);
assert_eq!(result, Value::Number(dec!(-1)));
}
#[test]
fn test_stringlist_slice() {
let result = run_and_get_result(r#"
parts = "a,b,c,d".split(",")
sliced = parts.slice(1, 3)
result = sliced.join(",")
"#);
assert_eq!(result, Value::String("b,c".into()));
}
#[test]
fn test_stringlist_reverse() {
let result = run_and_get_result(r#"
parts = "a,b,c".split(",")
reversed = parts.reverse()
result = reversed.join(",")
"#);
assert_eq!(result, Value::String("c,b,a".into()));
}
#[test]
fn test_stringlist_sort() {
let result = run_and_get_result(r#"
parts = "c,a,b".split(",")
sorted = parts.sort()
result = sorted.join(",")
"#);
assert_eq!(result, Value::String("a,b,c".into()));
}
#[test]
fn test_stringlist_isempty() {
let result = run_and_get_result(r#"
parts = "a,b".split(",")
result = parts.isEmpty()
"#);
assert_eq!(result, Value::Boolean(false));
}
// NumberList methods (using globals since we can't create NumberList literals yet)
#[test]
fn test_numberlist_contains() {
let ast = parser::program("result = nums.contains(3)").expect("parse");
let mut compiler = Compiler::new();
let bytecode = compiler.compile(ast).expect("compile");
let mut vm = VM::new(&bytecode);
vm.set_global("nums", Value::NumberList(Rc::new(vec![dec!(1), dec!(2), dec!(3), dec!(4)])));
vm.execute().expect("execute");
assert_eq!(vm.get_global("result").unwrap(), &Value::Boolean(true));
}
#[test]
fn test_numberlist_indexof() {
let ast = parser::program("result = nums.indexOf(3)").expect("parse");
let mut compiler = Compiler::new();
let bytecode = compiler.compile(ast).expect("compile");
let mut vm = VM::new(&bytecode);
vm.set_global("nums", Value::NumberList(Rc::new(vec![dec!(1), dec!(2), dec!(3)])));
vm.execute().expect("execute");
assert_eq!(vm.get_global("result").unwrap(), &Value::Number(dec!(2)));
}
#[test]
fn test_numberlist_slice() {
let ast = parser::program("sliced = nums.slice(1, 3)\nresult = sliced.sum()").expect("parse");
let mut compiler = Compiler::new();
let bytecode = compiler.compile(ast).expect("compile");
let mut vm = VM::new(&bytecode);
vm.set_global("nums", Value::NumberList(Rc::new(vec![dec!(10), dec!(20), dec!(30), dec!(40)])));
vm.execute().expect("execute");
// slice(1,3) = [20, 30], sum = 50
assert_eq!(vm.get_global("result").unwrap(), &Value::Number(dec!(50)));
}
#[test]
fn test_numberlist_reverse() {
let ast = parser::program("reversed = nums.reverse()\nresult = reversed.first()").expect("parse");
let mut compiler = Compiler::new();
let bytecode = compiler.compile(ast).expect("compile");
let mut vm = VM::new(&bytecode);
vm.set_global("nums", Value::NumberList(Rc::new(vec![dec!(1), dec!(2), dec!(3)])));
vm.execute().expect("execute");
assert_eq!(vm.get_global("result").unwrap(), &Value::Number(dec!(3)));
}
#[test]
fn test_numberlist_sort() {
let ast = parser::program("sorted = nums.sort()\nresult = sorted.first()").expect("parse");
let mut compiler = Compiler::new();
let bytecode = compiler.compile(ast).expect("compile");
let mut vm = VM::new(&bytecode);
vm.set_global("nums", Value::NumberList(Rc::new(vec![dec!(30), dec!(10), dec!(20)])));
vm.execute().expect("execute");
assert_eq!(vm.get_global("result").unwrap(), &Value::Number(dec!(10)));
}
#[test]
fn test_numberlist_isempty() {
let ast = parser::program("result = nums.isEmpty()").expect("parse");
let mut compiler = Compiler::new();
let bytecode = compiler.compile(ast).expect("compile");
let mut vm = VM::new(&bytecode);
vm.set_global("nums", Value::NumberList(Rc::new(vec![])));
vm.execute().expect("execute");
assert_eq!(vm.get_global("result").unwrap(), &Value::Boolean(true));
}
// ==================== EXTERNAL METHOD TESTS ====================
#[test]
fn test_external_method_on_number() {
let ast = parser::program("result = x.format(2)").expect("parse");
let mut compiler = Compiler::new();
let bytecode = compiler.compile(ast).expect("compile");
let mut vm = VM::new(&bytecode);
vm.set_global("x", Value::Number(dec!(3.14159)));
vm.register_method("Number", "format", |this, args| {
match (this, &args[0]) {
(Value::Number(n), Value::Number(decimals)) => {
let d = decimals.to_u32().unwrap_or(2);
let formatted = format!("{:.1$}", n, d as usize);
Ok(Value::String(formatted.into()))
}
_ => Err("format requires a number".to_string()),
}
});
vm.execute().expect("execute");
assert_eq!(vm.get_global("result").unwrap(), &Value::String("3.14".into()));
}
#[test]
fn test_external_method_on_string() {
let ast = parser::program(r#"result = "hello".repeat(3)"#).expect("parse");
let mut compiler = Compiler::new();
let bytecode = compiler.compile(ast).expect("compile");
let mut vm = VM::new(&bytecode);
vm.register_method("String", "repeat", |this, args| {
match (this, &args[0]) {
(Value::String(s), Value::Number(n)) => {
let count = n.to_usize().unwrap_or(0);
Ok(Value::String(s.repeat(count).into()))
}
_ => Err("repeat requires a number".to_string()),
}
});
vm.execute().expect("execute");
assert_eq!(vm.get_global("result").unwrap(), &Value::String("hellohellohello".into()));
}
// ==================== RAND FUNCTION TESTS ====================
#[test]
fn test_rand_returns_number_in_range() {
let result = run_expr("rand(1, 10)");
match result {
Value::Number(n) => {
assert!(n >= dec!(1) && n <= dec!(10), "rand result {} not in range 1..10", n);
}
_ => panic!("Expected Number, got {:?}", result),
}
}
#[test]
fn test_rand_same_min_max() {
let result = run_expr("rand(5, 5)");
assert_eq!(result, Value::Number(dec!(5)));
}
#[test]
fn test_rand_in_assignment() {
let result = run_and_get_result("result = rand(1, 100)");
match result {
Value::Number(n) => {
assert!(n >= dec!(1) && n <= dec!(100));
}
_ => panic!("Expected Number"),
}
}
// ==================== BYTECODE CACHE & RE-EXECUTE TESTS ====================
#[test]
fn test_bytecode_reuse_with_different_globals() {
// Compile once, execute multiple times with different inputs
let ast = parser::program("base * rate").expect("parse");
let mut compiler = Compiler::new();
let bytecode = compiler.compile(ast).expect("compile");
// First execution
let mut vm = VM::new(&bytecode);
vm.set_global("base", Value::Number(dec!(100)));
vm.set_global("rate", Value::Number(dec!(1.5)));
let result1 = vm.execute().expect("execute");
assert_eq!(result1, Value::Number(dec!(150.0)));
// Second execution with different values — reuse same bytecode
let mut vm2 = VM::new(&bytecode);
vm2.set_global("base", Value::Number(dec!(200)));
vm2.set_global("rate", Value::Number(dec!(2.0)));
let result2 = vm2.execute().expect("execute");
assert_eq!(result2, Value::Number(dec!(400.0)));
}
/*
#[test]
fn expr_test() {
let cases = vec![
("(18.49730)/81.6*63.0852-64.773/(32.2007)+41.2879-85.4-(13.50-56.0243)-(49.35612)+(59.8+63.30426)/(70.56281)-13.81690*(86.7057*(43.8*(86.41762)+72.6*58.35/28.62060))", "-4711911.631973682999201581558386"),
("(52.01054+91.492+69.997)*(60.42597)+20.1237/43.9-(47.94052)+82.54171*48.5715+61.48", "16924.0893449520574032"),
("(21.10121)+(21.4)+48.18/(11.31875)+(71.4+21.05*(41.412)-(64.6949)/44.1296/(24.184/90.3442*49.8813)+(46.02969)*(22.6)/(77.3720/(88.8365/55.5577)))", "1011.2692544086353375"),
("(11.204)*5.3+17.95658-40.86/0.93-91.2*58.71*(61.36106/81.8-(45.24309)*(14.735/76.41129))", "42731.423060682431273967518192"),
("(71.0626*45.04296)*(1.38)+(24.6055)-(95.7317)-(17.14)/(92.799)/(6.00*42.7395/81.6413)/(18.65)-39.71747-33.2061", "4273.1474690724873717"),
("(40.294)*(42.0572)+(6.6897-53.21*11.12257)/(38.5)-(81.02849)+(58.49)-10.5+81.53/(50.3951*(18.03091)/(30.81381)+(13.80113)*(88.8/(76.5940)*38.12896)-(66.044)+(56.9/64.7))", "1646.5577647573549686"),
("42.4322*76.024*57.70-79.228-(83.6/11.815)+(0.66)+(19.205+91.8077/39.740*(62.10317+60.054+41.0))", "186442.931938706969977122025"),
("76.98*(89.73+83.00)+11.56316-96.7821/(91.785-72.1*(51.6704*92.552/(32.9239-78.748)-(47.1707/84.408)))", "13308.3059193646290232"),
("41.25*(56.81*88.01)+68.0-(46.9003)-81.8620+71.9*14.100+(11.327/93.36124)", "207196.883149438278669"),
("(99.221)+93.1*67.70+(39.31+(2.72/21.097)-59.2016)*(53.94/23.348)+17.50364+24.95+90.45581", "6489.3434981248784293961014069820784"),
("(2.497*81.61)*60.72-(76.023)-(56.0)/(87.8+(55.44/(90.26)+(2.6925*70.1204)+(9.143)/79.8150)/(5.71574/85.39960+27.05540))", "12296.918129654197124"),
("67.2+46.9-78.01624*53.31480*(12.1)/85.4145*91.2-(63.6-45.6835*16.951)", "-52913.1074455104904888"),
("(97.4)-67.2+(54.019)*77.36*66.24/87.43608+(28.8)*47.9*75.8-(73.5)*78.007-66.2325/74.33*9.161", "102022.0053553527787263794"),
("57.42*(64.462)/63.02-(77.6516-(66.8*98.435+97.92*53.260*(51.96*(77.7742/(76.35+(7.7+(80.6245/77.98*6.5463)))))))", "238618.4626891788131077667264"),
("(73.01*14.485)/(0.4198)-(53.85/(86.5172)/(39.49)/72.4)*(28.2281)+(8.5936)+34.02-80.78209/(97.361)+(21.3635)-(26.825+66.087)", "2489.40467830588322294532"),
("(5.9+(45.805)/85.657)/(78.531*56.026+74.0075+15.9)+(50.9)*(39.8273-27.96138+47.631+(46.74843+84.12))", "9689.597748229417814"),
("7.162*66.353+66.9/43.84+97.3495-63.300*82.06213+(92.31)-11.878+98.7", "-4441.3051393503649635"),
("0.79/(77.43065-22.339-(78.8)-24.806+(85.917)+(22.0833*71.988)+69.30312+(82.91418/41.630))", "0.0004651354312546"),
("(99.0+55.06-86.646)-(54.454+33.35-(77.3)/(64.5733*(32.994*31.18645+96.5636-11.1298)-(92.3)/(13.94399/(68.3964)+67.65089)-(34.450/31.15957)))", "-20.3889257620919107"),
("3.5+79.5/(45.3103/(12.77)/12.34)/11.0+(75.0587+58.24958-45.2774)/(75.4)/(71.934*(23.72+19.81273))", "28.6356432996469259"),
("95.53+37.63780+(83.018+(72.230)*73.808)-(22.3)-35.5*43.7506*1.5+(75.1)+32.08/(78.287)+(19.9957)-47.08*72.5", "-122.4763357079719494"),
("77.7+(83.56)*(37.4)+12.0871+(87.23119*(31.86)/(36.6)/17.99570+54.3*76.9504-(62.6508*44.32)+(11.7022-40.7))", "4591.8761295565077158"),
("11.952*(92.7476+41.021/(98.62656)*76.29)/(99.1+29.589+2.559)-73.3466-91.59624", "-153.6073154918920115"),
("64.461-22.88+(22.9)+91.54*13.56/28.9252-12.1494/(66.725-85.332+8.0089)-65.4172-21.76", "21.3637039814725284"),
("92.787+(14.5723-(40.68935-3.8035/(12.61)*15.0999-28.4705)+64.3935)-6.8149+51.5883-35.3+(53.8352*89.633)", "4998.97234941522601063649"),
("83.839+(22.7557+(9.89*30.11)*(78.1989-20.077)*(96.440*7.22746)/52.9-(11.19*13.53987)*(41.137*37.409/33.22))", "221140.04372737897527175584969"),
("97.87/55.7470+93.266*(87.427)-63.19-(18.5+54.59274)*86.8917/(82.72*(89.825*16.328))", "8092.4798427571432436"),
("69.56577+10.19659*(53.9190/91.3*5.35)*(18.47)+2.30898*30.3*67.05780*(73.206)+77.82/(43.5503)-63.37332-(56.65939*54.6798)", "340951.1456145128387014845945685"),
("(20.0128)*4.11078/41.20165+(54.056)*54.17877+(40.54090)/33.53-(90.50882/(15.8201+(18.06068+50.3214)+(5.79341)*(27.1)/46.161)+(79.9/(3.727/59.92870)))", "1646.0995394189492598"),
("(43.80835)-10.839+(62.86974*18.671)*(92.474-(16.4505)/36.2*(35.1949-39.7541-98.9))", "163771.2724064544227594178868709248"),
("(60.4461)-37.71-(68.25714)/85.52189/56.16*65.06/(22.82-87.557)/(85.78)*(67.09395+(57.08)/98.169)/(23.48216-28.4621)+99.059-(85.884-57.1)", "93.0088373059094804"),
("(64.50745+34.278-31.140+(52.25116*87.741-94.3)-(93.000+(76.525)*68.8693)-21.82036-(74.2)-(21.5767+(29.58)-(67.8*26.02123)))", "811.75363106"),
("(22.558)/20.5*5.07-(81.397/81.9-50.27800)/(59.7534)-(1.214+(27.22)-19.94-(5.1)-5.74*(25.540)+(71.8-(42.79143)*31.356)-(75.8-71.2/57.72385))", "1494.14399091315859183"),
("(26.204+79.321)+(75.1655/17.009)/(68.4388+(92.299)-(91.37210)-(87.16871)+(18.89-3.914-12.98*(79.7636)+(28.92*85.5084-80.2871+27.092)+17.6564)+92.77317)-(95.97760*55.46)", "-5217.3897340542493272"),
("70.6901-(6.45-(10.7)*30.46112)+(82.64)/95.2541/(10.82)*(91.2162+47.9/82.4498)/7.1-(74.5198/86.87529)", "390.3529982272845288"),
("59.5189+70.409*62.585/(5.8410)+58.58*(38.9)+8.656*52.21-(69.29)*(80.58/11.87302)", "3074.368775273583646156"),
("55.351*(18.681-93.1)/77.04+(41.00399)+(29.5)/(31.07/(16.214-72.0-84.15313)/(98.888)+(13.93107-18.312/7.0036-14.77648))", "-20.984219520618196"),
("(66.9*39.9*61.29392)*75.85*(72.9214)-60.84/98.3528/(75.803-82.1356/48.1234)+(47.9*85.9084)/2.72-54.161+69.29430/(17.76/34.5989-36.563)", "904956477.2197517050761512"),
("(35.9+(13.05613)+20.00271-43.24/3.11)-76.3+53.85505*(19.25-(40.5604)-73.8-17.0298)", "-6060.5607749874919614"),
("(13.2+(67.5)+(81.77331-62.35)-37.9411)/(11.32/(87.70619*(69.077-(46.7)-(73.529*25.55316))))", "-894437.3132401834266139"),
("(46.1832/82.96469+97.2240-54.50*59.62)/84.11388-(20.3168*10.35291)-23.103+38.36584+(60.8)/(70.7*94.19)*(29.7735*(54.65937+(34.83763*11.485)))", "-108.91894850517456148018720811"),
("(21.00-(95.26747-28.84873-(41.7700)+82.19*95.57*(82.4241)*74.872)+(62.61/43.59))", "-48474600.0209386043871163"),
("(78.9)+(90.9746)+(88.622/(7.12-50.3430-(53.9/8.5136*63.44757+(9.46305-36.34)+(64.0+71.0940))))", "169.7143807630540127"),
("5.2*(96.47)-(84.84860-(70.951/(17.196*16.01543*29.327/9.8-(94.659)-(18.7*70.0))))", "416.6729665063680072"),
("91.6*(46.85)*(93.8273)+(34.3124)+73.482-(91.3897-(63.7819-(77.598)-(79.8)/(79.9703-38.8657)/(26.8+71.84517)*(5.22498/2.4943)))", "402658.65223186841219833803621004565091"),
("32.083/(3.3866)*34.553/(86.60+17.11649*(33.1554)*17.6+94.690)+63.418+(2.07)-(12.54225)/45.99069+(8.67)/(78.0303)*99.13520/81.036+8.464", "73.8474028679700169"),
("8.368+(12.708/57.36-69.67948-71.058)+(81.554*(65.53478)/32.251*24.576-58.81309)+34.275*20.9", "4598.111846663777242932"),
("(26.96991)/(15.313)-77.8-(54.211)/(63.9206/79.2+(35.398)+(37.0871)/6.925)+63.158+26.41", "12.2248588019904453"),
("(52.5)+(29.86)*(0.57)/(20.46534)-(27.85*(6.04183)/92.619/27.366)/77.0657*(12.397*39.057)", "52.9145631756737791057185"),
("(59.0/49.4)+(29.4310*(87.861)/56.9821)+(8.6)+(12.4982+97.82)/92.128*53.9236-37.1-20.652", "61.99268345398978546564"),
("(10.472)*7.5-(8.0348+76.886)+98.0794/(24.6+(90.903)+(13.593)*(3.1*(4.2+47.8421-40.94/42.377)*(82.1)/86.9)-(28.3393)*(66.38358*7.3453))", "-6.3892047123880735"),
("98.9/(14.49550)-22.174*(60.06002)/(28.67844)-87.3-(78.01201)*27.2/(65.24712*(38.744)/(52.4)-(0.84403)+(12.11000+1.7/75.90))", "-162.5590728690040862"),
("66.16/36.930*20.791*(50.127)*61.6-(85.5-82.02442)/(90.42+(3.3)/63.3)*62.97490*(85.1)+(40.1040+14.00162-(67.4883)+29.9688*(81.46/85.95))", "114821.36456608956795954937976"),
("85.3797+66.025+(22.2)*8.38/48.855-(4.8)-10.4595-(69.49066/79.25165-9.1)", "148.1762859019992207"),
("61.4374/22.8892/(48.8)-94.057*99.2987+(51.89039*39.5)+40.2567/71.06/(38.003+23.001-(82.22883)*7.028-(47.84255+(89.6098+1.66113-(48.98*41.63619)+68.535+11.491)))", "-7290.0119837261121902"),
("(65.66845)+48.2-(62.44*(8.3)*86.1969/64.1838/37.301)-(75.0316)*12.36*(3.86712/(41.50180/24.341-81.6)*63.2991*47.40702-(47.72*26.00311/(41.4)/95.819/31.0174))", "134805.6197977978758035622071345613888"),
("(88.719)-42.0846-(65.73638)-46.861*(79.4657)+97.45-(36.00793/99.07578-(36.2)/90.5)+57.8895/19.65274/99.176+75.85+22.95/70.9496+93.89555/(64.12-43.53)", "-3564.6941658600054532"),
("19.64337/56.96718-2.6+(59.55)*(66.910/45.478+(35.73+(82.6-(21.1305)-25.63702)+(12.2691/(1.70)+0.83995/(66.8476+(97.047/(17.54544)-26.945-(2.10-(61.3/92.3)))))))", "4777.820291065982541655"),
("(25.9338-(73.42-32.454*(54.0)-56.2936)/(25.28590/(88.28)+(65.46504)*69.73926*(84.336)/74.6121)-77.8075-(18.398)*8.96)/49.15*58.7420", "-258.6124192419976067014"),
("58.68444+(27.9771+38.9)+(6.0043)/(1.54893+(53.8248*(73.4-(0.76750)+26.29519+78.9)))", "125.5621672061336517"),
("6.26480/78.969/(32.05585)*(50.025)*89.4*26.7+(53.38076)-96.711+(25.3)*(7.47849-16.51)+29.1+(90.20164)/(60.1092/46.1)", "121.96632115022759394645"),
("(11.7)*(14.301)/(84.39*53.66)*93.8/(8.537)+(56.90072)*(8.803)*(49.63/20.1624)*67.9433*(69.967+97.229-(73.025-31.0))", "10485783.52967474232404148937388096712"),
("56.691-43.952-(32.10)-56.45673+(97.0-80.41)*91.2*(83.5671-11.51855/(15.53601)-28.08144)/19.70302-(42.2-65.3055)", "4151.1351250306809158"),
("(91.45417+(48.3132)/72.7769)+(51.50708)+(25.9703/(31.3)*(1.04)*(66.79)/47.8)*49.210/(84.367)/70.2-62.5*(16.29046)+(96.3-14.1879)", "-792.4065282171246921"),
("12.6443*(29.6)-(53.77038+(33.2-78.371*(74.10073/(2.29880/10.74854+(72.17-39.238+60.7811+50.107)/82.539)*(64.6157+29.1-(94.9886)*(89.712-37.216*93.9971)))))", "961382698.243192206397847895388770867776"),
("(38.4*(13.0848*55.053)-26.7/20.0669)-38.5*(65.6)*52.8340*21.6*3.04/46.6+(46.362+(71.05913)-28.55226)*(46.79905*(97.560+5.39-89.20))", "-103180.3262296021176548"),
("(33.5486+(98.3337-(11.063*(33.44014)*82.01)/(28.963/54.3977)*78.10826)/(50.8215+73.219))", "-35847.7923091900694923"),
("(74.99783)-(87.78+26.00356)+12.7+86.6+32.3+10.2969/46.8457-53.53427+(15.83-66.1848)", "-10.854995407476033"),
("(37.94885)-91.45941-(3.77*(82.82297-(83.076)/(37.47916*(58.91323)*67.541)*(12.2470*95.077)+90.147)-4.55-98.5)-(39.8397)*92.85+9.6273", "-4289.600780422766101115547362"),
("(27.7418*1.4960*55.51)/85.2012*68.42-(70.576/46.0*(74.068+63.04361)*(43.83113+(29.674*(38.8*86.98/(52.048-62.07-62.5)+20.79847)-(87.47*78.14))))", "1591112.334306702048791493272153652435667521408"),
("44.80585/18.6414/(42.769+(24.93)*30.321)*42.76-(90.1)/(71.16021)*10.7/(16.52649/89.184*42.730/99.88522*16.760)-94.0+45.6665-(62.374)*57.6+2.0+80.320", "-3568.824205650661715544"),
("(5.65-(91.81)+59.05*(31.927)-20.262)-(46.51542/(9.34940)/(71.2808)*(67.6013/(6.12)*11.913)-58.1*73.6247)+11.08*46.9/93.9978-41.720", "6011.08605300744308160020074732161430413"),
("(52.8*54.8099)*(42.2411/(32.56018-(58.43)*(3.5/39.339)*45.3794)+60.9)/(81.16609*85.3508)+17.716+86.23388/(51.1/50.50)", "128.2912092972705256"),
("(97.07*(31.1146+(31.29)*20.8833)/(93.49*15.31)*(79.9)/(4.61235)/(77.5129)/31.211)-(44.89769/(54.28109*12.1595)/(45.824)+20.94)+99.702/28.273+(96.592/52.19820)", "-15.232170162991231"),
("87.421/(78.11-(27.61153)-(78.1445-66.70131)/(55.5)/70.70)*32.625/33.09/(91.45273)*(42.3197)/(31.78+15.55452*15.573-(72.7587+63.652))", "0.0057404291598226"),
("(11.6757)+33.49685+(43.559)+72.80540-(26.3014*(11.17+52.0146)-(7.6)/33.3+2.42/94.7)", "-1500.1038145940315395"),
("60.273/(79.5631)-72.866-68.0664+80.659/77.57857/83.94+76.572/7.82247/51.15993-64.3118", "-204.2829282587953641"),
("23.6817/58.92240/(71.3797)+15.6402*30.8*8.185*5.935/23.767-(30.2134-6.5001/(26.5807-(69.3195)+(77.00+63.2)))", "954.4549081069203237"),
("(52.133-(46.31994*(47.631+4.676-6.5729)+(0.07)-(82.7+31.96*(51.414)+96.22890)/(91.4)+86.208)/41.0)-(4.0134+59.8961)", "-65.0629169960494209"),
("(3.76*98.61105)-(20.0617*(35.4185)+(4.8907+(18.555)-(25.17472)*(0.6227/36.8742)/(17.7785)*85.7/86.034)*88.9489)-(95.34045/80.87824)+65.472-77.7", "-2436.53507563541415315542"),
("27.011/53.45/(53.37)*1.1/(53.129+47.48254+(95.47028/64.1+86.0/81.19140))", "0.0001009662956334"),
("(20.44*33.1592)*80.1*(79.07)-(66.860)*8.896+27.24+6.043/47.15182*3.59+(15.3140*(25.5731)-(81.7570)+98.206/(7.2/(86.3)+4.782)+(59.04*(62.2660/98.68)))", "4292486.89845426863322881"),
("(74.3846/65.70273)+(78.58930*21.336)-67.2+(36.9657-(27.875*(13.09705)-26.4*8.047-(81.3)*(41.5-(70.3212-86.39)+(95.9-37.34099+13.70-94.196-19.01))))", "2846.3928276993377064"),
("(68.8553+20.60933/(17.10*21.562)-(80.980)+(55.81242-(52.3)-48.02297+(33.21/(36.68/26.78797+(10.8651)+92.46576*67.23))))", "-56.574022494248659"),
("(64.989)*32.9-(96.9)+60.4014-17.06603*(8.58+(18.48-(41.29564-29.391+44.0)))", "2593.9029915792"),
("8.96739+(83.4686/95.57831+12.158/68.1757/73.11929/(29.81572*69.0)+21.9)*(38.35)-(48.00/(46.683)-94.18665)/(54.2)+(68.03-(75.3649)/61.4930)-(99.592*55.135)", "-4540.15819935667646422"),
("(41.0+(82.2*14.0709)*(17.9189/(20.32)+90.08974-85.75252-(39.429)+25.4250)+50.8)/(84.2806-(50.55516+52.477)-(63.8)+40.7+54.4-67.324)", "183.824911350546589"),
("(8.2)+(61.31027)*(80.70)-46.72-62.4+(71.96-9.1/(51.82540)/(51.79740-(59.9*44.4)))", "4918.7788563334205222"),
("77.54*86.508+(95.63110)+93.443+22.16*96.48-(98.6136-1.76491)/(20.7910*13.23335)", "9034.5492152100066785"),
("77.35187/8.37-(49.84344+69.52151+(44.2723*87.0381-(40.50+(15.4)*49.0701*(72.0)*74.5430+50.5)/(81.005)*(86.53352*38.6/(57.03-80.610-35.8*31.834+71.6214))))", "-157169.86473751443870274723124909867384"),
("(23.7)-(77.288)/(91.9*8.904+(43.789/(46.0)+0.8*1.93290-(44.788)+(42.74)+45.2399)*(29.4)*(92.75+(38.02)+39.247+20.4827))", "23.699698934293117"),
("(6.3636*(20.7031+20.9)*56.61+(53.7145)/64.6063/53.5024-(23.0)-(43.8*39.67586/26.0938-81.45407*42.8631))", "18389.0332176857809571"),
("4.155*22.7+(75.01-22.98284+(82.62)-98.4994)-(48.3277)*96.253-(63.5951-10.199)", "-4574.6159481"),
("(22.35584)*5.744*(70.04/(80.9943)/(38.5-99.4)/69.4)/50.67159*(90.2400)-(63.7/75.7)*79.11-99.07+(73.20756*(88.95470/(44.115*(50.33195)+(80.85280-(49.22+88.592)))))", "-162.676134673564991559628"),
("55.320+(90.0*(7.32)+(49.149/(43.4-(1.4864)/(93.0026/(2.0/(31.62905+70.07+45.5)/65.67+32.289)/(22.65365/64.7)))))", "715.2571999910689178"),
("(50.37)*31.78158*59.84/13.19537/(72.38*(39.3889-(15.91*(14.288/(77.9524)*40.0427)+(45.3365/68.15663))))", "-1.2851101855344694"),
("67.34548/5.643+99.2370-(47.2-(90.0837/99.444)-(10.95663-75.4-69.73)*45.6/(42.9618)*(82.207*29.1))", "-340618.14427072036819048594"),
("(10.2910)*30.915-54.115/70.8586+(17.89031)/(80.11/27.0835-(63.24020)-35.05283)+(81.6275)+25.671-(82.7379/97.04-50.90291-39.40)+(27.87)/(63.40)/51.81*86.602", "514.6784854381998292716"),
("(53.447)-(88.1549)*(20.505)+(69.8)-(61.1982)+(88.510+(48.7213/(82.0957-(93.5-63.34-68.899))))", "-1656.6542183017804488"),
];
for (expr, expected) in cases {
let mut val1 = VM::execute(expr).unwrap();
val1.rescale(6);
let mut val2 = Decimal::from_str(expected).unwrap();
val2.rescale(6);
assert_eq!(val1, val2, "L: {} | R: {}", expr, expected);
}
}
*/
// ==================== ERROR LOCATION TESTS ====================
#[test]
fn test_error_location_division_by_zero() {
let code = r#"x = 10
y = 0
result = x / y"#;
let mut compiler = Compiler::new();
let (bytecode, debug_info) = compiler
.compile_from_source(code)
.expect("Failed to compile");
let mut vm = VM::new(&bytecode);
vm.set_debug_info(&debug_info);
let err = vm.execute().unwrap_err();
let err_msg = err.to_string();
// Error should include line 3 (where the division happens)
assert!(
err_msg.contains("line 3"),
"Error should contain line 3, got: {}",
err_msg
);
assert!(
err_msg.contains("Division by zero"),
"Error should mention division by zero, got: {}",
err_msg
);
}
#[test]
fn test_error_location_modulo_by_zero() {
let code = r#"x = 15
y = 0
result = x % y"#;
let mut compiler = Compiler::new();
let (bytecode, debug_info) = compiler
.compile_from_source(code)
.expect("Failed to compile");
let mut vm = VM::new(&bytecode);
vm.set_debug_info(&debug_info);
let err = vm.execute().unwrap_err();
let err_msg = err.to_string();
assert!(
err_msg.contains("line 3"),
"Error should contain line 3, got: {}",
err_msg
);
}
#[test]
fn test_string_number_auto_coercion() {
// string + number now auto-coerces to string concatenation
let code = r#"x = "hello"
y = 5
x + y"#;
let ast = parser::program(code).unwrap();
let mut compiler = Compiler::new();
let bytecode = compiler.compile(ast).unwrap();
let mut vm = VM::new(&bytecode);
let result = vm.execute().unwrap();
assert_eq!(result, Value::String("hello5".into()));
}
#[test]
fn test_error_location_method_not_found() {
let code = r#"x = 42
result = x.unknownMethod()"#;
let mut compiler = Compiler::new();
let (bytecode, debug_info) = compiler
.compile_from_source(code)
.expect("Failed to compile");
let mut vm = VM::new(&bytecode);
vm.set_debug_info(&debug_info);
let err = vm.execute().unwrap_err();
let err_msg = err.to_string();
assert!(
err_msg.contains("line 2"),
"Error should contain line 2, got: {}",
err_msg
);
assert!(
err_msg.contains("unknownMethod"),
"Error should mention method name, got: {}",
err_msg
);
}
#[test]
fn test_no_error_location_without_debug_info() {
// When debug info is not provided, errors should not include line numbers
let code = "result = 1 / 0";
let ast = parser::program(code).expect("Failed to parse");
let mut compiler = Compiler::new();
let bytecode = compiler.compile(ast).expect("Failed to compile");
// Create VM without setting debug info
let mut vm = VM::new(&bytecode);
let err = vm.execute().unwrap_err();
let err_msg = err.to_string();
// Should just be the plain error message
assert_eq!(err_msg, "Division by zero");
}
// ==================== OBJECT TYPE TESTS ====================
fn make_object(entries: Vec<(&str, Value)>) -> Value {
let mut map = IndexMap::new();
for (k, v) in entries {
map.insert(SmolStr::new(k), v);
}
Value::Object(Rc::new(map))
}
#[test]
fn test_object_property_access() {
let obj = make_object(vec![
("name", Value::String("Alice".into())),
("age", Value::Number(dec!(30))),
]);
let result = run_expr_with_globals("customer.name", vec![("customer", obj)]);
assert_eq!(result, Value::String("Alice".into()));
}
#[test]
fn test_object_property_access_number() {
let obj = make_object(vec![
("age", Value::Number(dec!(30))),
]);
let result = run_expr_with_globals("person.age", vec![("person", obj)]);
assert_eq!(result, Value::Number(dec!(30)));
}
#[test]
fn test_object_property_access_missing() {
let obj = make_object(vec![
("name", Value::String("Alice".into())),
]);
let result = run_expr_with_globals("person.missing", vec![("person", obj)]);
assert_eq!(result, Value::Null);
}
#[test]
fn test_object_nested_property_access() {
let address = make_object(vec![
("city", Value::String("Istanbul".into())),
("zip", Value::String("34000".into())),
]);
let person = make_object(vec![
("name", Value::String("Duhan".into())),
("address", address),
]);
let result = run_expr_with_globals("person.address.city", vec![("person", person)]);
assert_eq!(result, Value::String("Istanbul".into()));
}
#[test]
fn test_object_property_assignment() {
let obj = make_object(vec![
("name", Value::String("Alice".into())),
("age", Value::Number(dec!(30))),
]);
let code = r#"
person.name = "Bob"
person.name
"#;
let result = run_expr_with_globals(code, vec![("person", obj)]);
assert_eq!(result, Value::String("Bob".into()));
}
#[test]
fn test_object_nested_property_assignment() {
let address = make_object(vec![
("city", Value::String("Istanbul".into())),
]);
let person = make_object(vec![
("name", Value::String("Duhan".into())),
("address", address),
]);
let code = r#"
person.address.city = "Ankara"
person.address.city
"#;
let result = run_expr_with_globals(code, vec![("person", person)]);
assert_eq!(result, Value::String("Ankara".into()));
}
#[test]
fn test_object_method_keys() {
let obj = make_object(vec![
("name", Value::String("Alice".into())),
("age", Value::Number(dec!(30))),
]);
let result = run_expr_with_globals("person.keys()", vec![("person", obj)]);
assert_eq!(
result,
Value::StringList(Rc::new(vec![SmolStr::new("name"), SmolStr::new("age")]))
);
}
#[test]
fn test_object_method_length() {
let obj = make_object(vec![
("a", Value::Number(dec!(1))),
("b", Value::Number(dec!(2))),
("c", Value::Number(dec!(3))),
]);
let result = run_expr_with_globals("obj.length()", vec![("obj", obj)]);
assert_eq!(result, Value::Number(dec!(3)));
}
#[test]
fn test_object_method_contains() {
let obj = make_object(vec![
("name", Value::String("Alice".into())),
]);
let code = r#"obj.contains("name")"#;
let result = run_expr_with_globals(code, vec![("obj", obj)]);
assert_eq!(result, Value::Boolean(true));
}
#[test]
fn test_object_method_contains_missing() {
let obj = make_object(vec![
("name", Value::String("Alice".into())),
]);
let code = r#"obj.contains("missing")"#;
let result = run_expr_with_globals(code, vec![("obj", obj)]);
assert_eq!(result, Value::Boolean(false));
}
#[test]
fn test_object_method_get() {
let obj = make_object(vec![
("name", Value::String("Alice".into())),
]);
let code = r#"obj.get("name")"#;
let result = run_expr_with_globals(code, vec![("obj", obj)]);
assert_eq!(result, Value::String("Alice".into()));
}
#[test]
fn test_object_method_get_missing() {
let obj = make_object(vec![
("name", Value::String("Alice".into())),
]);
let code = r#"obj.get("missing")"#;
let result = run_expr_with_globals(code, vec![("obj", obj)]);
assert_eq!(result, Value::Null);
}
#[test]
fn test_object_method_values_strings() {
let obj = make_object(vec![
("a", Value::String("x".into())),
("b", Value::String("y".into())),
]);
let result = run_expr_with_globals("obj.values()", vec![("obj", obj)]);
assert_eq!(
result,
Value::StringList(Rc::new(vec![SmolStr::new("x"), SmolStr::new("y")]))
);
}
#[test]
fn test_object_method_values_numbers() {
let obj = make_object(vec![
("a", Value::Number(dec!(1))),
("b", Value::Number(dec!(2))),
]);
let result = run_expr_with_globals("obj.values()", vec![("obj", obj)]);
assert_eq!(result, Value::NumberList(Rc::new(vec![dec!(1), dec!(2)])));
}
#[test]
fn test_object_in_operator() {
let obj = make_object(vec![
("name", Value::String("Alice".into())),
("age", Value::Number(dec!(30))),
]);
let code = r#""name" in person"#;
let result = run_expr_with_globals(code, vec![("person", obj)]);
assert_eq!(result, Value::Boolean(true));
}
#[test]
fn test_object_in_operator_missing() {
let obj = make_object(vec![
("name", Value::String("Alice".into())),
]);
let code = r#""missing" in person"#;
let result = run_expr_with_globals(code, vec![("person", obj)]);
assert_eq!(result, Value::Boolean(false));
}
#[test]
fn test_object_property_in_expression() {
let obj = make_object(vec![
("price", Value::Number(dec!(100))),
("quantity", Value::Number(dec!(5))),
]);
let code = "order.price * order.quantity";
let result = run_expr_with_globals(code, vec![("order", obj)]);
assert_eq!(result, Value::Number(dec!(500)));
}
#[test]
fn test_object_property_in_if() {
let obj = make_object(vec![
("active", Value::Boolean(true)),
("discount", Value::Number(dec!(10))),
]);
let code = r#"
if customer.active then
result = customer.discount
else
result = 0
end
"#;
let ast = parser::program(code).unwrap();
let mut compiler = Compiler::new();
let bytecode = compiler.compile(ast).unwrap();
let mut vm = VM::new(&bytecode);
vm.set_global("customer", obj);
vm.execute().unwrap();
let val = vm.get_global("result").unwrap().clone();
assert_eq!(val, Value::Number(dec!(10)));
}
#[test]
fn test_object_property_with_method_call() {
let obj = make_object(vec![
("name", Value::String("hello world".into())),
]);
let code = "obj.name.upper()";
let result = run_expr_with_globals(code, vec![("obj", obj)]);
assert_eq!(result, Value::String("HELLO WORLD".into()));
}
#[test]
fn test_object_add_new_property() {
let obj = make_object(vec![
("name", Value::String("Alice".into())),
]);
let code = r#"
person.age = 25
person.age
"#;
let result = run_expr_with_globals(code, vec![("person", obj)]);
assert_eq!(result, Value::Number(dec!(25)));
}
// ==================== VALUE::FROM_JSON TESTS ====================
#[test]
fn test_from_json_object() {
let val = Value::from_json(r#"{"name": "Alice", "age": 30, "active": true}"#).unwrap();
let code = r#"
result = person.name
"#;
let result = run_expr_with_globals(code, vec![("person", val)]);
assert_eq!(result, Value::Null); // result is in global
// use the helper that reads globals
let val2 = Value::from_json(r#"{"name": "Alice", "age": 30, "active": true}"#).unwrap();
let r = run_and_get_result_with_globals("result = person.name", vec![("person", val2)]);
assert_eq!(r, Value::String("Alice".into()));
}
#[test]
fn test_from_json_nested_object() {
let json = r#"{"address": {"city": "Istanbul", "zip": "34000"}}"#;
let val = Value::from_json(json).unwrap();
let result = run_expr_with_globals("person.address.city", vec![("person", val)]);
assert_eq!(result, Value::String("Istanbul".into()));
}
#[test]
fn test_from_json_with_arrays() {
let json = r#"{"tags": ["vip", "tr"], "scores": [10, 20, 30]}"#;
let val = Value::from_json(json).unwrap();
if let Value::Object(map) = &val {
assert!(matches!(map.get("tags").unwrap(), Value::StringList(_)));
assert!(matches!(map.get("scores").unwrap(), Value::NumberList(_)));
} else {
panic!("Expected Object");
}
}
#[test]
fn test_from_json_primitives() {
assert_eq!(Value::from_json("null").unwrap(), Value::Null);
assert_eq!(Value::from_json("true").unwrap(), Value::Boolean(true));
assert_eq!(Value::from_json("42").unwrap(), Value::Number(dec!(42)));
assert_eq!(Value::from_json(r#""hello""#).unwrap(), Value::String("hello".into()));
}
#[test]
fn test_from_json_full_workflow() {
// Simulate: JSON from API → Value → VM execution
let customer_json = r#"{
"name": "Duhan",
"age": 30,
"active": true,
"tags": ["premium", "tr"]
}"#;
let customer = Value::from_json(customer_json).unwrap();
let code = r#"
if customer.active then
result = customer.tags.first()
else
result = "none"
end
"#;
let r = run_and_get_result_with_globals(code, vec![("customer", customer)]);
assert_eq!(r, Value::String("premium".into()));
}