Wire the real pipeline end to end: host/chez/driver.janet boots a compile-mode jolt ctx, runs the EXISTING Janet-hosted analyzer on actual Clojure source to real IR, feeds it to the Scheme emitter, and runs the result on Chez. Analysis stays on Janet (the analyzer ports to Chez in Phase 2); execution is on Chez. emit.janet now consumes live IR (pv/phm-normalized like the Janet backend) and covers what the analyzer actually emits, not the hand-built inc-1 shapes: - core ops arrive as :var clojure.core/+ etc., not :rt — lowered to native Scheme via a native-ops table (mirrors backend.janet's), `=` to jolt=. - var cells (host/chez/rt.ss): :def -> def-var!, :var -> var-deref. Late binding so cross-var calls (run -> count-point) and the entry crossing resolve at use. - named fns (defn / fn self-name) bind via letrec so self-recursion resolves. - unsupported stdlib/host refs (no core on Chez yet) are rejected at EMIT time (clean out-of-subset signal) instead of deref'ing to nil and failing at runtime. Number model: jolt is all-doubles (no ratios; (/ 1 2) is 0.5), so literals emit as flonums — matches the Janet host and keeps Chez out of exploding exact rationals (mandelbrot). jolt-num->string prints integer-valued without ".0". Two real bugs found via the corpus probe and fixed (regression rows added): - loop bound in parallel (Scheme named-let) but Clojure loop is sequential — a later init must see earlier bindings; wrap a let* around the loop. - #(...) shorthand gensyms params with a trailing `#`, invalid in Scheme — munge it to `_`. Gate: test/chez/emit-test.janet runs the real analyzer -> Chez for (+ 1 2), fib(30)=832040, mandelbrot run(40), and the two regressions, parity-checked against the Janet oracle (6/6). First parity number via the new subset probe (test/chez/run-corpus-chez.janet, JOLT_CHEZ_CORPUS=1): 182/182 compiled corpus cases pass, 0 divergences; 2473/2655 out of subset pending core on Chez. Full jpm/run-tests gate green (125 files). Chez tests skip cleanly without `chez`. Perf note (unchanged plan): emitted fib(30) ~23ms vs hand-Scheme ~5ms — the jolt-truthy? wrapper (~3x) plus flonum (not fixnum) arithmetic, both Phase-4 type-specialization levers.
3.3 KiB
Chez port — Phase 0 test contract harness
The host-neutral correctness gate for the Chez re-host (epic jolt-cf1q). The spec corpus is data, so the SAME contract validates every host.
Files
extract-corpus.janet— parsestest/spec/*.janet(defspec …)tables as data and writescorpus.edn(2655[label expected actual]cases). The file is valid as BOTH EDN (a future Chez-jolt runner) and Janet data (the runner below). Regenerate:janet test/chez/extract-corpus.janet.corpus.edn— the extracted contract (generated; checked in for convenience).run-corpus.janet— drives a TARGET jolt binary, one fresh subprocess per case (fresh ctx = per-case isolation), checking(= expected actual)printstrueat the CLI, or that a:throwscase exits non-zero. Pluggable target:janet test/chez/run-corpus.janet# default build/joltJOLT_BIN=build/jolt-chez janet test/chez/run-corpus.janet# Phase 1+JOLT_CORPUS_LIMIT=400 …# every-Nth stride, fast
known-divergences.edn— allowlist of cases that diverge at the CLI boundary. The gate fails only on a NEW divergence; known ones are reported but tolerated.values-test.ss/../../host/chez/values.ss— Phase 0a value model + tests.
The reference baseline (2026-06-17, Janet build/jolt, compile mode)
2641/2655 pass; 14 known divergences. They split into:
- interpret-vs-compile leniency —
:throwscases where interpret mode raises but compile mode returns (< nil,> with nil,neg? keyword,max/min-keyon non-numbers). Several are also non-canonical vs JVM Clojure. - invoke-collection-as-fn — the
transient / invokable lookupsuite invokes transients/collections as fns (((transient {:x 7}) :x)); compile mode (and JVM Clojure) reject it. xml-seq walks— one structural case.
The compile-only Chez host (JVM-canonical oracle) should MATCH OR FIX these. The gate's job is to catch regressions the port introduces, not to bless these.
Why the CLI boundary
The runner tests through jolt -e, exactly how the Chez host will be exercised —
not the in-process eval-string the Janet defspec harness uses. The two differ
on a handful of cases (the allowlist), and the CLI boundary is the portable one.
Phase 1 — first parity number (subset probe)
The full run-corpus.janet gate drives an -e-capable jolt binary; the Chez
host can't answer arbitrary -e until all of clojure.core is bootstrapped onto
Chez (Phase 2). Until then, run-corpus-chez.janet reports parity for the subset
the Phase-1 back end (host/chez/emit.janet) can already compile: each case is
run through the live analyzer → Scheme emitter → Chez via host/chez/driver.
Cases that reference unimplemented stdlib/host fns fail to EMIT (a clean
compile-time signal) and are counted "out of subset", not as divergences.
JOLT_CHEZ_CORPUS=1 janet test/chez/run-corpus-chez.janet
Baseline (2026-06-17): 182/182 compiled cases pass, 0 divergences; 2473/2655
out of subset (await core on Chez). It's a slow report (a Chez subprocess per
case), so it's gated behind JOLT_CHEZ_CORPUS out of the default suite, like the
benches. test/chez/emit-test.janet is the fast Phase-1 unit gate (real
analyzer → Chez parity for fib/mandelbrot + regressions); both skip cleanly when
chez isn't on PATH.