mod ast;
mod eval;
mod parser;
pub use eval::{EvalError, Interpreter, Value};
#[cfg(test)]
mod test {
use super::*;
use std::collections::HashMap;
#[test]
fn test_arithmetic() {
let mut interp = Interpreter::new();
assert_eq!(interp.eval("1+2").unwrap().to_string(), "3");
assert_eq!(interp.eval("2*3+4").unwrap().to_string(), "10");
assert_eq!(interp.eval("10/2").unwrap().to_string(), "5");
}
#[test]
fn test_variables() {
let mut interp = Interpreter::new();
interp.eval("x=10").unwrap();
interp.eval("y=20").unwrap();
assert_eq!(interp.eval("x+y").unwrap().to_string(), "30");
interp.eval("x=x+5").unwrap();
assert_eq!(interp.eval("x").unwrap().to_string(), "15");
}
#[test]
fn test_eval_with_context() {
let interp = Interpreter::new();
let context = HashMap::from([("x".to_string(), Value::Number(10.0))]);
assert_eq!(
interp.eval_with_context("x + 1", &context).unwrap().to_string(),
"11"
);
}
#[test]
fn test_list() {
let mut interp = Interpreter::new();
interp.eval("x = [1, 2, 3]").unwrap();
assert_eq!(
interp.eval("x + [4, 5, 6]").unwrap().to_string(),
"[1, 2, 3, 4, 5, 6]"
);
assert_eq!(
interp.eval("x[0] == 1").unwrap().to_string(),
"1"
);
}
#[test]
fn test_lambda() {
let mut interp = Interpreter::new();
interp.eval("inc = lambda a: a+1").unwrap();
assert_eq!(interp.eval("inc(10)").unwrap().to_string(), "11");
interp.eval("double = lambda a: a*2").unwrap();
assert_eq!(interp.eval("double(5)").unwrap().to_string(), "10");
}
#[test]
fn test_comparison() {
let mut interp = Interpreter::new();
assert_eq!(interp.eval("1 > 2").unwrap().to_string(), "0");
assert_eq!(interp.eval("1 < 2").unwrap().to_string(), "1");
assert_eq!(interp.eval("1 >= 2").unwrap().to_string(), "0");
assert_eq!(interp.eval("1 <= 2").unwrap().to_string(), "1");
assert_eq!(interp.eval("1 == 2").unwrap().to_string(), "0");
assert_eq!(interp.eval("1 != 2").unwrap().to_string(), "1");
}
#[test]
fn test_initialize_iterable() {
let mut interp = Interpreter::new();
interp.eval("x = [1, 2, 3]").unwrap();
assert_eq!(interp.eval("x[0]").unwrap().to_string(), "1");
interp.eval("y = (1, 2, 3)").unwrap();
assert_eq!(interp.eval("y[0]").unwrap().to_string(), "1");
interp.eval("z = {1, 2, 3}").unwrap();
assert_eq!(interp.eval("z[0]").unwrap().to_string(), "1");
interp.eval("w = {'a': 2, 'b': 3}").unwrap();
assert_eq!(interp.eval("w['a']").unwrap().to_string(), "2");
}
#[test]
fn test_builtin() {
let mut interp = Interpreter::new();
interp.eval("x = [1, 2, 3]").unwrap();
assert_eq!(interp.eval("len(x)").unwrap().to_string(), "3");
assert_eq!(interp.eval("len('abc')").unwrap().to_string(), "3");
interp.eval("x = [1, 2, 3]").unwrap();
assert_eq!(interp.eval("sum(x)").unwrap().to_string(), "6");
assert_eq!(interp.eval("abs(-10)").unwrap().to_string(), "10");
assert_eq!(interp.eval("max(1, 2, 3)").unwrap().to_string(), "3");
assert_eq!(interp.eval("min(1, 2, 3)").unwrap().to_string(), "1");
assert_eq!(interp.eval("int(1.5)").unwrap().to_string(), "1");
assert_eq!(interp.eval("float(1)").unwrap().to_string(), "1");
assert_eq!(interp.eval("str(1)").unwrap().to_string(), "'1'");
assert_eq!(interp.eval("str(1.5)").unwrap().to_string(), "'1.5'");
assert_eq!(
interp.eval("str([1, 2, 3])").unwrap().to_string(),
"'[1, 2, 3]'"
);
assert_eq!(
interp.eval("str({'a': 1, 'b': 2})").unwrap().to_string(),
"'{\\'a\\': 1, \\'b\\': 2}'"
);
assert_eq!(
interp.eval("dict({'a': 1, 'b': 2})").unwrap().to_string(),
"{'a': 1, 'b': 2}"
);
assert_eq!(
interp.eval("list([1, 2, 3])").unwrap().to_string(),
"[1, 2, 3]"
);
assert_eq!(
interp.eval("set([1, 2, 3])").unwrap().to_string(),
"{1, 2, 3}"
);
}
#[test]
fn test_list_comp() {
let mut interp = Interpreter::new();
interp.eval("x = [1, 2, 3, 4, 5]").unwrap();
assert_eq!(
interp.eval("[y * 2 for y in x]").unwrap().to_string(),
"[2, 4, 6, 8, 10]"
);
assert_eq!(
interp
.eval("[y * 2 for y in x if y > 2]")
.unwrap()
.to_string(),
"[6, 8, 10]"
);
}
#[test]
fn test_dict_comp() {
let mut interp = Interpreter::new();
interp.eval("x = [1, 2, 3, 4, 5]").unwrap();
assert_eq!(
interp.eval("{y: y * 2 for y in x}").unwrap().to_string(),
"{'1': 2, '2': 4, '3': 6, '4': 8, '5': 10}"
);
}
#[test]
fn test_if_expr() {
let mut interp = Interpreter::new();
interp.eval("x = 10").unwrap();
assert_eq!(interp.eval("x if x > 5 else 0").unwrap().to_string(), "10");
interp.eval("y = 20").unwrap();
assert_eq!(
interp
.eval("'big' if x > y else 'small'")
.unwrap()
.to_string(),
"'small'"
);
}
#[test]
fn test_chained_conditional() {
let mut interp = Interpreter::new();
assert_eq!(interp.eval("1 if 0 else 2 if 0 else 3").unwrap().to_string(), "3");
assert_eq!(interp.eval("1 if 1 else 2 if 0 else 3").unwrap().to_string(), "1");
assert_eq!(interp.eval("1 if 0 else 2 if 1 else 3").unwrap().to_string(), "2");
}
#[test]
fn test_string_method() {
let mut interp = Interpreter::new();
assert_eq!(interp.eval("'hello'.upper()").unwrap().to_string(), "'HELLO'");
assert_eq!(interp.eval("'Hello'.lower()").unwrap().to_string(), "'hello'");
interp.eval("x = 'a\nb\nc'").unwrap();
assert_eq!(
interp.eval("x.splitlines()").unwrap().to_string(),
"['a', 'b', 'c']"
);
}
#[test]
fn test_list_method() {
let mut interp = Interpreter::new();
interp.eval("x = [1, 2, 3]").unwrap();
assert_eq!(
interp.eval("x.append(4)").unwrap().to_string(),
"[1, 2, 3, 4]"
);
assert_eq!(interp.eval("x.clear()").unwrap().to_string(), "[]");
}
#[test]
fn test_dict_method() {
let mut interp = Interpreter::new();
interp.eval("x = {'a': 1, 'b': 2}").unwrap();
assert_eq!(interp.eval("x.clear()").unwrap().to_string(), "{}");
interp.eval("x = {'a': 1, 'b': 2}").unwrap();
assert_eq!(interp.eval("x.get('a')").unwrap().to_string(), "1");
assert_eq!(interp.eval("x.get('c', 0)").unwrap().to_string(), "0");
interp.eval("x = {'a': 1, 'b': 2}").unwrap();
assert_eq!(
interp.eval("x.items()").unwrap().to_string(),
"[('a', 1), ('b', 2)]"
);
interp.eval("x = {'a': 1, 'b': 2}").unwrap();
assert_eq!(interp.eval("x.keys()").unwrap().to_string(), "['a', 'b']");
interp.eval("x = {'a': 1, 'b': 2}").unwrap();
assert_eq!(interp.eval("x.values()").unwrap().to_string(), "[1, 2]");
}
#[test]
fn test_strings_non_empty_single_quote() {
let interp = Interpreter::new();
assert_eq!(
interp
.eval_with_context("'a'", &Default::default())
.unwrap()
.to_string(),
"'a'"
);
assert_eq!(
interp
.eval_with_context("'hello'", &Default::default())
.unwrap()
.to_string(),
"'hello'"
);
assert!(interp.eval_boolean("'a' == 'a'").unwrap());
}
#[test]
fn test_strings_single_quote_empty() {
let interp = Interpreter::new();
assert_eq!(
interp
.eval_with_context("''", &Default::default())
.unwrap()
.to_string(),
"''"
);
assert!(interp.eval_boolean("'' == ''").unwrap());
assert_eq!(
interp
.eval_with_context("len('')", &Default::default())
.unwrap()
.to_string(),
"0"
);
}
#[test]
fn test_strings_empty_comparison() {
let mut interp = Interpreter::new();
interp.eval("x = ''").unwrap();
assert!(interp.eval_boolean("x == ''").unwrap());
assert!(interp.eval_boolean("x != 'foo'").unwrap());
}
#[test]
fn test_leading_dot_decimals() {
let mut interp = Interpreter::new();
assert_eq!(interp.eval(".02").unwrap().to_string(), "0.02");
assert_eq!(interp.eval(".38").unwrap().to_string(), "0.38");
assert_eq!(interp.eval(".021").unwrap().to_string(), "0.021");
assert_eq!(interp.eval(".5").unwrap().to_string(), "0.5");
assert_eq!(interp.eval("-.021").unwrap().to_string(), "-0.021");
assert_eq!(interp.eval("-.5").unwrap().to_string(), "-0.5");
}
#[test]
fn test_trailing_dot_decimals() {
let mut interp = Interpreter::new();
assert_eq!(interp.eval("4.").unwrap().to_string(), "4");
assert_eq!(interp.eval("10.").unwrap().to_string(), "10");
assert_eq!(interp.eval("123.").unwrap().to_string(), "123");
}
#[test]
fn test_scientific_notation() {
let mut interp = Interpreter::new();
assert_eq!(interp.eval("1e-3").unwrap().to_string(), "0.001");
assert_eq!(interp.eval("1e3").unwrap().to_string(), "1000");
assert_eq!(interp.eval("2.5e-2").unwrap().to_string(), "0.025");
assert_eq!(interp.eval("1e+3").unwrap().to_string(), "1000");
assert_eq!(interp.eval("1E-3").unwrap().to_string(), "0.001");
assert_eq!(interp.eval("1E3").unwrap().to_string(), "1000");
assert_eq!(interp.eval(".5e2").unwrap().to_string(), "50");
assert_eq!(interp.eval(".1e-2").unwrap().to_string(), "0.001");
}
#[test]
fn test_number_parsing_in_expressions() {
let mut interp = Interpreter::new();
interp.eval("width = 10.0").unwrap();
interp.eval("weight = 100.0").unwrap();
interp.eval("reflect = 1.0").unwrap();
interp.eval("pi = 3.14159").unwrap();
assert_eq!(
interp.eval("reflect*(width+.02)").unwrap().to_string(),
"10.02"
);
assert_eq!(
interp.eval(".38 * weight").unwrap().to_string(),
"38"
);
let result = interp.eval("-0.0145 - .021").unwrap();
let n = match result {
Value::Number(n) => n,
other => panic!("Expected Number, got {:?}", other),
};
assert!((n - (-0.0355)).abs() < 1e-10, "Expected -0.0355, got {}", n);
assert_eq!(
interp.eval("1e-3 * weight").unwrap().to_string(),
"0.1"
);
let result = interp.eval("-pi*3/4.").unwrap();
let n = match result {
Value::Number(n) => n,
other => panic!("Expected Number, got {:?}", other),
};
let expected = -3.14159 * 3.0 / 4.0;
assert!((n - expected).abs() < 1e-5, "Expected {}, got {}", expected, n);
}
#[test]
fn test_number_parsing_error_location() {
let mut interp = Interpreter::new();
let result = interp.eval("123..456");
assert!(
matches!(&result, Err(EvalError::ParseError(_))),
"Expected a ParseError for '123..456', but got {:?}",
result
);
let result = interp.eval(".e5");
assert!(
matches!(&result, Err(EvalError::ParseError(_))),
"Expected a ParseError for '.e5', but got {:?}",
result
);
}
#[test]
fn test_not_operator_numbers() {
let mut interp = Interpreter::new();
assert_eq!(interp.eval("not 0").unwrap().to_string(), "1");
assert_eq!(interp.eval("not 1").unwrap().to_string(), "0");
assert_eq!(interp.eval("not 5").unwrap().to_string(), "0");
assert_eq!(interp.eval("not -1").unwrap().to_string(), "0");
assert_eq!(interp.eval("not 0.0").unwrap().to_string(), "1");
}
#[test]
fn test_not_operator_strings() {
let mut interp = Interpreter::new();
assert_eq!(interp.eval("not ''").unwrap().to_string(), "1");
assert_eq!(interp.eval("not 'foo'").unwrap().to_string(), "0");
assert_eq!(interp.eval("not 'hello'").unwrap().to_string(), "0");
}
#[test]
fn test_not_operator_collections() {
let mut interp = Interpreter::new();
assert_eq!(interp.eval("not []").unwrap().to_string(), "1");
assert_eq!(interp.eval("not [1, 2]").unwrap().to_string(), "0");
assert_eq!(interp.eval("not {}").unwrap().to_string(), "1");
assert_eq!(interp.eval("not {'a': 1}").unwrap().to_string(), "0");
assert_eq!(interp.eval("not (1, 2)").unwrap().to_string(), "0");
assert_eq!(interp.eval("not ()").unwrap().to_string(), "1");
}
#[test]
fn test_not_operator_chaining() {
let mut interp = Interpreter::new();
assert_eq!(interp.eval("not not 0").unwrap().to_string(), "0");
assert_eq!(interp.eval("not not 1").unwrap().to_string(), "1");
assert_eq!(interp.eval("not not 5").unwrap().to_string(), "1");
}
#[test]
fn test_not_operator_with_variables() {
let mut interp = Interpreter::new();
interp.eval("x = 0").unwrap();
assert_eq!(interp.eval("not x").unwrap().to_string(), "1");
interp.eval("y = 10").unwrap();
assert_eq!(interp.eval("not y").unwrap().to_string(), "0");
interp.eval("empty = ''").unwrap();
assert_eq!(interp.eval("not empty").unwrap().to_string(), "1");
}
#[test]
fn test_not_operator_in_expressions() {
let mut interp = Interpreter::new();
assert_eq!(interp.eval("not (1 > 2)").unwrap().to_string(), "1");
assert_eq!(interp.eval("not (1 < 2)").unwrap().to_string(), "0");
interp.eval("x = 5").unwrap();
assert_eq!(interp.eval("not (x - 5)").unwrap().to_string(), "1");
assert_eq!(interp.eval("not (x * 2)").unwrap().to_string(), "0");
}
#[test]
fn test_not_operator_precedence() {
let mut interp = Interpreter::new();
assert_eq!(interp.eval("not 1 == 0").unwrap().to_string(), "1");
assert_eq!(interp.eval("not 2 == 1").unwrap().to_string(), "1");
assert_eq!(interp.eval("(not 1) == 0").unwrap().to_string(), "1");
assert_eq!(interp.eval("(not 2) == 1").unwrap().to_string(), "0");
}
#[test]
fn test_builtin_constants() {
let mut interp = Interpreter::new();
assert_eq!(interp.eval("True").unwrap().to_string(), "1");
assert_eq!(interp.eval("False").unwrap().to_string(), "0");
}
#[test]
fn test_builtin_constants_comparisons() {
let mut interp = Interpreter::new();
assert_eq!(interp.eval("True == 1").unwrap().to_string(), "1");
assert_eq!(interp.eval("False == 0").unwrap().to_string(), "1");
assert_eq!(interp.eval("True != False").unwrap().to_string(), "1");
assert_eq!(interp.eval("True == False").unwrap().to_string(), "0");
}
#[test]
fn test_builtin_constants_with_not() {
let mut interp = Interpreter::new();
assert_eq!(interp.eval("not True").unwrap().to_string(), "0");
assert_eq!(interp.eval("not False").unwrap().to_string(), "1");
}
#[test]
fn test_builtin_constants_in_expressions() {
let mut interp = Interpreter::new();
interp.eval("foo = 0").unwrap();
assert_eq!(interp.eval("foo == True").unwrap().to_string(), "0");
assert_eq!(interp.eval("foo == False").unwrap().to_string(), "1");
interp.eval("foo = 1").unwrap();
assert_eq!(interp.eval("foo == True").unwrap().to_string(), "1");
assert_eq!(interp.eval("foo == False").unwrap().to_string(), "0");
assert_eq!(interp.eval("True + 1").unwrap().to_string(), "2");
assert_eq!(interp.eval("False + 1").unwrap().to_string(), "1");
assert_eq!(interp.eval("True * 5").unwrap().to_string(), "5");
assert_eq!(interp.eval("False * 5").unwrap().to_string(), "0");
}
#[test]
fn test_and_operator_basic() {
let mut interp = Interpreter::new();
assert_eq!(interp.eval("1 and 2").unwrap().to_string(), "2");
assert_eq!(interp.eval("0 and 2").unwrap().to_string(), "0");
assert_eq!(interp.eval("1 and 0").unwrap().to_string(), "0");
assert_eq!(interp.eval("0 and 0").unwrap().to_string(), "0");
assert_eq!(interp.eval("True and True").unwrap().to_string(), "1");
assert_eq!(interp.eval("True and False").unwrap().to_string(), "0");
assert_eq!(interp.eval("False and True").unwrap().to_string(), "0");
assert_eq!(interp.eval("False and False").unwrap().to_string(), "0");
}
#[test]
fn test_or_operator_basic() {
let mut interp = Interpreter::new();
assert_eq!(interp.eval("1 or 2").unwrap().to_string(), "1");
assert_eq!(interp.eval("0 or 2").unwrap().to_string(), "2");
assert_eq!(interp.eval("1 or 0").unwrap().to_string(), "1");
assert_eq!(interp.eval("0 or 0").unwrap().to_string(), "0");
assert_eq!(interp.eval("True or True").unwrap().to_string(), "1");
assert_eq!(interp.eval("True or False").unwrap().to_string(), "1");
assert_eq!(interp.eval("False or True").unwrap().to_string(), "1");
assert_eq!(interp.eval("False or False").unwrap().to_string(), "0");
}
#[test]
fn test_and_operator_with_comparisons() {
let mut interp = Interpreter::new();
assert_eq!(interp.eval("(5 > 3) and (10 < 20)").unwrap().to_string(), "1");
assert_eq!(interp.eval("(5 > 10) and (10 < 20)").unwrap().to_string(), "0");
assert_eq!(interp.eval("(5 > 3) and (10 > 20)").unwrap().to_string(), "0");
interp.eval("x = 5").unwrap();
interp.eval("y = 10").unwrap();
assert_eq!(interp.eval("(x > 3) and (y < 20)").unwrap().to_string(), "1");
assert_eq!(interp.eval("(x > 10) and (y < 20)").unwrap().to_string(), "0");
}
#[test]
fn test_or_operator_with_comparisons() {
let mut interp = Interpreter::new();
assert_eq!(interp.eval("(5 > 10) or (10 < 20)").unwrap().to_string(), "1");
assert_eq!(interp.eval("(5 > 10) or (10 > 20)").unwrap().to_string(), "0");
assert_eq!(interp.eval("(5 > 3) or (10 > 20)").unwrap().to_string(), "1");
interp.eval("x = 5").unwrap();
interp.eval("y = 10").unwrap();
assert_eq!(interp.eval("(x > 10) or (y < 20)").unwrap().to_string(), "1");
assert_eq!(interp.eval("(x > 10) or (y > 20)").unwrap().to_string(), "0");
}
#[test]
fn test_and_or_with_strings() {
let mut interp = Interpreter::new();
assert_eq!(interp.eval("'hello' and 'world'").unwrap().to_string(), "'world'");
assert_eq!(interp.eval("'' and 'world'").unwrap().to_string(), "''");
assert_eq!(interp.eval("'hello' and ''").unwrap().to_string(), "''");
assert_eq!(interp.eval("'hello' or 'world'").unwrap().to_string(), "'hello'");
assert_eq!(interp.eval("'' or 'world'").unwrap().to_string(), "'world'");
assert_eq!(interp.eval("'hello' or ''").unwrap().to_string(), "'hello'");
}
#[test]
fn test_and_or_combined() {
let mut interp = Interpreter::new();
assert_eq!(interp.eval("1 and 2 or 0").unwrap().to_string(), "2");
assert_eq!(interp.eval("0 and 2 or 3").unwrap().to_string(), "3");
assert_eq!(interp.eval("1 or 2 and 0").unwrap().to_string(), "1");
assert_eq!(interp.eval("0 or 2 and 3").unwrap().to_string(), "3");
}
#[test]
fn test_and_or_with_not() {
let mut interp = Interpreter::new();
assert_eq!(interp.eval("not 0 and 1").unwrap().to_string(), "1");
assert_eq!(interp.eval("not 1 and 1").unwrap().to_string(), "0");
assert_eq!(interp.eval("not 0 or 0").unwrap().to_string(), "1");
assert_eq!(interp.eval("not 1 or 1").unwrap().to_string(), "1");
}
#[test]
fn test_real_world_xacro_patterns() {
let mut interp = Interpreter::new();
interp.eval("robot_ns = 'my_robot'").unwrap();
assert_eq!(
interp.eval("(robot_ns != '') and (robot_ns != '/')").unwrap().to_string(),
"1"
);
interp.eval("robot_ns = ''").unwrap();
assert_eq!(
interp.eval("(robot_ns != '') and (robot_ns != '/')").unwrap().to_string(),
"0"
);
interp.eval("x = 'a'").unwrap();
assert_eq!(
interp.eval("(x == 'a') or (x == 'b')").unwrap().to_string(),
"1"
);
interp.eval("x = 'c'").unwrap();
assert_eq!(
interp.eval("(x == 'a') or (x == 'b')").unwrap().to_string(),
"0"
);
interp.eval("use_fake_hardware = 1").unwrap();
interp.eval("sim_ignition = 0").unwrap();
interp.eval("sim_isaac = 0").unwrap();
assert_eq!(
interp.eval("use_fake_hardware or sim_ignition or sim_isaac").unwrap().to_string(),
"1"
);
}
#[test]
fn test_mixed_type_eq_returns_false() {
let mut interp = Interpreter::new();
assert_eq!(interp.eval("1.0 == '/'").unwrap().to_string(), "0");
assert_eq!(interp.eval("42 == 'hello'").unwrap().to_string(), "0");
assert_eq!(interp.eval("0 == ''").unwrap().to_string(), "0");
interp.eval("x = 1.0").unwrap();
assert_eq!(interp.eval("x == '/'").unwrap().to_string(), "0");
interp.eval("s = '/'").unwrap();
assert_eq!(interp.eval("1.0 == s").unwrap().to_string(), "0");
}
#[test]
fn test_mixed_type_ne_returns_true() {
let mut interp = Interpreter::new();
assert_eq!(interp.eval("1.0 != '/'").unwrap().to_string(), "1");
assert_eq!(interp.eval("42 != 'hello'").unwrap().to_string(), "1");
assert_eq!(interp.eval("0 != ''").unwrap().to_string(), "1");
interp.eval("x = 1.0").unwrap();
assert_eq!(interp.eval("x != '/'").unwrap().to_string(), "1");
interp.eval("s = '/'").unwrap();
assert_eq!(interp.eval("1.0 != s").unwrap().to_string(), "1");
}
#[test]
fn test_mixed_type_comparison_commutative() {
let mut interp = Interpreter::new();
interp.eval("s = '/'").unwrap();
assert_eq!(interp.eval("s == 1.0").unwrap().to_string(), "0");
assert_eq!(interp.eval("s != 1.0").unwrap().to_string(), "1");
interp.eval("num = 42").unwrap();
interp.eval("str = 'test'").unwrap();
assert_eq!(interp.eval("num == str").unwrap().to_string(), "0");
assert_eq!(interp.eval("str == num").unwrap().to_string(), "0");
assert_eq!(interp.eval("num != str").unwrap().to_string(), "1");
assert_eq!(interp.eval("str != num").unwrap().to_string(), "1");
}
#[test]
fn test_mixed_type_falsy_values() {
let mut interp = Interpreter::new();
interp.eval("empty = ''").unwrap();
assert_eq!(interp.eval("empty != 0").unwrap().to_string(), "1");
assert_eq!(interp.eval("empty == 0").unwrap().to_string(), "0");
assert_eq!(interp.eval("'' == 0").unwrap().to_string(), "0");
assert_eq!(interp.eval("0 == ''").unwrap().to_string(), "0");
}
#[test]
fn test_mixed_type_in_boolean_expressions() {
let mut interp = Interpreter::new();
interp.eval("prefix = 1.0").unwrap();
assert_eq!(interp.eval("prefix == '/'").unwrap().to_string(), "0");
assert_eq!(interp.eval("prefix == ''").unwrap().to_string(), "0");
assert_eq!(interp.eval("prefix == ' '").unwrap().to_string(), "0");
assert_eq!(
interp.eval("prefix == '/' or prefix == '' or prefix == ' '").unwrap().to_string(),
"0"
);
}
#[test]
fn test_same_type_comparisons_still_work() {
let mut interp = Interpreter::new();
assert_eq!(interp.eval("1.0 == 1.0").unwrap().to_string(), "1");
assert_eq!(interp.eval("1.0 != 2.0").unwrap().to_string(), "1");
interp.eval("s1 = 'hello'").unwrap();
interp.eval("s2 = 'hello'").unwrap();
assert_eq!(interp.eval("s1 == s2").unwrap().to_string(), "1");
interp.eval("s3 = 'world'").unwrap();
assert_eq!(interp.eval("s1 != s3").unwrap().to_string(), "1");
assert_eq!(interp.eval("'' == ''").unwrap().to_string(), "1");
}
#[test]
fn test_collection_equality() {
let mut interp = Interpreter::new();
assert_eq!(interp.eval("[1, 2] == [1, 2]").unwrap().to_string(), "1");
assert_eq!(interp.eval("[1, 2] != [1, 3]").unwrap().to_string(), "1");
assert_eq!(interp.eval("[] == []").unwrap().to_string(), "1");
assert_eq!(interp.eval("(1, 2) == (1, 2)").unwrap().to_string(), "1");
assert_eq!(interp.eval("(1, 2) != (1, 3)").unwrap().to_string(), "1");
assert_eq!(interp.eval("() == ()").unwrap().to_string(), "1");
assert_eq!(interp.eval("{'a': 1} == {'a': 1}").unwrap().to_string(), "1");
assert_eq!(interp.eval("{'a': 1} != {'a': 2}").unwrap().to_string(), "1");
assert_eq!(interp.eval("{} == {}").unwrap().to_string(), "1");
assert_eq!(interp.eval("set([1, 2]) == set([1, 2])").unwrap().to_string(), "1");
assert_eq!(interp.eval("set([1, 2]) == set([2, 1])").unwrap().to_string(), "1"); assert_eq!(interp.eval("set([1, 2]) != set([1, 3])").unwrap().to_string(), "1");
assert_eq!(interp.eval("set() == set()").unwrap().to_string(), "1");
assert_eq!(interp.eval("[1, 2] == 5").unwrap().to_string(), "0");
assert_eq!(interp.eval("[1, 2] != 5").unwrap().to_string(), "1");
assert_eq!(interp.eval("set([1, 2]) == 5").unwrap().to_string(), "0");
}
#[test]
fn test_set_equality_with_duplicates() {
let mut interp = Interpreter::new();
assert_eq!(interp.eval("set([1, 1, 2]) == set([1, 2, 2])").unwrap().to_string(), "1");
assert_eq!(interp.eval("set([1, 2]) == set([1, 3])").unwrap().to_string(), "0");
assert_eq!(interp.eval("set([2, 1]) == set([1, 2])").unwrap().to_string(), "1");
}
#[test]
fn test_lambda_comparison() {
let mut interp = Interpreter::new();
interp.eval("f1 = lambda x: 1").unwrap();
interp.eval("f2 = lambda x: 1").unwrap();
assert!(matches!(
interp.eval("f1 == f2"),
Err(crate::EvalError::TypeError)
));
assert!(matches!(
interp.eval("f1 == f1"),
Err(crate::EvalError::TypeError)
));
assert!(matches!(
interp.eval("f1 != f2"),
Err(crate::EvalError::TypeError)
));
}
#[test]
fn test_builtin_comparison() {
let mut interp = Interpreter::new();
interp.eval("f1 = len").unwrap();
interp.eval("f2 = len").unwrap();
assert_eq!(interp.eval("f1 == f2").unwrap().to_string(), "1");
assert_eq!(interp.eval("f1 != f2").unwrap().to_string(), "0");
assert_eq!(interp.eval("len == sum").unwrap().to_string(), "0");
assert_eq!(interp.eval("len != sum").unwrap().to_string(), "1");
}
#[test]
fn test_var_stringlit_cross_type_equality() {
let mut interp = Interpreter::new();
interp.eval("d = {'a': 1, 'b': 2}").unwrap();
interp.eval("keys = [k for k in d]").unwrap();
assert_eq!(interp.eval("keys[0] == 'a'").unwrap().to_string(), "1");
assert_eq!(interp.eval("keys[1] == 'b'").unwrap().to_string(), "1");
assert_eq!(interp.eval("keys == ['a', 'b']").unwrap().to_string(), "1");
assert_eq!(interp.eval("'a' == keys[0]").unwrap().to_string(), "1");
}
#[test]
fn test_dict_display_round_trip_string_keys() {
let mut interp = Interpreter::new();
let input = "{'key': 'value'}";
let parsed = interp.eval(input).unwrap();
let output = parsed.to_string();
let reparsed = interp.eval(&output).unwrap();
assert_eq!(parsed, reparsed);
}
#[test]
fn test_dict_display_round_trip_nested() {
let mut interp = Interpreter::new();
let input = "{'outer': {'inner': 'value', 'num': 42}}";
let parsed = interp.eval(input).unwrap();
let output = parsed.to_string();
assert!(output.contains("'outer'"));
assert!(output.contains("'inner'"));
assert!(output.contains("'value'"));
let reparsed = interp.eval(&output).unwrap();
assert_eq!(parsed, reparsed);
}
#[test]
fn test_dict_display_mixed_value_types() {
let mut interp = Interpreter::new();
let input = "{'str': 'hello', 'num': 42, 'float': 3.14}";
let parsed = interp.eval(input).unwrap();
let output = parsed.to_string();
assert!(output.contains("'str': 'hello'"));
assert!(output.contains("'num': 42"));
assert!(output.contains("'float': 3.14"));
let reparsed = interp.eval(&output).unwrap();
assert_eq!(parsed, reparsed);
}
#[test]
fn test_list_display_round_trip() {
let mut interp = Interpreter::new();
let input = "['a', 'b', 42, 3.14]";
let parsed = interp.eval(input).unwrap();
let output = parsed.to_string();
let reparsed = interp.eval(&output).unwrap();
assert_eq!(parsed, reparsed);
}
#[test]
fn test_dict_display_numeric_string_keys() {
let mut interp = Interpreter::new();
let input = "{'1': 'one', '2': 'two', '3.5': 'three-point-five'}";
let parsed = interp.eval(input).unwrap();
let output = parsed.to_string();
assert!(output.contains("'1': 'one'"));
assert!(output.contains("'2': 'two'"));
assert!(output.contains("'3.5': 'three-point-five'"));
let reparsed = interp.eval(&output).unwrap();
assert_eq!(parsed, reparsed);
}
#[test]
fn test_dict_display_round_trip_simple() {
let mut interp = Interpreter::new();
let input = "{'name': 'test', 'count': 42}";
let parsed = interp.eval(input).unwrap();
let output = parsed.to_string();
let reparsed = interp.eval(&output).unwrap();
assert_eq!(parsed, reparsed);
assert!(output.contains("'name'"));
assert!(output.contains("'count'"));
}
#[test]
fn test_str_on_string_literal() {
use crate::Value;
let mut interp = Interpreter::new();
let result = interp.eval("str('36_11')").unwrap();
if let Value::StringLit(s) = result {
assert_eq!(s, "36_11"); } else {
panic!("Expected StringLit");
}
}
#[test]
fn test_str_on_variable() {
use crate::Value;
let mut interp = Interpreter::new();
interp.eval("x = 'hello'").unwrap();
let result = interp.eval("str(x)").unwrap();
if let Value::StringLit(s) = result {
assert_eq!(s, "hello");
} else {
panic!("Expected StringLit");
}
}
#[test]
fn test_str_on_number() {
use crate::Value;
let mut interp = Interpreter::new();
let result = interp.eval("str(42.5)").unwrap();
if let Value::StringLit(s) = result {
assert_eq!(s, "42.5");
} else {
panic!("Expected StringLit");
}
}
#[test]
fn test_str_on_list() {
use crate::Value;
let mut interp = Interpreter::new();
let result = interp.eval("str([1, 2, 3])").unwrap();
if let Value::StringLit(s) = result {
assert_eq!(s, "[1, 2, 3]");
} else {
panic!("Expected StringLit");
}
}
#[test]
fn test_str_on_dict() {
use crate::Value;
let mut interp = Interpreter::new();
let result = interp.eval("str({'a': 1})").unwrap();
if let Value::StringLit(s) = result {
assert_eq!(s, "{'a': 1}");
} else {
panic!("Expected StringLit");
}
}
#[test]
fn test_str_empty() {
use crate::Value;
let mut interp = Interpreter::new();
let result = interp.eval("str()").unwrap();
if let Value::StringLit(s) = result {
assert_eq!(s, "");
} else {
panic!("Expected StringLit");
}
}
#[test]
fn test_str_in_expression() {
use crate::Value;
let mut interp = Interpreter::new();
interp.eval("family = '36_11'").unwrap();
let result = interp.eval("str(family)").unwrap();
assert_eq!(result, Value::StringLit("36_11".to_string()));
}
#[test]
fn test_str_with_replace_method() {
use crate::Value;
let mut interp = Interpreter::new();
interp.eval("family = '36_11'").unwrap();
let result = interp.eval("str(family).replace('_', '-')").unwrap();
if let Value::StringLit(s) = result {
assert_eq!(s, "36-11"); } else {
panic!("Expected StringLit");
}
}
#[test]
fn test_lambda_equality_behavior() {
let mut interp = Interpreter::new();
let result = interp.eval("(lambda x: 1) == (lambda x: 1)");
assert!(matches!(result, Err(EvalError::TypeError)));
assert_eq!(
interp.eval("[lambda x: 1] == [lambda x: 1]").unwrap().to_string(),
"0"
);
assert_eq!(
interp.eval("[lambda x: 1] == [lambda x: 2]").unwrap().to_string(),
"0"
);
assert_eq!(
interp.eval("['a', lambda x: 1] == ['a', lambda x: 1]").unwrap().to_string(),
"0"
);
}
}