Move the Chez test oracle off Janet

The unit tests in test/chez/_*.janet now drive bin/joltc (the zero-Janet
spine) and judge against baked expected values instead of a live build/jolt
run. Ten of them captured the oracle from build/jolt per case; their values
are now literals (one env-dependent javastatic case became a predicate so it
stays portable). The rest already had literal expecteds with a redundant
build/jolt sanity check, now dropped.

Retire emit-test/emit-parity/reader-parity: they compared the Chez/Clojure
path against a live Janet evaluation, emitter, or reader. That migration check
is done, and run-corpus-zero-janet (Chez analyzer vs the JVM corpus) plus
certify.clj cover correctness now.

Rewrite the README for the current zero-Janet gate.

jolt-5oci
This commit is contained in:
Yogthos 2026-06-21 09:48:49 -04:00
parent 8d4f83e0f7
commit 9a273e71cd
21 changed files with 205 additions and 1510 deletions

View file

@ -1,248 +1,60 @@
# Chez port — Phase 0 test contract harness # Chez test harness
The host-neutral correctness gate for the Chez re-host (epic jolt-cf1q). The The correctness gate for jolt on Chez (epic jolt-cf1q). Correctness is judged
spec corpus is data, so the SAME contract validates every host. against the JVM-sourced conformance spec — no Janet decides pass/fail. The spec
itself lives in `test/conformance/` (see its `SPEC.md`); this directory holds the
Chez runners and unit tests.
## Files ## The spec corpus
- `extract-corpus.janet` — parses `test/spec/*.janet` `(defspec …)` tables as
data and writes `corpus.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)` prints `true`
at the CLI, or that a `:throws` case exits non-zero. Pluggable target:
- `janet test/chez/run-corpus.janet` # default build/jolt
- `JOLT_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) - `corpus.edn` — the contract: ~2920 rows `{:suite :label :expected :actual}`,
2641/2655 pass; 14 known divergences. They split into: with `:expected` sourced from reference JVM Clojure. Valid as both EDN and Janet
- **interpret-vs-compile leniency**`:throws` cases where interpret mode raises data. Generated by `test/conformance/regen-corpus.clj` (the answers) over the
but compile mode returns (`< nil`, `> with nil`, `neg? keyword`, `max`/`min-key` case list from `extract-corpus.janet` (the `:actual` strings, pulled from
on non-numbers). Several are also non-canonical vs JVM Clojure. `test/spec/*.janet` and `conformance-test.janet`).
- **invoke-collection-as-fn** — the `transient / invokable lookup` suite invokes - `extract-corpus.janet` — re-derives the case list when spec rows change. Writes
transients/collections as fns (`((transient {:x 7}) :x)`); compile mode (and only when `JOLT_EXTRACT_CORPUS_OUT` is set, so a gate run never clobbers the
JVM Clojure) reject it. JVM-sourced `corpus.edn`. After re-deriving, re-source the answers with
- **`xml-seq walks`** — one structural case. `regen-corpus.clj`.
The compile-only Chez host (JVM-canonical oracle) should MATCH OR FIX these. The ## The standing gate
gate's job is to catch *regressions* the port introduces, not to bless these.
## Why the CLI boundary `run-corpus-zero-janet.janet` runs every corpus case through the zero-Janet
The runner tests through `jolt -e`, exactly how the Chez host will be exercised — spine: read → analyze → IR → emit → eval, all on Chez (the analyzer is
not the in-process `eval-string` the Janet `defspec` harness uses. The two differ `jolt.analyzer` cross-compiled to Scheme over `host-contract.ss`). Each result is
on a handful of cases (the allowlist), and the CLI boundary is the portable one. compared by value-equality against the JVM `:expected`. A `known-fail` allowlist
covers cases jolt can't match because Chez has no JVM host (Java classes, arrays,
`BigDecimal`, opaque host-object printers, …); the gate fails only on a NEW
divergence or if the pass count drops below the floor.
## Phase 1 — first parity number (subset probe) JOLT_CHEZ_ZEROJANET_CORPUS=1 janet test/chez/run-corpus-zero-janet.janet
The full `run-corpus.janet` gate drives an `-e`-capable jolt binary; the Chez JOLT_CORPUS_LIMIT=200 … # every-Nth stride, fast iteration
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 Floor: 2678 (`JOLT_CHEZ_ZJ_FLOOR` overrides). Raise it as host gaps close.
Baseline after inc 3g (letfn + declare): **672/672 compiled cases pass**, 0 ## Unit tests
divergences; 1986/2658 out of subset (await clojure.core on Chez). Inc 3e
(throw/try + ex-info) was 632/632; inc 3f's quote support + a seq.ss fix (empty
`map`/`filter` results are `()` not nil, matching Clojure) reached 664/664; inc 3g
(letfn -> Scheme `letrec*`, declare/def-no-init -> a reserved var cell) pulled 8
more corpus cases into the subset. `emit-fn` lowers multi-arity fns to a Scheme
`case-lambda` and variadic fns to a rest-arg lambda (rest list coerced to a jolt
seq, nil when empty).
## Phase 1 — clojure.core prelude emission (inc 3d, jolt-ocvi) `_*.janet` drive `bin/joltc` (the pure-Chez CLI; `JOLT_BIN` overrides) one
The `-e`-capable jolt-chez path: emit the clojure.core tiers subprocess per case and compare the last stdout line against a baked expected
(`jolt-core/clojure/core/NN-*.clj`) through the same analyzer → emit pipeline as a value. `:throws` asserts a non-zero exit. Each file targets one area —
Scheme PRELUDE of `def-var!` forms, so user code's `(var-deref "clojure.core" …)` `_type`/`_class`/`_seqpred` (taxonomy + predicates), `_str`/`_strns`/`_reader`
resolves the fn at runtime. `emit/set-prelude-mode!` flips a switch: in the default (strings + reader), `_io`/`_ioreader`/`_insttime`/`_javastatic` (host interop),
(subset) mode a non-native `clojure.core` ref is rejected ("out of subset"); in `_dynbind`/`_var_meta`/`_ns` (vars + namespaces), `_atomwatch`/`_walk`/`_dotform`
prelude mode it lowers to a runtime `var-deref` so core fns chain through each /`_stdlib` (refs, walk, dot-forms, stdlib). Run one with
other. Host interop (`:host`) and unhandled IR ops still error in both modes — `janet test/chez/_type.janet`.
those are the real gaps that need a hand-written RT shim or new emit support.
`core-prelude-probe.janet` (gated behind `JOLT_CHEZ_PRELUDE=1`) measures reach and ## Self-host gates
catalogs the gaps; macros are skipped (analyze-time only, not a runtime value):
JOLT_CHEZ_PRELUDE=1 janet test/chez/core-prelude-probe.janet - `spine-test.janet` — the zero-Janet compile/eval spine end to end.
- `cli-test.janet``bin/joltc -e` behavior.
- `bootstrap-test.janet``host/chez/bootstrap.ss` rebuilds the prelude + image
from source on Chez and matches the checked-in seed (`host/chez/seed/`).
- `fixpoint-test.janet` — the on-Chez compiler reproduces itself (stage2 == stage3,
prelude pstage3 == pstage4).
Baseline after inc 3i (regex): **355/355 non-macro core forms emit** to Scheme — ## Bench
the whole non-macro clojure.core now lowers. inc 3i closed the last gap, the regex
literal in `parse-uuid`: a `#"…"` literal lowers to a `:regex` IR node and the Chez
emitter emits a `jolt-regex` value over **vendored irregex** (Alex Shinn, BSD,
`vendor/irregex` submodule) — a portable Scheme regex with PCRE/Java-style string
patterns. `re-pattern`/`re-matches`/`re-find`/`re-seq`/`regex?` are `def-var!`'d
into clojure.core (`host/chez/regex.ss`); they stay OUT of the subset native-ops
(irregex's Unicode/property-class semantics differ from the seed's byte-PEG
approximation), so they resolve in prelude mode — the path the assembled prelude
takes — without dragging engine-difference divergences into the subset corpus. The
Janet back end punts `:regex` to the interpreter (the seed compiles `#"…"` to a
Janet PEG). Prior incs: inc 3h `.method``:host-call` (`jolt-host-call` for
`.write`/`.isDirectory`/`.listFiles`); `:quote`, `:throw`, `:try`, `ex-info`,
`letfn``letrec*`, `declare`/def-no-init → reserved var cell. The probe has a
regression floor (355) — every non-macro core form must keep emitting.
## Phase 1 — the assembled prelude: -e-capable jolt-chez (inc 3j, jolt-9ziu) `bench-pipeline.janet` (opt-in via `JOLT_CHEZ_BENCH=1`) times fib + mandelbrot
Once the whole non-macro clojure.core emits (inc 3i), the milestone is to ASSEMBLE through the real pipeline against the spike ceiling. Skipped in the gate.
it: `driver/emit-core-prelude` emits every non-macro core form across the
dependency-ordered tiers as a `def-var!` (prelude mode), concatenated into a
Scheme prelude. `bin/jolt-chez -e EXPR` loads `rt.ss` + that prelude + a
post-prelude override, emits the user expression in prelude mode, and runs it on
Chez — an `-e`-capable jolt-chez (analysis on Janet, execution on Chez). The
prelude is cached on disk keyed by a fingerprint of the core sources + the RT.
`run-corpus-prelude.janet` is the full parity gate this opens (the prelude-backed All Chez runners skip cleanly when `chez` is not on PATH.
sibling of `run-corpus-chez.janet`): it assembles the prelude once, then runs every
corpus case with all of core present, bucketing the result —
JOLT_CHEZ_PRELUDE_CORPUS=1 janet test/chez/run-corpus-prelude.janet
JOLT_CORPUS_LIMIT=200 … (every-Nth stride, fast)
Parity baseline: inc 3j **1220/2497**; 3k (converters, jolt-t6cr) **1326**;
3l (transients, jolt-kl2l) **1382**; 3m (numeric-edge emit + variadic assoc!,
jolt-q3w8) **1407/2497**, 0 NEW divergences (14 allowlisted:
dynamic vars `*ns*`/`*clojure-version*`/`*unchecked-math*`, var/`*ns*` rendering,
class names, eval-order, with-open — all deferred Phase-2 / dynamic-var gaps).
- inc 3k `host/chez/converters.ss`: `str`/`subs`/`vec`/`keyword`/`symbol`/`compare`/
`int`/`double`/`gensym` (seed natives — `str` reuses the printer, `compare` is the
3-way port, the symbol no-ns sentinel is `#f` to match emit's quoted-symbol
lowering so `(= 'x (symbol "x"))` holds).
- inc 3l `host/chez/transients.ss`: `transient`/`persistent!`/`conj!`/`assoc!`/
`dissoc!`/`disj!`/`pop!` as copy-on-write over the persistent collections (correct
semantics, no in-place perf), plus persistent `disj`. `get`/`count`/`contains?`
are redefined to see THROUGH a transient (frequencies/group-by do `(get tm k)` on a
transient map); `vector?` on a transient vector is false, which group-by relies on.
- inc 3m: `##Inf`/`##-Inf`/`##NaN` emitted to bare `inf`/`nan` (unbound in Chez);
emit-const now lowers them to `+inf.0`/`-inf.0`/`+nan.0`, the `-e` printer renders
`inf`/`-inf`/`nan` and `str` renders `Infinity`/`-Infinity`/`NaN` (Clojure). Plus
variadic `assoc!`. (`str` of inf *inside a collection* still wants the long form —
the Phase-2 recursive str renderer — so `[inf inside coll]` is allowlisted.)
- inc 3n `host/chez/natives-seq.ss` (jolt-y6mv): the dominant prelude-parity crash
bucket was `apply non-procedure jolt-nil` — core fns calling seed-native seq fns
(`src/jolt/core_coll.janet`) that have no Chez shim, so `var-deref` yields
jolt-nil. A static scan of the assembled prelude found 52 referenced-but-undefined
`clojure.core` names; this increment shims the safe, high-value seq fns: `mapcat`/
`take-while`/`drop-while`/`partition` (collection arities — the 1-arg transducer
forms are jolt-kxsr), `sort` (compare default; a comparator may return a 3-way
number or a boolean less-than), and `reduced`/`reduced?` — a `jolt-reduced` record
in `seq.ss` that `reduce` short-circuits on and `deref` unwraps (so `unreduced`
works). `identical?` = `jolt=` (the seed's definition). `list?` was deferred: a
Chez lazy seq and a list are both `cseq`, so it can't be told apart without a
distinct list type (a real divergence risk).
- inc 3o transducer arities (jolt-kxsr): the 1-arg `map`/`filter`/`remove`/`take`/
`drop`/`take-while`/`drop-while`/`mapcat` return a transducer `(fn [rf] rf')`, and
`into` gets a 3-arg `(into to xform from)`. These were the `cdr () is not a pair` /
`incorrect number of arguments` crash bucket: the emitter lowers `(map f)` /
`(into to xf from)` at the wrong arity to the bare native procedure (the
value-position path), so the fix is RT-side — `case-lambda` the seq procedures and
`jolt-into`. The `td-*` factories are ported from the seed (core_coll.janet); a
`reduced` step stops the fold via reduce-seq's short-circuit. `transduce`/`comp`/
`completing` are overlay and compose over these for free.
- inc 3p misc seq/regex gaps (jolt-y1zq): 0-arg `(conj)` -> `[]`, 0-arg `(conj!)` ->
a fresh transient vector, `nth` sees through a transient (like get/count/contains?),
and irregex `\p{...}`/`\P{...}` property classes translate to the seed's ASCII char
classes (regex.ss; `\p{L}`->`[a-zA-Z]`, `\p{N}`->`[0-9]`, `\p{Ps}`->`[([{]`, …) —
ASCII-only (the seed counts UTF-8 high bytes as letters, which a Unicode-char Scheme
string can't reproduce; the corpus tests ASCII, where they agree) and
`clojure.math/PI` (missing ns).
Two pre-existing bugs surfaced here, since fixed (inc 3x / 3y below).
- inc 3q multimethod dispatch + late-bind (jolt-9ls5, Phase 2): `host/chez/
multimethods.ss` implements the multimethod runtime — `defmulti`/`defmethod`
expand to `defmulti-setup`/`defmethod-setup` calls (+ `get-method`/`methods`/
`remove-method`/`prefer-method`/`prefers`). A `jolt-multifn` record carries its
dispatch fn and a `jolt=`-keyed method table; `jolt-invoke` dispatches it (exact
match, then isa?/hierarchy with `prefer-method`, then `:default`), reusing the
overlay's `isa?`/`derive`/`make-hierarchy`. The multifn's ns is set via a runtime
`chez-current-ns` (default "user"; the prelude load sets "clojure.core").
Two emit-side changes made this work:
- **late-bind** (`:late-bind-unresolved?` ctx flag, default OFF): `defmulti`
expands to a bare-symbol setup *call*, so the analyzer doesn't intern the name
and a forward reference (`(area …)` after `(defmulti area …)` in one form) was
"Unable to resolve symbol". The strict compiler punts these to the interpreter;
the Chez back end has none, so the flag makes an unresolved symbol lower to a
`var-ref` against the compile ns — the open-world semantics of `-e`. Set only by
the Chez `make-ctx`/`jolt-chez`; the main compiler keeps its strict behavior.
- a `:var` call head now routes through `jolt-invoke` (not a direct application),
since a late-bound var can hold a multifn (or a keyword/coll IFn), not just a
procedure. Transparent for procedures; the hot self-recursive call is a `:local`
known-proc, so it stays direct. (Class-based dispatch — `(class x)`/`String` — is
deferred; it needs the deftype/class subsystem.)
- inc 3r dynamic-var constants (jolt-9ls5): `host/chez/dynamic-vars.ss` binds the seed
natives `*clojure-version*` (the `{:major 1 :minor 11 …}` map) and `*unchecked-math*`
(false), removing their two parity allowlist entries. `*ns*` is deferred (jolt-b4kl):
it needs a namespace value that is not a map (`map?` false) yet answers
`(get ns :name)` for the overlay `ns-name`, plus `str`/`find-ns` support.
- inc 3x non-ASCII string literals (jolt-x0os): `emit.janet`'s `chez-str-lit` replaces
the `%j` string encoder. Janet's `%j` renders a non-ASCII char as raw UTF-8 bytes
(`\xC3\xA9`) and a control char / DEL as `\xHH` with NO terminating semicolon — both
forms Chez's reader rejects. `chez-str-lit` UTF-8-decodes each char and emits a
codepoint hex escape `\x<cp>;` (`é`->`\xe9;`, `日`->`\x65e5;`), keeping `\n`/`\t`/`\r`/
`\"`/`\\` and being byte-identical to `%j` for printable ASCII. Applied to every
string-content site (string/keyword/symbol/var-name/regex-source). This unblocks the
`p{L} utf-8` corpus case (the `\p{L}` translation was already correct). Note: `count`/
`subs`/`nth` over a multibyte string index by BYTES in the seed but by codepoints on
Chez — a separate semantic gap, not addressed here.
- inc 3y seed assoc! odd-args (jolt-ea9k): `core-assoc!` (src/jolt/core_extra.janet) now
throws on an odd key/val count, like plain `assoc` and Clojure's `assoc!` — the former
lenient nil-fill was non-Clojure and inconsistent with the seed's own `assoc`. The 4
`assoc! odd args` spec rows became 3 `:throws` + 1 even-args positive; corpus.edn
regenerated. The Chez host already threw, so this only realigns the corpus contract.
- Phase 1 close-out (jolt-nkcb): truthy? elision + end-to-end compute bench. `emit.janet`
now drops the redundant `jolt-truthy?` wrapper on an `:if` test that provably yields a
Scheme boolean (a native comparison/`not`, or a boolean const) — sound because
`jolt-truthy?` of `#t`/`#f` is identity; the hot fib/mandelbrot tests are all
comparisons, so this is a direct ceiling lever (fib 30 end-to-end 24.0 -> 14.4 ms).
`bench-pipeline.janet` (JOLT_CHEZ_BENCH=1, opt-in) times fib(30) + mandelbrot(200)
through the real pipeline vs the spike ceiling: mandelbrot 87 ms runs AT the
generic-flonum ceiling (98 ms); fib is 2x its hand-flonum ceiling, the residual being
jolt's all-double number model (typed fl*/fx* emission = Phase 4). Compile-only is
total for the compute subset (every form emits; Chez has no interpreter fallback).
The remaining buckets are the punch-list the next increments chase: ~361 emit-fail
(genuine host interop — qualified Java/Janet refs, runtime `defmacro`/`eval`, out of
the analyzer's subset) and the runtime crashes still dominated by `apply jolt-nil`
(more host-coupled natives without a shim: `meta`/`with-meta`, `format`, the
`clojure.string` natives, bit ops, `var`/`volatile`/`future`/thread-binding ops),
plus the small jolt-y1zq tail (0-arg `conj`, `\p{}` regex classes, a few one-offs)
and multimethod dispatch (Phase 2 jolt-9ls5).
Two host shims landed with the prelude. `host/chez/atoms.ss`: atom/deref/swap!/
reset! (+ compare-and-set!/swap-vals!/reset-vals!) — host-coupled mutable cells the
overlay assumes are native; needed at the prelude's LOAD time
(`global-hierarchy = (atom (make-hierarchy))`). `host/chez/predicates.ss`: the type
predicates + `name`/`namespace`/`boolean` the overlay assumes are seed natives
(`nil?`/`number?`/`string?`/`map?`/`vector?`/`set?`/`seq?`/`coll?`/`fn?`/…), matching
the seed's strict semantics. `host/chez/post-prelude.ss` re-asserts `char?`/`atom?`
AFTER the prelude — the overlay implements those two by reading a value's
`:jolt/type` key (a Janet-host assumption that's false for Chez-native chars/atoms),
and its `def-var!` would otherwise clobber the correct native shims.
The 8 print-method/print-dup `defmulti`/`defmethod` forms (50-io) can't LOAD yet
(no multimethod runtime on Chez — Phase 2); a silent load guard in the assembled
prelude lets the rest load and turns them into lazy gaps.
Prior, inc 3b (seq tier + dynamic IFn, jolt-5pso): 595/595 compiled, 0 divergences,
2060/2655 out of subset. The seq tier brought up a list/lazy-seq type with
first/rest/next/seq/cons/list, map/filter/reduce/into/remove,
range/take/drop/concat/apply, keys/vals, and nth/peek/pop over seqs; dynamic IFn
dispatch (a keyword/vector/coll held in a local and called as a fn) routes through
the `jolt-invoke` fallback, closing the 3 ex-known divergences. The probe exits
non-zero on any NEW divergence.
(Prior, inc 3a: 433/436 compiled, 3 known IFn divergences, 2219 out of subset.
Inc 2: 182/182 compiled, 0 divergences, 2473 out of subset.)
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 + collections + regressions); both skip cleanly when
`chez` isn't on PATH.

View file

@ -1,24 +1,24 @@
# jolt-mn9o — atom watches + validators on Chez. The Chez atom record carries # jolt-mn9o — atom watches + validators on Chez. The Chez atom record carries
# watches (alist) + validator slots; swap!/reset! validate-then-set-then-notify; # watches (alist) + validator slots; swap!/reset! validate-then-set-then-notify;
# add-watch/remove-watch/set-validator!/get-validator are native (post-prelude.ss # add-watch/remove-watch/set-validator!/get-validator are native (post-prelude.ss
# re-asserts them over the overlay's ref-put!-based versions). Oracle = build/jolt. # re-asserts them over the overlay's ref-put!-based versions).
# #
# janet test/chez/_atomwatch.janet # janet test/chez/_atomwatch.janet
(def jolt-bin (or (os/getenv "JOLT_BIN") "bin/jolt-chez")) (def jolt-bin (or (os/getenv "JOLT_BIN") "bin/joltc"))
# [expr throws?] — when throws? the case is expected to exit non-zero on both # [expr expected] — :throws asserts a non-zero exit (validator rejection); the
# (the corpus :throws contract; the exception message text may differ). # exception message text is not compared.
(def cases (def cases
[["(let [a (atom 0) seen (atom 0)] (add-watch a :k (fn [k r o n] (reset! seen 1))) (reset! a 5) @seen)" false] [["(let [a (atom 0) seen (atom 0)] (add-watch a :k (fn [k r o n] (reset! seen 1))) (reset! a 5) @seen)" "1"]
["(let [a (atom 0) seen (atom 0)] (add-watch a :k (fn [k r o n] (swap! seen inc))) (remove-watch a :k) (reset! a 5) @seen)" false] ["(let [a (atom 0) seen (atom 0)] (add-watch a :k (fn [k r o n] (swap! seen inc))) (remove-watch a :k) (reset! a 5) @seen)" "0"]
["(let [a (atom 0) log (atom [])] (add-watch a :k (fn [k r o n] (swap! log conj [o n]))) (reset! a 1) (reset! a 2) @log)" false] ["(let [a (atom 0) log (atom [])] (add-watch a :k (fn [k r o n] (swap! log conj [o n]))) (reset! a 1) (reset! a 2) @log)" "[[0 1] [1 2]]"]
["(let [a (atom 0)] (set-validator! a number?) (reset! a 5) @a)" false] ["(let [a (atom 0)] (set-validator! a number?) (reset! a 5) @a)" "5"]
["(let [a (atom 5)] (set-validator! a pos?) (swap! a inc) @a)" false] ["(let [a (atom 5)] (set-validator! a pos?) (swap! a inc) @a)" "6"]
["(let [a (atom 0)] (set-validator! a number?) (fn? (get-validator a)))" false] ["(let [a (atom 0)] (set-validator! a number?) (fn? (get-validator a)))" "true"]
["(let [a (atom 0)] (nil? (get-validator a)))" false] ["(let [a (atom 0)] (nil? (get-validator a)))" "true"]
["(let [a (atom 0)] (set-validator! a pos?) (reset! a -1))" true] ["(let [a (atom 0)] (set-validator! a pos?) (reset! a -1))" :throws]
["(let [a (atom 5)] (set-validator! a pos?) (swap! a (fn [_] -1)) @a)" true] ["(let [a (atom 5)] (set-validator! a pos?) (swap! a (fn [_] -1)) @a)" :throws]
["(let [a (atom 0) c (atom 0)] (add-watch a :k (fn [k r o n] (swap! c inc))) (swap! a inc) (swap! a inc) @c)" false]]) ["(let [a (atom 0) c (atom 0)] (add-watch a :k (fn [k r o n] (swap! c inc))) (swap! a inc) (swap! a inc) @c)" "2"]])
(defn run-capture [bin expr] (defn run-capture [bin expr]
(def proc (os/spawn [bin "-e" expr] :p {:out :pipe :err :pipe})) (def proc (os/spawn [bin "-e" expr] :p {:out :pipe :err :pipe}))
@ -31,17 +31,15 @@
(var pass 0) (var pass 0)
(def fails @[]) (def fails @[])
(each [expr throws?] cases (each [expr expected] cases
(def [ocode oracle _] (run-capture "build/jolt" expr))
(def [code got err] (run-capture jolt-bin expr)) (def [code got err] (run-capture jolt-bin expr))
(cond (cond
throws? (= expected :throws)
(if (and (not= ocode 0) (not= code 0)) (++ pass) (if (not= code 0) (++ pass)
(array/push fails [expr (string "expected both throw; oracle exit " ocode ", chez exit " code)])) (array/push fails [expr (string "expected throw; exit " code)]))
(not= ocode 0) (array/push fails [expr (string "ORACLE FAILED exit " ocode)])
(not= code 0) (array/push fails [expr (string "exit " code "; err: " err)]) (not= code 0) (array/push fails [expr (string "exit " code "; err: " err)])
(= got oracle) (++ pass) (= got expected) (++ pass)
(array/push fails [expr (string "want `" oracle "`, got `" got "`")]))) (array/push fails [expr (string "want `" expected "`, got `" got "`")])))
(printf "\n_atomwatch parity [%s]: %d/%d passed" jolt-bin pass (length cases)) (printf "\n_atomwatch parity [%s]: %d/%d passed" jolt-bin pass (length cases))
(when (> (length fails) 0) (when (> (length fails) 0)

View file

@ -2,12 +2,11 @@
# Keyword, File...) evaluates to its JVM canonical-name STRING — the same value # Keyword, File...) evaluates to its JVM canonical-name STRING — the same value
# (class instance) returns — so (= String (class "x")) holds and a (defmethod m # (class instance) returns — so (= String (class "x")) holds and a (defmethod m
# String ...) keys against a (class ...) dispatch. host-class.ss ports # String ...) keys against a (class ...) dispatch. host-class.ss ports
# src/jolt/eval_resolve.janet's class-canonical-names + core-class (scalar arms). # Scalar (class x) returns the JVM-style class-name symbol.
# Oracle = build/jolt. Collection (class ...) is host-taxonomy-dependent (the seed # Collection (class ...) is host-taxonomy-dependent and is NOT compared here.
# leaks the Janet host type "table"/"struct") and is NOT compared here.
# #
# janet test/chez/_class.janet # janet test/chez/_class.janet
(def jolt-bin (or (os/getenv "JOLT_BIN") "bin/jolt-chez")) (def jolt-bin (or (os/getenv "JOLT_BIN") "bin/joltc"))
(def cases (def cases
[# --- bare class tokens evaluate to canonical strings --- [# --- bare class tokens evaluate to canonical strings ---
@ -46,11 +45,8 @@
(var pass 0) (var pass 0)
(def fails @[]) (def fails @[])
(each [expr expected] cases (each [expr expected] cases
(def [ocode oracle _] (run-capture "build/jolt" expr))
(def [code got err] (run-capture jolt-bin expr)) (def [code got err] (run-capture jolt-bin expr))
(cond (cond
(not= ocode 0) (array/push fails [expr (string "ORACLE FAILED exit " ocode)])
(not= oracle expected) (array/push fails [expr (string "ORACLE MISMATCH want `" expected "` got `" oracle "`")])
(not= code 0) (array/push fails [expr (string "exit " code "; err: " err)]) (not= code 0) (array/push fails [expr (string "exit " code "; err: " err)])
(= got expected) (++ pass) (= got expected) (++ pass)
(array/push fails [expr (string "want `" expected "`, got `" got "`")]))) (array/push fails [expr (string "want `" expected "`, got `" got "`")])))

View file

@ -2,45 +2,45 @@
# analyzer lowers (. target member arg*) and (.-field target) to a :host-call; # analyzer lowers (. target member arg*) and (.-field target) to a :host-call;
# the Chez emit routes a non-shimmed :host-call through record-method-dispatch, # the Chez emit routes a non-shimmed :host-call through record-method-dispatch,
# which dot-forms.ss extends with field access + map/vector member dispatch. # which dot-forms.ss extends with field access + map/vector member dispatch.
# Expectations are the build/jolt (seed) oracle, captured per case. # Each case carries its expected printed value.
# #
# janet test/chez/_dotform.janet # janet test/chez/_dotform.janet
(def jolt-bin (or (os/getenv "JOLT_BIN") "bin/jolt-chez")) (def jolt-bin (or (os/getenv "JOLT_BIN") "bin/joltc"))
(def exprs (def cases
[# strings: method calls via the String surface [# strings: method calls via the String surface
"(. \"HI\" toLowerCase)" ["(. \"HI\" toLowerCase)" "hi"]
"(. \"abc\" length)" ["(. \"abc\" length)" "3"]
"(. \"abc\" toUpperCase)" ["(. \"abc\" toUpperCase)" "ABC"]
# vectors / maps: collection interop (count/nth/get/containsKey) # vectors / maps: collection interop (count/nth/get/containsKey)
"(. [1 2 3] count)" ["(. [1 2 3] count)" "3"]
"(. [10 20 30] nth 1)" ["(. [10 20 30] nth 1)" "20"]
"(. {:a 1 :b 2} count)" ["(. {:a 1 :b 2} count)" "2"]
"(. {:a 1 :b 2} get :b)" ["(. {:a 1 :b 2} get :b)" "2"]
"(. {:a 1} containsKey :a)" ["(. {:a 1} containsKey :a)" "true"]
"(. {:count 99} count)" ["(. {:count 99} count)" "1"]
# map member: stored fn called with self (+ args), else field value # map member: stored fn called with self (+ args), else field value
"(. {:value 41 :describe (fn [self] (str \"v=\" (:value self)))} describe)" ["(. {:value 41 :describe (fn [self] (str \"v=\" (:value self)))} describe)" "v=41"]
"(. {:greet (fn [self n] (str \"Hello \" n))} greet \"Alice\")" ["(. {:greet (fn [self n] (str \"Hello \" n))} greet \"Alice\")" "Hello Alice"]
"(. {:value 41} value)" ["(. {:value 41} value)" "41"]
# field access via .-field head # field access via .-field head
"(.-value {:value 41})" ["(.-value {:value 41})" "41"]
"(.-x {:x 7 :y 9})" ["(.-x {:x 7 :y 9})" "7"]
# field access via (. obj -field) # field access via (. obj -field)
"(. {:value 41} -value)" ["(. {:value 41} -value)" "41"]
# records: .-field reads a field, .method dispatches the protocol method # records: .-field reads a field, .method dispatches the protocol method
"(do (defrecord Rf [x]) (.-x (->Rf 7)))" ["(do (defrecord Rf [x]) (.-x (->Rf 7)))" "7"]
"(do (defrecord Rg [a b]) (.-b (->Rg 1 2)))" ["(do (defrecord Rg [a b]) (.-b (->Rg 1 2)))" "2"]
"(do (defprotocol Greet (hi [_])) (defrecord Rh [nm] Greet (hi [_] (str \"hi \" nm))) (. (->Rh \"x\") hi))" ["(do (defprotocol Greet (hi [_])) (defrecord Rh [nm] Greet (hi [_] (str \"hi \" nm))) (. (->Rh \"x\") hi))" "hi x"]
# universal object-methods on a non-record map win over a field lookup # universal object-methods on a non-record map win over a field lookup
"(try (throw (ex-info \"bad\" {})) (catch Throwable e (.getMessage e)))" ["(try (throw (ex-info \"bad\" {})) (catch Throwable e (.getMessage e)))" "bad"]
"(try (throw (ex-info \"bad\" {:k 1})) (catch Throwable e (.getMessage e)))" ["(try (throw (ex-info \"bad\" {:k 1})) (catch Throwable e (.getMessage e)))" "bad"]
"(try (throw \"boom\") (catch Throwable e (.getMessage e)))" ["(try (throw \"boom\") (catch Throwable e (.getMessage e)))" "boom"]
"(try (throw (Exception. \"boom\")) (catch Throwable e (.getMessage e)))" ["(try (throw (Exception. \"boom\")) (catch Throwable e (.getMessage e)))" "boom"]
"(try (throw (IllegalArgumentException. \"bad\")) (catch Exception e (.getMessage e)))" ["(try (throw (IllegalArgumentException. \"bad\")) (catch Exception e (.getMessage e)))" "bad"]
"(.equals \"a\" \"a\")" ["(.equals \"a\" \"a\")" "true"]
"(.equals \"a\" \"b\")" ["(.equals \"a\" \"b\")" "false"]
"(. {:value 41 :describe (fn [self] (str \"v=\" (:value self)))} describe)"]) ["(. {:value 41 :describe (fn [self] (str \"v=\" (:value self)))} describe)" "v=41"]])
(defn run-capture [bin expr] (defn run-capture [bin expr]
(def proc (os/spawn [bin "-e" expr] :p {:out :pipe :err :pipe})) (def proc (os/spawn [bin "-e" expr] :p {:out :pipe :err :pipe}))
@ -53,16 +53,14 @@
(var pass 0) (var pass 0)
(def fails @[]) (def fails @[])
(each expr exprs (each [expr expected] cases
(def [ocode oracle _] (run-capture "build/jolt" expr))
(def [code got err] (run-capture jolt-bin expr)) (def [code got err] (run-capture jolt-bin expr))
(cond (cond
(not= ocode 0) (array/push fails [expr (string "ORACLE FAILED exit " ocode)])
(not= code 0) (array/push fails [expr (string "exit " code "; err: " err)]) (not= code 0) (array/push fails [expr (string "exit " code "; err: " err)])
(= got oracle) (++ pass) (= got expected) (++ pass)
(array/push fails [expr (string "want `" oracle "`, got `" got "`")]))) (array/push fails [expr (string "want `" expected "`, got `" got "`")])))
(printf "\n_dotform parity [%s]: %d/%d passed" jolt-bin pass (length exprs)) (printf "\n_dotform parity [%s]: %d/%d passed" jolt-bin pass (length cases))
(when (> (length fails) 0) (when (> (length fails) 0)
(printf "%d FAIL(s):" (length fails)) (printf "%d FAIL(s):" (length fails))
(each [e m] fails (printf " FAIL %s\n %s" e m))) (each [e m] fails (printf " FAIL %s\n %s" e m)))

View file

@ -1,10 +1,10 @@
# jolt-2o7x — dynamic var binding (binding / with-bindings* / var-set / # jolt-2o7x — dynamic var binding (binding / with-bindings* / var-set /
# thread-bound? / with-local-vars / with-redefs / bound-fn* / get-thread-bindings). # thread-bound? / with-local-vars / with-redefs / bound-fn* / get-thread-bindings).
# Expectations are the JVM-canonical build/jolt values. TDD harness: bin/jolt-chez # Expectations are the JVM-canonical values. TDD harness: bin/joltc
# -e per case, last non-empty line == expected. # -e per case, last non-empty line == expected.
# #
# janet test/chez/_dynbind.janet # janet test/chez/_dynbind.janet
(def jolt-bin (or (os/getenv "JOLT_BIN") "bin/jolt-chez")) (def jolt-bin (or (os/getenv "JOLT_BIN") "bin/joltc"))
(def cases (def cases
[# --- binding: install / restore / seen across a fn call --- [# --- binding: install / restore / seen across a fn call ---
@ -26,10 +26,8 @@
["thread-bound? in scope" "(do (def ^:dynamic *tc* 1) (binding [*tc* 2] (thread-bound? (var *tc*))))" "true"] ["thread-bound? in scope" "(do (def ^:dynamic *tc* 1) (binding [*tc* 2] (thread-bound? (var *tc*))))" "true"]
# --- with-bindings* / bound-fn* / get-thread-bindings --- # --- with-bindings* / bound-fn* / get-thread-bindings ---
# NOTE: build/jolt (the seed) returns 1 here — its persistent-hash-map can't # Binding frames look up by var cell identity, so a var-keyed binding map
# find a var key, which is why the seed's `binding` macro uses array-map. On # resolves: the Clojure-correct value is 7.
# Chez array-map IS hash-map and frames look up by cell identity, so this is
# the correct Clojure value 7 (a place Chez is more correct than the seed).
["with-bindings*" "(do (def ^:dynamic *wb* 1) (with-bindings* {(var *wb*) 7} (fn [] *wb*)))" "7"] ["with-bindings*" "(do (def ^:dynamic *wb* 1) (with-bindings* {(var *wb*) 7} (fn [] *wb*)))" "7"]
["bound-fn* conveys" "(do (def ^:dynamic *cf* 1) (let [g (binding [*cf* 3] (bound-fn* (fn [] *cf*)))] (g)))" "3"] ["bound-fn* conveys" "(do (def ^:dynamic *cf* 1) (let [g (binding [*cf* 3] (bound-fn* (fn [] *cf*)))] (g)))" "3"]
["get-thread-bindings" "(do (def ^:dynamic *gb* 1) (binding [*gb* 9] (get (get-thread-bindings) (var *gb*))))" "9"] ["get-thread-bindings" "(do (def ^:dynamic *gb* 1) (binding [*gb* 9] (get (get-thread-bindings) (var *gb*))))" "9"]

View file

@ -1,13 +1,13 @@
# jolt-at0a (inc X) — #inst / #uuid literals + java.time formatting on Chez. # jolt-at0a (inc X) — #inst / #uuid literals + java.time formatting on Chez.
# #inst lowers (analyzer :inst node) to a jinst value (RFC3339 ms, partial # #inst lowers (analyzer :inst node) to a jinst value (RFC3339 ms, partial
# defaults + offsets); #uuid to a juuid; the java.time shim (DateTimeFormatter/ # defaults + offsets); #uuid to a juuid; the java.time shim (DateTimeFormatter/
# Instant/ZoneId/LocalDateTime/FormatStyle/Locale/Date) ports java_base.janet. # Instant/ZoneId/LocalDateTime/FormatStyle/Locale/Date.
# Mirrors the :regex IR-leaf precedent: Janet back end punts to the interpreter's # Reader data-readers and statics emit the runtime value
# data-readers, Chez emits the runtime value (host/chez/inst-time.ss). # (host/chez/inst-time.ss).
# Oracle = build/jolt. #
# #
# janet test/chez/_insttime.janet # janet test/chez/_insttime.janet
(def jolt-bin (or (os/getenv "JOLT_BIN") "bin/jolt-chez")) (def jolt-bin (or (os/getenv "JOLT_BIN") "bin/joltc"))
(def cases (def cases
[# --- #inst reading + identity --- [# --- #inst reading + identity ---
@ -60,11 +60,8 @@
(var pass 0) (var pass 0)
(def fails @[]) (def fails @[])
(each [expr expected] cases (each [expr expected] cases
(def [ocode oracle _] (run-capture "build/jolt" expr))
(def [code got err] (run-capture jolt-bin expr)) (def [code got err] (run-capture jolt-bin expr))
(cond (cond
(not= ocode 0) (array/push fails [expr (string "ORACLE FAILED exit " ocode)])
(not= oracle expected) (array/push fails [expr (string "ORACLE MISMATCH want `" expected "` got `" oracle "`")])
(not= code 0) (array/push fails [expr (string "exit " code "; err: " err)]) (not= code 0) (array/push fails [expr (string "exit " code "; err: " err)])
(= got expected) (++ pass) (= got expected) (++ pass)
(array/push fails [expr (string "want `" expected "`, got `" got "`")]))) (array/push fails [expr (string "want `" expected "`, got `" got "`")])))

View file

@ -1,12 +1,11 @@
# jolt-yyud — clojure.java.io/file + java.io.File interop + slurp/spit/flush/ # jolt-yyud — clojure.java.io/file + java.io.File interop + slurp/spit/flush/
# file-seq on Chez. A File is a path-backed jfile record (instance? java.io.File, # file-seq on Chez. A File is a path-backed jfile record (instance? java.io.File,
# str -> path, the File method surface). io.clj is a Janet-backed shim (janet.*), # str -> path, the File method surface). This is a Chez-native implementation.
# so this is a Chez-native implementation, not a port of it. Reader/StringReader- # Reader-coupled cases (line-seq, slurp over a reader, toURL) are deferred to jolt-at0a.
# coupled cases (line-seq, slurp over a reader, toURL) are deferred to jolt-at0a. #
# Oracle = build/jolt.
# #
# janet test/chez/_io.janet # janet test/chez/_io.janet
(def jolt-bin (or (os/getenv "JOLT_BIN") "bin/jolt-chez")) (def jolt-bin (or (os/getenv "JOLT_BIN") "bin/joltc"))
(defn io [body] (string "(do (require (quote [clojure.java.io :as io])) " body ")")) (defn io [body] (string "(do (require (quote [clojure.java.io :as io])) " body ")"))
@ -48,11 +47,8 @@
(var pass 0) (var pass 0)
(def fails @[]) (def fails @[])
(each [expr expected] cases (each [expr expected] cases
(def [ocode oracle _] (run-capture "build/jolt" expr))
(def [code got err] (run-capture jolt-bin expr)) (def [code got err] (run-capture jolt-bin expr))
(cond (cond
(not= ocode 0) (array/push fails [expr (string "ORACLE FAILED exit " ocode)])
(not= oracle expected) (array/push fails [expr (string "ORACLE MISMATCH want `" expected "` got `" oracle "`")])
(not= code 0) (array/push fails [expr (string "exit " code "; err: " err)]) (not= code 0) (array/push fails [expr (string "exit " code "; err: " err)])
(= got expected) (++ pass) (= got expected) (++ pass)
(array/push fails [expr (string "want `" expected "`, got `" got "`")]))) (array/push fails [expr (string "want `" expected "`, got `" got "`")])))

View file

@ -4,10 +4,10 @@
# StringReader, and with-open's __close seam over both jhost readers and plain # StringReader, and with-open's __close seam over both jhost readers and plain
# :close maps. All Chez-native (host/chez/io.ss); no analyzer change. Reader/edn # :close maps. All Chez-native (host/chez/io.ss); no analyzer change. Reader/edn
# runtime read (clojure.edn/read over a PushbackReader) stays jolt-r8ku. # runtime read (clojure.edn/read over a PushbackReader) stays jolt-r8ku.
# Oracle = build/jolt. #
# #
# janet test/chez/_ioreader.janet # janet test/chez/_ioreader.janet
(def jolt-bin (or (os/getenv "JOLT_BIN") "bin/jolt-chez")) (def jolt-bin (or (os/getenv "JOLT_BIN") "bin/joltc"))
(defn io [body] (string "(do (require (quote [clojure.java.io :as io])) " body ")")) (defn io [body] (string "(do (require (quote [clojure.java.io :as io])) " body ")"))
@ -47,11 +47,8 @@
(var pass 0) (var pass 0)
(def fails @[]) (def fails @[])
(each [expr expected] cases (each [expr expected] cases
(def [ocode oracle _] (run-capture "build/jolt" expr))
(def [code got err] (run-capture jolt-bin expr)) (def [code got err] (run-capture jolt-bin expr))
(cond (cond
(not= ocode 0) (array/push fails [expr (string "ORACLE FAILED exit " ocode)])
(not= oracle expected) (array/push fails [expr (string "ORACLE MISMATCH want `" expected "` got `" oracle "`")])
(not= code 0) (array/push fails [expr (string "exit " code "; err: " err)]) (not= code 0) (array/push fails [expr (string "exit " code "; err: " err)])
(= got expected) (++ pass) (= got expected) (++ pass)
(array/push fails [expr (string "want `" expected "`, got `" got "`")]))) (array/push fails [expr (string "want `" expected "`, got `" got "`")])))

View file

@ -1,67 +1,67 @@
# jolt-avt6 — host class statics + constructors on Chez. The analyzer lowers # jolt-avt6 — host class statics + constructors on Chez. The analyzer lowers
# Class/member to :host-static and (Class. ...) to :host-new; the Chez emit lowers # Class/member to :host-static and (Class. ...) to :host-new; the Chez emit lowers
# them to host-static-ref/host-static-call/host-new (host-static.ss registry). # them to host-static-ref/host-static-call/host-new (host-static.ss registry).
# Expectations are the build/jolt (seed) oracle, captured per case. # Each case carries its expected printed value. Env-dependent values (os.name) are
# asserted via a predicate so the case stays portable across machines.
# #
# janet test/chez/_javastatic.janet # janet test/chez/_javastatic.janet
(def jolt-bin (or (os/getenv "JOLT_BIN") "bin/jolt-chez")) (def jolt-bin (or (os/getenv "JOLT_BIN") "bin/joltc"))
# [label expr] — expected is whatever build/jolt prints (captured at runtime). (def cases
(def exprs [["(Math/sqrt 16)" "4"]
["(Math/sqrt 16)" ["(Math/abs -3)" "3"]
"(Math/abs -3)" ["(Math/max 2 7)" "7"]
"(Math/max 2 7)" ["(pos? Long/MAX_VALUE)" "true"]
"(pos? Long/MAX_VALUE)" ["(String/valueOf 42)" "42"]
"(String/valueOf 42)" ["(String/valueOf \"hi\")" "hi"]
"(String/valueOf \"hi\")" ["(String/valueOf :k)" ":k"]
"(String/valueOf :k)" ["(String/valueOf nil)" "null"]
"(String/valueOf nil)" ["(Long/parseLong \"42\")" "42"]
"(Long/parseLong \"42\")" ["(Long/valueOf \"42\")" "42"]
"(Long/valueOf \"42\")" ["(Integer/parseInt \"ff\" 16)" "255"]
"(Integer/parseInt \"ff\" 16)" ["(.byteValue (Integer/valueOf \"ff\" 16))" "-1"]
"(.byteValue (Integer/valueOf \"ff\" 16))" ["(Boolean/parseBoolean \"true\")" "true"]
"(Boolean/parseBoolean \"true\")" ["(Boolean/parseBoolean \"yes\")" "false"]
"(Boolean/parseBoolean \"yes\")" ["(Character/isUpperCase \\A)" "true"]
"(Character/isUpperCase \\A)" ["(Character/isLowerCase \\a)" "true"]
"(Character/isLowerCase \\a)" ["(Character/isUpperCase \\a)" "false"]
"(Character/isUpperCase \\a)" ["(Thread/interrupted)" "false"]
"(Thread/interrupted)" ["(string? (System/getProperty \"os.name\"))" "true"]
"(System/getProperty \"os.name\")" ["(string? (get (System/getenv) \"HOME\"))" "true"]
"(string? (get (System/getenv) \"HOME\"))" ["(string? (System/getenv \"HOME\"))" "true"]
"(string? (System/getenv \"HOME\"))" ["(fn? System/exit)" "true"]
"(fn? System/exit)" ["(string? (get (System/getProperties) \"os.name\"))" "true"]
"(string? (get (System/getProperties) \"os.name\"))" ["(pos? (count (seq (System/getenv))))" "true"]
"(pos? (count (seq (System/getenv))))" ["(let [es (map (fn [[k v]] [k v]) (System/getenv))] (and (pos? (count es)) (every? vector? es)))" "true"]
"(let [es (map (fn [[k v]] [k v]) (System/getenv))] (and (pos? (count es)) (every? vector? es)))"
# constructors + their methods # constructors + their methods
"(.toString (StringBuilder. \"x\"))" ["(.toString (StringBuilder. \"x\"))" "x"]
"(.toString (-> (StringBuilder.) (.append \"a\") (.append \\b) (.append 1)))" ["(.toString (-> (StringBuilder.) (.append \"a\") (.append \\b) (.append 1)))" "ab1"]
"(.toString (.append (StringBuilder. 16) \"x\"))" ["(.toString (.append (StringBuilder. 16) \"x\"))" "x"]
"(let [sb (StringBuilder.)] (.append sb \"abcd\") (.setLength sb 2) (.toString sb))" ["(let [sb (StringBuilder.)] (.append sb \"abcd\") (.setLength sb 2) (.toString sb))" "ab"]
"(let [w (StringWriter.)] (.write w \"a\") (.append w \\b) (.toString w))" ["(let [w (StringWriter.)] (.write w \"a\") (.append w \\b) (.toString w))" "ab"]
"(let [r (StringReader. \"ab\")] [(.read r) (.read r) (.read r)])" ["(let [r (StringReader. \"ab\")] [(.read r) (.read r) (.read r)])" "[97 98 -1]"]
"(let [r (StringReader. \"ab\")] (.mark r 1) [(.read r) (do (.reset r) (.read r))])" ["(let [r (StringReader. \"ab\")] (.mark r 1) [(.read r) (do (.reset r) (.read r))])" "[97 97]"]
"(let [r (java.io.PushbackReader. (java.io.StringReader. \"ab\"))] [(.read r) (.read r)])" ["(let [r (java.io.PushbackReader. (java.io.StringReader. \"ab\"))] [(.read r) (.read r)])" "[97 98]"]
"(let [r (PushbackReader. (StringReader. \"ab\")) a (.read r)] (.unread r a) [a (.read r) (.read r)])" ["(let [r (PushbackReader. (StringReader. \"ab\")) a (.read r)] (.unread r a) [a (.read r) (.read r)])" "[97 97 98]"]
"(let [r (PushbackReader. (StringReader. \"a\"))] (.unread r \\x) [(.read r) (.read r)])" ["(let [r (PushbackReader. (StringReader. \"a\"))] (.unread r \\x) [(.read r) (.read r)])" "[120 97]"]
"(BigInteger. \"123\")" ["(BigInteger. \"123\")" "123"]
"(let [m (HashMap. {:a 1 :b 2})] (.get m :b))" ["(let [m (HashMap. {:a 1 :b 2})] (.get m :b))" "2"]
"(let [m (HashMap. {})] (.put m :x 1) (.put m :y 2) (.size m))" ["(let [m (HashMap. {})] (.put m :x 1) (.put m :y 2) (.size m))" "2"]
"(let [t (StringTokenizer. \"a=1&b=2\" \"&\")] [(.nextToken t) (.nextToken t)])" ["(let [t (StringTokenizer. \"a=1&b=2\" \"&\")] [(.nextToken t) (.nextToken t)])" "[a=1 b=2]"]
"(.toString (StringBuilder. \"x\"))" ["(.toString (StringBuilder. \"x\"))" "x"]
# ring-codec surface # ring-codec surface
"(URLEncoder/encode \"a b=c\")" ["(URLEncoder/encode \"a b=c\")" "a+b%3Dc"]
"(URLDecoder/decode (URLEncoder/encode \"x &=%?\"))" ["(URLDecoder/decode (URLEncoder/encode \"x &=%?\"))" "x &=%?"]
"(String. (.encode (Base64/getEncoder) (.getBytes \"hello\")))" ["(String. (.encode (Base64/getEncoder) (.getBytes \"hello\")))" "aGVsbG8="]
"(String. (.decode (Base64/getDecoder) (String. (.encode (Base64/getEncoder) (.getBytes \"hello\")))))" ["(String. (.decode (Base64/getDecoder) (String. (.encode (Base64/getEncoder) (.getBytes \"hello\")))))" "hello"]
"(Integer/parseInt \"ff\" 16)" ["(Integer/parseInt \"ff\" 16)" "255"]
# Pattern statics # Pattern statics
"(regex? (Pattern/compile \"a.c\"))" ["(regex? (Pattern/compile \"a.c\"))" "true"]
"(.split (Pattern/compile \",\") \"a,b,c\")" ["(.split (Pattern/compile \",\") \"a,b,c\")" "(a b c)"]
"(do (require '[clojure.string :as s]) (s/replace \"a1b2\" (Pattern/compile \"[0-9]\") \"\"))" ["(do (require '[clojure.string :as s]) (s/replace \"a1b2\" (Pattern/compile \"[0-9]\") \"\"))" "ab"]
"(boolean (re-find (Pattern/compile \"^x\" Pattern/MULTILINE) \"y\\nx\"))" ["(boolean (re-find (Pattern/compile \"^x\" Pattern/MULTILINE) \"y\\nx\"))" "true"]
"(boolean (re-find (re-pattern (Pattern/quote \"a.c\")) \"za.cy\"))" ["(boolean (re-find (re-pattern (Pattern/quote \"a.c\")) \"za.cy\"))" "true"]
"(boolean (re-find (re-pattern (Pattern/quote \"a.c\")) \"zabcy\"))"]) ["(boolean (re-find (re-pattern (Pattern/quote \"a.c\")) \"zabcy\"))" "false"]])
(defn run-capture [bin expr] (defn run-capture [bin expr]
(def proc (os/spawn [bin "-e" expr] :p {:out :pipe :err :pipe})) (def proc (os/spawn [bin "-e" expr] :p {:out :pipe :err :pipe}))
@ -74,16 +74,14 @@
(var pass 0) (var pass 0)
(def fails @[]) (def fails @[])
(each expr exprs (each [expr expected] cases
(def [ocode oracle _] (run-capture "build/jolt" expr))
(def [code got err] (run-capture jolt-bin expr)) (def [code got err] (run-capture jolt-bin expr))
(cond (cond
(not= ocode 0) (array/push fails [expr (string "ORACLE FAILED exit " ocode)])
(not= code 0) (array/push fails [expr (string "exit " code "; err: " err)]) (not= code 0) (array/push fails [expr (string "exit " code "; err: " err)])
(= got oracle) (++ pass) (= got expected) (++ pass)
(array/push fails [expr (string "want `" oracle "`, got `" got "`")]))) (array/push fails [expr (string "want `" expected "`, got `" got "`")])))
(printf "\n_javastatic parity [%s]: %d/%d passed" jolt-bin pass (length exprs)) (printf "\n_javastatic parity [%s]: %d/%d passed" jolt-bin pass (length cases))
(when (> (length fails) 0) (when (> (length fails) 0)
(printf "%d FAIL(s):" (length fails)) (printf "%d FAIL(s):" (length fails))
(each [e m] fails (printf " FAIL %s\n %s" e m))) (each [e m] fails (printf " FAIL %s\n %s" e m)))

View file

@ -1,10 +1,10 @@
# jolt-yxqm — namespace value model (find-ns/ns-name/all-ns/resolve/ns-publics/ # jolt-yxqm — namespace value model (find-ns/ns-name/all-ns/resolve/ns-publics/
# in-ns/*ns* …). TDD harness: drives bin/jolt-chez -e per case (fresh subprocess # in-ns/*ns* …). TDD harness: drives bin/joltc -e per case (fresh subprocess
# = per-case isolation), checks the LAST printed line == expected. Expected # = per-case isolation), checks the LAST printed line == expected. Expected
# values are the JVM-canonical reference (build/jolt), captured up front. # values are the JVM-canonical reference, baked per case.
# #
# janet test/chez/_ns.janet # janet test/chez/_ns.janet
(def jolt-bin (or (os/getenv "JOLT_BIN") "bin/jolt-chez")) (def jolt-bin (or (os/getenv "JOLT_BIN") "bin/joltc"))
# [label expr expected-last-line] # [label expr expected-last-line]
(def cases (def cases

View file

@ -1,14 +1,14 @@
# jolt-r8ku (inc Y) — Chez-side Clojure data reader: read-string / read / # jolt-r8ku (inc Y) — Chez-side Clojure data reader: read-string / read /
# read+string / with-in-str / clojure.edn. The reader (host/chez/reader.ss) # read+string / with-in-str / clojure.edn. The reader (host/chez/reader.ss)
# produces the same jolt forms the Janet reader yields; the *in* family and # produces jolt forms directly; the *in* family and
# clojure.edn are Clojure over the read-string / __parse-next seams. # clojure.edn are Clojure over the read-string / __parse-next seams.
# #
# Outputs are kept order-stable (equality checks, scalars) so set/map iteration # Outputs are kept order-stable (equality checks, scalars) so set/map iteration
# order — which legitimately differs from build/jolt — doesn't masquerade as a # order — which is host-dependent — doesn't masquerade as a
# divergence. Oracle = build/jolt. # divergence.
# #
# janet test/chez/_reader.janet # janet test/chez/_reader.janet
(def jolt-bin (or (os/getenv "JOLT_BIN") "bin/jolt-chez")) (def jolt-bin (or (os/getenv "JOLT_BIN") "bin/joltc"))
(def cases (def cases
[# --- scalars + collections (value equality, order-independent) --- [# --- scalars + collections (value equality, order-independent) ---
@ -81,11 +81,8 @@
(var pass 0) (var pass 0)
(def fails @[]) (def fails @[])
(each [expr expected] cases (each [expr expected] cases
(def [ocode oracle _] (run-capture "build/jolt" expr))
(def [code got err] (run-capture jolt-bin expr)) (def [code got err] (run-capture jolt-bin expr))
(cond (cond
(not= ocode 0) (array/push fails [expr (string "ORACLE FAILED exit " ocode)])
(not= oracle expected) (array/push fails [expr (string "ORACLE MISMATCH want `" expected "` got `" oracle "`")])
(not= code 0) (array/push fails [expr (string "exit " code "; err: " err)]) (not= code 0) (array/push fails [expr (string "exit " code "; err: " err)])
(= got expected) (++ pass) (= got expected) (++ pass)
(array/push fails [expr (string "want `" expected "`, got `" got "`")]))) (array/push fails [expr (string "want `" expected "`, got `" got "`")])))

View file

@ -3,14 +3,14 @@
# is overlay (`(defn sequential? [x] (or (vector? x) (seq? x)))`), so it inherits # is overlay (`(defn sequential? [x] (or (vector? x) (seq? x)))`), so it inherits
# the fix transitively; this pins that both predicates agree with the JVM oracle # the fix transitively; this pins that both predicates agree with the JVM oracle
# over every lazy-seq-producing form (and the native =/hash path via set!). # over every lazy-seq-producing form (and the native =/hash path via set!).
# Expectations are the build/jolt (JVM-canonical) values. # Expectations are the JVM-canonical values.
# #
# janet test/chez/_seqpred.janet # janet test/chez/_seqpred.janet
(def jolt-bin (or (os/getenv "JOLT_BIN") "bin/jolt-chez")) (def jolt-bin (or (os/getenv "JOLT_BIN") "bin/joltc"))
(def cases (def cases
[# --- seq? over lazy seqs --- [# --- seq? over lazy seqs ---
# (NB: not (seq? (range 3)) — the seed makes range an eager vector, chez a lazy # (NB: range is lazy, so (type (range 3)) is :seq not :vector
# seq; a range-container divergence, not the predicate. sequential? agrees on it.) # seq; a range-container divergence, not the predicate. sequential? agrees on it.)
["seq? map" "(seq? (map inc [1 2 3]))" "true"] ["seq? map" "(seq? (map inc [1 2 3]))" "true"]
["seq? filter" "(seq? (filter odd? [1 2 3]))" "true"] ["seq? filter" "(seq? (filter odd? [1 2 3]))" "true"]

View file

@ -11,11 +11,11 @@
# through the host seam). # through the host seam).
# #
# Outputs are order-stable (value-equality / scalars) so set/map iteration order # Outputs are order-stable (value-equality / scalars) so set/map iteration order
# — which legitimately differs from build/jolt — never masquerades as a divergence. # — which is host-dependent — never masquerades as a divergence.
# Oracle = build/jolt. #
# #
# janet test/chez/_stdlib.janet # janet test/chez/_stdlib.janet
(def jolt-bin (or (os/getenv "JOLT_BIN") "bin/jolt-chez")) (def jolt-bin (or (os/getenv "JOLT_BIN") "bin/joltc"))
(def cases (def cases
[# --- clojure.math (jolt-22vo / jolt-h79) --- [# --- clojure.math (jolt-22vo / jolt-h79) ---
@ -31,7 +31,7 @@
["(long (clojure.math/cbrt 27))" "3"] ["(long (clojure.math/cbrt 27))" "3"]
["(< 4.6 (clojure.math/log 100) 4.7)" "true"] ["(< 4.6 (clojure.math/log 100) 4.7)" "true"]
# Chez has no native log10 (computed as log(x)/log(10)), so it can differ from # Chez has no native log10 (computed as log(x)/log(10)), so it can differ from
# the seed's C log10 in the last ulp (3 vs 2.9999…); range-check, don't pin. # C log10 in the last ulp (3 vs 2.9999…); range-check, don't pin.
["(< 2.99 (clojure.math/log10 1000) 3.01)" "true"] ["(< 2.99 (clojure.math/log10 1000) 3.01)" "true"]
["(do (require (quote [clojure.math :as m])) (long (m/hypot 3 4)))" "5"] ["(do (require (quote [clojure.math :as m])) (long (m/hypot 3 4)))" "5"]
["(mapv (comp long clojure.math/sqrt) [1 4])" "[1 2]"] ["(mapv (comp long clojure.math/sqrt) [1 4])" "[1 2]"]
@ -63,11 +63,8 @@
(var pass 0) (var pass 0)
(def fails @[]) (def fails @[])
(each [expr expected] cases (each [expr expected] cases
(def [ocode oracle _] (run-capture "build/jolt" expr))
(def [code got err] (run-capture jolt-bin expr)) (def [code got err] (run-capture jolt-bin expr))
(cond (cond
(not= ocode 0) (array/push fails [expr (string "ORACLE FAILED exit " ocode)])
(not= oracle expected) (array/push fails [expr (string "ORACLE MISMATCH want `" expected "` got `" oracle "`")])
(not= code 0) (array/push fails [expr (string "exit " code "; err: " err)]) (not= code 0) (array/push fails [expr (string "exit " code "; err: " err)])
(= got expected) (++ pass) (= got expected) (++ pass)
(array/push fails [expr (string "want `" expected "`, got `" got "`")]))) (array/push fails [expr (string "want `" expected "`, got `" got "`")])))

View file

@ -1,11 +1,11 @@
# jolt-nfca — host java.lang.String method interop on Chez: (.toUpperCase s), # jolt-nfca — host java.lang.String method interop on Chez: (.toUpperCase s),
# (.indexOf s x), (.substring s a b), the regex methods (.matches/.replaceAll/ # (.indexOf s x), (.substring s a b), the regex methods (.matches/.replaceAll/
# .replaceFirst), etc. Ported from the seed's string-methods surface # .replaceFirst), etc. The string-methods surface. Each case carries its
# (src/jolt/eval_resolve.janet). Expectations are the build/jolt (seed) oracle. # expected value.
# An expected of :throws asserts a non-zero exit (unsupported method). # An expected of :throws asserts a non-zero exit (unsupported method).
# #
# janet test/chez/_str.janet # janet test/chez/_str.janet
(def jolt-bin (or (os/getenv "JOLT_BIN") "bin/jolt-chez")) (def jolt-bin (or (os/getenv "JOLT_BIN") "bin/joltc"))
(def cases (def cases
[["toLowerCase" "(.toLowerCase \"HI\")" "hi"] [["toLowerCase" "(.toLowerCase \"HI\")" "hi"]

View file

@ -2,10 +2,10 @@
# alias `s` established by a runtime (require '[clojure.string :as s]). The Chez # alias `s` established by a runtime (require '[clojure.string :as s]). The Chez
# AOT driver pre-evals require forms against the ctx so the alias resolves at # AOT driver pre-evals require forms against the ctx so the alias resolves at
# analyze time, and clojure.string is emitted as a prelude tier over the str-* # analyze time, and clojure.string is emitted as a prelude tier over the str-*
# primitives. Expectations are the build/jolt (seed) oracle. # primitives. Each case carries its expected value.
# #
# janet test/chez/_strns.janet # janet test/chez/_strns.janet
(def jolt-bin (or (os/getenv "JOLT_BIN") "bin/jolt-chez")) (def jolt-bin (or (os/getenv "JOLT_BIN") "bin/joltc"))
(defn- with-req [body] (string "(do (require (quote [clojure.string :as s])) " body ")")) (defn- with-req [body] (string "(do (require (quote [clojure.string :as s])) " body ")"))

View file

@ -1,12 +1,11 @@
# jolt-fmm4 — (type x) on Chez: :type meta override, record class-name symbol, # jolt-fmm4 — (type x) on Chez: :type meta override, record class-name symbol,
# and a comprehensive value->taxonomy mapping (no value type crashes -> must be # and a comprehensive value->taxonomy mapping (no value type crashes -> must be
# total, the recorded gotcha). Expectations are the build/jolt (seed) oracle. # total, the recorded gotcha). Each case carries its expected value.
# Producers that the seed makes eager (range) are avoided: (type (range 3)) is # (type (range 3)) is :seq (range is lazy); range-container divergences are
# :vector on the seed (eager) but :seq on chez (lazy) — a range-container # covered elsewhere.
# divergence unrelated to `type`, covered elsewhere.
# #
# janet test/chez/_type.janet # janet test/chez/_type.janet
(def jolt-bin (or (os/getenv "JOLT_BIN") "bin/jolt-chez")) (def jolt-bin (or (os/getenv "JOLT_BIN") "bin/joltc"))
(def cases (def cases
[# --- scalars --- [# --- scalars ---

View file

@ -1,16 +1,16 @@
# jolt-zikh — var def-time metadata capture (^:private / ^Type tag / docstring). # jolt-zikh — var def-time metadata capture (^:private / ^Type tag / docstring).
# (meta (var v)) must carry the def-time reader metadata + :ns/:name, matching the # (meta (var v)) must carry the def-time reader metadata + :ns/:name, matching the
# JVM-canonical build/jolt. TDD harness: bin/jolt-chez -e per case, last line == # JVM-canonical reference. TDD harness: bin/joltc -e per case, last line ==
# expected. # expected.
# #
# janet test/chez/_var_meta.janet # janet test/chez/_var_meta.janet
(def jolt-bin (or (os/getenv "JOLT_BIN") "bin/jolt-chez")) (def jolt-bin (or (os/getenv "JOLT_BIN") "bin/joltc"))
(def cases (def cases
# NOTE: ^{:map} metadata on a def name (e.g. (def ^{:doc "hi"} dv 1)) reads as # NOTE: ^{:map} metadata on a def name (e.g. (def ^{:doc "hi"} dv 1)) reads as
# (def (with-meta name m) v) and is uncompilable for the COMPILER generally # (def (with-meta name m) v) and is uncompilable for the COMPILER generally
# (analyzer.clj rejects it; the Janet back end punts to its interpreter, which # (analyzer.clj rejects it) — out of subset, not a meta-capture gap. Shorthand
# Chez lacks) — out of subset, not a meta-capture gap. Shorthand ^:kw / ^Type # ^:kw / ^Type
# and the docstring form keep the name a plain symbol, so they're in scope. # and the docstring form keep the name a plain symbol, so they're in scope.
[["^:private on var" "(do (def ^:private pv 1) (:private (meta (var pv))))" "true"] [["^:private on var" "(do (def ^:private pv 1) (:private (meta (var pv))))" "true"]
["^Type tag on var" "(do (def ^String tv \"a\") (:tag (meta (var tv))))" "String"] ["^Type tag on var" "(do (def ^String tv \"a\") (:tag (meta (var tv))))" "String"]

View file

@ -1,8 +1,8 @@
# jolt-75sv — list? (a list marker on cseq, since cseq backs both lists and # jolt-75sv — list? (a list marker on cseq, since cseq backs both lists and
# realized/lazy seqs) + map-entry-as-vector + clojure.walk. Oracle = build/jolt. # realized/lazy seqs) + map-entry-as-vector + clojure.walk.
# #
# janet test/chez/_walk.janet # janet test/chez/_walk.janet
(def jolt-bin (or (os/getenv "JOLT_BIN") "bin/jolt-chez")) (def jolt-bin (or (os/getenv "JOLT_BIN") "bin/joltc"))
# -e reads only the FIRST form — wrap require + use in a single (do ...). # -e reads only the FIRST form — wrap require + use in a single (do ...).
(defn w [body] (string "(do (require (quote [clojure.walk :as w])) " body ")")) (defn w [body] (string "(do (require (quote [clojure.walk :as w])) " body ")"))
@ -63,11 +63,8 @@
(var pass 0) (var pass 0)
(def fails @[]) (def fails @[])
(each [expr expected] cases (each [expr expected] cases
(def [ocode oracle _] (run-capture "build/jolt" expr))
(def [code got err] (run-capture jolt-bin expr)) (def [code got err] (run-capture jolt-bin expr))
(cond (cond
(not= ocode 0) (array/push fails [expr (string "ORACLE FAILED exit " ocode)])
(not= oracle expected) (array/push fails [expr (string "ORACLE MISMATCH want `" expected "` got `" oracle "`")])
(not= code 0) (array/push fails [expr (string "exit " code "; err: " err)]) (not= code 0) (array/push fails [expr (string "exit " code "; err: " err)])
(= got expected) (++ pass) (= got expected) (++ pass)
(array/push fails [expr (string "want `" expected "`, got `" got "`")]))) (array/push fails [expr (string "want `" expected "`, got `" got "`")])))

View file

@ -1,210 +0,0 @@
# Chez Phase 3 inc 1 (jolt-hg7z) — value-parity gate for the PORTABLE Clojure
# emitter (jolt.backend-scheme) vs the Janet host oracle.
#
# The new emitter is jolt-core Clojure; here it runs interpreted ON THE JANET HOST
# (loaded via bootstrap-load-source) as a drop-in for host/chez/emit.janet. Each
# case is analyzed to IR, emitted to Scheme by the CLOJURE emitter, run on Chez,
# and compared to the same program evaluated by the Janet host (jolt's own oracle).
# This isolates "is the translation correct" from "does it run on Chez" — the
# emitter's logic is validated before it has to execute on Chez itself.
#
# janet test/chez/emit-parity.janet (from repo root)
(import ../../src/jolt/api :as api)
(import ../../src/jolt/backend :as backend)
(import ../../src/jolt/reader :as r)
(import ../../src/jolt/evaluator :as evlr)
(import ../../host/chez/driver :as d)
(import ../../host/chez/emit :as emit)
(import ../../src/jolt/types_ctx :as tctx)
(import ../../src/jolt/types_ns :as tns)
(import ../../src/jolt/types_var :as tvar)
(unless (d/chez-available?)
(print "skip: chez not on PATH")
(os/exit 0))
(var total 0) (var fails 0)
(defn ok [name pred &opt extra]
(++ total)
(if pred (printf "ok: %s" name)
(do (++ fails) (printf "FAIL: %s %s" name (or extra "")))))
# ctx with the analyzer pipeline + late-bind (same as the driver), plus the
# Clojure emitter loaded interpreted so we can call jolt.backend-scheme/emit.
(def ctx (d/make-ctx))
(def bs-src (get (get (ctx :env) :embedded-sources) "jolt.backend-scheme"))
(assert bs-src "jolt.backend-scheme not embedded — check stdlib_embed collect")
(backend/bootstrap-load-source ctx "jolt.backend-scheme" bs-src)
(def emit-clj-var (tns/ns-find (tctx/ctx-find-ns ctx "jolt.backend-scheme") "emit"))
(assert emit-clj-var "jolt.backend-scheme/emit not found after load")
(defn emit-clj [ir] (string ((tvar/var-get emit-clj-var) ir)))
# Janet host oracle, via the real CLI (-e), exactly like run-corpus.janet: take the
# last non-empty stdout line so collection values use jolt's real printer.
(defn cli-oracle [src]
(def proc (os/spawn ["build/jolt" "-e" src] :p {:out :pipe :err :pipe}))
(def out (ev/read (proc :out) 0x100000))
(ev/read (proc :err) 0x100000)
(os/proc-wait proc)
(def lines (filter (fn [l] (not (empty? l))) (string/split "\n" (string/trim (if out (string out) "")))))
(if (empty? lines) "" (last lines)))
(defn- parse-all [src]
(def out @[])
(var s src)
(while (> (length (string/trim s)) 0)
(def parsed (r/parse-next s))
(set s (in parsed 1))
(def f (in parsed 0))
(unless (nil? f) (array/push out f)))
out)
# Drain a pipe to EOF (a stdout side effect can flush in >1 write).
(defn- drain [pipe]
(def b @"")
(var c (ev/read pipe 0x10000))
(while c (buffer/push b c) (set c (ev/read pipe 0x10000)))
(string b))
# Compile `src` to a Chez program using the CLOJURE emitter, run it, return
# [code stdout stderr]. Mirrors driver/compile-program + run-on-chez but swaps
# emit/emit -> emit-clj.
(defn run-clj [src]
(def forms (parse-all src))
(def n (length forms))
(def def-scm @[])
(for i 0 (- n 1)
(def f (in forms i))
(array/push def-scm (emit-clj (backend/analyze-form ctx f)))
(evlr/eval-form ctx @{} f))
(def final-scm (emit-clj (backend/analyze-form ctx (in forms (- n 1)))))
(def prog (emit/program def-scm final-scm))
(def path (string "/tmp/jolt-chez-parity-" (os/getpid) ".ss"))
(spit path prog)
(def proc (os/spawn ["chez" "--script" path] :p {:out :pipe :err :pipe}))
(def out (drain (proc :out)))
(def err (drain (proc :err)))
(def code (os/proc-wait proc))
[code (string/trim out) (string/trim err)])
# A case passes when the Clojure emitter's Chez output equals the Janet oracle.
# An emit-time throw (out-of-subset op) is a clean FAIL, not a crash.
(defn check [name src]
(def want (cli-oracle src))
(def res (protect (run-clj src)))
(if (not (res 0))
(ok name false (string "emit/run threw: " (res 1)))
(let [[code out err] (res 1)]
(ok name (and (= code 0) (= out want))
(string "chez=" out " oracle=" want " code=" code " | " err)))))
# For cases where the Janet oracle carries a known latent bug (e.g. quoted-set
# literals don't reconstruct, jolt-tg9s) but Chez is correct: assert Chez output
# against the hand-verified real-Clojure value directly.
(defn check-exact [name src want]
(def res (protect (run-clj src)))
(if (not (res 0))
(ok name false (string "emit/run threw: " (res 1)))
(let [[code out err] (res 1)]
(ok name (and (= code 0) (= out want))
(string "chez=" out " want=" want " code=" code " | " err)))))
# --- inc 1 subset: const/local/var/if/do/let/loop/recur/invoke/fn/def ----------
(check "(+ 1 2)" "(+ 1 2)")
(check "arith mixed" "(- (* 3 4) (/ 10 2))")
(check "nested let" "(let [a 1 b (+ a 10) c (* b 2)] (- c a))")
(check "let sequential" "(loop [a 1 b (+ a 10)] (+ a b))")
(check "if comparison" "(if (< 3 5) 100 200)")
(check "if =" "(if (= 2 2) :y :n)")
(check "do side-effect ret" "(do 1 2 3)")
(check "fib 30" "(defn fib [n] (if (< n 2) n (+ (fib (- n 1)) (fib (- n 2))))) (fib 30)")
(check "factorial loop" "(defn fact [n] (loop [i n acc 1] (if (< i 2) acc (recur (- i 1) (* acc i))))) (fact 10)")
(check "multi-arity" "(defn g ([x] (g x 10)) ([x y] (+ x y))) (g 5)")
(check "variadic" "(defn s [& xs] (reduce + 0 xs)) (s 1 2 3 4)")
(check "higher-order inc" "(reduce + 0 (map inc (range 5)))")
(check "anon fn invoke" "((fn [x] (* x x)) 7)")
(check "shorthand fn" "(#(+ %1 %2) 3 4)")
(check "truthy local" "(defn t [x] (if x 1 2)) (t false)")
(check "mod rem quot" "(+ (mod 17 5) (rem 17 5) (quot 17 5))")
(check "min max" "(+ (min 3 1 2) (max 3 1 2))")
# --- inc 2 subset: collection literals (vector/map/set) + quote -----------------
(check "vector literal" "[1 2 3]")
(check "nested vector" "[1 [2 3] [4 [5 6]]]")
(check "vector of exprs" "[(+ 1 2) (* 3 4)]")
(check "map literal eq" "(= {:a 1 :b 2} {:a 1 :b 2})")
(check "map get" "(get {:a 1 :b 2} :b)")
(check "set literal eq" "(= #{1 2 3} #{3 2 1})")
(check "set contains" "(contains? #{1 2 3} 2)")
(check "vector count" "(count [10 20 30 40])")
(check "conj vector" "(conj [1 2] 3 4)")
(check "assoc map" "(get (assoc {:a 1} :b 2) :b)")
(check "coll as fn" "({:a 1 :b 2} :a)")
(check "kw as fn" "(:b {:a 1 :b 2})")
(check "kw as fn default" "(:z {:a 1} 99)")
(check "quote list" "(count (quote (a b c d)))")
(check "quote vector eq" "(= (quote [1 2 3]) [1 2 3])")
(check "quote symbol eq" "(= (quote foo) (quote foo))")
(check "quote ns symbol eq" "(= (quote my.ns/bar) (quote my.ns/bar))")
(check "quote nested count" "(count (quote (a [1 2] {:k v})))")
(check "quote keyword in list" "(first (quote (:a :b :c)))")
(check "quote map" "(get (quote {:x 1 :y 2}) :y)")
# Janet oracle is buggy here (jolt-tg9s): quoted-set doesn't reconstruct. Chez is
# correct (real Clojure: true) — assert against the verified value.
(check-exact "quote set contains" "(contains? (quote #{:p :q}) :p)" "true")
(check-exact "quote set eq literal" "(= (quote #{1 2 3}) #{1 2 3})" "true")
(check "vector map filter" "(into [] (filter even? (map inc [1 2 3 4 5])))")
(check "map over literal" "(reduce + (vals {:a 1 :b 2 :c 3}))")
# --- inc 3 subset: try/throw + def-meta + quoted-sym-meta + inst/uuid + regex ----
(check "try catch" `(try (throw (ex-info "boom" {})) (catch Throwable e 42))`)
(check "try catch value" `(+ (try (throw (ex-info "" {})) (catch Throwable e 10)) 5)`)
(check "try no throw" `(try 7 (catch Throwable e 0))`)
(check "try finally" `(try 1 (finally 2))`)
(check "try finally side" `(try (throw (ex-info "" {})) (catch Throwable e 3) (finally 9))`)
(check "def private runs" `(do (defn ^:private secret [] 99) (secret))`)
(check "def tagged runs" `(do (def ^:dynamic *q* 5) *q*)`)
(check "quoted sym meta eq" `(do (def x (quote ^:foo bar)) (= x x))`)
(check "inst eq" `(= #inst "2020-01-01" #inst "2020-01-01")`)
(check "uuid eq" `(= #uuid "00000000-0000-0000-0000-000000000000" #uuid "00000000-0000-0000-0000-000000000000")`)
(check "regex smoke" `(do (def r #"[0-9]+") true)`)
(check "char eq" `(= \a \a)`)
(check "char int" `(do (def c \newline) (= c \newline))`)
(check "quoted char" `(= (quote \z) \z)`)
(check "mandelbrot run(20)"
(string ``
(defn count-point [cr ci cap]
(loop [i 0 zr 0.0 zi 0.0]
(if (or (>= i cap) (> (+ (* zr zr) (* zi zi)) 4.0))
i
(recur (inc i) (+ (- (* zr zr) (* zi zi)) cr) (+ (* 2.0 (* zr zi)) ci)))))
(defn run [n]
(let [cap 200 nd (* 1.0 n)]
(loop [y 0 acc 0]
(if (< y n)
(let [ci (- (/ (* 2.0 y) nd) 1.0)
row (loop [x 0 a 0]
(if (< x n)
(let [cr (- (/ (* 2.0 x) nd) 1.5)]
(recur (inc x) (+ a (count-point cr ci cap))))
a))]
(recur (inc y) (+ acc row)))
acc))))
`` "\n(run 20)"))
# Structural: a ^:private def must now take the def-var-with-meta! path (the meta
# is a portable struct after the h-sym-meta fix; before, map?/count failed on the
# raw table and it silently fell back to the lean def-var!, dropping the meta).
(let [scm (emit-clj (backend/analyze-form ctx (in (r/parse-next "(def ^:private p 1)") 0)))]
(ok "def ^:private emits def-var-with-meta!"
(truthy? (string/find "def-var-with-meta!" scm)) scm))
(let [scm (emit-clj (backend/analyze-form ctx (in (r/parse-next "(def plain 1)") 0)))]
(ok "plain def stays lean def-var!"
(and (truthy? (string/find "(def-var! " scm))
(not (string/find "with-meta" scm))) scm))
(printf "\n%d/%d ok" (- total fails) total)
(when (> fails 0) (os/exit 1))

View file

@ -1,721 +0,0 @@
# Phase 1 (jolt-cf1q.2) — REAL pipeline end to end: actual Clojure source ->
# Janet-hosted analyzer -> host-neutral IR -> Scheme emitter -> run on Chez.
# Correctness is checked by parity against the SAME program evaluated by the
# Janet host (jolt's own oracle), so a divergence is the back end's, not the
# program's.
# janet test/chez/emit-test.janet (from repo root)
(import ../../src/jolt/api :as api)
(import ../../src/jolt/backend :as backend)
(import ../../src/jolt/reader :as r)
(import ../../host/chez/driver :as d)
(import ../../host/chez/emit :as emit)
(unless (d/chez-available?)
(print "skip: chez not on PATH")
(os/exit 0))
(var total 0) (var fails 0)
(defn ok [name pred &opt extra]
(++ total)
(if pred (printf "ok: %s" name)
(do (++ fails) (printf "FAIL: %s %s" name (or extra "")))))
# Janet-host oracle: evaluate the same program, stringify its value the way jolt
# prints it at the CLI (so "832040" not "832040.0", "0.5" not 1/2, etc.).
(def oracle-ctx (api/init {:compile? true}))
(defn oracle [src] (string (api/load-string oracle-ctx src)))
# Canonical CLI oracle (the run-corpus gate's boundary): collection values don't
# round-trip through (string value) — they need jolt's real `-e` printer. Take
# the last non-empty stdout line, exactly like run-corpus.janet.
(defn cli-oracle [src]
(def proc (os/spawn ["build/jolt" "-e" src] :p {:out :pipe :err :pipe}))
(def out (ev/read (proc :out) 0x100000))
(ev/read (proc :err) 0x100000)
(os/proc-wait proc)
(def lines (filter (fn [l] (not (empty? l))) (string/split "\n" (string/trim (if out (string out) "")))))
(if (empty? lines) "" (last lines)))
(def ctx (d/make-ctx))
# 1) constant-folded arithmetic: (+ 1 2) -> the analyzer folds to const 3.
(let [[code out err] (d/run-on-chez ctx "(+ 1 2)")]
(ok "(+ 1 2) = 3" (and (= code 0) (= out "3") (= out (oracle "(+ 1 2)"))) (string out " | " err)))
# 2) fib: var-cell def + named-fn self-recursion + native arith, via real IR.
(let [src "(defn fib [n] (if (< n 2) n (+ (fib (- n 1)) (fib (- n 2))))) (fib 30)"
[code out err] (d/run-on-chez ctx src)]
(ok "(fib 30) = 832040" (and (= code 0) (= out "832040") (= out (oracle src))) (string out " | " err)))
# 3) mandelbrot kernel: loop/recur, let, or-expansion, cross-var call
# (run -> count-point), flonum compute. Parity vs the Janet host on run(40).
(def mandel-defs ``
(defn count-point [cr ci cap]
(loop [i 0 zr 0.0 zi 0.0]
(if (or (>= i cap) (> (+ (* zr zr) (* zi zi)) 4.0))
i
(recur (inc i)
(+ (- (* zr zr) (* zi zi)) cr)
(+ (* 2.0 (* zr zi)) ci)))))
(defn run [n]
(let [cap 200
nd (* 1.0 n)]
(loop [y 0 acc 0]
(if (< y n)
(let [ci (- (/ (* 2.0 y) nd) 1.0)
row (loop [x 0 a 0]
(if (< x n)
(let [cr (- (/ (* 2.0 x) nd) 1.5)]
(recur (inc x) (+ a (count-point cr ci cap))))
a))]
(recur (inc y) (+ acc row)))
acc))))
``)
(let [src (string mandel-defs "\n(run 40)")
[code out err] (d/run-on-chez ctx src)]
(ok "mandelbrot run(40) parity" (and (= code 0) (= out (oracle src)))
(string "chez=" out " janet=" (oracle src) " | " err)))
# 3a2) truthy? elision (jolt-nkcb): an :if test that provably yields a Scheme
# boolean (native comparison/not, or a boolean const) needs no jolt-truthy?
# wrapper — (if (jolt-truthy? (< a b)) …) === (if (< a b) …). Sound because
# jolt-truthy? of #t/#f is identity. The hot fib/mandelbrot tests are all
# comparisons, so this is a direct ceiling lever. Inspect the emitted Scheme.
(defn- emit-scm [src] (d/scheme-emit ctx (backend/analyze-form ctx (in (r/parse-next src) 0))))
(each [label src wrapped?] [["if < elides wrapper" "(fn [n] (if (< n 2) 1 2))" false]
["if > elides wrapper" "(fn [n] (if (> n 2) 1 2))" false]
["if = elides wrapper" "(fn [n] (if (= n 2) 1 2))" false]
["if <= elides wrapper" "(fn [n] (if (<= n 2) 1 2))" false]
["if not elides wrapper" "(fn [x] (if (not x) 1 2))" false]
["if true const elides" "(fn [] (if true 1 2))" false]
["if local keeps wrapper" "(fn [x] (if x 1 2))" true]
["if + keeps wrapper" "(fn [n] (if (+ n 1) 1 2))" true]]
(let [scm (emit-scm src) has (truthy? (string/find "jolt-truthy?" scm))]
(ok (string "truthy elision: " label) (= has wrapped?)
(string "wrapped?=" has " want " wrapped? " | " scm))))
# 3b) regressions found via the corpus probe:
# - loop binds SEQUENTIALLY (Scheme named-let is parallel); b must see a.
# - #(...) shorthand gensyms params with a trailing `#` (invalid in Scheme).
(each [label src] [["loop sequential init" "(loop [a 1 b (+ a 10)] (+ a b))"]
["#() shorthand" "(#(+ %1 %2) 1 2)"]]
(let [[code out err] (d/run-on-chez ctx src)]
(ok label (and (= code 0) (= out (oracle src))) (string "chez=" out " janet=" (oracle src) " | " err))))
# 3c) persistent collections (jolt-wgbz): vector/map/set literals + leaf ops.
# Maps/sets print in jolt's INTERNAL hash order, which a Scheme HAMT won't
# reproduce — so unordered cases are checked via `(= ...)` (prints true/false,
# exactly how the run-corpus gate compares them), and only ORDERED vectors are
# compared by printed form. Parity is still vs the Janet oracle in both shapes.
(each src [# ordered: direct printed-form parity
"[1 2 3]"
"(conj [1 2] 3)"
"(count [1 2 3])"
"(nth [10 20 30] 1)"
"(get [10 20 30] 0)"
"(peek [1 2 3])"
"(pop [1 2 3])"
# unordered / boolean: equality-wrapped, order-independent
"(= {:a 1 :b 2} {:b 2 :a 1})"
"(= {:a 1 :b 2} (assoc {:a 1} :b 2))"
"(= 1 (get {:a 1} :a))"
"(= 2 (count {:a 1 :b 2}))"
"(= 99 (get {:a 1} :z 99))"
"(= {:a 1} (dissoc {:a 1 :b 2} :b))"
"(= #{1 2 3} (conj #{1 2} 3))"
"(= #{1 2} (conj #{1 2} 2))"
"(contains? #{1 2} 1)"
"(contains? #{1 2} 9)"
"(contains? {:a 1} :a)"
"(empty? [])"
"(empty? [1])"
"(empty? {})"
"(= [1 2] [1 2])"
"(= [1 2] [1 3])"
"(= #{1 2} #{2 1})"
"(= {1 2} {1 3})"]
(let [[code out err] (d/run-on-chez ctx src)
want (cli-oracle src)]
(ok (string "coll: " src) (and (= code 0) (= out want))
(string "chez=" out " janet=" want " | " err))))
# 3d) dynamic IFn dispatch (inc 3b): a keyword/vector/coll held in a LOCAL (let
# binding or fn param) and called as a fn. The 3 ex-known-divergences. The
# callee is a :local that's NOT the fn's self-name, so emit routes it through
# the jolt-invoke fallback (procedure? -> apply; keyword/coll -> lookup).
(each [src want] [["(let [v [10 20 30]] (v 1))" "20"]
["(let [k :a] (k {:a 7}))" "7"]
["((fn [f] (f {:a 1})) :a)" "1"]]
(let [[code out err] (d/run-on-chez ctx src)]
(ok (string "ifn: " src) (and (= code 0) (= out want))
(string "chez=" out " want=" want " | " err))))
# 3e) seq tier (inc 3b): jolt list type, first/rest/next/seq/cons/list, lazy-seq
# (range/take over an infinite seq), map/filter/reduce/into/remove, keys/vals.
# Lists and lazy seqs print as (...) and are sequential-= to vectors. Ordered
# shapes -> printed-form parity vs the CLI oracle.
(each src ["(first [1 2 3])"
"(rest [1 2 3])"
"(rest [1])"
"(rest [])"
"(next [1 2 3])"
"(next [1])"
"(cons 0 [1 2 3])"
"(cons 1 nil)"
"(list 1 2 3)"
"(list)"
"(seq [])"
"(conj (list 2 3) 1)"
"(conj nil 1 2)"
"(map inc [1 2 3])"
"(map + [1 2 3] [10 20 30])"
"(map :a [{:a 1} {:a 2}])"
"(filter even? [1 2 3 4])"
"(remove even? [1 2 3 4])"
"(reduce + 0 [1 2 3])"
"(reduce + [1 2 3])"
"(reduce + (map inc (range 4)))"
"(into [] [1 2 3])"
"(into [1] (list 2 3))"
"(take 3 (range))"
"(reverse [1 2 3])"
"(apply + [1 2 3])"
"(count (map inc [1 2 3]))"]
(let [[code out err] (d/run-on-chez ctx src)
want (cli-oracle src)]
(ok (string "seq: " src) (and (= code 0) (= out want))
(string "chez=" out " janet=" want " | " err))))
# 3f) seq tier — unordered / cross-type, equality-wrapped (prints true/false):
# keys/vals order is HAMT order, into-map / into-set unordered; sequential =
# across vector and list.
(each src ["(= 2 (count (keys {:a 1 :b 2})))"
"(= 3 (reduce + (vals {:a 1 :b 2})))"
"(= {:a 1 :b 2} (into {} [[:a 1] [:b 2]]))"
"(= #{1 2 3} (into #{} [1 2 3]))"
"(= [1 2 3] (list 1 2 3))"
"(= [1 2 3] (map inc [0 1 2]))"
# jolt returns a vector for (seq vec) / bounded (range); Chez returns a
# Clojure-canonical lazy seq. Values are sequential-=, printed forms differ.
"(= [1 2 3] (seq [1 2 3]))"
"(= [0 1 2 3 4] (range 5))"]
(let [[code out err] (d/run-on-chez ctx src)]
(ok (string "seq=: " src) (and (= code 0) (= out "true"))
(string "chez=" out " | " err))))
# 3g) multi-arity + variadic fns (inc 3c): case-lambda dispatch, a Scheme rest
# arg collected into a jolt seq (nil when empty), recur within an arity and a
# self-call across arities. Value parity vs the CLI oracle.
(each src ["((fn ([x] (* x 2)) ([x y] (+ x y))) 5)"
"((fn ([x] (* x 2)) ([x y] (+ x y))) 3 4)"
"(defn g ([x] x) ([x y] (+ x y))) (g 10)"
"(defn g ([x] x) ([x y] (+ x y))) (g 10 20)"
"(defn h [a & more] (count more)) (h 1 2 3 4)"
# empty rest is nil (Clojure): count 0, first nil (prints "")
"(defn h [a & more] (count more)) (h 1)"
"(defn h [a & more] (first more)) (h 1)"
"(defn h [a & more] (first more)) (h 1 2 3)"
"(defn h [a & more] (reduce + a more)) (h 1 2 3 4)"
"(defn h [a & more] (reduce + a more)) (apply h [1 2 3 4])"
# self-call from one arity to another, then recur within it
"(defn f ([n] (f n 0)) ([n acc] (if (zero? n) acc (recur (- n 1) (+ acc n))))) (f 5)"
"((fn r [& xs] (if (seq xs) (+ (first xs) (apply r (rest xs))) 0)) 1 2 3)"]
(let [[code out err] (d/run-on-chez ctx src)
want (cli-oracle src)]
(ok (string "arity: " src) (and (= code 0) (= out want))
(string "chez=" out " janet=" want " | " err))))
# 3i) throw / try / catch / finally + ex-info (inc 3e). Value parity vs the CLI
# oracle for caught throws; an uncaught throw must exit non-zero.
(each src [# jolt catch syntax is (catch Class binding body); the class is dropped
# in the IR (catch-all). catch binds the thrown value raw.
"(try (throw 42) (catch Exception e e))"
"(try (+ 1 (throw 7)) (catch Exception e (* e 10)))"
# finally runs and its value is discarded (try returns the body value)
"(try 5 (finally 99))"
"(try (throw 3) (catch Exception e (+ e 1)) (finally 99))"
# body value passes through when nothing throws
"(try (+ 2 3) (catch Exception e :nope))"
# ex-info builds a real jolt map: read message/data via get (native-op)
"(get (ex-info \"boom\" {:a 1}) :message)"
"(get (ex-info \"boom\" {:a 1}) :data)"
"(try (throw (ex-info \"boom\" {:a 1})) (catch Exception e (get e :message)))"
"(try (throw (ex-info \"boom\" {:a 7})) (catch Exception e (get (get e :data) :a)))"
# nested try: inner rethrows, outer catches
"(try (try (throw 1) (catch Exception e (throw (+ e 1)))) (catch Exception e (* e 100)))"]
(let [[code out err] (d/run-on-chez ctx src)
want (cli-oracle src)]
(ok (string "throw/try: " src) (and (= code 0) (= out want))
(string "chez=" out " janet=" want " | " err))))
# an uncaught throw aborts the program (non-zero exit) — matches the corpus
# `:throws` semantics (interpret/compile both bail).
(let [[code out err] (d/run-on-chez ctx "(throw (ex-info \"unhandled\" {}))")]
(ok "throw: uncaught exits non-zero" (not= code 0)
(string "code=" code " out=" out)))
# 3j) quoted literals (inc 3f): a :quote node reconstructs the reader form as RT
# values — symbols, lists, vectors, maps, sets, nested. Value parity vs the CLI.
(each src ["'foo"
"'foo/bar"
"':kw"
"'(1 2 3)"
"'[1 2 3]"
"'(a b c)"
"'{:a 1}"
"'(1 (2 3) 4)"
"(first '(10 20 30))"
"(count '[1 2 3])"
"(rest '(1 2 3))"
"(= 'foo 'foo)"
"(= 'a 'b)"
"(map inc '(1 2 3))"
"(conj '[1 2] 3)"
"(get '{:a 7} :a)"]
(let [[code out err] (d/run-on-chez ctx src)
want (cli-oracle src)]
(ok (string "quote: " src) (and (= code 0) (= out want))
(string "chez=" out " janet=" want " | " err))))
# 3k) letfn + declare/def-no-init (inc 3g). letfn lowers to a Scheme `letrec*`
# (mutual recursion between the named local fns — a plain let* can't forward-
# ref a sibling). declare/(def x) with no init pre-creates the var cell so a
# forward reference resolves; the real def runs before any call.
(each src [# single local fn
"(letfn [(twice [x] (* x 2))] (twice 5))"
# self-recursion within a local fn
"(letfn [(fact [n] (if (zero? n) 1 (* n (fact (dec n)))))] (fact 5))"
# MUTUAL recursion — the letrec semantics a sequential let* lacks
"(letfn [(ev? [n] (if (zero? n) true (od? (dec n)))) (od? [n] (if (zero? n) false (ev? (dec n))))] (ev? 10))"
"(letfn [(ev? [n] (if (zero? n) true (od? (dec n)))) (od? [n] (if (zero? n) false (ev? (dec n))))] (od? 7))"
# local fn passed to a higher-order fn
"(letfn [(sq [x] (* x x))] (map sq [1 2 3]))"
# declare + forward reference (the canonical mutually-recursive top-level use)
"(declare is-ev) (defn is-od [n] (if (zero? n) false (is-ev (dec n)))) (defn is-ev [n] (if (zero? n) true (is-od (dec n)))) (is-ev 10)"
# declare then redefine: the real def overwrites the reserved cell
"(declare foo) (def foo 10) foo"]
(let [[code out err] (d/run-on-chez ctx src)
want (cli-oracle src)]
(ok (string "letfn/declare: " src) (and (= code 0) (= out want))
(string "chez=" out " janet=" want " | " err))))
# 3l) host interop method calls (inc 3h). (.method target arg*) analyzes to a
# :host-call IR node and lowers to a dispatch. The Janet back end PUNTS these
# (no interop model -> interpreter); the Chez RT shims the File methods
# jolt-core's io tier uses via jolt-host-call (.isDirectory -> file-directory?,
# .listFiles -> directory-list). All OTHER methods (.write/.append/.read/... on
# a StringWriter/StringReader/record) route through record-method-dispatch
# (jolt-avt6 removed .write from the jolt-host-call fast-path so a StringWriter
# jhost handles it). Interop has no portable oracle (the Janet host models it
# differently), so these are emit-shape checks plus one deterministic runtime
# probe (the root "/" is always a directory).
(each [label src needle]
[["emit .write -> record-method-dispatch" "(fn [w x] (.write w x))" "record-method-dispatch"]
["emit .write keeps method name" "(fn [w x] (.write w x))" "\"write\""]
["emit .isDirectory -> jolt-host-call" "(fn [f] (.isDirectory f))" "isDirectory"]
["emit .listFiles -> jolt-host-call" "(fn [f] (.listFiles f))" "listFiles"]]
(let [scm (protect (emit/emit (backend/analyze-form ctx (in (r/parse-next src) 0))))]
(ok label (and (scm 0) (string/find needle (scm 1))) (string/format "%p" scm))))
(let [[code out err] (d/run-on-chez ctx "(.isDirectory \"/\")")]
(ok "runtime .isDirectory \"/\" = true" (and (= code 0) (= out "true"))
(string "chez=" out " | " err)))
# 3l') host class statics + constructors (jolt-avt6). Class/member lowers to a
# :host-static node (host-static-ref in value position, host-static-call as a
# call head); (Class. ...) / (new Class ...) lower to :host-new. The Janet back
# end punts all three; the Chez RT resolves them from the class-statics /
# class-ctors / jhost-method registries (host/chez/host-static.ss). Emit-shape
# checks; the registry behaviour is covered by test/chez/_javastatic.janet.
(each [label src needle]
[["emit Class/field -> host-static-ref" "(fn [] Long/MAX_VALUE)" "(host-static-ref \"Long\" \"MAX_VALUE\")"]
["emit Class/method call -> host-static-call" "(fn [] (System/getenv))" "(host-static-call \"System\" \"getenv\")"]
["emit static call passes args" "(fn [x] (Long/parseLong x))" "(host-static-call \"Long\" \"parseLong\""]
["emit (Class.) -> host-new" "(fn [] (StringBuilder.))" "(host-new \"StringBuilder\")"]
["emit (new Class ...) -> host-new" "(fn [s] (new java.io.StringReader s))" "(host-new \"java.io.StringReader\""]]
(let [scm (protect (emit/emit (backend/analyze-form ctx (in (r/parse-next src) 0))))]
(ok label (and (scm 0) (string/find needle (scm 1))) (string/format "%p" scm))))
# 3l'') the `.` special form + `.-field` desugar (jolt-kuic). (. target member
# arg*) and (.-field target) lower to a :host-call routed through
# record-method-dispatch (a non-shimmed method); the dash on a field member
# survives in the method name so dot-forms.ss reads it as a field access. The
# Janet back end punts :host-call (interpreter re-runs the original `.` form);
# runtime behaviour is covered by test/chez/_dotform.janet.
(each [label src needle]
[["emit (. obj method) -> record-method-dispatch" "(fn [o] (. o frob))" "record-method-dispatch"]
["emit (. obj method) keeps method name" "(fn [o] (. o frob))" "\"frob\""]
["emit (. obj method arg) passes args" "(fn [o a] (. o frob a))" "record-method-dispatch"]
["emit (.-field obj) keeps dash" "(fn [o] (.-value o))" "\"-value\""]
["emit (. obj -field) keeps dash" "(fn [o] (. o -value))" "\"-value\""]]
(let [scm (protect (emit/emit (backend/analyze-form ctx (in (r/parse-next src) 0))))]
(ok label (and (scm 0) (string/find needle (scm 1))) (string/format "%p" scm))))
# 3m) regex (jolt-i0s3): the #"…" literal lowers to a jolt-regex value over the
# vendored irregex; re-pattern/re-matches/re-find/re-seq/regex? are def-var!'d
# into clojure.core (not subset native-ops — irregex's Unicode/property
# semantics differ from the seed's byte-PEG), so they resolve in PRELUDE mode,
# the path the assembled prelude takes. Parity vs the CLI oracle on standard
# PCRE patterns both engines agree on.
(defn run-prelude [src]
(emit/set-prelude-mode! true)
(def r (protect (emit/emit (backend/analyze-form ctx (in (r/parse-next src) 0)))))
(emit/set-prelude-mode! false)
(if (not (r 0)) [:emit-err (r 1) ""]
(do
# PID-unique path: two emit-test processes (or a foreground -e) must not
# read each other's half-written program file.
(def path (string "/tmp/chez-prelude-" (os/getpid) ".ss"))
(spit path (emit/program @[] (r 1)))
(def proc (os/spawn ["chez" "--script" path] :p {:out :pipe :err :pipe}))
(def out (ev/read (proc :out) 0x100000))
(def err (ev/read (proc :err) 0x100000))
[(os/proc-wait proc) (string/trim (if out (string out) "")) (string/trim (if err (string err) ""))])))
# bare #"…" literal runs in plain subset mode (the :regex node needs no core fn).
(each src ["#\"\\d+\"" "(do #\"a.c\")"]
(let [[code out err] (d/run-on-chez ctx src) want (cli-oracle src)]
(ok (string "regex literal: " src) (and (= code 0) (= out want))
(string "chez=" out " janet=" want " | " err))))
# re-* surface via prelude mode (def-var!'d fns), parity vs the CLI oracle.
(each src ["(re-matches #\"\\d+\" \"123\")"
"(re-matches #\"\\d+\" \"12a\")"
"(re-find #\"\\d+\" \"abc123def\")"
"(re-find #\"([a-z])(\\d)\" \"--a1--\")"
"(re-seq #\"\\d+\" \"a1b22c333\")"
"(regex? #\"\\d+\")"
"(re-matches #\"[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}\" \"550e8400-e29b-41d4-a716-446655440000\")"]
(let [[code out err] (run-prelude src) want (cli-oracle src)]
(ok (string "regex: " src) (and (= code 0) (= out want))
(string "chez=" out " janet=" want " | " err))))
# 3h) prelude mode (inc 3d): emitting clojure.core ITSELF, a core->core ref must
# lower to a runtime var-deref instead of being rejected as "out of subset".
# `frequencies` is a core fn but not a native-op, so it exercises the switch.
(let [ir (backend/analyze-form ctx (in (r/parse-next "(fn [x] (frequencies x))") 0))]
# subset mode (the default): a non-native core ref is rejected at emit time.
(ok "prelude: subset mode rejects non-native core ref"
(let [r (protect (emit/emit ir))] (not (r 0))))
# prelude mode: the same ref lowers to (var-deref "clojure.core" "frequencies").
(emit/set-prelude-mode! true)
(def scm (protect (emit/emit ir)))
(emit/set-prelude-mode! false)
(ok "prelude: mode lowers non-native core ref to var-deref"
(and (scm 0)
(string/find "var-deref" (scm 1))
(string/find "frequencies" (scm 1)))
(string/format "%p" scm)))
# 3n) atoms (jolt-9ziu): atom/deref/swap!/reset! are host-coupled (stay in the
# Janet seed, no overlay def-var!), so the Chez host needs an RT shim
# (host/chez/atoms.ss). They lower to var-deref in prelude mode. The hierarchy
# machinery (global-hierarchy = (atom (make-hierarchy))) needs `atom` at the
# prelude's LOAD time, so this is a load blocker, not just a lazy gap. swap!
# invokes its fn through jolt-invoke; compare-and-set!/swap-vals!/reset-vals!
# are overlay fns that compose the native kernel.
(each src ["(deref (atom 42))"
"@(atom 99)"
"(let [a (atom 0)] (reset! a 7) (deref a))"
"(let [a (atom 0)] (swap! a inc) (swap! a inc) (deref a))"
"(let [a (atom 10)] (swap! a + 5) (deref a))"
"(let [a (atom 1)] (reset! a 2) [(deref a) @a])"
"(let [a (atom 0)] (compare-and-set! a 0 5) (deref a))"
"(let [a (atom 0)] (compare-and-set! a 9 5) (deref a))"]
(let [[code out err] (run-prelude src) want (cli-oracle src)]
(ok (string "atom: " src) (and (= code 0) (= out want))
(string "chez=" out " janet=" want " | " err))))
# 3o) type predicates + name/namespace (jolt-9ziu): seed natives the overlay
# assumes; the Chez host shims them (host/chez/predicates.ss) and def-var!s them
# into clojure.core, so they resolve in prelude mode. Semantics match the seed
# (core_types.janet): map?/vector?/set? strict over the persistent records,
# seq? only for real sequences, coll? the union. Parity vs the CLI oracle.
(each src ["(nil? nil)" "(nil? 0)"
"(number? 3)" "(number? :a)" "(string? \"x\")" "(string? 1)"
"(integer? 3.5)"
"(symbol? 'x)" "(keyword? :x)" "(keyword? 'x)"
"(map? {:a 1})" "(map? [1 2])"
"(vector? [1 2])" "(vector? '(1 2))"
"(set? #{1 2})" "(set? [1])"
# NB: (seq? (seq [1 2])) is true on Chez (Clojure-correct — a seq IS a
# seq) but the seed oracle returns false (non-canonical), so it's not a
# like-for-like cli-oracle comparison; the corpus encodes the canonical
# value, where Chez agrees. Test seq? on the unambiguous cases here.
"(seq? [1 2])" "(seq? '(1 2))"
"(coll? [1])" "(coll? {:a 1})" "(coll? 3)"
"(fn? inc)" "(fn? 3)"
"(boolean nil)" "(boolean 5)"
"(name :foo)" "(name 'bar)" "(name \"baz\")"
"(namespace :a/b)" "(namespace :x)"]
(let [[code out err] (run-prelude src) want (cli-oracle src)]
(ok (string "pred: " src) (and (= code 0) (= out want))
(string "chez=" out " janet=" want " | " err))))
# 3p) converters + string ops (jolt-t6cr): str/subs/vec/keyword/symbol/compare/
# int/double/gensym are host-coupled seed natives (host/chez/converters.ss),
# def-var!'d into clojure.core, resolved in prelude mode. Semantics match the
# seed (str-render-one for str, the 3-way core-compare, truncating int). Parity
# vs the CLI oracle.
(each src ["(str)" "(str \"a\")" "(str \"a\" \"b\" \"c\")" "(str 1 2)"
"(str :k)" "(str nil)" "(str \"x\" nil \"y\")" "(str \\a)"
"(str 'sym)" "(str [1 2])" "(str (* 1.0 5))"
"(subs \"hello\" 1)" "(subs \"hello\" 1 3)"
"(vec (list 1 2 3))" "(vec (range 3))" "(vec \"ab\")" "(count (vec (range 4)))"
"(keyword \"foo\")" "(keyword \"ns\" \"bar\")" "(keyword 'sym)"
"(name (keyword \"a\" \"b\"))" "(namespace (keyword \"a\" \"b\"))"
"(symbol \"x\")" "(str (symbol \"ns\" \"y\"))" "(name (symbol \"z\"))"
"(compare 1 2)" "(compare 2 1)" "(compare 1 1)" "(compare \"a\" \"b\")"
"(compare :a :b)" "(compare [1 2] [1 3])" "(compare nil nil)" "(compare nil 1)"
"(int 3.7)" "(int \\A)" "(double 5)" "(double \\A)"]
(let [[code out err] (run-prelude src) want (cli-oracle src)]
(ok (string "conv: " src) (and (= code 0) (= out want))
(string "chez=" out " janet=" want " | " err))))
# gensym uses a per-process counter, so only the PREFIX is stable across the
# Chez run vs the Janet oracle; the numeric suffix legitimately differs.
(each src ["(symbol? (gensym))" "(subs (name (gensym \"foo_\")) 0 4)" "(string? (name (gensym)))"]
(let [[code out err] (run-prelude src) want (cli-oracle src)]
(ok (string "conv: " src) (and (= code 0) (= out want))
(string "chez=" out " janet=" want " | " err))))
# 3q) transients (jolt-kl2l): transient/persistent!/conj!/assoc!/dissoc!/disj!/
# pop! as copy-on-write over the persistent collections (host/chez/transients.ss),
# plus persistent disj. get/count/contains? see THROUGH a transient (frequencies
# and group-by do (get tm k) on a transient map). vector? on a transient vector
# is false. Map/set print order isn't canonical, so assert via get/count/contains?.
(each src ["(persistent! (conj! (transient []) 1 2 3))"
"(count (conj! (conj! (transient []) 1) 2))"
"(get (assoc! (transient {}) :a 5) :a)"
"(get (transient {:x 9}) :x)"
"(contains? (assoc! (transient {}) :k 1) :k)"
"(count (persistent! (dissoc! (assoc! (assoc! (transient {}) :a 1) :b 2) :a)))"
"(vector? (transient []))"
"(persistent! (pop! (conj! (transient [1 2 3]) 4)))"
"(count (persistent! (disj! (transient #{1 2 3}) 2)))"
"(contains? (disj #{1 2 3} 2) 2)"
"(count (disj #{1 2 3} 2))"]
(let [[code out err] (run-prelude src) want (cli-oracle src)]
(ok (string "transient: " src) (and (= code 0) (= out want))
(string "chez=" out " janet=" want " | " err))))
# frequencies/group-by/into are OVERLAY fns built on transients — they need the
# full assembled prelude, so exercise them end-to-end through the jolt-chez -e
# binary (which loads rt.ss + the prelude). This doubles as a smoke test of the
# assembled -e-capable jolt-chez itself.
(defn run-jolt-chez [src]
(def proc (os/spawn ["bin/jolt-chez" "-e" src] :p {:out :pipe :err :pipe}))
(def out (ev/read (proc :out) 0x100000))
(def err (ev/read (proc :err) 0x100000))
[(os/proc-wait proc) (string/trim (if out (string out) "")) (string/trim (if err (string err) ""))])
(when (os/stat "bin/jolt-chez")
(each src ["(get (frequencies [1 1 2 3 3 3]) 3)"
"(get (frequencies [:a :b :a]) :a)"
"(get (group-by even? [1 2 3 4 5]) true)"
"(count (get (group-by even? (range 10)) false))"
"(into [] (range 5))"
"(count (into #{} [1 2 2 3]))"]
(let [[code out err] (run-jolt-chez src) want (cli-oracle src)]
(ok (string "jolt-chez -e: " src) (and (= code 0) (= out want))
(string "chez=" out " janet=" want " | " err)))))
# 3r) numeric-edge literals (jolt-q3w8): ##Inf/##-Inf/##NaN emitted to bare
# inf/nan (unbound on Chez) — fix emit-const to +inf.0/-inf.0/+nan.0, the
# -e printer to inf/-inf/nan, and str to Infinity/-Infinity/NaN (Clojure).
# Value/print cases are pure literals -> subset path (d/run-on-chez).
(each src ["(< 5 ##Inf)" "(> 5 ##-Inf)" "(= ##Inf ##Inf)"
"##Inf" "##-Inf" "##NaN" "[##Inf]" "[##NaN ##-Inf]"]
(let [[code out err] (d/run-on-chez ctx src) want (cli-oracle src)]
(ok (string "numedge: " src) (and (= code 0) (= out want))
(string "chez=" out " janet=" want " | " err))))
# str of inf/nan needs the prelude (str is a converter shim).
(each src ["(str ##Inf)" "(str ##-Inf)" "(str ##NaN)"]
(let [[code out err] (run-prelude src) want (cli-oracle src)]
(ok (string "numedge: " src) (and (= code 0) (= out want))
(string "chez=" out " janet=" want " | " err))))
# variadic assoc! (jolt-q3w8): (assoc! t k v & kvs).
(each src ["(count (persistent! (assoc! (transient {}) :a 1 :b 2 :c 3)))"
"(get (persistent! (assoc! (transient {}) :a 1 :b 2)) :b)"]
(let [[code out err] (run-prelude src) want (cli-oracle src)]
(ok (string "numedge: " src) (and (= code 0) (= out want))
(string "chez=" out " janet=" want " | " err))))
# 3s) seq-native shims + reduced (jolt-y6mv): the dominant prelude-parity crash
# bucket was 'apply jolt-nil' — core fns calling seed-native seq fns with no Chez
# shim. host/chez/natives-seq.ss shims the safe, high-value ones (mapcat/
# take-while/drop-while/partition collection arities, sort) over the seq layer,
# plus reduced/reduced? (reduce short-circuits on a reduced; deref unwraps it)
# and identical?. They lower to var-deref in prelude mode. Asserted as
# (= expected (expr)) -> "true" so seq-vs-vector equality (not print form) is the
# contract, exactly like the corpus gate.
(each src [# reduced
"(reduced? (reduced 1))" "(reduced? 1)" "(deref (reduced 9))"
"(reduce (fn [a x] (if (> a 2) (reduced a) (+ a x))) 0 [1 2 3 4 5])"
"(= [1 1 2 2] (mapcat (fn [x] [x x]) [1 2]))"
"(= [1 3 2 4] (mapcat vector [1 2] [3 4]))"
"(= [1 2 3] (mapcat identity [[1 2] [3]]))"
"(= () (mapcat vector [] [1 2]))"
"(= [1 2] (take-while (fn [x] (< x 3)) [1 2 3 1]))"
"(= [3 1] (drop-while (fn [x] (< x 3)) [1 2 3 1]))"
"(= () (take-while pos? []))"
"(= [[1 2] [3 4]] (partition 2 [1 2 3 4 5]))"
"(= [[1 2] [4 5]] (partition 2 3 [1 2 3 4 5 6]))"
"(= [[1 2] [3 :p]] (partition 2 2 [:p] [1 2 3]))"
"(= [1 2 3] (sort [3 1 2]))"
"(= [3 2 1] (sort > [1 3 2]))"
"(= [nil 1 3] (sort compare [3 nil 1]))"
"(= () (sort []))"
"(identical? :a :a)" "(identical? :a :b)"]
(let [[code out err] (run-prelude src) want (cli-oracle src)]
(ok (string "seq-native: " src) (and (= code 0) (= out want))
(string "chez=" out " janet=" want " | " err))))
# reduce-kv honors reduced is an OVERLAY fn over the native reduce — exercise it
# end-to-end through the assembled -e binary.
(when (os/stat "bin/jolt-chez")
(each src ["(= [:a] (reduce-kv (fn [a i v] (if (= i 1) (reduced a) (conj a v))) [] [:a :b :c]))"
"(= 9 (unreduced (reduced 9)))" "(= 9 (unreduced 9))"]
(let [[code out err] (run-jolt-chez src) want (cli-oracle src)]
(ok (string "seq-native -e: " src) (and (= code 0) (= out want))
(string "chez=" out " janet=" want " | " err)))))
# 3t) transducer arities (jolt-kxsr): the 1-arg map/filter/take/drop/remove/
# take-while/drop-while/mapcat return a transducer (fn [rf] rf'), and into gets a
# 3-arg (into to xform from). These lowered to the bare native procedure at the
# wrong arity (the 'cdr () not a pair' / 'incorrect number of arguments' bucket),
# so the fix is RT-side: case-lambda the seq fns + jolt-into. (map inc)/into are
# native, so into+single-xform runs in run-prelude; transduce/comp are overlay,
# so those go through the -e binary.
(each src ["(= [2 3 4] (into [] (map inc) [1 2 3]))"
"(= #{2 3 4} (into #{} (map inc) [1 2 3]))"
"(= [2 4] (into [] (filter even?) [1 2 3 4 5]))"
"(= [1 3 5] (into [] (remove even?) [1 2 3 4 5]))"
"(= [1 2] (into [] (take 2) [1 2 3 4]))"
"(= [3 4] (into [] (drop 2) [1 2 3 4]))"
"(= [1 2] (into [] (take-while (fn [x] (< x 3))) [1 2 3 1]))"
"(= [3 1] (into [] (drop-while (fn [x] (< x 3))) [1 2 3 1]))"
"(= [1 1 2 2] (into [] (mapcat (fn [x] [x x])) [1 2]))"]
(let [[code out err] (run-prelude src) want (cli-oracle src)]
(ok (string "transducer: " src) (and (= code 0) (= out want))
(string "chez=" out " janet=" want " | " err))))
(when (os/stat "bin/jolt-chez")
(each src ["(= 6 (transduce (map inc) + [0 1 2]))"
"(= 5 (transduce (map inc) + [1 2]))"
"(= 6 (transduce (map inc) (completing +) 0 [0 1 2]))"
"(= [4 6] (into [] (comp (map inc) (filter even?)) [2 3 4 5]))"
"(= 3 (reduce (fn [a x] (if (> a 2) (reduced a) (+ a x))) 0 (range 100)))"
"(into #{} (map inc) [1 2 3])"]
(let [[code out err] (run-jolt-chez src) want (cli-oracle src)]
(ok (string "transducer -e: " src) (and (= code 0) (= out want))
(string "chez=" out " janet=" want " | " err)))))
# 3u) misc seq/regex gaps (jolt-y1zq): 0-arg (conj) -> []; 0-arg (conj!) -> a
# fresh transient vector; nth sees through a transient; and irregex \p{...} /
# \P{...} unicode property classes translate to the seed's ASCII char classes
# (regex.ss). Deferred: the assoc!-odd-args seed quirk (non-Clojure, trailing key
# gets nil) and clojure.math/PI (missing ns). \p/conj!/nth run in run-prelude;
# halt-when (overlay, exercises conj 0-arg as the transduce init) via the binary.
(each src ["(= [] (conj))" "(= [1] (conj nil 1))"
"(= [] (persistent! (conj!)))"
"(= 2 (nth (transient [1 2 3]) 1))"
"(re-matches #\"^\\p{L}+$\" \"hello\")"
"(boolean (re-matches #\"^\\p{L}+$\" \"ab1\"))"
"(re-seq #\"\\p{N}+\" \"a12b345\")"
"(re-matches #\"^\\P{N}+$\" \"abc\")"
"(re-matches #\"^\\p{Ll}\\p{Lu}$\" \"aB\")"
"(re-matches #\"^\\p{Ps}x\\p{Pe}$\" \"(x)\")"
# \p{} INSIDE a [...] class emits the class content, not a nested [...]
"(= \" \" (re-matches #\"(?u)^[\\s\\p{Z}]+$\" \" \"))"]
(let [[code out err] (run-prelude src) want (cli-oracle src)]
(ok (string "y1zq: " src) (and (= code 0) (= out want))
(string "chez=" out " janet=" want " | " err))))
(when (os/stat "bin/jolt-chez")
(each src ["(= 7 (transduce (halt-when (fn [x] (> x 5))) conj [1 2 7 3]))"
"(= [1 2 3] (transduce (halt-when (fn [x] (> x 5))) conj [1 2 3]))"]
(let [[code out err] (run-jolt-chez src) want (cli-oracle src)]
(ok (string "y1zq -e: " src) (and (= code 0) (= out want))
(string "chez=" out " janet=" want " | " err)))))
# 3v) multimethod dispatch (jolt-9ls5): defmulti/defmethod expand to
# defmulti-setup/defmethod-setup (+ get-method/methods/remove-method/
# prefer-method/prefers); host/chez/multimethods.ss provides the runtime. A
# jolt-multifn record carries its dispatch fn + method table; jolt-invoke
# dispatches it (direct match, then isa?/hierarchy + prefers, then :default).
# Dispatch uses the overlay isa?/derive/make-hierarchy, so these need the full
# prelude -> the -e binary. (Class-based dispatch — (class x)/String — is
# deferred; it needs the deftype/class subsystem.)
(when (os/stat "bin/jolt-chez")
(each src ["(= \"two\" (do (defmulti f identity) (defmethod f 1 [_] \"one\") (defmethod f 2 [_] \"two\") (f 2)))"
"(= \"circle\" (do (defmulti area :shape) (defmethod area :circle [_] \"circle\") (area {:shape :circle})))"
"(= \"other\" (do (defmulti f identity) (defmethod f 1 [_] \"one\") (defmethod f :default [_] \"other\") (f 99)))"
"(= 5 (do (defmulti g (fn [a b] a)) (defmethod g :add [_ b] b) (g :add 5)))"
"(= :is-shape (do (derive :hsq :hshape) (defmulti hmm identity) (defmethod hmm :hshape [_] :is-shape) (hmm :hsq)))"
"(= :parent (do (def hh (atom (derive (make-hierarchy) :c :p))) (defmulti cmm identity :hierarchy hh) (defmethod cmm :p [_] :parent) (cmm :c)))"
"(= :exact (do (derive :de1 :de2) (defmulti emm identity) (defmethod emm :de2 [_] :parent) (defmethod emm :de1 [_] :exact) (emm :de1)))"
"(= \"one\" (do (defmulti f identity) (defmethod f 1 [_] \"one\") ((get-method f 1) 1)))"
"(= \"one\" (do (defmulti f identity) (defmethod f 1 [_] \"one\") ((get (methods f) 1) 1)))"
"(= 2 (do (defmulti f identity) (defmethod f 1 [_] \"one\") (defmethod f 2 [_] \"two\") (count (methods f))))"]
(let [[code out err] (run-jolt-chez src) want (cli-oracle src)]
(ok (string "multimethod: " src) (and (= code 0) (= out want))
(string "chez=" out " janet=" want " | " err))))
# no-match throws (exits non-zero), like the corpus :throws row.
(let [[code out err] (run-jolt-chez "(do (defmulti f identity) (defmethod f 1 [_] \"one\") (f 99))")]
(ok "multimethod: no match throws" (not= code 0) (string "code=" code))))
# 3w) dynamic-var constants (jolt-9ls5): *clojure-version* (a map) and
# *unchecked-math* (false) are seed natives, def-var!'d by host/chez/dynamic-vars.ss.
# (*ns* needs a namespace value with get-see-through and map?=false — deferred.)
# (map? *clojure-version*) is intentionally NOT asserted: the seed stores it as a
# mutable table, so its map? is false (a seed quirk); Chez models it as a real map
# (map? true). Not a corpus contract, so the divergence is moot.
(each src ["(== 1 (:major *clojure-version*))" "(== 11 (:minor *clojure-version*))"
"(= false *unchecked-math*)"]
(let [[code out err] (run-prelude src) want (cli-oracle src)]
(ok (string "dynvar: " src) (and (= code 0) (= out want))
(string "chez=" out " janet=" want " | " err))))
# 3x) non-ASCII / control-char string literals (jolt-x0os): Janet's %j renders
# a non-ASCII char as raw UTF-8 bytes (\xC3\xA9) and a control char as \xHH
# with NO terminating semicolon — both forms Chez's reader rejects ("invalid
# character \ in string hex escape"). emit-const must emit a Chez string with
# codepoint escapes (\x<cp>;) so a literal that just passes through (str/=)
# round-trips. (count/subs/nth over a multibyte string index by BYTES in the
# seed but by codepoints on Chez — a separate semantic gap, not tested here.)
(each src ["(str \"h\xC3\xA9llo\")"
"\"h\xC3\xA9llo\""
"(str \"na\xC3\xAFve caf\xC3\xA9\")"
"(str \"\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E\")"
"(str \"\xCE\xB1\xCE\xB2\xCE\xB3 \xCE\xB4\xCE\xB5\xCE\xB6\")"
"(= \"h\xC3\xA9llo\" (str \"h\xC3\xA9llo\"))"
"(str \"a\x01b\")"
"(str \"tab\tend\")"]
(let [[code out err] (run-prelude src) want (cli-oracle src)]
(ok (string "non-ascii str: " src) (and (= code 0) (= out want))
(string "chez=" out " janet=" want " | " err))))
# 4) perf signal: emitted fib(30) in-Scheme timing (excludes Chez startup), to
# track against the spike ceiling (hand-Scheme fixnum fib ~5ms). Informational
# — the truthy? wrapper is now elided (jolt-nkcb); the residual gap is jolt's
# all-flonum number model vs the spike's fixnum fib (typed fl*/fx* = Phase 4).
# The full timed bench (fib + mandelbrot vs ceilings) is bench-pipeline.janet.
(let [fib-ir (backend/analyze-form ctx (in (r/parse-next "(defn fib [n] (if (< n 2) n (+ (fib (- n 1)) (fib (- n 2)))))") 0))
fib-scm (emit/emit fib-ir)
timed (string "(import (chezscheme))\n(load \"host/chez/rt.ss\")\n"
fib-scm "\n"
"(define fib (var-deref \"user\" \"fib\"))\n"
"(define (now-ns) (let ((t (current-time 'time-monotonic))) (+ (* (time-second t) 1000000000) (time-nanosecond t))))\n"
"(fib 24)(fib 24)\n"
"(let* ((t0 (now-ns)) (r (fib 30)) (ms (/ (- (now-ns) t0) 1000000.0)))\n"
" (printf \"~a ~a\\n\" (jolt-pr-str r) (exact->inexact ms)))")]
(spit "/tmp/chez-jolt-fib-timed.ss" timed)
(def proc (os/spawn ["chez" "--script" "/tmp/chez-jolt-fib-timed.ss"] :p {:out :pipe :err :pipe}))
(def out (string/trim (string (ev/read (proc :out) 0x100000))))
(def err (string/trim (string (or (ev/read (proc :err) 0x100000) ""))))
(def code (os/proc-wait proc))
(def parts (string/split " " out))
(def result (get parts 0))
(def ms (scan-number (or (get parts 1) "999")))
(ok "timed fib(30) correct" (and (= code 0) (= result "832040")) (string out " | " err))
(printf " emitted fib(30): %s in %.2f ms (hand-Scheme spike ~5ms)" result ms))
(printf "\nemit-test: %d/%d passed" (- total fails) total)
(os/exit (if (> fails 0) 1 0))

View file

@ -1,154 +0,0 @@
# Chez Phase 3 inc 5a (jolt-50xx) — value-parity gate for the PORTABLE Clojure
# reader (jolt.reader) vs the Janet seed reader (src/jolt/reader.janet, oracle).
#
# jolt.reader holds the lexing/parsing LOGIC in portable Clojure and delegates
# form construction + number parsing to the jolt.host contract. Here it runs
# interpreted ON THE JANET HOST (loaded via bootstrap-load-source); each input is
# read by BOTH readers and the resulting FORMS compared with jolt's own = (so the
# representation, host-built either way, matches structurally). Positions differ
# (char vs byte indices) and are not compared.
#
# janet test/chez/reader-parity.janet (from repo root)
(import ../../src/jolt/api :as api)
(import ../../src/jolt/backend :as backend)
(import ../../src/jolt/reader :as r)
(import ../../src/jolt/types_ctx :as tctx)
(import ../../src/jolt/types_ns :as tns)
(import ../../src/jolt/types_var :as tvar)
(import ../../src/jolt/core :as core)
(var total 0) (var fails 0)
(defn ok [name pred &opt extra]
(++ total)
(if pred (printf "ok: %s" name)
(do (++ fails) (printf "FAIL: %s %s" name (or extra "")))))
(def ctx (api/init {:compile? true}))
(def src (get (get (ctx :env) :embedded-sources) "jolt.reader"))
(assert src "jolt.reader not embedded (check stdlib_embed collect)")
(backend/bootstrap-load-source ctx "jolt.reader" src)
(def read-one (tvar/var-get (tns/ns-find (tctx/ctx-find-ns ctx "jolt.reader") "read-one")))
(assert read-one "jolt.reader/read-one not found")
# jolt's own value equality (the host =), the same comparator the corpus gate uses.
(defn jeq [a b] (core/jolt-equal? a b))
(defn check [input]
(def w (protect (in (r/parse-next input) 0))) # Janet seed reader (oracle)
(def g (protect (read-one input))) # portable Clojure reader
(cond
# both readers throw on the same input = faithful parity (safety net for
# genuinely-invalid input). We FIX latent bugs rather than reproduce them
# (fix-bugs-dont-reproduce), so this should be rare.
(and (not (w 0)) (not (g 0))) (ok input true)
(not (w 0)) (ok input false (string "janet threw, clj didn't: clj=" (string/format "%p" (g 1))))
(not (g 0)) (ok input false (string "clj threw, janet ok: " (string (g 1))))
(ok input (jeq (w 1) (g 1))
(string "clj=" (string/format "%p" (g 1)) " janet=" (string/format "%p" (w 1))))))
# For inputs where the Janet seed reader is a BUGGY oracle (a latent bug we FIX in
# the port rather than reproduce, fix-bugs-dont-reproduce), assert the portable
# reader against the hand-verified correct value. The Janet seed isn't fixed —
# it's deleted in Phase 5 — so we don't compare against it here.
(defn check-correct [input expected]
(def g (protect (read-one input)))
(if (not (g 0))
(ok input false (string "clj threw: " (string (g 1))))
(ok input (jeq expected (g 1))
(string "clj=" (string/format "%p" (g 1)) " want=" (string/format "%p" expected)))))
# --- inc 5a: atoms -------------------------------------------------------------
# nil / bool
(each i ["nil" "true" "false"] (check i))
# symbols (plain, ns'd, punctuation, special chars)
(each i ["foo" "foo-bar" "my.ns/bar" "+" "-" "*" "->" "<=" "some?" "a1" "x'" "ns/+"] (check i))
# keywords
(each i [":a" ":foo-bar" ":my.ns/key" "::auto" ":a1" ":+"] (check i))
# strings (escapes)
(each i [`"hello"` `"with space"` `"tab\there"` `"nl\nhere"` `"q\"q"` `"back\\slash"` `""`] (check i))
# integers / signs / hex / radix
(each i ["0" "42" "-7" "123456" "0xFF" "0x10" "-0xff" "2r1010" "16rFF" "36rZ" "8r17"] (check i))
# floats / exponent / ratio / N|M suffix
(each i ["3.14" "-2.5" "0.0" "1e10" "1.5e-3" "2E5" "10N" "3.14M" "1/2" "-3/4" "22/7"] (check i))
# leading + reads as the positive number (jolt-if19 fixed in the port; the Janet
# seed reader still errors on these, so assert against the correct value).
(check-correct "+5" 5)
(check-correct "+42" 42)
(check-correct "+0xff" 255)
(check-correct "+3.5" 3.5)
# characters
(each i [`\a` `\Z` `\0` `\newline` `\tab` `\space` `\return` `\\` `\(` `\{` `\%` `A` `\o101`] (check i))
# For #() forms the two readers gensym DIFFERENT param names, so compare modulo
# gensyms: canonicalize each "#"-suffixed symbol to G__<first-occurrence-order>.
(defn- gsym? [nm] (and (> (length nm) 0) (= (in nm (- (length nm) 1)) (chr "#"))))
(defn normalize [form mp]
(cond
(and (struct? form) (= :symbol (form :jolt/type)) (gsym? (form :name)))
(do (when (nil? (get mp (form :name))) (put mp (form :name) (string "G__" (length mp))))
{:jolt/type :symbol :ns nil :name (get mp (form :name))})
(and (struct? form) (= :symbol (form :jolt/type))) form
(array? form) (map |(normalize $ mp) form)
(tuple? form) (tuple/slice (tuple ;(map |(normalize $ mp) form)))
(and (struct? form) (= :jolt/set (form :jolt/type)))
{:jolt/type :jolt/set :value (tuple/slice (tuple ;(map |(normalize $ mp) (form :value))))}
(struct? form) (let [order (r/form-kv-order form)]
(if order (r/reader-map (array ;(map |(normalize $ mp) order))) form))
form))
(defn check-anon [input]
(def w (in (r/parse-next input) 0))
(def g (protect (read-one input)))
(if (not (g 0))
(ok input false (string "clj threw: " (string (g 1))))
(ok input (core/jolt-equal? (normalize w @{}) (normalize (g 1) @{}))
(string "clj=" (string/format "%p" (normalize (g 1) @{})) " janet=" (string/format "%p" (normalize w @{}))))))
# --- inc 5b: collections + quote/deref/meta -----------------------------------
# lists
(each i ["(1 2 3)" "(a b c)" "()" "(foo (bar baz) qux)" "(+ 1 (* 2 3))"] (check i))
(check "(1 ; c\n 2 3)") # comment inside a list
# vectors
(each i ["[1 2 3]" "[]" "[a [b c]]" "[1 [2 [3]]]" "[:a :b :c]"] (check i))
# maps
(each i ["{:a 1 :b 2}" "{}" "{:a {:b 1}}" "{:x [1 2] :y {:z 3}}" "{1 2 3 4}"] (check i))
(check "{:a 1 ; c\n :b 2}") # comment inside a map (key/value slots)
# mixed / nested forms (real code shapes)
(each i ["(defn f [x] (+ x 1))" "{:list (1 2) :vec [3 4]}" "(let [a 1 b 2] (+ a b))"] (check i))
# quote / syntax-quote / unquote / deref
(each i ["'foo" "'(1 2 3)" "''x" "'[a b]" "'{:a 1}"] (check i))
(each i ["`foo" "`(a b c)" "`[x y]"] (check i))
(check "`\"meow\"") # syntax-quote of a literal collapses
(each i ["~x" "~@xs" "@x" "@(atom 1)"] (check i))
(check "`(a ~b ~@c)")
# metadata
(each i ["^:dynamic x" "^String s" "^:private foo" "^{:a 1} v" "^t/Ray r"] (check i))
(check "(defn ^:private g [x] x)")
# --- inc 5c: dispatch (#) ------------------------------------------------------
# sets
(each i ["#{1 2 3}" "#{}" "#{:a :b}" "#{[1 2] {:k 1}}"] (check i))
# var-quote
(each i ["#'foo" "#'my.ns/bar"] (check i))
# regex (tagged :regex form — compared by source string)
(each i [`#"[0-9]+"` `#"\d+"` `#"a.c"` `#"(?i)foo"`] (check i))
# tagged literals (#inst / #uuid / arbitrary #tag)
(each i [`#inst "2020-01-01"` `#uuid "00000000-0000-0000-0000-000000000000"`
`#foo bar` `#foo [1 2]` `#my.ns/Tag {:a 1}`] (check i))
# #_ discard (in collections + top-level + map slots)
(each i ["[1 #_2 3]" "(a #_b c)" "#_ x y" "{:a #_1 2 :b 3}" "[#_#_1 2 3]"] (check i))
# #? reader-conditional (jolt + default active; cljs inactive)
(each i ["#?(:jolt 1 :clj 2)" "#?(:clj 1 :default 2)" "[#?(:cljs 1) 2]"
"#?(:jolt :yes :default :no)"] (check i))
# #?@ splice
(each i ["[#?@(:jolt [1 2]) 3]" "[#?@(:cljs [9]) 4]" "(0 #?@(:default [1 2 3]) 4)"] (check i))
# ## symbolic (Inf/-Inf comparable; NaN checked by property)
(each i ["##Inf" "##-Inf"] (check i))
(let [g (read-one "##NaN")] (ok "##NaN" (not= g g) (string "got " (string/format "%p" g))))
# #^ deprecated metadata reader macro (= ^)
(each i ["#^String x" "#^:dynamic y"] (check i))
# #() anonymous fns (gensym-normalized)
(each i ["#(+ %1 %2)" "#(* % %)" "#(do %2 %&)" "#(foo)" "#(vector %1 %2 %3)"
"#(%)" "#(apply + %&)" "#(get {:a %} :a)" "#(conj #{%1} %2)" "#(inc %)"] (check-anon i))
(printf "\n%d/%d ok" (- total fails) total)
(when (> fails 0) (os/exit 1))