#[cfg(test)]
mod tests {
use crate::install_full_stdlib_with;
use crate::Interpreter;
use crate::Value;
use tatara_lisp::read_spanned;
struct NoHost;
struct ParityCase {
name: &'static str,
src: &'static str,
}
const PARITY_CASES: &[ParityCase] = &[
ParityCase {
name: "literal-int",
src: "42",
},
ParityCase {
name: "literal-float",
src: "3.14",
},
ParityCase {
name: "literal-bool",
src: "#t",
},
ParityCase {
name: "literal-string",
src: "\"hello\"",
},
ParityCase {
name: "literal-keyword",
src: ":foo",
},
ParityCase {
name: "arithmetic-add",
src: "(+ 1 2 3 4 5)",
},
ParityCase {
name: "arithmetic-mixed",
src: "(+ (* 3 4) (- 10 5))",
},
ParityCase {
name: "comparison",
src: "(< 1 2 3)",
},
ParityCase {
name: "modulo",
src: "(modulo 17 5)",
},
ParityCase {
name: "if-then",
src: "(if #t 1 2)",
},
ParityCase {
name: "if-else",
src: "(if #f 1 2)",
},
ParityCase {
name: "and-truthy",
src: "(and 1 2 3)",
},
ParityCase {
name: "or-short-circuit",
src: "(or #f #f 7)",
},
ParityCase {
name: "not-true",
src: "(not #t)",
},
ParityCase {
name: "define-then-use",
src: "(define x 42) x",
},
ParityCase {
name: "define-then-set",
src: "(define x 1) (set! x 99) x",
},
ParityCase {
name: "let-binding",
src: "(let ((x 10) (y 20)) (+ x y))",
},
ParityCase {
name: "nested-let",
src: "(let ((x 1)) (let ((y 2)) (+ x y)))",
},
ParityCase {
name: "list-construct",
src: "(list 1 2 3)",
},
ParityCase {
name: "list-cons",
src: "(cons 0 (list 1 2 3))",
},
ParityCase {
name: "list-length",
src: "(length (list 1 2 3 4 5))",
},
ParityCase {
name: "list-reverse",
src: "(reverse (list 1 2 3))",
},
ParityCase {
name: "list-append",
src: "(append (list 1 2) (list 3 4))",
},
ParityCase {
name: "hash-map-construct-and-get",
src: "(hash-map-get (hash-map :a 1 :b 2) :a)",
},
ParityCase {
name: "hash-map-set-returns-new",
src: "(hash-map-count (hash-map-set (hash-map :a 1) :b 2))",
},
ParityCase {
name: "lambda-inline",
src: "((lambda (x y) (+ x y)) 3 4)",
},
ParityCase {
name: "closure-make-adder",
src: "(define (make-adder n) (lambda (x) (+ x n)))
((make-adder 10) 32)",
},
ParityCase {
name: "closure-captures-let-local",
src: "(let ((x 10)) ((lambda (y) (+ x y)) 5))",
},
ParityCase {
name: "closure-captures-chain",
src: "(let ((x 5))
(let ((f (lambda (a) (lambda (b) (+ x a b)))))
((f 3) 4)))",
},
ParityCase {
name: "recursion-factorial",
src: "(define (fact n) (if (= n 0) 1 (* n (fact (- n 1)))))
(fact 6)",
},
ParityCase {
name: "tco-deep-loop",
src: "(define (loop n) (if (= n 0) :done (loop (- n 1))))
(loop 10000)",
},
ParityCase {
name: "map-square-lambda",
src: "(map (lambda (x) (* x x)) (list 1 2 3 4))",
},
ParityCase {
name: "filter-evens-lambda",
src: "(filter (lambda (x) (= 0 (modulo x 2))) (list 1 2 3 4 5))",
},
ParityCase {
name: "map-with-toplevel-fn",
src: "(define (sqr x) (* x x))
(map sqr (list 1 2 3 4))",
},
ParityCase {
name: "filter-with-toplevel-fn",
src: "(define (even? x) (= 0 (modulo x 2)))
(filter even? (list 1 2 3 4 5))",
},
ParityCase {
name: "foldl-sum-native",
src: "(foldl + 0 (list 1 2 3 4 5))",
},
ParityCase {
name: "foldr-sum-native",
src: "(foldr + 0 (list 1 2 3 4 5))",
},
ParityCase {
name: "reduce-product-native",
src: "(reduce * 1 (list 1 2 3 4 5))",
},
ParityCase {
name: "foldl-with-lambda",
src: "(foldl (lambda (acc x) (+ acc (* x x))) 0 (list 1 2 3 4))",
},
ParityCase {
name: "map-then-filter-pipeline",
src: "(filter (lambda (x) (> x 4))
(map (lambda (x) (* x x))
(list 1 2 3 4)))",
},
ParityCase {
name: "try-no-throw",
src: "(try (+ 1 2) (catch (e) :unreachable))",
},
ParityCase {
name: "try-catches-throw",
src: "(try
(throw (ex-info \"boom\" (list)))
(catch (e) (error-message e)))",
},
ParityCase {
name: "try-catches-runtime",
src: "(try (/ 1 0) (catch (e) (error-tag e)))",
},
ParityCase {
name: "fallback-eval-quoted",
src: "(eval (quote (+ 1 2 3)))",
},
ParityCase {
name: "quasi-quote-with-global",
src: "(define x 99) `(a ,x c)",
},
ParityCase {
name: "quasi-quote-with-let-local",
src: "(let ((x 42)) `(a ,x c))",
},
ParityCase {
name: "quasi-quote-arithmetic-unquote",
src: "(let ((y 10)) `(sum is ,(+ y 5)))",
},
ParityCase {
name: "quasi-quote-unquote-splice",
src: "(let ((xs (list 2 3 4))) `(1 ,@xs 5))",
},
ParityCase {
name: "quasi-quote-mixed-splice",
src: "(let ((a 1) (bs (list 2 3)) (c 4)) `(start ,a ,@bs ,c end))",
},
ParityCase {
name: "quasi-quote-empty-list-result",
src: "`()",
},
ParityCase {
name: "quasi-quote-keyword-and-string",
src: "`(:tag \"hello\" 42)",
},
ParityCase {
name: "channel-send-recv",
src: "(define ch (chan))
(>! ch 1)
(>! ch 2)
(list (<! ch) (<! ch))",
},
ParityCase {
name: "type-the-passes",
src: "(the :int 42)",
},
ParityCase {
name: "type-of-keyword",
src: "(type-of :foo)",
},
ParityCase {
name: "type-is-true-on-match",
src: "(is? 42 :int)",
},
];
fn eval_tree(name: &str, src: &str) -> Value {
let mut i: Interpreter<NoHost> = Interpreter::new();
install_full_stdlib_with(&mut i, &mut NoHost);
let forms = read_spanned(src).unwrap();
i.eval_program(&forms, &mut NoHost)
.unwrap_or_else(|e| panic!("tree-walker failed on case {name}: {e:?}"))
}
fn eval_vm(name: &str, src: &str) -> Value {
let mut i: Interpreter<NoHost> = Interpreter::new();
install_full_stdlib_with(&mut i, &mut NoHost);
let forms = read_spanned(src).unwrap();
i.eval_program_vm(&forms, &mut NoHost)
.unwrap_or_else(|e| panic!("vm failed on case {name}: {e:?}"))
}
#[test]
fn parity_across_paths() {
let mut failures: Vec<(String, String, String)> = Vec::new();
for case in PARITY_CASES {
let tree = eval_tree(case.name, case.src);
let vm = eval_vm(case.name, case.src);
let tree_str = format!("{tree}");
let vm_str = format!("{vm}");
if tree_str != vm_str {
failures.push((case.name.to_string(), tree_str, vm_str));
}
}
if !failures.is_empty() {
let msg = failures
.into_iter()
.map(|(n, t, v)| format!(" {n:30} tree={t:?} vm={v:?}"))
.collect::<Vec<_>>()
.join("\n");
panic!(
"VM parity failures in {} cases:\n{msg}",
PARITY_CASES.len()
);
}
}
#[test]
fn parity_table_has_minimum_coverage() {
assert!(
PARITY_CASES.len() >= 30,
"parity table shrunk to {} cases — keep coverage broad",
PARITY_CASES.len()
);
}
}