use dexpr::{ast::value::Value, compiler::Compiler, parser, vm::VM}; use indexmap::IndexMap; use rust_decimal::Decimal; use smol_str::SmolStr; use std::collections::HashMap; use std::rc::Rc; use std::str::FromStr; #[derive(serde::Deserialize)] struct TestCase { name: String, code: String, #[serde(default)] globals: HashMap, expected: ValueDef, } #[derive(serde::Deserialize)] struct ValueDef { #[serde(rename = "type")] typ: String, #[serde(default)] value: Option, } fn value_def_to_value(def: &ValueDef) -> Value { match def.typ.as_str() { "null" => Value::Null, "number" => { let s = def.value.as_ref().unwrap().as_str().unwrap(); Value::Number(Decimal::from_str(s).unwrap()) } "string" => { let s = def.value.as_ref().unwrap().as_str().unwrap(); Value::String(s.into()) } "boolean" => { let b = def.value.as_ref().unwrap().as_bool().unwrap(); Value::Boolean(b) } "object" => { fn json_obj_to_value(obj: &serde_json::Map) -> Value { let mut map = IndexMap::new(); for (k, v) in obj { let val = match v { serde_json::Value::String(s) => { if let Ok(d) = Decimal::from_str(s) { Value::Number(d) } else { Value::String(SmolStr::from(s.as_str())) } } serde_json::Value::Bool(b) => Value::Boolean(*b), serde_json::Value::Object(nested) => json_obj_to_value(nested), _ => Value::String(SmolStr::from(v.to_string())), }; map.insert(SmolStr::from(k.as_str()), val); } Value::Object(Rc::new(map)) } let obj = def.value.as_ref().unwrap().as_object().unwrap(); json_obj_to_value(obj) } other => panic!("Unknown type: {other}"), } } #[test] fn test_all_cases() { let json = include_str!("test_cases.json"); let cases: Vec = serde_json::from_str(json).expect("Failed to parse test_cases.json"); let mut failures = Vec::new(); let total = cases.len(); for case in &cases { let ast = match parser::program(&case.code) { Ok(ast) => ast, Err(e) => { failures.push(format!( "FAIL: {}\n code: {}\n error: parse failed: {e}", case.name, case.code.replace('\n', "\\n"), )); continue; } }; let mut compiler = Compiler::new(); let bytecode = match compiler.compile(ast) { Ok(bc) => bc, Err(e) => { failures.push(format!( "FAIL: {}\n code: {}\n error: compile failed: {e}", case.name, case.code.replace('\n', "\\n"), )); continue; } }; let mut vm = VM::new(&bytecode); for (name, def) in &case.globals { vm.set_global(name, value_def_to_value(def)); } let result = match vm.execute() { Ok(v) => v, Err(e) => { failures.push(format!( "FAIL: {}\n code: {}\n error: execute failed: {e}", case.name, case.code.replace('\n', "\\n"), )); continue; } }; let expected = value_def_to_value(&case.expected); if result != expected { failures.push(format!( "FAIL: {}\n code: {}\n expected: {:?}\n got: {:?}", case.name, case.code.replace('\n', "\\n"), expected, result )); } } if !failures.is_empty() { panic!( "\n{} / {} test cases failed:\n\n{}\n", failures.len(), total, failures.join("\n\n") ); } eprintln!("All {total} test cases passed."); }