This commit is contained in:
2026-08-18 14:07:33 +03:00
parent 0fa388abeb
commit f4d089e5e5
21 changed files with 913 additions and 521 deletions
+130 -1
View File
@@ -1939,4 +1939,133 @@ fn test_invoice_full_scenario() {
("kdvOrani", Value::Number(dec!(0.20))),
]);
assert_eq!(result, Value::Number(dec!(272760.00)));
}
}
// ==================== COMPARISON TESTS ====================
fn run_expr_err(code: &str, globals: Vec<(&str, Value)>) -> String {
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().unwrap_err().to_string()
}
#[test]
fn test_compare_number_list_equality() {
let a = Value::NumberList(Rc::new(vec![dec!(1), dec!(2)]));
let b = Value::NumberList(Rc::new(vec![dec!(1), dec!(2)]));
let c = Value::NumberList(Rc::new(vec![dec!(1), dec!(3)]));
assert_eq!(
run_expr_with_globals("a == b", vec![("a", a.clone()), ("b", b.clone())]),
Value::Boolean(true)
);
assert_eq!(
run_expr_with_globals("a == c", vec![("a", a.clone()), ("c", c.clone())]),
Value::Boolean(false)
);
assert_eq!(
run_expr_with_globals("a != c", vec![("a", a), ("c", c)]),
Value::Boolean(true)
);
}
#[test]
fn test_compare_string_list_equality() {
let a = Value::StringList(Rc::new(vec![SmolStr::new("x"), SmolStr::new("y")]));
let b = Value::StringList(Rc::new(vec![SmolStr::new("x"), SmolStr::new("y")]));
let c = Value::StringList(Rc::new(vec![SmolStr::new("y"), SmolStr::new("x")]));
assert_eq!(
run_expr_with_globals("a == b", vec![("a", a.clone()), ("b", b)]),
Value::Boolean(true)
);
assert_eq!(
run_expr_with_globals("a == c", vec![("a", a), ("c", c)]),
Value::Boolean(false)
);
}
#[test]
fn test_compare_list_of_objects_equality() {
let mk = |n: i64| {
let mut m = IndexMap::new();
m.insert(SmolStr::new("id"), Value::Number(dec!(1) * rust_decimal::Decimal::from(n)));
Value::Object(Rc::new(m))
};
let a = Value::List(Rc::new(vec![mk(1), mk(2)]));
let b = Value::List(Rc::new(vec![mk(1), mk(2)]));
let c = Value::List(Rc::new(vec![mk(1)]));
assert_eq!(
run_expr_with_globals("a == b", vec![("a", a.clone()), ("b", b)]),
Value::Boolean(true)
);
assert_eq!(
run_expr_with_globals("a == c", vec![("a", a), ("c", c)]),
Value::Boolean(false)
);
}
#[test]
fn test_compare_different_types_never_equal() {
let list = Value::NumberList(Rc::new(vec![dec!(1)]));
assert_eq!(
run_expr_with_globals("l == 1", vec![("l", list.clone())]),
Value::Boolean(false)
);
assert_eq!(
run_expr_with_globals("l == \"1\"", vec![("l", list.clone())]),
Value::Boolean(false)
);
assert_eq!(
run_expr_with_globals("l == null", vec![("l", list)]),
Value::Boolean(false)
);
}
#[test]
fn test_compare_ordering_string_vs_number_errors() {
let err = run_expr_err("\"a\" < 1", vec![]);
assert!(err.contains("less than"), "got: {err}");
assert!(err.contains("String"), "got: {err}");
assert!(err.contains("Number"), "got: {err}");
}
#[test]
fn test_compare_ordering_boolean_errors() {
let err = run_expr_err("true > false", vec![]);
assert!(err.contains("greater than"), "got: {err}");
assert!(err.contains("Boolean"), "got: {err}");
}
#[test]
fn test_compare_ordering_null_errors() {
let err = run_expr_err("null <= 1", vec![]);
assert!(err.contains("less than or equal"), "got: {err}");
assert!(err.contains("Null"), "got: {err}");
}
#[test]
fn test_compare_ordering_lists_errors() {
let list = Value::NumberList(Rc::new(vec![dec!(1)]));
let err = run_expr_err("l >= l", vec![("l", list)]);
assert!(err.contains("greater than or equal"), "got: {err}");
}
#[test]
fn test_null_is_reserved_keyword() {
assert!(parser::program("null = 5").is_err());
// identifiers that merely start with "null" are still fine
assert_eq!(run_expr("nullable = 3\nnullable"), Value::Number(dec!(3)));
}
#[test]
fn test_string_compare_in_condition() {
let code = r#"
grade = "B"
result = 0
if grade == "A" then result = 100 else if grade == "B" then result = 80 else result = 0 end
"#;
assert_eq!(run_and_get_result(code), Value::Number(dec!(80)));
}
+174
View File
@@ -1284,5 +1284,179 @@
"maxPrice": { "type": "number", "value": "200" }
},
"expected": { "type": "number", "value": "200" }
},
{
"name": "compare: string equality",
"code": "\"a\" == \"a\"",
"expected": { "type": "boolean", "value": true }
},
{
"name": "compare: string inequality",
"code": "\"a\" == \"b\"",
"expected": { "type": "boolean", "value": false }
},
{
"name": "compare: string not equal",
"code": "\"a\" != \"b\"",
"expected": { "type": "boolean", "value": true }
},
{
"name": "compare: string case sensitive",
"code": "\"A\" == \"a\"",
"expected": { "type": "boolean", "value": false }
},
{
"name": "compare: string less than",
"code": "\"a\" < \"b\"",
"expected": { "type": "boolean", "value": true }
},
{
"name": "compare: string greater than",
"code": "\"b\" > \"a\"",
"expected": { "type": "boolean", "value": true }
},
{
"name": "compare: string lte equal",
"code": "\"a\" <= \"a\"",
"expected": { "type": "boolean", "value": true }
},
{
"name": "compare: string gte",
"code": "\"abc\" >= \"abd\"",
"expected": { "type": "boolean", "value": false }
},
{
"name": "compare: string prefix ordering",
"code": "\"ab\" < \"abc\"",
"expected": { "type": "boolean", "value": true }
},
{
"name": "compare: boolean equality",
"code": "true == true",
"expected": { "type": "boolean", "value": true }
},
{
"name": "compare: boolean inequality",
"code": "true == false",
"expected": { "type": "boolean", "value": false }
},
{
"name": "compare: boolean not equal",
"code": "true != false",
"expected": { "type": "boolean", "value": true }
},
{
"name": "compare: null literal",
"code": "null",
"expected": { "type": "null" }
},
{
"name": "compare: null == null",
"code": "null == null",
"expected": { "type": "boolean", "value": true }
},
{
"name": "compare: null != null",
"code": "null != null",
"expected": { "type": "boolean", "value": false }
},
{
"name": "compare: number == null is false",
"code": "1 == null",
"expected": { "type": "boolean", "value": false }
},
{
"name": "compare: number != null",
"code": "1 != null",
"expected": { "type": "boolean", "value": true }
},
{
"name": "compare: string == number is false",
"code": "\"1\" == 1",
"expected": { "type": "boolean", "value": false }
},
{
"name": "compare: string != number",
"code": "\"1\" != 1",
"expected": { "type": "boolean", "value": true }
},
{
"name": "compare: boolean == number is false",
"code": "true == 1",
"expected": { "type": "boolean", "value": false }
},
{
"name": "compare: number equality still works",
"code": "1 == 1",
"expected": { "type": "boolean", "value": true }
},
{
"name": "compare: number decimal equality",
"code": "1.0 == 1",
"expected": { "type": "boolean", "value": true }
},
{
"name": "compare: string eq in if",
"code": "x = \"ok\"\nif x == \"ok\" then 1 else 2 end",
"expected": { "type": "number", "value": "1" }
},
{
"name": "compare: string in condition with globals",
"code": "if status == \"active\" then \"yes\" else \"no\" end",
"globals": {
"status": {"type": "string", "value": "active"}
},
"expected": { "type": "string", "value": "yes" }
},
{
"name": "compare: null global",
"code": "x == null",
"globals": {
"x": {"type": "null"}
},
"expected": { "type": "boolean", "value": true }
},
{
"name": "compare: non-null global vs null",
"code": "x == null",
"globals": {
"x": {"type": "number", "value": "5"}
},
"expected": { "type": "boolean", "value": false }
},
{
"name": "compare: object equality",
"code": "a == b",
"globals": {
"a": {"type": "object", "value": {"k": "1"}},
"b": {"type": "object", "value": {"k": "1"}}
},
"expected": { "type": "boolean", "value": true }
},
{
"name": "compare: object inequality",
"code": "a == b",
"globals": {
"a": {"type": "object", "value": {"k": "1"}},
"b": {"type": "object", "value": {"k": "2"}}
},
"expected": { "type": "boolean", "value": false }
},
{
"name": "compare: object field string eq",
"code": "o.name == \"ali\"",
"globals": {
"o": {"type": "object", "value": {"name": "ali"}}
},
"expected": { "type": "boolean", "value": true }
},
{
"name": "compare: object field string ordering",
"code": "o.name < \"b\"",
"globals": {
"o": {"type": "object", "value": {"name": "ali"}}
},
"expected": { "type": "boolean", "value": true }
}
]