WAT — leading (define ...) forms in a lambda body now bind LOCALLY
(letrec*-equivalent) instead of polluting the global env. Implementation:
apply for closures pre-processes the body in three passes:
1. hoist_internal_defines walks leading defines, env_define each name
to VOID in the new env, returns the extended env.
2. strip_leading_defines returns the body with the defines removed.
3. fill_internal_defines evaluates each define's value-expression in
the new env (so mutual references work) and env_set the real value.
(define x 1)
(define (f) (define x 99) x)
(f) ; → 99 (was 99, still 99)
x ; → 1 (was 99 wrongly — fixed)
(define (h) (define helper (lambda (x) (* x 2))) (helper 5))
helper ; → unbound (was a leaked global procedure — fixed)
C-WASM — added a thorough doc-block in lumbda_wasm_entry.c covering
the gc.c fallback malloc situation and three plausible real fixes
(Boehm-em build, custom mark-sweep over NaN-boxed heap, generational
reset). Repl tabbar already surfaces the pressure to the user.
Parity corpus locks the new scoping behavior:
internal-define-local — global x stays 1
internal-define-returns — f returns 99
internal-define-mutual — mutually-recursive internal defines
Tests: 20 unit, 8 integration, 11 functional, 249 parity all green.
156 lines
11 KiB
JavaScript
156 lines
11 KiB
JavaScript
// wasm/tests/parity-corpus.mjs
|
|
// The cross-tier parity corpus. Each entry is one expression; we expect
|
|
// every tier to produce the same output. Entries are tagged with the
|
|
// whitepaper section / R7RS concept they exercise so a regression can be
|
|
// traced to the normative claim it violates.
|
|
//
|
|
// `expected` is set to the python tier's output (the reference, per
|
|
// "Exact rational arithmetic uses Python's Fraction type"); we mark the
|
|
// known-divergent ones with `knownDiverge: ["asm"]` so the suite remains
|
|
// green while documenting the gap.
|
|
|
|
export const CORPUS = [
|
|
// ─── numeric tower: rationals ────────────────────────────────────
|
|
{ tag: "rat-div", src: "(/ 67 7)", expected: "67/7" },
|
|
{ tag: "rat-div-one", src: "(/ 1 3)", expected: "1/3" },
|
|
{ tag: "rat-div-exact", src: "(/ 6 2)", expected: "3" },
|
|
{ tag: "rat-add", src: "(+ 1/3 1/6)", expected: "1/2" },
|
|
{ tag: "rat-mul", src: "(* 2/3 3/4)", expected: "1/2" },
|
|
{ tag: "rat-sub", src: "(- 3/4 1/2)", expected: "1/4" },
|
|
{ tag: "rat-mixed-add", src: "(+ 1 1/2)", expected: "3/2" },
|
|
{ tag: "rat-eq-norm", src: "(= 1/2 2/4)", expected: "#t" },
|
|
{ tag: "rat-eq-int", src: "(= 3 6/2)", expected: "#t" },
|
|
{ tag: "rat-lt", src: "(< 1/3 1/2)", expected: "#t" },
|
|
{ tag: "number?-rat", src: "(number? 1/3)", expected: "#t" },
|
|
{ tag: "integer?-rat", src: "(integer? 1/3)", expected: "#f" },
|
|
{ tag: "integer?-int", src: "(integer? 6/2)", expected: "#t" },
|
|
|
|
// ─── numeric tower: bignums (whitepaper §2.1 claim) ──────────────
|
|
{ tag: "expt-2-100", src: "(expt 2 100)",
|
|
expected: "1267650600228229401496703205376" },
|
|
{ tag: "expt-3-50", src: "(expt 3 50)",
|
|
expected: "717897987691852588770249" },
|
|
{ tag: "big-arith", src: "(* 12345678901234567890 12345678901234567890)",
|
|
expected: "152415787532388367501905199875019052100" },
|
|
{ tag: "expt-2-1024", src: "(expt 2 1024)",
|
|
expected: "179769313486231590772930519078902473361797697894230657273430081157732675805500963132708477322407536021120113879871393357658789768814416622492847430639474124377767893424865485276302219601246094119453082952085005768838150682342462881473913110540827237163350510684586298239947245938479716304835356329624224137216" },
|
|
{ tag: "big-zero", src: "(- (expt 2 100) (expt 2 100))", expected: "0" },
|
|
{ tag: "big-cmp", src: "(< 99999999999 1000000000000)", expected: "#t" },
|
|
{ tag: "big-eq", src: "(= (expt 2 100) (* (expt 2 50) (expt 2 50)))", expected: "#t" },
|
|
{ tag: "big-int?", src: "(integer? (expt 2 100))", expected: "#t" },
|
|
{ tag: "big-num?", src: "(number? (expt 2 100))", expected: "#t" },
|
|
|
|
// ─── GC / memory pressure (allocate-and-drop) ────────────────────
|
|
// These exercise the asm-wasm copying collector and verify the
|
|
// Python + C tier GCs handle the same workload identically.
|
|
{ tag: "gc-throwaway", expected: "ok",
|
|
src: "(let loop ((i 0)) (if (= i 500) (quote ok) (begin (cons i i) (loop (+ i 1)))))" },
|
|
{ tag: "gc-retained-length", expected: "500",
|
|
src: "(define lst (let loop ((i 0) (acc (quote ()))) (if (= i 500) acc (loop (+ i 1) (cons i acc))))) (length lst)" },
|
|
{ tag: "gc-survives-eval", expected: "(1 4 9 16)",
|
|
src: "(let loop ((i 0)) (if (= i 200) 0 (begin (cons i i) (loop (+ i 1))))) (map (lambda (x) (* x x)) (list 1 2 3 4))" },
|
|
|
|
// ─── Internal-define scoping (R7RS letrec*) ──────────────────────
|
|
{ tag: "internal-define-local", expected: "1",
|
|
src: "(define x 1) (define (f) (define x 99) x) (f) x" },
|
|
{ tag: "internal-define-returns", expected: "99",
|
|
src: "(define (f) (define x 99) x) (f)" },
|
|
{ tag: "internal-define-mutual", expected: "#t",
|
|
src: "(define (g) (define (e? n) (if (= n 0) #t (o? (- n 1)))) (define (o? n) (if (= n 0) #f (e? (- n 1)))) (e? 6)) (g)" },
|
|
|
|
// ─── division semantics ──────────────────────────────────────────
|
|
{ tag: "quotient-pos", src: "(quotient 17 5)", expected: "3" },
|
|
{ tag: "quotient-neg", src: "(quotient -17 5)", expected: "-3" },
|
|
{ tag: "remainder-pos", src: "(remainder 17 5)", expected: "2" },
|
|
{ tag: "remainder-neg", src: "(remainder -17 5)", expected: "-2" },
|
|
{ tag: "modulo-pos", src: "(modulo 17 5)", expected: "2" },
|
|
{ tag: "modulo-neg", src: "(modulo -17 5)", expected: "3" },
|
|
|
|
// ─── arithmetic edge cases ───────────────────────────────────────
|
|
{ tag: "neg-arith", src: "(- 5)", expected: "-5" },
|
|
{ tag: "empty-add", src: "(+)", expected: "0" },
|
|
{ tag: "empty-mul", src: "(*)", expected: "1" },
|
|
{ tag: "multi-arith", src: "(+ 1 2 3 4 5)", expected: "15" },
|
|
{ tag: "abs-pos", src: "(abs 5)", expected: "5" },
|
|
{ tag: "abs-neg", src: "(abs -5)", expected: "5" },
|
|
{ tag: "min", src: "(min 5 3 8 1 7)", expected: "1" },
|
|
{ tag: "max", src: "(max 5 3 8 1 7)", expected: "8" },
|
|
|
|
// ─── booleans / truthiness ───────────────────────────────────────
|
|
{ tag: "if-true", src: "(if #t 'yes 'no)", expected: "yes" },
|
|
{ tag: "if-false", src: "(if #f 'yes 'no)", expected: "no" },
|
|
{ tag: "if-zero", src: "(if 0 'truthy 'falsy)", expected: "truthy" },
|
|
{ tag: "if-nil", src: "(if '() 'truthy 'falsy)", expected: "truthy" },
|
|
{ tag: "if-empty-str", src: "(if \"\" 'truthy 'falsy)", expected: "truthy" },
|
|
{ tag: "not-#f", src: "(not #f)", expected: "#t" },
|
|
{ tag: "not-#t", src: "(not #t)", expected: "#f" },
|
|
{ tag: "not-zero", src: "(not 0)", expected: "#f" },
|
|
|
|
// ─── equality ────────────────────────────────────────────────────
|
|
{ tag: "eq?-int", src: "(eq? 1 1)", expected: "#t" },
|
|
{ tag: "eq?-symbol", src: "(eq? 'a 'a)", expected: "#t" },
|
|
{ tag: "equal?-list", src: "(equal? '(1 2 3) '(1 2 3))", expected: "#t" },
|
|
{ tag: "equal?-str", src: "(equal? \"abc\" \"abc\")", expected: "#t" },
|
|
|
|
// ─── pairs / lists ───────────────────────────────────────────────
|
|
{ tag: "cons", src: "(cons 1 2)", expected: "(1 . 2)" },
|
|
{ tag: "list", src: "(list 1 2 3)", expected: "(1 2 3)" },
|
|
{ tag: "length", src: "(length '(a b c d))", expected: "4" },
|
|
{ tag: "reverse", src: "(reverse '(1 2 3))", expected: "(3 2 1)" },
|
|
{ tag: "append", src: "(append '(1 2) '(3 4))", expected: "(1 2 3 4)" },
|
|
{ tag: "map", src: "(map (lambda (x) (* x x)) '(1 2 3 4))",
|
|
expected: "(1 4 9 16)" },
|
|
{ tag: "filter-odd", src: "(filter odd? '(1 2 3 4 5))",
|
|
expected: "(1 3 5)" },
|
|
{ tag: "fold-left", src: "(fold-left + 0 '(1 2 3 4 5))", expected: "15" },
|
|
|
|
// ─── strings ─────────────────────────────────────────────────────
|
|
{ tag: "str-len", src: "(string-length \"hello\")", expected: "5" },
|
|
{ tag: "str-append", src: "(string-append \"foo\" \"bar\")",
|
|
expected: "foobar" },
|
|
{ tag: "substring", src: "(substring \"hello world\" 6 11)",
|
|
expected: "world" },
|
|
{ tag: "str-upcase", src: "(string-upcase \"hello\")", expected: "HELLO" },
|
|
{ tag: "str-num", src: "(string->number \"42\")", expected: "42" },
|
|
{ tag: "num-str", src: "(number->string 42)", expected: "42" },
|
|
|
|
// ─── characters ──────────────────────────────────────────────────
|
|
{ tag: "char-int", src: "(char->integer #\\A)", expected: "65" },
|
|
{ tag: "int-char", src: "(integer->char 65)", expected: "A" },
|
|
{ tag: "char-alpha", src: "(char-alphabetic? #\\a)", expected: "#t" },
|
|
|
|
// ─── vectors ─────────────────────────────────────────────────────
|
|
{ tag: "vec-make", src: "(vector 1 2 3)", expected: "#(1 2 3)" },
|
|
{ tag: "vec-len", src: "(vector-length (vector 1 2 3))", expected: "3" },
|
|
{ tag: "vec-ref", src: "(vector-ref (vector 10 20 30) 1)", expected: "20" },
|
|
|
|
// ─── special forms ──────────────────────────────────────────────
|
|
{ tag: "let-basic", src: "(let ((x 3) (y 4)) (+ x x y))", expected: "10" },
|
|
{ tag: "let*-shadow", src: "(let* ((x 1) (x (+ x 10)) (x (* x 2))) x)",
|
|
expected: "22" },
|
|
{ tag: "letrec", src: "(letrec ((f (lambda (n) (if (= n 0) 1 (* n (f (- n 1))))))) (f 5))",
|
|
expected: "120" },
|
|
{ tag: "named-let", src: "(let loop ((i 0) (acc 0)) (if (= i 10) acc (loop (+ i 1) (+ acc i))))",
|
|
expected: "45" },
|
|
{ tag: "cond-else", src: "(cond ((= 1 2) 'no) (else 'yes))", expected: "yes" },
|
|
{ tag: "case-match", src: "(case 2 ((1) 'one) ((2 3) 'two-three) (else 'big))",
|
|
expected: "two-three" },
|
|
{ tag: "when", src: "(when (> 3 1) 'yes)", expected: "yes" },
|
|
{ tag: "and-pass", src: "(and 1 2 3)", expected: "3" },
|
|
{ tag: "or-first", src: "(or #f 7 8)", expected: "7" },
|
|
|
|
// ─── deep recursion / TCO ───────────────────────────────────────
|
|
{ tag: "tco-loop", src: "(let loop ((i 0)) (if (= i 50000) i (loop (+ i 1))))",
|
|
expected: "50000" },
|
|
{ tag: "mutual-tco", src: "(define (a n) (if (= n 0) 'done-a (b (- n 1)))) (define (b n) (if (= n 0) 'done-b (a (- n 1)))) (a 100000)",
|
|
expected: "done-a" },
|
|
];
|
|
|
|
// Tiers where each tag is known to diverge today. Keep this short and
|
|
// remove entries as the gaps close — that's how we track "% to parity".
|
|
// As of the WAT bignum landing (tag 10 + num_add/sub/mul/cmp promotion),
|
|
// the corpus has zero known divergences and the whitepaper §2.1 claim
|
|
// "(expt 2 1024) returns the exact value across all three tiers" is
|
|
// satisfied. New tests that surface a fresh gap get added here so the
|
|
// regression suite stays green.
|
|
export const KNOWN_DIVERGE = {};
|