diff --git a/foundational-runtime-handoff.md b/foundational-runtime-handoff.md new file mode 100644 index 0000000..31f123f --- /dev/null +++ b/foundational-runtime-handoff.md @@ -0,0 +1,198 @@ +# Foundational Runtime Epic — Handoff + +**Epic:** jolt-5vsp · **Predecessor:** jolt-ffn (targeted specialization — concluded) +**Date:** 2026-06-16 + +This is a cold-start handoff. Read it top to bottom before touching code. Its +whole point is to keep the fresh session from re-running the experiments that +already came back flat, and to start from the one measurement that actually +tells us where to invest. + +## Why this epic exists + +The targeted-specialization epic (jolt-ffn) tried to close jolt's constant-factor +gap vs JVM Clojure with per-form compiler passes. Three independent attempts all +came back flat: + +| Attempt | Bead | Result | +|---|---|---| +| Record field-read guard removal (bare field reads) | jolt-3ko | ~3% on dispatch (shipped #141 — kept for correctness, not speed) | +| Protocol inline cache (runtime, per-method) | jolt-ez5h | ~0% — the per-dispatch gen-check exactly cancels the find-protocol-method saving; `find` was never the bottleneck | +| Record-ctor descriptor-baking (fewer allocs/record) | jolt-p7fo | flat on binary-trees + broke the gate; reverted | + +The conclusion: **the gap is structural to jolt-on-Janet, not a missing +optimization.** Targeted passes remove only the cheap parts; the structural floor +remains. + +## The scorecard (jolt / JVM Clojure) + +Regenerate any time with `JVM=1 bench/run.sh` (the absolute-reference mode). + +| Axis | Bench | jolt/JVM | +|---|---|---| +| Pure float compute | `mandelbrot` | **~15× ← THE FLOOR** | +| Persistent collections (HAMT) | `collections` | ~28× | +| Recursion (call + arith) | `fib` | ~37× | +| Megamorphic dispatch | `dispatch` | ~76× | +| Monomorphic dispatch | `mono-dispatch` | ~109× | +| Allocation / GC | `binary-trees` | ~314× (≈150× at depth 10) | + +`mandelbrot` is the floor: pure tight arithmetic loops — no dispatch, no +allocation, no collections — and native arith already fires (jolt-3pl). So ~15× +is what jolt's *execution substrate* costs on the simplest possible workload. +Every other axis adds structural overhead **on top** of that floor. + +**Machine caveat:** the dev machine swaps heavily (~13 GB). Alloc-heavy benches +(`binary-trees`, `collections`) inflate badly; light benches (`mandelbrot`, +`fib`, `dispatch`) are trustworthy. Get absolute alloc numbers on a clean machine. + +## The four structural walls + +1. **Bytecode-VM execution.** jolt's backend emits **Janet** (a register-bytecode + VM) and runs it on the Janet interpreter loop — no JIT, no native code. Every + op is bytecode dispatch. This is the `mandelbrot` 15× floor. +2. **Mark-sweep GC.** Janet's GC scans all live objects each cycle (no + generations). Live-data + alloc-heavy workloads (`binary-trees` retains the + tree) pay O(live) per GC. The JVM's generational GC makes young-object churn + nearly free. +3. **Indirect calls.** Protocol dispatch and fn calls go through indirection + (closures, the protocol registry). The JVM inlines/devirtualizes. jolt's + devirt (jolt-41m) only fires on *statically*-proven monomorphic sites; + `reduce`/`mapv` over a collection doesn't give that proof, so the common + runtime-monomorphic case pays full dispatch (that's why `mono-dispatch` is + *worse* than megamorphic — the JVM inline-caches it to near-free, jolt doesn't). +4. **Boxed / generic representations.** Records are tuples `[descriptor field…]`; + field access goes through a tag guard unless the type is proven. Generic ops + carry runtime type checks. (Open question: are Janet *numbers* boxed? Verify + in the spike — it decides whether unboxing is a lever or already done.) + +## Foundational levers (ranked) + +1. **Native codegen — emit C, not Janet bytecode.** The Stalin approach. Compile + jolt IR → C → machine code via the system compiler. The *only* lever that + moves the 15× compute floor; could approach C/JVM speed on compute-bound code. + Massive (a new backend). Plausible incremental shape: a jolt-IR→C compiler for + *hot* fns with a fallback to the existing bytecode path for unsupported forms — + mirroring today's interpret/compile hybrid. Needs to confirm Janet's C-API / + native-module story can be targeted incrementally. +2. **Structural GC-pressure reduction.** Value-type small records (avoid heap), + transient/editable-node hot paths (RFC 0003 future work — pvec/phm/sorted are + now tries/HAMT/RB, so O(1) `transient`/`persistent!` via editable nodes is + open). Helps the alloc-bound axes (`binary-trees`, `collections`). Does **not** + touch the compute floor. +3. **Deeper devirt + body inline.** Propagate element/return types so devirt + fires on runtime-monomorphic collections, then inline the method body + (jolt-4x9 element types + jolt-t6r). Helps dispatch. Bounded ceiling (still + bytecode underneath). + +## STATUS (2026-06-16) — lever 1 (native codegen) built and working + +The spike ran and lever 1 is now implemented. Full writeups: +`docs/foundational-runtime-spike-results.md` (floor localization) and +`docs/foundational-runtime-lever1-native-codegen.md` (native codegen). + +Done (all merged to main, PRs #143–#148): +- **Floor localized:** the 15.4× decomposes into a **Janet-VM floor ≈10.8× JVM** + (only native codegen moves it) + a **jolt loop-lowering ≈1.43×** (cheap backend + win, bead **jolt-v28u**). Janet numbers are already unboxed (not a lever). +- **Native codegen (jolt-ihdp, CLOSED):** `src/jolt/cgen.janet` translates + numeric-leaf fns (numeric in/out, native-op arithmetic + loop/recur/if/let/do) + to C. Wired into the backend `:def` emit under **`JOLT_CGEN=1`** (opt-in). The + `.so` is content-addressed + cached. **mandelbrot 224ms → 12.4ms (~18×)**, + beats JVM. Leaf-first falls out free (callers stay bytecode, call native fn). +- **Build-time AOT (jolt-a7ds, partial):** `:cgen-collect?` records leaf fns at + build, `aot-build` compiles them into one `.so` + manifest; `:cgen-prebuilt` + + `load-aot` install them at deploy with **no cc** (proven with cc off PATH). + +Open work under epic jolt-5vsp: +- **jolt-a7ds** — fuse the prebuilt `.so` + manifest into the `jpm` exe for a + literal single binary (+ a `jolt cgen-build -m app` CLI). The heaviest piece; + into jpm executable-build, not the compiler. +- **jolt-v28u** — `while`-loop lowering for tail `recur` (cheap ~30%, independent + of cgen; helps ALL loops, not just cgen candidates). +- **jolt-l1l4** — widen cgen numeric grammar (mod/rem/bit-ops/min/max, mixed fns). +- **jolt-qx70** — hot-fn auto-detection (drop the global `JOLT_CGEN` knob). +- Lever 2 (GC-pressure) and lever 3 (deeper devirt) — untouched; see below. + +The original spike instructions are preserved below for context. + +**Localize the 15× floor.** Build three `mandelbrot` implementations and compare: + +- **jolt-compiled** `mandelbrot` (already in `bench/mandelbrot.clj`), +- **hand-written Janet** `mandelbrot` (the same nested loop, idiomatic Janet — + write it directly, no jolt), +- **JVM Clojure** `mandelbrot`. + +Two ratios fall out: + +- **jolt-emitted-Janet vs hand-Janet** → how much overhead jolt's *backend* adds + over optimal Janet. To see jolt's emitted Janet, use the backend emit path + (`backend/emit-ir` on the analyzed `run`/`count-point` fns) — note `:arities` + etc. are jolt pvecs, so introspection is awkward; easier to read the emitted + Janet via the compile path or just A/B the timings. +- **hand-Janet vs JVM** → the Janet VM's own floor. + +Decision: + +- If **hand-Janet ≈ jolt** and hand-Janet is ~15× JVM → the floor is **Janet's + bytecode VM**. Native codegen (lever 1) is the only fix. Commit to the spike of + a jolt-IR→C path for one hot fn and measure. +- If **jolt ≫ hand-Janet** → jolt's backend emits suboptimal Janet; there's + headroom in the **backend** (cheaper, no new runtime). Find what it emits that + hand-Janet doesn't. + +Also measure the **GC share** on `binary-trees` (Janet GC stats around the run — +`(gccollect)` / `gcinterval`, or count allocations) to size lever 2 honestly. + +## Key files / mechanisms + +- **Backend (IR → Janet emit):** `src/jolt/backend.janet`. `native-ops` (~L322) + emits native Janet arith; `emit-ir` (~L674) runs passes then emits. A native-C + backend would branch here. +- **Passes / inference:** `jolt-core/jolt/passes.clj` (`run-passes`), + `jolt-core/jolt/passes/types.clj` (inference; the `:fn` branch ~L527 now seeds + ^Record param hints — #141), `jolt-core/jolt/passes/inline.clj` + (scalar-replace, `ctor-shape`). +- **Record representation:** `src/jolt/types_protocols.janet` — `make-record` + (~L145, the ~5-alloc/record path), `record-shape-for` (~L139, rebuilds its + cache key every call), `record-tag`. Records are tuples `[descriptor field…]`. +- **Dispatch + ctors:** `src/jolt/eval_runtime.janet` — + `protocol-dispatch-impl` (~L62), `make-deftype-ctor-impl` (~L382). +- **Config knobs:** `src/jolt/config.janet` — `JOLT_DIRECT_LINK`, + `JOLT_WHOLE_PROGRAM`, `JOLT_OPTIMIZE`, the `ctx-shaping-env-vars` list (any new + ctx-shaping env var MUST be added there and to `image-cache-path`). +- **Self-hosting design:** `docs/self-hosting-compiler.md` (the kernel/value-layer + boundary), `docs/rfc/0003-transients.md` (editable-node future work). + +## How to build, run, measure + +```sh +jpm build # build/jolt (ctx baked, ~20ms startup); from-source is ~8s cold +export PATH="$PWD/build:$PATH" +bench/run.sh # jolt only, WP on +JVM=1 bench/run.sh # jolt vs JVM scorecard (needs `clojure` on PATH) +bench/run.sh mandelbrot 400 # one bench, custom size +JOLT_WHOLE_PROGRAM=0 bench/run.sh # measure what WP buys +``` + +Gate: `jpm build; janet run-tests.janet` (parallel, ~100s; `JOLT_TEST_JOBS` +overrides). Bench memory hygiene (`bd memories bench-isolation-gotcha`): never run +a perf matrix while other CPU work runs — it starves later configs and produces +bogus numbers. Sandwich A/B/A. + +## What NOT to repeat (already flat — see beads for detail) + +- Runtime protocol inline cache (jolt-ez5h): gen-check cancels the saving. +- Field-read guard removal as a *speed* play (jolt-3ko): ~3%; machinery dominates. + (The #141 change is kept for correctness + the `with-meta`-on-symbols fix.) +- `make-record` descriptor-baking (jolt-p7fo): flat — `binary-trees` is dominated + by the live retained tree + GC, not the short-lived intermediate allocs. + +## Open questions for the spike + +- Are Janet numbers boxed? (Lever or already done.) +- Does Janet expose a native-module / C-codegen path jolt can target incrementally + (hot fns → C, rest → bytecode)? +- What fraction of `binary-trees` is GC vs execution? +- Is there a cheaper record representation (Janet struct vs tuple-with-descriptor) + that lowers field-read + alloc cost without a new backend? diff --git a/host/chez/async.ss b/host/chez/async.ss index 0d2a1ca..cab5c0b 100644 --- a/host/chez/async.ss +++ b/host/chez/async.ss @@ -3,7 +3,7 @@ ;; No mature Chez fibers library exists, and this Chez is a threaded build, so a ;; `go` block is just an OS thread and a channel is a mutex+condition blocking ;; queue: ! are the blocking !! (they "park" by blocking the thread). -;; Like the Janet stackful-fiber model, ! work ANYWHERE (no CPS transform) — +;; ! work ANYWHERE (no CPS transform) — ;; here because they are ordinary blocking calls. Real parallelism, shared heap. ;; Trade-off: one OS thread per go block (fine for typical use / conformance, not ;; for thousands of simultaneous go blocks). @@ -13,7 +13,7 @@ ;; buffers never block the putter. A transducer is applied on the put side. ;; ;; The fns are def-var!'d into clojure.core.async; go/go-loop/thread are macros -;; (mark-macro!) expanding to go-spawn, mirroring src/jolt/async.janet. Loaded after +;; (mark-macro!) expanding to go-spawn. Loaded after ;; concurrency.ss (reuses ms->duration). Requires a threaded Chez build. ;; --- buffers ---------------------------------------------------------------- @@ -49,7 +49,7 @@ ;; A transducer is a jolt fn (xform); (xform add-rf) yields the channel's reducing ;; fn. add-rf: 0-arg init, 1-arg completion, 2-arg step (enqueue the output). A -;; `reduced` step result closes the channel. Mirrors async.janet make-add-rf. +;; `reduced` step result closes the channel. (define (ac-make-add-rf ch) (lambda args (cond ((null? args) ch) ; init @@ -135,8 +135,8 @@ (else ac-poll-empty)))) ;; (alts! [ch ...]) — take from whichever channel is ready first; returns -;; [value channel] (value nil if that channel closed). Take-only (like the Janet -;; impl). Polls with a 1ms backoff — no cross-channel wait-set yet. +;; [value channel] (value nil if that channel closed). Take-only. +;; Polls with a 1ms backoff — no cross-channel wait-set yet. (define ac-1ms (make-time 'time-duration 1000000 0)) (define (jolt-async-alts chans) (let ((cs (seq->list (jolt-seq chans)))) diff --git a/host/chez/atoms.ss b/host/chez/atoms.ss index ab32d62..f92ce57 100644 --- a/host/chez/atoms.ss +++ b/host/chez/atoms.ss @@ -1,7 +1,7 @@ ;; atoms (jolt-9ziu) — host-coupled mutable reference cells for the Chez host. ;; -;; atom/deref/swap!/reset! stay in the Janet seed (not the clojure.core overlay), -;; so the Chez runtime needs native shims, def-var!'d into clojure.core. They +;; atom/deref/swap!/reset! are host primitives (not the clojure.core overlay), +;; so the Chez runtime provides native shims, def-var!'d into clojure.core. They ;; lower to var-deref in prelude mode. The hierarchy machinery ;; (global-hierarchy = (atom (make-hierarchy))) calls `atom` at the prelude's ;; LOAD time, so without this shim the whole prelude fails to load. @@ -42,7 +42,7 @@ (error #f "Invalid reference state")))) ;; notify each watch (k ref old new), in insertion order (alist is reverse-built, -;; so walk it reversed to match add order — matches the seed's :pairs iteration). +;; so walk it reversed to match add order). (define (jolt-atom-notify a old new) (for-each (lambda (kv) (jolt-invoke (cdr kv) (car kv) a old new)) (reverse (jolt-atom-watches a)))) @@ -65,7 +65,7 @@ ;; (swap! a f arg*): JVM-style CAS loop — read, compute f OUTSIDE the lock, then ;; atomically compare-and-set; retry if another thread changed it. Validate the -;; new value before storing, notify watches after (the seed order). +;; new value before storing, notify watches after. (define (jolt-swap! a f . args) (let retry () (let* ((old (jolt-atom-val a)) diff --git a/host/chez/bootstrap.ss b/host/chez/bootstrap.ss index d74b280..498f115 100644 --- a/host/chez/bootstrap.ss +++ b/host/chez/bootstrap.ss @@ -6,7 +6,7 @@ ;; compiler image from the .clj/.ss sources using the ON-CHEZ compiler (emit-image.ss), ;; writing fresh artifacts. No Janet is invoked: read -> analyze -> emit all run on ;; Chez. The seed is a JOINT fixpoint, so a rebuild from an up-to-date seed -;; reproduces it byte-for-byte (test/chez/bootstrap-test.janet checks this); when +;; reproduces it byte-for-byte (`make selfhost` checks this); when ;; the sources change, run it twice to reconverge and re-mint the seed. ;; ;; Run from the repo root: diff --git a/host/chez/collections.ss b/host/chez/collections.ss index 0c5b865..f99a6c4 100644 --- a/host/chez/collections.ss +++ b/host/chez/collections.ss @@ -205,7 +205,7 @@ ((pset? coll) (pset-conj coll x)) ;; a list/seq conjs by PREPENDING (seq.ss: cseq / empty-list). conj onto a ;; list stays a list, conj onto a lazy/realized seq yields a seq cell (a - ;; Cons) — list?-preserving, matching the seed. + ;; Cons) — list?-preserving. ((cseq? coll) (if (cseq-list? coll) (cseq-list x coll) (cseq-realized x coll))) ((empty-list-t? coll) (cseq-list x jolt-nil)) ((pmap? coll) diff --git a/host/chez/compile-eval.ss b/host/chez/compile-eval.ss index 35b7160..1b4f3d1 100644 --- a/host/chez/compile-eval.ss +++ b/host/chez/compile-eval.ss @@ -115,7 +115,7 @@ (loop (cdr fs) (jolt-compile-eval-form (car fs) ns))))) ;; runtime defmacro: def the expander fn + mark the var a macro so subsequent ;; forms expand it (hc-macro? reads var-macro-table). Mirrors emit-image.ss - ;; ei-emit-ns and the Janet seed eval-defmacro. + ;; ei-emit-ns. ((ce-macro-form? form) (let-values (((nm fn-form) (ce-defmacro->fn form))) (def-var! ns nm (jolt-compile-eval-form fn-form ns)) @@ -132,7 +132,7 @@ ;; clojure.core/load-string: read every form from the source string and compile+ ;; eval each in the current ns, returning the last value (nil for blank input). -;; Mirrors src/jolt/api.janet load-string (the parse-next loop). jolt-r8ku. +;; jolt-r8ku. (define (jolt-load-string s) (let loop ((src s) (result jolt-nil)) (let ((pn (jolt-parse-next src))) diff --git a/host/chez/converters.ss b/host/chez/converters.ss index 47e83aa..8e56795 100644 --- a/host/chez/converters.ss +++ b/host/chez/converters.ss @@ -45,7 +45,7 @@ (cond ((jolt-nil? a) jolt-nil) ((keyword? a) a) - ;; a 1-arg string splits on the FIRST "/" into ns/name, like the seed + ;; a 1-arg string splits on the FIRST "/" into ns/name: ;; (keyword "x/y") => :x/y with ns "x" — destructure's {:keys [x/y]} builds ;; the key this way, so without the split the namespaced key never matches. ((string? a) @@ -79,7 +79,7 @@ ((= (length args) 2) (jolt-symbol (car args) (cadr args))) (else (error #f "symbol: wrong arity")))) -;; gensym: per-process counter, like the seed's gensym_counter. +;; gensym: per-process counter. (define jolt-gensym-counter 0) (define (jolt-gensym . prefix) (let ((p (if (null? prefix) "G__" (car prefix)))) diff --git a/host/chez/dot-forms.ss b/host/chez/dot-forms.ss index 4b016e3..dd73774 100644 --- a/host/chez/dot-forms.ss +++ b/host/chez/dot-forms.ss @@ -3,11 +3,11 @@ ;; to a :host-call; the Chez emit routes a non-shimmed :host-call through ;; record-method-dispatch. This file extends that dispatcher with the collection ;; arms the interpreter's dispatch-member covers but the record/string base does -;; not, mirroring src/jolt/interop/collections.janet precedence exactly: +;; not, with this precedence: ;; ;; * collection interop wins first — count/seq/nth/get/valAt/containsKey on a ;; vector/map/set/seq/record (so (. {:count 9} count) is the entry count, 1, -;; like the seed, NOT the :count field). +;; NOT the :count field). ;; * field access — a "-name" member reads the field (records and maps). ;; * map member — a stored fn is a method (called with self + args); any ;; other value is returned as a field. @@ -20,7 +20,7 @@ (define %dot-rmd record-method-dispatch) ;; Vectors / maps / sets only (records are jolt-map? here). Raw seqs are excluded: -;; the seed's coll-interop accepts some seq representations and not others (a +;; coll-interop accepts some seq representations and not others (a ;; plain (seq v) returns nil from .count, a lazy-seq returns the count), an ;; inconsistency Chez's normalized cseq can't mirror — so a raw seq target falls ;; through to the base dispatcher rather than risk a divergence the corpus would @@ -40,7 +40,7 @@ ((string=? name "containsKey") (list (jolt-contains? obj (car args)))) (else #f))) -;; Mirror the seed's universal object-methods (src/jolt/eval_resolve.janet): on a +;; Universal object-methods: on a ;; non-record map these win OVER a field lookup, like dispatch-member. getMessage ;; on an ex-info reads its :message (the one the corpus exercises); getCause reads ;; :cause; toString/hashCode/equals round out the set. Returns a boxed result or diff --git a/host/chez/dyn-binding.ss b/host/chez/dyn-binding.ss index fb42ff0..e1d59fe 100644 --- a/host/chez/dyn-binding.ss +++ b/host/chez/dyn-binding.ss @@ -9,7 +9,7 @@ ;; The binding macro builds a frame as a jolt map (array-map of (var x) -> value); ;; push-thread-bindings folds it into the alist. Lookups walk frames by cell ;; IDENTITY (eq?) — vars are interned, so (var x) always yields the same cell, and -;; this sidesteps the seed's persistent-hash-map-can't-find-a-var-key quirk. +;; this sidesteps a persistent-hash-map-can't-find-a-var-key quirk. ;; ;; var reads (var-deref in compiled code, jolt-var-get / deref on a cell) consult ;; the stack before falling back to the cell root. Loaded LAST (after vars.ss and diff --git a/host/chez/emit-image.ss b/host/chez/emit-image.ss index ff62960..ea63d29 100644 --- a/host/chez/emit-image.ss +++ b/host/chez/emit-image.ss @@ -1,9 +1,6 @@ ;; emit-image.ss (jolt-cf1q.4 inc8) — the on-Chez compiler-image emitter. ;; -;; This is the stage2/stage3 half of the self-hosting fixpoint. driver.janet's -;; emit-compiler-image cross-compiles the compiler sources (jolt.ir + -;; jolt.analyzer + jolt.backend-scheme) to a Scheme def-var! image USING THE JANET -;; analyzer/emitter — that is stage1. This file does the SAME job but the +;; This is the stage2/stage3 half of the self-hosting fixpoint. The ;; analyze->emit runs ON CHEZ (jolt-ce-analyze / jolt-ce-emit, loaded from a ;; previously-built image): feed it stage1's image and it produces stage2; feed it ;; stage2 and it produces stage3. stage2 == stage3 byte-for-byte proves the @@ -12,8 +9,8 @@ ;; Loaded after compile-eval.ss (needs jolt-ce-analyze/jolt-ce-emit/ce-scan-requires!, ;; make-analyze-ctx) and rt.ss (read-file-string, the reader's rdr-read-form). -;; Read every top-level form from a source string (a Chez read-all). Mirrors the -;; Janet driver's parse-all; uses the same reader the spine reads single forms with. +;; Read every top-level form from a source string (a Chez read-all). +;; Uses the same reader the spine reads single forms with. (define (ei-read-all src) (let ((end (string-length src))) (let loop ((i 0) (acc '())) @@ -23,7 +20,7 @@ (loop j (cons form acc))))))) ;; Is `f` an (ns ...) form? (Its only role in the image is alias registration; we -;; never emit it — the def-var!s carry explicit ns names. Matches driver.janet.) +;; never emit it — the def-var!s carry explicit ns names.) (define (ei-ns-form? f) (and (cseq? f) (cseq-list? f) (let ((items (seq->list f))) @@ -34,7 +31,6 @@ ;; ce-defmacro->fn, loaded before this) — shared with the runtime defmacro spine. ;; Cross-compile one namespace's source to a list of guard-wrapped Scheme strings. -;; Mirrors driver.janet emit-ns-forms-list/emit-core-prelude + emit-form-scheme. ;; Each form is analyzed with a fresh ctx — resolution is via the runtime var-table ;; + alias tables, not ctx-accumulated state, so this matches the spine's per-form ;; analyze. A defmacro emits its expander fn as (def-var! ns name ) + @@ -69,19 +65,18 @@ (cons (string-append "(guard (e (#t #f))\n " scm ")") acc) acc))))))))) -;; Scheme string literal for a ns/name — uses the runtime's own writer so it -;; matches the Janet driver's %j (printable ASCII identifiers only here). +;; Scheme string literal for a ns/name — uses the runtime's own writer +;; (printable ASCII identifiers only here). (define (ei-str-lit s) (with-output-to-string (lambda () (write s)))) -;; The compiler namespaces, in load order — same list as driver.janet -;; compiler-ns-files. +;; The compiler namespaces, in load order. (define ei-compiler-ns-files (list (cons "jolt.ir" "jolt-core/jolt/ir.clj") (cons "jolt.analyzer" "jolt-core/jolt/analyzer.clj") (cons "jolt.backend-scheme" "jolt-core/jolt/backend_scheme.clj"))) -;; The clojure.core tiers + stdlib namespaces, in load order — same lists as -;; driver.janet core-tier-files / stdlib-ns-files. Re-emitting these on Chez is the +;; The clojure.core tiers + stdlib namespaces, in load order. +;; Re-emitting these on Chez is the ;; prelude half of the fixpoint (the whole emitted system reproducing itself). (define ei-prelude-ns-files (append @@ -94,8 +89,7 @@ (cons "clojure.set" "src/jolt/clojure/set.clj") (cons "clojure.pprint" "src/jolt/clojure/pprint.clj")))) -;; Join a list of form strings with "\n", no trailing newline — byte-identical -;; layout to the Janet driver's (string/join out "\n"). +;; Join a list of form strings with "\n", no trailing newline. (define (ei-join forms) (let join ((fs forms) (out "")) (cond diff --git a/host/chez/host-class.ss b/host/chez/host-class.ss index fa7794f..6d0002a 100644 --- a/host/chez/host-class.ss +++ b/host/chez/host-class.ss @@ -1,20 +1,16 @@ ;; host class tokens (jolt-13zk) — a bare class name (String, Keyword, File...) ;; evaluates to its JVM canonical-name STRING, the same value (class instance) ;; returns, so (= String (class "x")) holds and a (defmethod m String ...) keys -;; against a (class …) dispatch (ring.util.request does this). Mirrors -;; src/jolt/eval_resolve.janet's class-canonical-names + core_refs.janet's -;; core-class. The analyzer already resolves these names to clojure.core vars (the -;; seed ctx interns them via setup-class-ctors), so the back end emits +;; against a (class …) dispatch (ring.util.request does this). +;; The analyzer resolves these names to clojure.core vars, so the back end emits ;; (var-deref "clojure.core" "String") — def-var!'ing the canonical strings here is -;; all that's needed at runtime. No analyzer change, so the Janet back end is -;; untouched. +;; all that's needed at runtime. ;; ;; Loaded after natives-meta.ss (jolt-type) + the printer (jolt-str-render-one). ;; (class x) — Clojure's class of a value. Scalars map to their JVM class name, -;; matching core-class. Collections/seqs have no JVM class on this host; the seed -;; leaks the Janet host type ("table"/"struct"/"tuple") there, which we don't -;; reproduce (Janet is going away) — (str (type x)) is the clean host taxonomy and +;; matching core-class. Collections/seqs have no JVM class on this host; +;; (str (type x)) is the clean host taxonomy and ;; is never compared against a class token in the corpus. Records yield their ;; ns-qualified class name (= (str (type x))). Total — never crashes. (define (jolt-class x) diff --git a/host/chez/host-contract.ss b/host/chez/host-contract.ss index 02a2375..d1db751 100644 --- a/host/chez/host-contract.ss +++ b/host/chez/host-contract.ss @@ -1,7 +1,7 @@ ;; host-contract.ss (jolt-hs9n, Phase 3 inc6) — the jolt.host contract on Chez. ;; ;; The portable seam between jolt-core (analyzer/IR/emitter, cross-compiled to -;; Scheme) and the host. Mirrors src/jolt/host_iface.janet's `exports`: every +;; Scheme) and the host. Every ;; contract fn is def-var!'d into the "jolt.host" namespace so the cross-compiled ;; jolt.analyzer / jolt.backend-scheme — whose unqualified form-*/resolve-global/ ;; ... refs lower to (var-deref "jolt.host" ...) — resolve here at runtime. @@ -149,8 +149,7 @@ ;; def-var! of its cross-compiled expander fn plus (mark-macro! ns name), so the ;; var cell is flagged a macro (rt.ss var-macro-table). form-macro? checks the ;; flag; form-expand-1 applies the expander to the unevaluated arg forms (the rest -;; of the list), and the analyzer re-analyzes the returned form. Mirrors -;; host_iface.janet h-macro?/h-expand-1. +;; of the list), and the analyzer re-analyzes the returned form. (define (hc-macro? ctx sym) (macro-var? (hc-resolve-cell ctx sym))) (define (hc-expand-1 ctx form) @@ -179,7 +178,7 @@ (define (hc-intern! ctx ns-name nm) (declare-var! ns-name nm) jolt-nil) ;; --- syntax-quote lowering (jolt-qjr0, inc7) --------------------------------- -;; Mirrors src/jolt/eval_base.janet syntax-quote-lower/sq-symbol. Lowers a `form +;; Lowers a `form ;; to CONSTRUCTION CODE — Chez reader forms calling __sqcat/__sqvec/__sqmap/ ;; __sqset/__sq1 + quote — that the analyzer re-analyzes, so a backtick compiles ;; with zero runtime cost (read -> macroexpand -> compile). Symbols resolve to diff --git a/host/chez/host-static.ss b/host/chez/host-static.ss index 58c2396..46571b9 100644 --- a/host/chez/host-static.ss +++ b/host/chez/host-static.ss @@ -2,11 +2,11 @@ ;; ;; The analyzer lowers `Class/member` to a :host-static node and `(Class. ...)` / ;; `(new Class ...)` to a :host-new node (jolt-core/jolt/analyzer.clj); the Chez -;; emit (emit.janet) lowers a value ref to (host-static-ref "Class" "member"), a +;; emit lowers a value ref to (host-static-ref "Class" "member"), a ;; call head to (host-static-call "Class" "member" args...), and a constructor to ;; (host-new "Class" args...). This file is the runtime registry those three -;; resolve against — the Chez port of the seed's class-statics / class-ctors / -;; tagged-methods registries (src/jolt/interop/java_base.janet + host_io.janet), +;; resolve against — the class-statics / class-ctors / +;; tagged-methods registries, ;; restricted to the java.lang/util/net/io surface portable cljc code calls. ;; (java.time formatting is a separate increment.) ;; @@ -111,7 +111,7 @@ ;; numeric tower (jolt-n6al): currentTimeMillis/nanoTime are exact longs (JVM). (define (->num x) x) (define (jnum->exact n) (exact (truncate n))) -;; parse an integer string in radix; #f on failure (matches the seed's scan-number) +;; parse an integer string in radix; #f on failure (define (parse-int-str s radix) (let ((n (string->number (str-trim (if (string? s) s (jolt-str-render-one s))) radix))) (and n (integer? n) (->num n)))) @@ -488,7 +488,7 @@ (if (regex-t? obj) (let ((rest (if (jolt-nil? rest-args) '() (seq->list rest-args)))) (cond ((string=? method-name "split") - ;; .split returns a String[] — a seq, like the seed's array (prints + ;; .split returns a String[] — a seq (prints ;; (a b c), not a vector). re-split with no limit; drop trailing ;; empties (JVM default). (let ((parts (re-split (regex-t-irx obj) (car rest) #f))) diff --git a/host/chez/host-table.ss b/host/chez/host-table.ss index 4f19fcc..3aa8ed0 100644 --- a/host/chez/host-table.ss +++ b/host/chez/host-table.ss @@ -11,7 +11,7 @@ ;; these — a red-black tree + :ops table travel inside the htable. ;; 2. a sorted-coll arm on the collection dispatchers, set!-extended the same ;; way records.ss extends them for jrec: each op routes through the value's -;; own :ops table (the seed's dispatch pattern, core_coll.janet). first/rest/ +;; own :ops table (the dispatch pattern). first/rest/ ;; next/last fall out free once jolt-seq has a sorted arm (they seq first). ;; ;; Loaded LAST (after records.ss / transients.ss / natives-meta.ss): it wraps the @@ -118,7 +118,7 @@ (def-var! "clojure.core" "coll?" (lambda (x) (or (htable-sorted? x) (jrec? x) (jolt-coll-pred? x)))) ;; --- equality / hash --------------------------------------------------------- -;; A sorted coll canonicalizes like its unordered counterpart (core_types.janet): +;; A sorted coll canonicalizes like its unordered counterpart: ;; a sorted-map equals ANY map (hash or sorted) with the same entries, a ;; sorted-set ANY set with the same elements — the comparator is irrelevant to =. ;; Convert to the plain persistent coll and delegate to the prior jolt=2 / hash. @@ -140,8 +140,8 @@ ;; --- printing ---------------------------------------------------------------- ;; sorted colls render in SORTED order (the value's :seq), not HAMT order — and -;; a sorted-map prints "{k v, k v}" (", " between pairs) like the seed's -;; pr-render-pairs, NOT the space-only form the unordered pmap arm uses. +;; a sorted-map prints "{k v, k v}" (", " between pairs), +;; NOT the space-only form the unordered pmap arm uses. (define (sorted-map-render sc render) (string-append "{" (let loop ((es (seq->list (sc-call sc kw-op-seq))) (first #t) (acc "")) diff --git a/host/chez/inst-time.ss b/host/chez/inst-time.ss index db7720d..6930385 100644 --- a/host/chez/inst-time.ss +++ b/host/chez/inst-time.ss @@ -3,13 +3,12 @@ ;; A #inst literal lowers (analyzer :inst node -> emit) to (jolt-inst-from-string ;; "…"); this file parses the RFC3339 string to epoch-ms and models the value as a ;; `jinst` record (one flonum field, ms). Equality / map-key hashing are by the -;; INSTANT (offset-normalized), matching the seed (types_ctx.janet parse-inst / -;; inst->rfc3339). The overlay inst?/inst-ms read (get x :jolt/type)/(get x :ms), +;; INSTANT (offset-normalized). The overlay inst?/inst-ms read (get x :jolt/type)/(get x :ms), ;; so jolt-get answers those off a jinst — the overlay fns then work unchanged. ;; ;; The java.time surface (DateTimeFormatter/Instant/ZoneId/LocalDateTime/ -;; FormatStyle/Locale + the .format/.atZone/.toInstant/… methods) is the Chez port -;; of java_base.janet, registered through host-static.ss's class-statics / host- +;; FormatStyle/Locale + the .format/.atZone/.toInstant/… methods) is +;; registered through host-static.ss's class-statics / host- ;; methods registries — so this loads LAST in rt.ss, after host-static.ss and io.ss. ;; --- civil <-> days since the Unix epoch (Howard Hinnant's algorithms) ------- @@ -113,7 +112,7 @@ "T" (pad2 (list-ref f 3)) ":" (pad2 (list-ref f 4)) ":" (pad2 (list-ref f 5)) "." (pad3 (list-ref f 6)) "-00:00"))) -;; --- DateTimeFormatter pattern engine (port of java_base.janet format-ms) ----- +;; --- DateTimeFormatter pattern engine ----- (define month-names (vector "January" "February" "March" "April" "May" "June" "July" "August" "September" "October" "November" "December")) (define day-names (vector "Sunday" "Monday" "Tuesday" "Wednesday" "Thursday" "Friday" "Saturday")) diff --git a/host/chez/io.ss b/host/chez/io.ss index 5588737..5ad274d 100644 --- a/host/chez/io.ss +++ b/host/chez/io.ss @@ -1,12 +1,11 @@ -;; java.io.File + host file I/O (jolt-yyud). The seed's clojure.java.io (io.clj) -;; is a Janet-backed shim (janet.*/janet.file) — not reusable here, so this is a +;; java.io.File + host file I/O (jolt-yyud). A ;; Chez-native implementation over Chez's filesystem primitives. A File is a ;; path-backed jfile record: (instance? java.io.File f) is true, str/slurp coerce ;; it to its path, and the File method surface (getName/getPath/exists/ ;; isDirectory/isFile/listFiles) dispatches through record-method-dispatch. ;; -;; Mirrors src/jolt/core_io.janet (core-make-file/file?/slurp/spit/flush/dir?/ -;; list-dir) and the overlay file-seq (20-coll.clj), which calls __file?/__dir?/ +;; Provides make-file/file?/slurp/spit/flush/dir?/ +;; list-dir for the overlay file-seq (20-coll.clj), which calls __file?/__dir?/ ;; __list-dir + the .isDirectory/.listFiles/.isFile method surface. ;; ;; Reader/StringReader-coupled io (io/reader, line-seq over a file, .toURL, @@ -47,7 +46,7 @@ ;; --- java.net.URL (a jhost "url", state #(spec)) ---------------------------- ;; A File.toURL value: .toString / .toExternalForm give the spec, .getPath / -;; .getFile strip the "file:" scheme. (Mirrors host_io.janet's :jolt/url.) +;; .getFile strip the "file:" scheme. (define (make-url spec) (make-jhost "url" (vector spec))) (define (url-spec u) (vector-ref (jhost-state u) 0)) (define (url-strip-scheme spec) @@ -91,7 +90,7 @@ (%io-rmd obj method-name rest-args)))) ;; .isDirectory / .listFiles emit to jolt-host-call (rt.ss), not record-method- -;; dispatch (emit.janet supported-host-methods) — the Phase-1 shims there assume a +;; dispatch — the Phase-1 shims there assume a ;; path STRING target. Make them jfile-aware so file-seq's File branch works. (define %io-host-call jolt-host-call) (set! jolt-host-call @@ -171,7 +170,7 @@ (set! jolt-str-render-one (lambda (v) (if (jfile? v) (jfile-path v) (%io-str-render v)))) -;; (type f) -> :jolt/file, matching the seed's tagged-file :jolt/type. Re-def-var! +;; (type f) -> :jolt/file (the tagged-file :jolt/type). Re-def-var! ;; "type": natives-meta.ss already bound the var to the old jolt-type value, so the ;; set! alone (which updates the symbol for internal callers) wouldn't reach it. (define io-kw-file (keyword "jolt" "file")) @@ -192,7 +191,7 @@ (%io-instance-check type-sym val))))) (def-var! "clojure.core" "instance-check" instance-check) -;; --- def-var! the seed-native names the overlay file-seq + str/slurp use ---- +;; --- def-var! the native names the overlay file-seq + str/slurp use ---- (def-var! "clojure.core" "__make-file" jolt-make-file) (def-var! "clojure.core" "__file?" jolt-file?) (def-var! "clojure.core" "__dir?" jolt-dir?) @@ -202,8 +201,8 @@ (def-var! "clojure.core" "flush" jolt-flush) ;; --- char-array: a seq of chars over a string (the JVM char[]). io/reader's -;; char[] branch + selmer's (char-array template) feed on this. Mirrors the seed -;; core-char-array (string -> chars). A leaf array native; lives here as io/reader +;; char[] branch + selmer's (char-array template) feed on this. +;; char-array (string -> chars). A leaf array native; lives here as io/reader ;; is its only Chez consumer so far. (define (jolt-char-array a . rest) (cond @@ -215,7 +214,7 @@ ;; --- with-open's close seam (__close): a map-like value closes via its :close ;; fn; a jhost reader/writer/file via its .close method (a no-op here); anything -;; else is an error. Mirrors core_extra.janet core-close-resource. +;; else is an error. (define (jolt-close x) (cond ((jolt-nil? x) jolt-nil) diff --git a/host/chez/lazy-bridge.ss b/host/chez/lazy-bridge.ss index 0510b42..d035bb3 100644 --- a/host/chez/lazy-bridge.ss +++ b/host/chez/lazy-bridge.ss @@ -2,8 +2,8 @@ ;; ;; The `lazy-seq` macro (00-syntax.clj) expands to ;; (make-lazy-seq (fn* [] (coll->cells (do body)))) -;; and `lazy-cat` to (concat (lazy-seq c) ...). make-lazy-seq / coll->cells are -;; seed natives (src/jolt/lazyseq.janet) with no Chez shim, so EVERY overlay fn +;; and `lazy-cat` to (concat (lazy-seq c) ...). make-lazy-seq / coll->cells had +;; no Chez shim, so EVERY overlay fn ;; built on lazy-seq — repeat / iterate / cycle / dedupe / take-nth / keep / ;; interpose / reductions / tree-seq (-> flatten) / lazy-cat — resolved the call ;; to jolt-nil and hit the apply-jolt-nil crash bucket. diff --git a/host/chez/math.ss b/host/chez/math.ss index c6cc437..fddd914 100644 --- a/host/chez/math.ss +++ b/host/chez/math.ss @@ -1,15 +1,14 @@ ;; clojure.math (jolt-22vo) — Chez host shim over native flonum math. ;; -;; On the Janet seed clojure.math is registered as native math/ bindings -;; (api.janet install-clojure-math!, jolt-h79), NOT a .clj file — so there's no -;; source tier to emit. Chez provides its own def-var! shims here, one per -;; clojure.math fn, over Chez's native procedures. The analyzer already knows the -;; clojure.math ns exists (init interns the same fns on the Janet side), so a ref +;; clojure.math is registered as native bindings (jolt-h79), NOT a .clj file — so +;; there's no source tier to emit. Chez provides its own def-var! shims here, one per +;; clojure.math fn, over Chez's native procedures. The analyzer knows the +;; clojure.math ns exists, so a ref ;; like clojure.math/sqrt lowers to a var-deref; these cells back it at runtime. ;; ;; jolt is all-flonum, so every result is a flonum (inputs arrive as flonums; Chez -;; sqrt/sin/expt/... return flonums for flonum args). Semantics match the seed -;; (Clojure 1.11 clojure.math): round = floor(x+0.5), rint = round-half-even, +;; sqrt/sin/expt/... return flonums for flonum args). Semantics match +;; Clojure 1.11 clojure.math: round = floor(x+0.5), rint = round-half-even, ;; floor/ceil/floor-div return doubles, to-degrees/to-radians via PI. (define jolt-math-pi (acos -1.0)) diff --git a/host/chez/multimethods.ss b/host/chez/multimethods.ss index be04c1a..2aa5660 100644 --- a/host/chez/multimethods.ss +++ b/host/chez/multimethods.ss @@ -2,8 +2,8 @@ ;; ;; defmulti/defmethod are macros that expand to ctx-capturing setup CALLS ;; (defmulti-setup / defmethod-setup, + the table ops get-method/methods/ -;; remove-method/prefer-method/prefers). The Janet seed implements these against -;; the evaluator's ns/var machinery (eval_runtime.janet); this is the Chez port. +;; remove-method/prefer-method/prefers), implemented here against +;; the runtime's ns/var machinery. ;; ;; A multimethod VALUE is a jolt-multifn record carrying its dispatch fn and a ;; mutable method table (dispatch-val -> method fn, keyed with jolt= so keyword/ diff --git a/host/chez/natives-array.ss b/host/chez/natives-array.ss index db3c319..b7ddf1d 100644 --- a/host/chez/natives-array.ss +++ b/host/chez/natives-array.ss @@ -3,8 +3,7 @@ ;; A jolt-array wraps a Chez mutable vector + a `kind` tag (for bytes?). The array ;; CONSTRUCTORS are native (they build the backing); the overlay's aget/aset/alength ;; are pure over count / nth / jolt.host/ref-put!, so we extend those dispatchers -;; to see a jolt-array. Mirrors src/jolt/core_refs.janet (Janet uses its mutable -;; arrays/buffers; here a Chez vector). Loaded after host-table.ss (ref-put!), +;; to see a jolt-array (backed by a Chez vector). Loaded after host-table.ss (ref-put!), ;; transients.ss, seq.ss (the dispatchers it chains). (define-record-type jolt-array (fields (mutable vec) kind) (nongenerative jolt-array-v1)) diff --git a/host/chez/natives-coll.ss b/host/chez/natives-coll.ss index c07cb6d..f7a6d5e 100644 --- a/host/chez/natives-coll.ss +++ b/host/chez/natives-coll.ss @@ -1,12 +1,10 @@ -;; Collection constructors + rand (jolt-cf1q.3 Phase 2 inc A) — host-coupled seed +;; Collection constructors + rand (jolt-cf1q.3 Phase 2 inc A) — host-coupled ;; natives the overlay assumes as bare clojure.core vars but which were never ;; def-var!'d, so they resolved to jolt-nil and any call hit the apply-jolt-nil ;; crash bucket. The persistent-collection constructors already exist in ;; collections.ss (jolt-hash-map / jolt-hash-set / jolt-vector); this just binds ;; the public clojure.core names to them. Loaded after def-var! (rt.ss) + the -;; collections + seq tiers. Semantics match the Janet seed (core_coll.janet -;; core-hash-map/core-array-map/core-hash-set/core-set, core_types.janet -;; core-rand). +;; collections + seq tiers. hash-map/array-map/hash-set/set/rand semantics. ;; hash-map / hash-set: variadic kvs / elems straight onto the existing ctors. ;; array-map: Clojure preserves insertion order, but jolt's `=` is structural and diff --git a/host/chez/natives-meta.ss b/host/chez/natives-meta.ss index f8ce0ca..82b0dec 100644 --- a/host/chez/natives-meta.ss +++ b/host/chez/natives-meta.ss @@ -47,8 +47,8 @@ ;; (type x) — Clojure's (or (:type (meta x)) (class x)). With no JVM classes the ;; "class" is a host taxonomy: a record yields its ns-qualified class-name SYMBOL -;; (user.TyR), everything else a keyword (:number/:vector/:seq/…). Mirrors the -;; seed's core-type (src/jolt/core_io.janet). MUST be total — a non-record value +;; (user.TyR), everything else a keyword (:number/:vector/:seq/…). +;; MUST be total — a non-record value ;; falling through to a crash would read as a divergence, not the right keyword. ;; Forward refs (jolt-lazyseq?, the sorted-htable / wrapper predicates) all bind by ;; call time (every host .ss loads before any user expr runs). @@ -89,7 +89,7 @@ ((keyword? x) ty-keyword) ((symbol-t? x) ty-symbol) ((char? x) ty-char) - ;; host wrappers — match the seed's :jolt/* tags (checked before the + ;; host wrappers — keyed by their :jolt/* tags (checked before the ;; collection arms; none of these are pvec/pmap/pset). ((jolt-atom? x) ty-atom) ((jvol? x) ty-volatile) @@ -99,7 +99,7 @@ ((juuid? x) ty-uuid) ((htable-sorted-set? x) ty-sorted-set) ((htable-sorted-map? x) ty-map) - ;; collections — pvec INCLUDES map entries (:vector, like the seed's jvec?). + ;; collections — pvec INCLUDES map entries (:vector). ((pvec? x) ty-vector) ((pmap? x) ; a :jolt/type-tagged map (ex-info) -> its tag (let ((t (jolt-get x ty-kw-jtype jolt-nil))) (if (jolt-nil? t) ty-map t))) diff --git a/host/chez/natives-misc.ss b/host/chez/natives-misc.ss index e6b5af9..e2acd29 100644 --- a/host/chez/natives-misc.ss +++ b/host/chez/natives-misc.ss @@ -26,7 +26,7 @@ (loop (fx+ i 1))))))) (define (jolt-random-uuid) (make-juuid (random-uuid-str))) -;; #uuid literal -> a uuid value (emit.janet lowers the :uuid node to this). The +;; #uuid literal -> a uuid value (the emitter lowers the :uuid node to this). The ;; reader already validated the shape; lowercase for value equality. (define (jolt-uuid-from-string s) (make-juuid (string-downcase s))) diff --git a/host/chez/natives-num.ss b/host/chez/natives-num.ss index 6dc85bd..663f915 100644 --- a/host/chez/natives-num.ss +++ b/host/chez/natives-num.ss @@ -1,7 +1,7 @@ ;; bit ops + string->number parsers (jolt-cf1q.3 Phase 2 inc C) — host-coupled -;; seed natives (core_refs.janet bit family, core_io.janet parse-long/double) that +;; natives (bit family, parse-long/double) that ;; resolved to jolt-nil. jolt models every number as a double, so bit ops coerce -;; to an exact integer, operate, and return a flonum. parse-* match the seed's +;; to an exact integer, operate, and return a flonum. parse-* use ;; strict shapes (Clojure 1.11: nil on malformed, throw on a non-string). ;; bit ops return EXACT integers (= JVM long). ->int coerces the operand. diff --git a/host/chez/natives-parity.ss b/host/chez/natives-parity.ss index f088fa1..c5a188e 100644 --- a/host/chez/natives-parity.ss +++ b/host/chez/natives-parity.ss @@ -1,11 +1,10 @@ ;; natives-parity.ss (jolt-cf1q.7) — native Chez shims for clojure.core fns that -;; live in the Janet seed (src/jolt/core*.janet) but had no Chez shim, so on the -;; zero-Janet spine they resolved to nil ("not a fn"). Pure-Chez, JVM-matching. +;; had no Chez shim, so they resolved to nil ("not a fn"). Pure-Chez, JVM-matching. ;; ;; Loaded after host-table.ss (htable-sorted?), transients.ss (jolt-transient?), ;; values.ss (jolt-hash), seq.ss (jolt-seq/seq->list/list->cseq/jolt-invoke). -;; --- hash family (mirrors core_extra.janet: 24-bit masked so int? holds) ------ +;; --- hash family (24-bit masked so int? holds) ------ (define (np-h24 x) (bitwise-and (jolt-hash x) #xffffff)) (define (np-hash x) (np-h24 x)) (define (np-hash-combine a b) @@ -26,7 +25,7 @@ (list->cseq (reverse (seq->list (jolt-seq coll)))) (jolt-throw (jolt-ex-info "rseq requires a vector or sorted collection" (jolt-hash-map))))) -;; --- cat transducer (mirrors core_refs.janet core-cat): each item of the input +;; --- cat transducer: each item of the input ;; is itself a collection, concatenated into the downstream reducing fn. (define (np-cat rf) (lambda a diff --git a/host/chez/natives-seq.ss b/host/chez/natives-seq.ss index 2c9eefe..12b831e 100644 --- a/host/chez/natives-seq.ss +++ b/host/chez/natives-seq.ss @@ -1,6 +1,6 @@ -;; seq-native shims (jolt-y6mv) — seed-native seq fns the overlay assumes are -;; clojure.core natives but which live in the Janet seed (src/jolt/core_coll.janet), -;; so they have no def-var! in the assembled prelude and resolve to jolt-nil on +;; seq-native shims (jolt-y6mv) — native seq fns the overlay assumes are +;; clojure.core natives but which have no def-var! in the assembled prelude and +;; resolve to jolt-nil on ;; Chez. This was the dominant prelude-parity crash bucket ('apply jolt-nil'). ;; Each is a pure fn over the existing seq layer (seq.ss) — collection arities ;; only; the 1-arg transducer arities are jolt-kxsr. Loaded last (after @@ -16,8 +16,7 @@ ;; ============================================================================ ;; transducers (jolt-kxsr) — the 1-arg arity of map/filter/take/... returns a ;; transducer (fn [rf] rf') where rf' is a reducing fn with arities -;; []=init, [acc]=complete, [acc x]=step. Ported from the seed's td-* factories -;; (core_coll.janet). rf and the mapping/predicate fns are jolt values, so every +;; []=init, [acc]=complete, [acc x]=step. rf and the mapping/predicate fns are jolt values, so every ;; call routes through jolt-invoke. A `reduced` step stops the fold — reduce-seq ;; (seq.ss) already short-circuits on a jolt-reduced. ;; ============================================================================ @@ -181,9 +180,8 @@ (if (jolt-nil? s) jolt-empty-list (list->cseq (list-sort less? (seq->list s)))))) -;; identical?: jolt reference identity. The seed defines it as (= a b) over its -;; value model (core_types.janet core-identical?), where interned keywords/small -;; values compare equal — so jolt= is the faithful port. +;; identical?: jolt reference identity, defined as (= a b) over the +;; value model, where interned keywords/small values compare equal. (define (jolt-identical? a b) (jolt= a b)) ;; Give the seq.ss native procedures their transducer (1-arg) arity — the emitter diff --git a/host/chez/natives-str.ss b/host/chez/natives-str.ss index a39b7da..a108e12 100644 --- a/host/chez/natives-str.ss +++ b/host/chez/natives-str.ss @@ -2,7 +2,7 @@ ;; ;; (.method s arg*) on a string target lowers to record-method-dispatch (emit.ss), ;; which falls through to jolt-string-method here when the target is a string. -;; Ported from the seed surface (src/jolt/eval_resolve.janet string-methods): the +;; Covers the ;; portable java.lang.String/CharSequence methods cljc libraries actually call. ;; Case mapping is ASCII (the whole engine is byte-oriented), indexOf returns -1 ;; on miss as on the JVM, indices come in as flonums, char results are Scheme @@ -11,7 +11,7 @@ ;; Loaded from rt.ss AFTER regex.ss (the regex methods reuse jolt-re-pattern / ;; regex-t-irx) and records.ss (which calls jolt-string-method). -;; --- ASCII case mapping (match the seed's byte-oriented string/ascii-*) ------- +;; --- ASCII case mapping (byte-oriented) ------- (define (ascii-up-char c) (if (and (char<=? #\a c) (char<=? c #\z)) (integer->char (fx- (char->integer c) 32)) c)) @@ -131,9 +131,9 @@ (else (error #f (string-append "No method " method " for value"))))) ;; --- clojure.core str-* primitives (the substrate clojure.string.clj calls) --- -;; clojure.string.clj (src/jolt/clojure/string.clj) is pure Clojure over these -;; seed natives (core.janet core-bindings); def-var!'d here so the emitted -;; clojure.string prelude tier's var-derefs resolve. Ported from the seed: +;; clojure.string.clj is pure Clojure over these +;; natives; def-var!'d here so the emitted +;; clojure.string prelude tier's var-derefs resolve: ;; string/ascii-* (ASCII), string/find (index or nil), core-str-* (regex|literal). ;; (string/split sep s) -> parts, splitting on each non-overlapping sep. @@ -168,8 +168,8 @@ ;; (re-split irx s limit) -> parts, splitting at each match. Keeps interior AND ;; trailing empty strings (the clojure.string wrapper drops trailing for limit 0); -;; a positive limit yields at most `limit` parts (the rest kept unsplit). Mirrors -;; the seed re-split (src/jolt/regex.janet); the clojure.string.clj split wrapper +;; a positive limit yields at most `limit` parts (the rest kept unsplit). +;; The clojure.string.clj split wrapper ;; layers the trailing-empty trim on top. (define (re-split irx s limit) (let ((len (string-length s))) @@ -224,14 +224,14 @@ ;; One match's replacement text. A string gets $N expansion; a fn (jolt closure) ;; is called with the match result (whole string, or [whole g1 ...] when grouped) -;; and its result stringified (mirrors the seed replacement-for). +;; and its result stringified. (define (replacement-text replacement m) (cond ((string? replacement) (expand-dollar replacement m)) ((procedure? replacement) (jolt-str-render-one (jolt-invoke replacement (irx-result m)))) (else (jolt-str-render-one replacement)))) -;; regex replace, first or all matches. Mirrors the seed re-replace-all/first. +;; regex replace, first or all matches. (define (re-replace irx s replacement all?) (let ((len (string-length s))) (let loop ((start 0) (last 0) (acc '())) diff --git a/host/chez/predicates.ss b/host/chez/predicates.ss index 3fa3350..5fe7767 100644 --- a/host/chez/predicates.ss +++ b/host/chez/predicates.ss @@ -1,16 +1,16 @@ -;; type predicates + simple accessors (jolt-9ziu) — host-coupled seed natives. +;; type predicates + simple accessors (jolt-9ziu) — host-coupled natives. ;; -;; These are seed primitives (not clojure.core overlay fns), so they're never -;; def-var!'d by the assembled prelude; the Chez host must provide them. Semantics -;; match the Janet seed (src/jolt/core_types.janet): map?/vector?/set? are STRICT +;; These are host primitives (not clojure.core overlay fns), so they're never +;; def-var!'d by the assembled prelude; the Chez host must provide them. +;; map?/vector?/set? are STRICT ;; over the persistent-collection records, seq? is true only for real sequences, ;; coll? is the union. Records (shape-recs) are Phase 2, so the record arms of the -;; seed predicates are simply absent here for now. +;; predicates are simply absent here for now. (define (jolt-map? x) (pmap? x)) ;; a map entry is a pvec under the hood AND is vector? — Clojure's MapEntry -;; implements IPersistentVector, so (vector? (first {:a 1})) is true (the seed -;; agrees; jolt-75sv corrected the earlier exclusion). +;; implements IPersistentVector, so (vector? (first {:a 1})) is true +;; (jolt-75sv corrected the earlier exclusion). (define (jolt-vector? x) (pvec? x)) (define (jolt-set? x) (pset? x)) (define (jolt-seq? x) (or (cseq? x) (empty-list-t? x))) diff --git a/host/chez/printing.ss b/host/chez/printing.ss index 545b0d9..2187c69 100644 --- a/host/chez/printing.ss +++ b/host/chez/printing.ss @@ -33,8 +33,8 @@ ((and (flonum? x) (fl= x +inf.0)) "Infinity") ((and (flonum? x) (fl= x -inf.0)) "-Infinity") ((and (flonum? x) (not (fl= x x))) "NaN") - ;; transients print as a cold tagged type (the seed routes this through a - ;; print-method multimethod; the readable fallback renders it directly). + ;; transients print as a cold tagged type (print-method routes this through a + ;; multimethod; the readable fallback renders it directly). ;; forward refs to transients.ss (loaded later) — resolved at call time. ((jolt-transient? x) (if (pvec? (jolt-transient-coll x)) "#" "#")) diff --git a/host/chez/reader.ss b/host/chez/reader.ss index 4c4caff..58eded3 100644 --- a/host/chez/reader.ss +++ b/host/chez/reader.ss @@ -1,16 +1,16 @@ ;; Chez-side Clojure data reader (jolt-r8ku, inc Y). ;; ;; The data half of runtime read/eval: a recursive-descent reader that parses -;; ONE Clojure form off a string and produces the same jolt runtime values the -;; Janet reader's parse-next yields (the analyzer/eval half — eval, load-string, +;; ONE Clojure form off a string and produces jolt runtime values +;; (the analyzer/eval half — eval, load-string, ;; runtime defmacro — stays Phase-3, it needs the compiler at runtime). Two host -;; seams hang off it, matching the Janet seed (eval_runtime.janet): +;; seams hang off it: ;; read-string : string -> first form (clojure.core seam, src 772) ;; __parse-next : string -> [form rest] | nil (the *in* family seam, src 801) ;; read / read+string / with-in-str / line-seq / clojure.edn are Clojure over ;; these (jolt-core/clojure/core/50-io.clj, src/jolt/clojure/edn.clj). ;; -;; Form shapes are pinned to the Janet reader's output (probed against build/jolt): +;; Form shapes: ;; sets -> {:jolt/type :jolt/set :value [...]} (a FORM, not a set) ;; #tag frm -> {:jolt/type :jolt/tagged :tag :#tag :form ...} (NO data reader) ;; #"src" -> {:jolt/type :jolt/tagged :tag :regex :form "src"} @@ -61,7 +61,7 @@ ;; --- numbers ---------------------------------------------------------------- ;; A token is a number iff it (after an optional sign) starts with a digit and -;; parses. Ratios and big-N/M decimals follow the seed's all-double rendering +;; parses. Ratios and big-N/M decimals use all-double rendering ;; for division; ints/bignums stay exact (Chez's tower IS Clojure's). (define (rdr-string-index-char str c) (let ((n (string-length str))) @@ -287,10 +287,10 @@ (make-symbol-t (symbol-t-ns target) (symbol-t-name target) (rdr-merge-meta (symbol-t-meta target) meta)) ;; non-symbol target (a collection): lower to a runtime (with-meta form meta) - ;; the analyzer compiles like any invoke — same as the Janet reader, so e.g. + ;; the analyzer compiles like any invoke, so e.g. ;; (meta ^{:tag :int} [1 2]) and ^:foo {} carry their meta at runtime. The meta ;; pmap doubles as its own map-literal form. Use the BARE `with-meta` symbol - ;; (ns #f) to match the Janet reader exactly — the fn/defn macros unwrap a + ;; (ns #f) — the fn/defn macros unwrap a ;; (with-meta _) return-hint by matching the unqualified head, ;; so a qualified clojure.core/with-meta would slip past them (^bytes [b]). (jolt-list (jolt-symbol #f "with-meta") target meta))) @@ -299,7 +299,7 @@ ;; #(...) anonymous fn shorthand (jolt-qjr0): % -> p1, %N -> pN, %& -> rest. The ;; fixed arity is the MAX positional used (Clojure: #(do %2 %&) -> [p1 p2 & rest]). ;; Param names carry a trailing "#" so a #() inside a syntax-quote still reads them -;; as auto-gensyms. Mirrors src/jolt/reader.janet read-anon-fn. +;; as auto-gensyms. (define rdr-anon-counter 0) (define (rdr-anon-gensym) (set! rdr-anon-counter (+ rdr-anon-counter 1)) @@ -427,7 +427,7 @@ ;; --- keyword ---------------------------------------------------------------- (define (rdr-read-keyword s i end) ; i points just past the leading ':' - ;; ::kw auto-resolves; the seed drops the ns, so skip a second ':' + ;; ::kw auto-resolves; drop the ns, so skip a second ':' (let ((i (if (and (< i end) (char=? (string-ref s i) #\:)) (+ i 1) i))) (let-values (((tok j) (rdr-read-token s i end))) (let-values (((ns name) (rdr-sym-parts tok))) @@ -494,7 +494,7 @@ ;; --- the two host seams ----------------------------------------------------- ;; clojure.core/read-string: first form, or nil for blank / comment-only input -;; (the seed's parse-string wart, matched deliberately). +;; (parse-string wart, matched deliberately). (define (jolt-read-string s) (let-values (((form j) (rdr-read-form s 0 (string-length s)))) (if (rdr-eof? form) jolt-nil form))) diff --git a/host/chez/records.ss b/host/chez/records.ss index 9005665..93f527e 100644 --- a/host/chez/records.ss +++ b/host/chez/records.ss @@ -1,6 +1,6 @@ ;; records + protocols (jolt-cf1q.3 Phase 2 inc D) — the deftype/defrecord + -;; defprotocol/extend-type subsystem. These are ctx-capturing seed natives -;; (eval_runtime.janet) that resolved to jolt-nil on the prelude, so every record +;; defprotocol/extend-type subsystem. These are ctx-capturing natives +;; that resolved to jolt-nil on the prelude, so every record ;; case hit the apply-jolt-nil crash bucket. ;; ;; A record is a `jrec`: a type tag ("ns.Name") + an alist of (kw . val) in @@ -138,7 +138,7 @@ (define (record-tag obj) (and (jrec? obj) (jrec-tag obj))) -;; ---- the seed-native handles the analyzer/overlay call ---------------------- +;; ---- the native that handles the analyzer/overlay call ---------------------- ;; make-deftype-ctor: (name-sym field-kws field-tags field-muts) -> ctor closure. ;; The tag is baked at definition time in the type's ns (chez-current-ns). (define (make-deftype-ctor name-sym field-kws . _ignored) @@ -156,7 +156,7 @@ (keyword #f "name") (jolt-symbol jolt-nil name-str) (keyword #f "methods") methods)) -;; register-protocol-methods!: devirt hint in the seed; a no-op for Chez dispatch. +;; register-protocol-methods!: a no-op for Chez dispatch. (define (register-protocol-methods! proto-name method-names) jolt-nil) ;; register-method: extend-type/extend register an impl. Host type names keep a diff --git a/host/chez/regex.ss b/host/chez/regex.ss index 02f4492..f2b5b9d 100644 --- a/host/chez/regex.ss +++ b/host/chez/regex.ss @@ -1,7 +1,6 @@ ;; Phase 1 (jolt-cf1q.2) — regex on Chez via vendored irregex (jolt-i0s3). ;; -;; jolt's seed regex (src/jolt/regex.janet) compiles patterns to Janet's PEG -;; engine; Chez has no regex at all. Rather than re-host that engine, we vendor +;; Chez has no regex at all. We vendor ;; Alex Shinn's irregex (vendor/irregex, BSD) — a portable Scheme regex with ;; PCRE/Java-style STRING patterns — and wrap jolt's re-* surface over it. ;; @@ -14,7 +13,7 @@ ;; ;; The re-* fns are def-var!'d into clojure.core so prelude / -e code resolves ;; them at runtime (they're NOT subset native-ops: irregex's Unicode/property- -;; class semantics differ from the seed's byte-PEG approximation, so they stay out +;; class semantics keep them out ;; of the subset-parity corpus). Loaded from rt.ss after def-var! is defined. ;; irregex.scm is portable R[457]RS; two small adaptations for Chez's top level: @@ -35,17 +34,16 @@ (load "vendor/irregex/irregex.scm") ;; Unicode property classes \p{...} (jolt-y1zq): irregex's string syntax has no -;; \p{...}, so translate the ones the seed's byte-PEG maps (src/jolt/regex.janet -;; prop-frag) to ASCII char classes before compiling. ASCII-only — the seed counts -;; UTF-8 high bytes as letters for \p{L}, which a Unicode-char Scheme string can't +;; \p{...}, so translate a fixed set of property names +;; to ASCII char classes before compiling. ASCII-only — \p{L} would need +;; UTF-8 high bytes counted as letters, which a Unicode-char Scheme string can't ;; reproduce byte-for-byte; the corpus tests ASCII inputs, where they agree. An ;; unmapped name is left as-is (irregex errors, as before — no new behavior). The ;; ORIGINAL source is kept for printing; only the compiled pattern is translated. (define (prop-class name) (cond - ;; L/Alpha: ASCII letters + any non-ASCII codepoint (the seed counts UTF-8 high - ;; bytes as letters, so ^\p{L}+$ accepts accented words). N/Z stay ASCII-only, - ;; matching the seed's byte-PEG. + ;; L/Alpha: ASCII letters + any non-ASCII codepoint (UTF-8 high + ;; bytes count as letters, so ^\p{L}+$ accepts accented words). N/Z stay ASCII-only. ((or (string=? name "L") (string=? name "Alpha")) "a-zA-Z\\x80-\\x{10FFFF}") ((string=? name "Lu") "A-Z") ((string=? name "Ll") "a-z") @@ -55,7 +53,7 @@ ((string=? name "Pe") ")\\]}") (else #f))) ;; Tracks whether the cursor is inside a [...] char class: a \p{X} there emits the -;; class CONTENT (the seed inlines it), standalone it emits a wrapping [X]. Escapes +;; class CONTENT (inlined), standalone it emits a wrapping [X]. Escapes ;; (\[, \]) don't toggle the class. \P (negation) only wraps when standalone. (define (translate-prop-classes src) (let ((len (string-length src)) (out (open-output-string))) @@ -120,7 +118,7 @@ ;; All non-overlapping matches, left to right. Advance past each match end (or by ;; one on a zero-width match). nil when there are no matches (Clojure: seq-able as -;; nil, so (if-let [m (re-seq ...)] ...) works), matching the seed. +;; nil, so (if-let [m (re-seq ...)] ...) works). (define (jolt-re-seq re s) (let ((irx (regex-t-irx (jolt-re-pattern re))) (len (string-length s))) diff --git a/host/chez/rt.ss b/host/chez/rt.ss index e229212..4cde04a 100644 --- a/host/chez/rt.ss +++ b/host/chez/rt.ss @@ -6,7 +6,7 @@ ;; reference reads at call time, so redefinition / mutual recursion work); ;; 2. the rt primitive shims the emitter names (jolt-inc/dec/not) and jolt's ;; number printing (all jolt numbers model Clojure doubles; integer-valued -;; print without a trailing ".0", matching the Janet host). +;; print without a trailing ".0"). ;; ;; Emitted programs do `(load "host/chez/rt.ss")`; this loads values.ss in turn. @@ -14,15 +14,15 @@ (load "host/chez/collections.ss") (load "host/chez/seq.ss") -;; --- rt arithmetic / logic shims (named in emit.janet's native-ops) ---------- +;; --- rt arithmetic / logic shims (named in the emitter's native-ops) ---------- (define (jolt-inc x) (+ x 1)) (define (jolt-dec x) (- x 1)) ;; jolt `not`: only nil and false are falsey. (define (jolt-not x) (if (jolt-truthy? x) #f #t)) ;; --- exceptions (jolt-vcsl) -------------------------------------------------- -;; throw raises the jolt value RAW (no envelope), like the Janet compiled back -;; end; catch (emitted as `guard`) binds it directly. Chez `raise` accepts any +;; throw raises the jolt value RAW (no envelope); +;; catch (emitted as `guard`) binds it directly. Chez `raise` accepts any ;; object, so a thrown number/map/ex-info all work; uncaught -> non-zero exit. (define (jolt-throw v) (raise v)) ;; ex-info builds the tagged map {:jolt/type :jolt/ex-info :message :data :cause} @@ -130,7 +130,7 @@ ;; --- jolt number printing ---------------------------------------------------- ;; jolt models every number as a Clojure double: integer-valued values print -;; without a ".0" (the Janet host prints (* 1.0 5) as "5", (/ 1 2) as "0.5"). +;; without a ".0" ((* 1.0 5) prints as "5", (/ 1 2) as "0.5"). (define (jolt-num->string x) (cond ;; the -e / element printer renders the infinities and NaN as inf/-inf/nan @@ -144,7 +144,7 @@ ;; Program-final-value printer. jolt's `-e` prints in str-style: strings raw (no ;; quotes), chars as `\c`/`\newline`, collections recursively. NOTE: maps/sets -;; render in HAMT-iteration order, which does NOT match the Janet host's order — +;; render in HAMT-iteration order, which is not a stable insertion order — ;; so unordered values are compared via `=` (true/false), not printed form. ;; The full canonical printer is Phase 2. (define (jolt-str-join strs) @@ -236,7 +236,7 @@ ;; and the printer (jolt-str-render-one). (load "host/chez/host-class.ss") -;; dynamic vars (jolt-9ls5): *clojure-version* / *unchecked-math* constants the seed +;; dynamic vars (jolt-9ls5): *clojure-version* / *unchecked-math* constants the host ;; binds natively. After collections.ss (jolt-hash-map) + def-var!. (load "host/chez/dynamic-vars.ss") diff --git a/host/chez/seed/README.md b/host/chez/seed/README.md index 881caf8..e4eccb7 100644 --- a/host/chez/seed/README.md +++ b/host/chez/seed/README.md @@ -12,28 +12,14 @@ Both are **generated**, not hand-written. They are checked in because a fresh checkout must be able to build jolt-on-Chez using only Chez: `host/chez/bootstrap.ss` loads this seed, then rebuilds the prelude + image from the `.clj`/`.ss` sources via the on-Chez compiler (read → analyze → emit, all on Chez). The seed is a **joint -byte-fixpoint**: rebuilding from an up-to-date seed reproduces it exactly -(`test/chez/bootstrap-test.janet` verifies this). - -Janet was used once, historically, to mint the very first seed (the Janet analyzer/ -emitter cross-compiled the sources, then the on-Chez compiler iterated to the -fixpoint — see `test/chez/fixpoint-test.janet`). After that, Janet is never needed -to build or run jolt-on-Chez. +byte-fixpoint**: rebuilding from an up-to-date seed reproduces it exactly. +`make selfhost` (`host/chez/selfcheck.sh`) runs `host/chez/bootstrap.ss` and diffs +the rebuilt artifacts against the checked-in seed. ## Re-minting When the seed sources change (the core tiers, the compiler namespaces, the host -contract, the reader, `emit-image.ss`), the seed drifts and `bootstrap-test` -fails. Re-mint it: - -```janet -(import host/chez/driver :as d) -(import host/chez/jolt-chez :as jc) -(def ctx (d/make-ctx)) -(d/mint-chez-seed* (jc/ensure-prelude ctx) - (d/ensure-compiler-image ctx "/tmp/stage1.ss") - "host/chez/seed/prelude.ss" - "host/chez/seed/image.ss") -``` - -Then commit the refreshed `prelude.ss` / `image.ss`. +contract, the reader, `emit-image.ss`), the seed drifts and `make selfhost` +fails. Re-mint it by running `host/chez/bootstrap.ss` and writing the freshly +rebuilt prelude/image back to `host/chez/seed/prelude.ss` / +`host/chez/seed/image.ss`, then commit the refreshed files. diff --git a/host/chez/seq.ss b/host/chez/seq.ss index 6691746..dd2ed99 100644 --- a/host/chez/seq.ss +++ b/host/chez/seq.ss @@ -23,7 +23,7 @@ ;; only thing that distinguishes a list from any other realized seq on this host, ;; since one record type backs both (clojure.core/list? — jolt-75sv). The marker ;; lives on the cell, so (rest a-list) / (seq a-vector) / (map …) yield plain seq -;; cells and are not list?, matching the seed. +;; cells and are not list?. (define-record-type cseq (fields head (mutable tail) (mutable forced?) list?) (nongenerative chez-cseq-v2)) (define (cseq-realized head tail) (make-cseq head tail #t #f)) ; tail already a seq (define (cseq-lazy head tail-thunk) (make-cseq head tail-thunk #f #f)) @@ -84,11 +84,11 @@ ;; other (if (next s) ...) loops over a lazy seq ran one step too far. (let ((s (jolt-seq x))) (if (jolt-nil? s) jolt-nil (jolt-seq (seq-more s))))) ;; Only the HEAD cell carries the list marker — (rest a-list)/(next a-list) return -;; the unmarked tail, so they are seqs and not list?, matching the seed (which -;; makes rest-of-a-list a non-list seq). cons/list/reverse/conj therefore mark +;; the unmarked tail, so they are seqs and not list? (rest-of-a-list is a non-list +;; seq). cons/list/reverse/conj therefore mark ;; just the cell they create. ;; -;; cons always yields a list — (list? (cons x anything)) is true on the seed (cons +;; cons always yields a list — (list? (cons x anything)) is true (cons ;; onto a vector/seq/nil all report list?). (define (jolt-cons x coll) (cseq-list x (jolt-seq coll))) ;; Scheme list -> a jolt PersistentList: head is a list cell, the tail chain is diff --git a/host/chez/syntax-quote.ss b/host/chez/syntax-quote.ss index 3a284c3..ab1d50c 100644 --- a/host/chez/syntax-quote.ss +++ b/host/chez/syntax-quote.ss @@ -1,7 +1,6 @@ -;; syntax-quote form builders (jolt-r9lm, inc6b) — the Chez analogs of -;; core_types.janet's core-sq*. A cross-compiled macro expander whose body was a -;; syntax-quote template (lowered by jolt.host/form-syntax-quote-lower at Janet -;; cross-compile time) calls these at RUNTIME on Chez to build the EXPANSION as +;; syntax-quote form builders (jolt-r9lm, inc6b). A macro expander whose body was a +;; syntax-quote template (lowered by jolt.host/form-syntax-quote-lower) calls these +;; at RUNTIME on Chez to build the EXPANSION as ;; Chez READER forms (cseq list / pvec / pmap / tagged-set pmap) so the on-Chez ;; analyzer can re-analyze it. def-var!'d into clojure.core, so the lowered body's ;; unqualified __sqcat/__sqvec/__sqmap/__sqset/__sq1 refs (which lower to var-deref diff --git a/jolt-core/clojure/core/00-kernel.clj b/jolt-core/clojure/core/00-kernel.clj index 93a16e4..c390de5 100644 --- a/jolt-core/clojure/core/00-kernel.clj +++ b/jolt-core/clojure/core/00-kernel.clj @@ -1,4 +1,4 @@ -;; clojure.core — kernel tier (stage just above the Janet seed). +;; clojure.core — kernel tier (stage just above the host primitives). ;; ;; These are the structural fns the self-hosted compiler itself uses ;; (jolt.analyzer): second/peek/subvec/mapv/update. Because the compiler must be @@ -6,12 +6,11 @@ ;; before it is built. So this tier is loaded FIRST and, in compile mode, is ;; bootstrap-compiled directly into clojure.core (not routed through the ;; self-hosted pipeline, which would need these to already exist — the -;; circularity that previously forced `second` to stay in Janet). With this tier -;; in place the analyzer is built against the Clojure definitions and the Janet -;; primitives are gone. +;; circularity that previously forced `second` to stay a host primitive). With this tier +;; in place the analyzer is built against the Clojure definitions. ;; ;; Constraint: depend only on core-renames primitives (first/next/nth/count/conj/ -;; vec/map/apply/assoc/get/…, all hardwired to the Janet seed) and on each other. +;; vec/map/apply/assoc/get/…, all hardwired to host primitives) and on each other. (defn second [coll] (first (next coll))) diff --git a/jolt-core/clojure/core/00-syntax.clj b/jolt-core/clojure/core/00-syntax.clj index 0e3fc71..8d18e24 100644 --- a/jolt-core/clojure/core/00-syntax.clj +++ b/jolt-core/clojure/core/00-syntax.clj @@ -157,8 +157,8 @@ (defmacro declare [& syms] `(do ~@(map (fn* [s] `(def ~s)) syms))) -;; destructure — Clojure's binding-vector expander, ported from the Janet seed -;; (was core-destructure). Turns a binding vector that may contain destructuring +;; destructure — Clojure's binding-vector expander. +;; Turns a binding vector that may contain destructuring ;; patterns into a plain binding vector (alternating symbol / init-form) built from ;; nth/nthnext/get, so the COMPILER only ever sees plain symbols (analyze-bindings ;; rejects patterns). `let` consumes it directly; `loop`/`fn` reuse it transitively diff --git a/jolt-core/clojure/core/10-seq.clj b/jolt-core/clojure/core/10-seq.clj index 03c7177..f933562 100644 --- a/jolt-core/clojure/core/10-seq.clj +++ b/jolt-core/clojure/core/10-seq.clj @@ -1,5 +1,5 @@ ;; clojure.core — seq tier. Pure-Clojure leaf sequence fns on top of the kernel -;; tier (00-kernel) and the Janet seed. Loaded after the kernel tier; in compile +;; tier (00-kernel) and the host primitives. Loaded after the kernel tier; in compile ;; mode these self-host through the now-built analyzer (interpreted otherwise). ;; ;; Migration rule for adding fns here: the fn must (1) NOT be in diff --git a/jolt-core/clojure/core/20-coll.clj b/jolt-core/clojure/core/20-coll.clj index a5c00c3..10d4e81 100644 --- a/jolt-core/clojure/core/20-coll.clj +++ b/jolt-core/clojure/core/20-coll.clj @@ -16,7 +16,7 @@ ;; neg? throws on non-numbers via <, as Clojure's Numbers.isNeg does. (defn neg? [x] (< x 0)) -;; even?/odd? stay in the seed: (filter even? ...) is idiomatic-hot and the +;; even?/odd? stay host primitives: (filter even? ...) is idiomatic-hot and the ;; overlay versions cost an extra call layer per element (seq-pipe bench 4x). ;; Variadic bit ops — canonical Clojure arities folding the binary host op @@ -355,9 +355,9 @@ (make-hierarchy) (partition 2 deriv-seq)) h)))) -;; --- Stage 3 tier shrink: pure-over-core leaves moved off the Janet seed ---- +;; --- Stage 3 tier shrink: pure-over-core leaves moved off the host primitives ---- -;; Representation predicates over the overlay's own predicates (no Janet reps). +;; Representation predicates over the overlay's own predicates. (defn sequential? [x] (or (vector? x) (seq? x))) (defn associative? [x] (or (map? x) (vector? x))) (defn counted? [x] @@ -381,10 +381,10 @@ ;; realized?: defined on the pending types only (delay/lazy-seq/future read -;; Tagged-value predicates. The constructors (atom/volatile!/...) stay in Janet, -;; but every tagged value carries its kind under :jolt/type (records under -;; :jolt/deftype), reachable via get — which is nil on non-tables — so the -;; predicates are pure over get and move out of the seed. +;; Tagged-value predicates. The constructors (atom/volatile!/...) are host +;; primitives, but every tagged value carries its kind under :jolt/type (records +;; under :jolt/deftype), reachable via get — which is nil on non-tables — so the +;; predicates are pure over get. (defn atom? [x] (= (get x :jolt/type) :jolt/atom)) (defn volatile? [x] (= (get x :jolt/type) :jolt/volatile)) (defn reader-conditional? [x] (= (get x :jolt/type) :jolt/reader-conditional)) @@ -418,7 +418,7 @@ :else (throw (str "pop not supported on: " coll)))) ;; doall/dorun: realization boundaries. dorun walks (optionally at most n -;; steps — the Janet seed version ignored n); doall walks then returns coll. +;; steps); doall walks then returns coll. (defn dorun ([coll] (loop [s (seq coll)] @@ -791,7 +791,7 @@ (defn numerator [x] (throw (ex-info "numerator requires a ratio (Jolt has no ratios)" {}))) (defn denominator [x] (throw (ex-info "denominator requires a ratio (Jolt has no ratios)" {}))) -;; No class hierarchy on the Janet host. +;; No class hierarchy on this host. (defn supers [x] #{}) ;; Like Clojure's munge: rewrite dashes to underscores, preserving the argument's @@ -949,7 +949,7 @@ ;; inst-ms itself. (defn inst-ms* [i] (inst-ms i)) -;; Canonical comp — here rather than the seed so each stage is invoked with +;; Canonical comp — here rather than a host primitive so each stage is invoked with ;; jolt call semantics: (comp seq :content) works because the keyword stage ;; goes through IFn dispatch (raw Janet keyword application does not). (defn comp @@ -1078,7 +1078,7 @@ (let [xf (xform f)] (xf (reduce xf init coll))))) -;; into stays in the seed: it's perf-wall hot (the into-vec bench pays ~11% +;; into stays a host primitive: it's perf-wall hot (the into-vec bench pays ~11% ;; through the overlay call layers — same lesson as even?/odd? in round 4). ;; eduction is EAGER on jolt (documented divergence, as before): the composed @@ -1116,8 +1116,8 @@ ;; print-method / print-dup are real multimethods in the io tier (50-io.clj). -;; JVM proxies don't exist on a Janet host: the read-only surface is inert, -;; the constructive surface throws (matching the prior seed stubs). +;; JVM proxies don't exist on this host: the read-only surface is inert, +;; the constructive surface throws. (defn proxy-mappings [p] {}) (defn proxy-call-with-super [f p meth] (f)) (defn init-proxy [p mappings] p) diff --git a/jolt-core/clojure/core/25-sorted.clj b/jolt-core/clojure/core/25-sorted.clj index 689590f..6a0e948 100644 --- a/jolt-core/clojure/core/25-sorted.clj +++ b/jolt-core/clojure/core/25-sorted.clj @@ -17,7 +17,7 @@ ;; and the methods become functions — the algorithm is identical. ;; ;; A sorted-SET stores its elements as keys with a nil value; its ops project the -;; key. ALL the semantics live here in Clojure; the Janet seed keeps only its +;; key. ALL the semantics live here in Clojure; the host keeps only its ;; dispatch branches (conj/assoc/get/seq/count/…), each a one-line call through ;; the value's own :ops table, so the ops travel WITH the value (correct across ;; contexts, forks, and AOT images). The wrapper is minted/read through the host @@ -286,7 +286,7 @@ (defn- ss-disj-many [ss xs] (reduce ss-disj-1 ss xs)) -;; --- the ops tables the Janet seed dispatches through ------------------------ +;; --- the ops tables the host dispatches through ------------------------ (def ^:private sm-ops {:count (fn [sm] (sfield sm :cnt)) diff --git a/jolt-core/clojure/core/30-macros.clj b/jolt-core/clojure/core/30-macros.clj index c80411e..a0af4e2 100644 --- a/jolt-core/clojure/core/30-macros.clj +++ b/jolt-core/clojure/core/30-macros.clj @@ -5,9 +5,9 @@ ;; IMPORTANT — only macros NOT used by the self-hosted compiler (jolt-core/jolt/*) ;; or by the earlier overlay tiers belong here; those (and/or/when/when-not/ ;; when-let/cond/case/doseq/declare/cond->/->) must stay available before this -;; tier loads, so they remain in Janet for now. Everything here is user-facing. +;; tier loads, so they remain host primitives for now. Everything here is user-facing. ;; -;; Migration: remove the Janet core-X macro fn AND its core-macro-names entry when +;; Migration: remove the host core-X macro fn AND its core-macro-names entry when ;; moving a macro here (defmacro installs the :macro flag itself). (defmacro comment [& body] nil) diff --git a/jolt-core/clojure/core/40-lazy.clj b/jolt-core/clojure/core/40-lazy.clj index 878b13d..444c13b 100644 --- a/jolt-core/clojure/core/40-lazy.clj +++ b/jolt-core/clojure/core/40-lazy.clj @@ -86,7 +86,7 @@ ())) ;; --- repeatedly --- ((f) throws on a non-fn; (take n …) throws on a non-number -;; count — both now enforced in the seed (jolt-call / core-take), so the canonical +;; count — both enforced by the host (jolt-call / take), so the canonical ;; CLJ form matches the repeatedly.cljc exception cases.) (defn repeatedly ([f] (lazy-seq (cons (f) (repeatedly f)))) @@ -105,8 +105,8 @@ ;; --- partition-all --- (transducer + [n coll] + [n step coll]) ;; The collection arities realize EXACTLY n per chunk via a first/rest loop and ;; continue from the advanced cursor (not a re-drop / nthrest), so they realize -;; minimally — matching the Janet pstep the §6.3 laziness counters were written -;; against. (A take/nthrest form is correct but over-realizes.) +;; minimally — the §6.3 laziness counters depend on this. +;; (A take/nthrest form is correct but over-realizes.) (defn partition-all ([n] (fn [rf] diff --git a/jolt-core/jolt/analyzer.clj b/jolt-core/jolt/analyzer.clj index ccd61e1..4bbdc02 100644 --- a/jolt-core/jolt/analyzer.clj +++ b/jolt-core/jolt/analyzer.clj @@ -7,7 +7,7 @@ bootstrap can compile this namespace via its plain :var path. ctx is an opaque host handle threaded to the contract fns; the analyzer never inspects it. - Coverage grows toward compiler.janet; unsupported forms throw :jolt/uncompilable + Unsupported forms throw :jolt/uncompilable so the caller falls back to the interpreter (the hybrid contract). `env` carries lexical state: {:locals #{names} :recur recur-target-name|nil}. @@ -140,8 +140,8 @@ ;; form (the Chez data reader) wraps an arglist vector carrying a return-type hint ;; (^bytes [b] / ^String [x y]). Unwrap to the underlying vector so fn parsing sees ;; the params — the hint is ignored at runtime. Only the (with-meta _) shape -;; matches, so a real arity clause (head is a vector) and the Janet reader's -;; meta-on-tuple (already a vector) pass through unchanged. +;; matches, so a real arity clause (head is a vector) and a +;; meta-on-vector arglist pass through unchanged. (defn- strip-arglist-meta [form] (if (form-list? form) (let [es (vec (form-elements form))] @@ -212,10 +212,10 @@ ;; letfn: (letfn [(name [params] body*)...] body*). The named local fns are ;; MUTUALLY recursive, so bind every name into the env BEFORE analyzing any spec ;; — each spec then resolves its siblings (and itself) as locals. Emitted as a -;; :let flagged :letrec so the back ends know the bindings forward-reference each -;; other: Chez lowers it to `letrec*`; the Janet back end punts to the -;; interpreter (its shared mutable env already gives the letrec semantics that a -;; compiled sequential let* lacks — the reason letfn was uncompilable before). +;; :let flagged :letrec so the back end knows the bindings forward-reference each +;; other: Chez lowers it to `letrec*`. The interpreter's shared mutable env already +;; gives the letrec semantics that a +;; compiled sequential let* lacks — the reason letfn was uncompilable before. (defn- analyze-letfn [ctx items env] (let [specs (vec (form-vec-items (nth items 1))) names (mapv #(form-sym-name (first (vec (form-elements %)))) specs) @@ -252,9 +252,9 @@ (uncompilable "def name with map metadata")) (if (< (count items) 3) ;; (def name) with no init (declare): intern + reserve the cell so a - ;; forward reference resolves. The back ends key on :no-init — Chez - ;; def-var!s an unbound placeholder; the Janet back end punts to the - ;; interpreter, which interns a genuinely-unbound var. + ;; forward reference resolves. The back end keys on :no-init — Chez + ;; def-var!s an unbound placeholder; the interpreter interns a + ;; genuinely-unbound var. (let [nm (form-sym-name name-sym) cur (compile-ns ctx)] (host-intern! ctx cur nm) {:op :def :ns cur :name nm :no-init true}) @@ -297,8 +297,7 @@ ;; Host interop method call (jolt-0kf5). `(.method target arg*)` — a head that ;; starts with "." but not ".-" (field access stays punted). Analyzes to a -;; :host-call node; the Janet back end punts it at emit (no interop model -> the -;; interpreter runs it), the Chez back end lowers it to a jolt-host-call dispatch. +;; :host-call node; the Chez back end lowers it to a jolt-host-call dispatch. (defn- method-head? [nm] (and (> (count nm) 1) (= "." (subs nm 0 1)) @@ -320,9 +319,8 @@ (not (= "." (subs nm 0 1))))) ;; `(Class. args*)` and `(new Class args*)` -> a :host-new node carrying the class -;; token and the analyzed args. The Janet back end punts it (the interpreter runs -;; the constructor from its class-ctors registry); the Chez back end lowers it to -;; a runtime constructor dispatch (jolt-avt6). +;; token and the analyzed args. The Chez back end lowers it to a runtime +;; constructor dispatch (jolt-avt6). (defn- analyze-ctor [ctx class args env] (host-new class (mapv #(analyze ctx % env) args))) @@ -330,10 +328,8 @@ ;; A symbol member whose name starts with "-" is a field read; otherwise it is a ;; method (call with the trailing args). Both lower to a :host-call carrying the ;; member name verbatim (the leading "-" survives so the runtime dispatcher reads -;; it as a field). The Janet back end punts :host-call to the interpreter, which -;; re-runs the original `.` form via eval-dot — so Janet behavior is unchanged; -;; the Chez back end dispatches it through record-method-dispatch (jolt-kuic). -;; A non-symbol member (e.g. a keyword) stays punted — the interpreter handles it. +;; it as a field). The Chez back end dispatches it through record-method-dispatch +;; (jolt-kuic). (defn- analyze-dot [ctx items env] (when (< (count items) 3) (throw (str "Malformed (. target member ...) form"))) @@ -367,11 +363,8 @@ (if (= :var (:kind r)) (var-ref (:ns r) (:name r)) ;; A non-var qualified ref `Class/member` is a host class static - ;; (Math/sqrt, Long/MAX_VALUE, System/getenv). The Janet back end - ;; punts the :host-static node (the interpreter resolves it from its - ;; class-statics registry, exactly as it did when this was an - ;; uncompilable); the Chez back end lowers it to a runtime static - ;; dispatch (jolt-avt6). + ;; (Math/sqrt, Long/MAX_VALUE, System/getenv). The Chez back end + ;; lowers it to a runtime static dispatch (jolt-avt6). (host-static ns nm))) :else (let [r (resolve-global ctx form)] (case (:kind r) @@ -445,12 +438,10 @@ (form-map-pairs form))) (form-set? form) (set-node (mapv #(analyze ctx % env) (form-set-items form))) (form-list? form) (analyze-list ctx form env) - ;; regex literal #"…" -> a :regex IR node (leaf). The Janet back end punts it - ;; (interpreter compiles via the seed PEG engine); the Chez back end emits a + ;; regex literal #"…" -> a :regex IR node (leaf). The Chez back end emits a ;; jolt-regex value over the vendored irregex. (form-regex? form) {:op :regex :source (form-regex-source form)} - ;; #inst / #uuid literals -> :inst / :uuid IR leaves. Like :regex, the Janet - ;; back end punts (the interpreter's data-readers parse them); the Chez back + ;; #inst / #uuid literals -> :inst / :uuid IR leaves. The Chez back ;; end emits a runtime inst/uuid value (host/chez/inst-time.ss). (form-inst? form) {:op :inst :source (form-inst-source form)} (form-uuid? form) {:op :uuid :source (form-uuid-source form)} diff --git a/jolt-core/jolt/backend_scheme.clj b/jolt-core/jolt/backend_scheme.clj index 5750d02..b9684b5 100644 --- a/jolt-core/jolt/backend_scheme.clj +++ b/jolt-core/jolt/backend_scheme.clj @@ -1,15 +1,14 @@ (ns jolt.backend-scheme "Portable Clojure IR -> Chez Scheme emitter (Chez Phase 3, jolt-cf1q.4). - The no-Janet replacement for host/chez/emit.janet: consumes the same + Consumes the host-neutral IR (jolt.ir, see jolt-core/jolt/ir.clj) the analyzer produces and emits Chez Scheme source TEXT. Pure jolt-core (clojure.core + clojure.string only) so that, once cross-compiled, it runs ON Chez and the analyzer can emit - its own code with no Janet in the loop — the bootstrap spine. + its own code — the bootstrap spine. Output is a STRING of Scheme source; `host/compile` on Chez is `(eval (read - ...))`. Mirrors emit.janet op-for-op so the value-parity gate holds against the - Janet emitter while the port is in flight. + ...))`. Lowers each IR op to Scheme. INCREMENT 1 (jolt-hg7z): const/local/var/the-var/if/do/let/loop/recur/invoke (+ native-ops)/fn/def + the escaping/flonum/munge helpers. @@ -17,7 +16,7 @@ quote (emit-quoted, walks the raw reader form via the portable jolt.host form-* contract — same seam the analyzer uses, so it stays host-neutral). INCREMENT 3 (jolt-me6m): try/throw + host-call + regex/inst/uuid + def-meta + - quoted-symbol-meta. With this the emitter covers every op emit.janet handles. + quoted-symbol-meta. With this the emitter covers every IR op. emit-quoted now also reconstructs plain jolt VALUES (def/symbol :meta), enabled by making :meta a portable struct at the host seam (h-sym-meta). Program assembly + the prelude driver port land with compile-from-source (inc 4+)." @@ -27,8 +26,8 @@ form-literal? form-elements form-vec-items form-map-pairs form-set-items form-char-code]])) -;; Hot clojure.core primitives lowered to native Scheme, mirroring the Janet -;; backend's native-ops. `=` is the exactness-aware jolt= from values.ss; inc/dec/ +;; Hot clojure.core primitives lowered to native Scheme. +;; `=` is the exactness-aware jolt= from values.ss; inc/dec/ ;; not are rt shims; mod/rem/quot map to Scheme's (Scheme has all three). (def ^:private native-ops {"+" "+" "-" "-" "*" "*" "/" "/" @@ -80,7 +79,7 @@ ;; Host interop methods with a Chez RT shim (rt.ss jolt-host-call). A `.method` ;; call on any other method routes to record-method-dispatch (a reify/record -;; protocol method). Keep in sync with emit.janet's supported-host-methods. +;; protocol method). (def ^:private supported-host-methods #{"isDirectory" "listFiles"}) ;; Native-op Scheme procedures that return a genuine Scheme boolean (#t/#f), so an @@ -173,8 +172,7 @@ ;; reconstruct each as the matching Chez RT constructor — the runtime value of a ;; quote is just that literal data. The form is walked via the jolt.host form-* ;; contract (the portable seam the analyzer uses), NOT host-native predicates, so -;; this stays host-neutral: on Janet the contract walks Janet reader forms, on -;; Chez it walks Chez reader forms. +;; this stays host-neutral — the contract walks the host's reader forms. (declare emit-quoted) (defn- emit-quoted-map [pairs] ;; pairs: a jolt vector of [k-form v-form] pairs (form-map-pairs) diff --git a/jolt-core/jolt/ir.clj b/jolt-core/jolt/ir.clj index c68f4ab..60a9639 100644 --- a/jolt-core/jolt/ir.clj +++ b/jolt-core/jolt/ir.clj @@ -29,14 +29,13 @@ ;; A qualified static reference to a host class member, `Class/member` (e.g. ;; Math/sqrt, Long/MAX_VALUE, System/getenv). A leaf node carrying the class and -;; member names. The Janet back end punts it (the interpreter resolves the static -;; from its class-statics registry); the Chez back end lowers a value ref to -;; host-static-ref and a call head to host-static-call (host-static.ss). +;; member names. The Chez back end lowers a value ref to host-static-ref and a +;; call head to host-static-call (host-static.ss). (defn host-static [class member] {:op :host-static :class class :member member}) ;; A host constructor, `(Class. args*)` / `(new Class args*)`. Carries the class -;; name and the analyzed argument nodes. Janet punts (interpreter runs the -;; constructor); Chez lowers to host-new (host-static.ss class-ctor registry). +;; name and the analyzed argument nodes. Chez lowers to host-new (host-static.ss +;; class-ctor registry). (defn host-new [class args] {:op :host-new :class class :args args}) (defn if-node [test then else] {:op :if :test test :then then :else else}) diff --git a/jolt-core/jolt/passes/types.clj b/jolt-core/jolt/passes/types.clj index 105070a..6bca28c 100644 --- a/jolt-core/jolt/passes/types.clj +++ b/jolt-core/jolt/passes/types.clj @@ -25,7 +25,7 @@ ;; :phm (persistent hash map; NOT raw-get-safe) ;; :any (top), nil (bottom, identity for join). ;; Compound types are small jolt maps, so they compare by value on both the -;; Clojure and the Janet (orchestrator) side. struct/vec/set use distinct keys so +;; Clojure and the host (orchestrator) side. struct/vec/set use distinct keys so ;; a type is recognised by which key it carries. ;; (get t :KEY) is nil for a keyword type and the child for a compound, so a ;; compound is detected by some? — no map?/contains? needed. diff --git a/jolt-core/jolt/reader.clj b/jolt-core/jolt/reader.clj index 53b797e..bb0504e 100644 --- a/jolt-core/jolt/reader.clj +++ b/jolt-core/jolt/reader.clj @@ -1,16 +1,16 @@ (ns jolt.reader "Portable Clojure reader: source text -> reader forms (Chez Phase 3, jolt-cf1q.4). - The no-Janet replacement for src/jolt/reader.janet. All the lexing/parsing LOGIC + All the lexing/parsing LOGIC is portable Clojure; form CONSTRUCTION and string->number parsing delegate to the jolt.host contract (form-make-symbol/char, form-char-from-name, form-scan-number) — a Clojure source file cannot write a {:jolt/type :symbol} literal (it parses as a tagged reader form), and the concrete form representation is the host's to own. Same split the analyzer uses for the form-* readers. Once cross-compiled this runs - ON Chez so compile-from-source needs no Janet reader. + ON Chez to drive compile-from-source. - Positions are CHARACTER indices (the Janet reader uses byte indices); for ASCII - source they coincide, and form VALUES are identical either way — the parity gate + Positions are CHARACTER indices; for ASCII + source they coincide with byte indices, and form VALUES are identical either way — the parity gate compares values, not positions. INCREMENT 5a (jolt-50xx): the ATOM layer — whitespace/comments, symbols (+ nil/ @@ -27,8 +27,8 @@ form-elements form-vec-items form-set-items form-map-pairs]])) -;; Source access by CHARACTER codepoint, mirroring the Janet reader's byte access -;; (identical for ASCII). cp = codepoint at i; len = character count. +;; Source access by CHARACTER codepoint +;; (identical to byte access for ASCII). cp = codepoint at i; len = character count. (defn- cp [s i] (int (nth s i))) (defn- len [s] (count s)) @@ -71,8 +71,7 @@ (if (and (< end (len s)) (symbol-char? (cp s end))) (recur s pos (inc end)) end)) (defn- read-keyword* [s pos] - ;; pos is at the first colon; ::foo is treated as :foo (no auto-resolution), - ;; matching the Janet reader. + ;; pos is at the first colon; ::foo is treated as :foo (no auto-resolution). (let [start (if (and (< (inc pos) (len s)) (= (cp s (inc pos)) 58)) (+ pos 2) (inc pos)) end (read-keyword-name s start start)] [(keyword (subs s start end)) end])) @@ -182,7 +181,7 @@ ;; :form payload=the form a real datum ;; :skip payload=nil a comment (;) or #_ discard — produced nothing ;; :splice payload=items-vector #?@ — contributes 0+ items to the enclosing coll -;; Out-of-band control (vs the Janet reader's :jolt/skip / :jolt/splice sentinel +;; Out-of-band control (rather than :jolt/skip / :jolt/splice sentinel ;; FORMS) keeps it collision-free and host-neutral — no tagged-struct to build or ;; recognize. Collection readers dispatch on kind; read-next-form skips :skip. (declare read-form) @@ -345,7 +344,7 @@ (defn- read-tagged* [s pos] ;; unknown dispatch -> a tagged literal (#inst, #uuid, #foo). The tag includes - ;; the leading # (read-symbol-name starts at #), matching the Janet reader. + ;; the leading # (read-symbol-name starts at #). (let [end (read-symbol-name s pos pos) tag (subs s pos end) [form np] (read-next-form s end)] diff --git a/src/jolt/clojure/java/io.clj b/src/jolt/clojure/java/io.clj deleted file mode 100644 index 1f29f23..0000000 --- a/src/jolt/clojure/java/io.clj +++ /dev/null @@ -1,95 +0,0 @@ -; Jolt Standard Library: clojure.java.io -; -; A Janet-backed shim: file I/O via Janet's file/ and os/ through the janet.* -; interop bridge. It deals in plain path strings and Janet file handles, not -; java.io objects — so JVM-specific interop on the results (.toURL, .lastModified, -; …) won't work, but file/reader/writer/resource/copy/slurp do. - -(defn file - "A java.io.File. With a parent and child, joins them with '/'. Returns a - :jolt/file value (instance? File true) with the File method surface; str/slurp - and io/reader coerce it back to its path." - ([path] (__make-file path)) - ([parent child] (__make-file parent child))) - -(defn as-file [x] (if (__file? x) x (__make-file x))) - -(defn as-url - "Coerce `x` to a java.net.URL value (a :jolt/url). Strings are parsed." - [x] - (if (= :jolt/url (get x :jolt/type)) x (java.net.URL. (str x)))) - -(defn reader [x] - (cond - ;; already a reader (java.io shim or janet file handle) — pass through, - ;; like the JVM's io/reader on a Reader - (= :jolt/jio-string-reader (get x :jolt/type)) x - (= :jolt/url (get x :jolt/type)) (java.io.StringReader. (janet/slurp (.getPath x))) - (= :core/file (janet/type x)) x - (sequential? x) (java.io.StringReader. (apply str x)) ; char[] → in-memory reader - ;; a path: an in-memory reader over the file's content — gives the - ;; java.io.Reader surface (.read/.mark/.reset) plus line-seq's - ;; :read-line-fn, which a raw janet file handle has neither of - :else (java.io.StringReader. (janet/slurp (str x))))) -(defn writer [x] - (cond - ;; already a Writer/StringWriter shim — pass through, like the JVM's - ;; io/writer on a Writer (markdown-clj's md-to-html writes into a StringWriter) - (= :jolt/writer (get x :jolt/type)) x - (= :core/file (janet/type x)) x ; already a janet file handle - :else (janet.file/open (str x) :w))) ; a path -(defn input-stream [x] (reader x)) -(defn output-stream [x] (writer x)) - -(defn resource - "Returns a slurp-able path for `path` if it exists, else nil. (Clojure - returns a classpath URL; here the loader's source roots play the classpath - role — the bare path is tried first, then path under each root.)" - [path] - (let [p (str path)] - (if (janet.os/stat p) - p - (loop [roots (seq (__source-roots))] - (when roots - (let [cand (str (first roots) "/" p)] - (if (janet.os/stat cand) - cand - (recur (next roots))))))))) - -(defn delete-file - ([f] (delete-file f false)) - ([f silently] - (try (do (janet.os/rm (str f)) true) - (catch Throwable e (if silently false (throw e)))))) - -(defn make-parents - "Create the parent directories of `f`." - [f] - (let [path (str f) - i (clojure.string/last-index-of path "/")] - (when (and i (pos? i)) - (let [parent (subs path 0 i)] - (make-parents parent) - (when-not (janet.os/stat parent) (janet.os/mkdir parent)))))) - -(defn copy - "Copy from `in` to `out`. A value carrying a truthy `:jolt/output-stream` - marker is treated as a java.io OutputStream (write via its .write); a - `:jolt/input-stream` source is pumped through its .read. This lets host-shim - libraries (e.g. jolt-lang/http-client's byte streams) participate without core - knowing their concrete types. Falls back to the original path/handle copy." - [in out & opts] - (cond - (get out :jolt/output-stream) - (if (get in :jolt/input-stream) - (let [buf (byte-array 8192)] - (loop [] - (let [n (.read in buf 0 8192)] - (when (not= -1 n) - (.write out buf 0 n) - (recur))))) - ;; in is a byte-array / string - (.write out in)) - :else - (let [content (if (string? in) (slurp in) (janet.file/read in :all))] - (if (string? out) (spit out content) (janet.file/write out content))))) diff --git a/src/jolt/jolt/interop.clj b/src/jolt/jolt/interop.clj deleted file mode 100644 index 28073c8..0000000 --- a/src/jolt/jolt/interop.clj +++ /dev/null @@ -1,26 +0,0 @@ -; Jolt Standard Library: jolt.interop -; Janet interop helpers for Jolt. - -(defn janet-eval - [s] - (eval (parse s))) - -(defn janet-type - [x] - (janet/type x)) - -(defn janet-describe - [x] - (describe x)) - -(defn janet-table-keys - [t] - (keys t)) - -(defn janet-table-vals - [t] - (vals t)) - -(defn janet-table->map - [t] - (into {} (map (fn [k] [k (get t k)]) (keys t)))) diff --git a/src/jolt/jolt/nrepl.clj b/src/jolt/jolt/nrepl.clj deleted file mode 100644 index 55bb4a6..0000000 --- a/src/jolt/jolt/nrepl.clj +++ /dev/null @@ -1,236 +0,0 @@ -; Jolt Standard Library: jolt.nrepl -; -; An nREPL (https://nrepl.org) server and client written in Clojure, on top of -; Jolt's Janet interop bridge (the `janet.*` namespace segment). The bencode -; codec follows nrepl.bencode and the op/response shapes follow babashka.nrepl -; (the SCI-targeted nREPL server). Because the whole thing is ordinary Clojure -; over `janet.net/*`, the networking it uses is reusable for anything else. -; -; Notes: -; - One Jolt runtime backs the server; sessions are tracked ids and share the -; runtime (defs persist across a connection, like a dev REPL). -; - eval uses Jolt's own `eval`/`read-string`; printed output is captured by -; rebinding Janet's :out dynamic. -; - No true interrupt: an in-flight synchronous eval can't be stopped. - -;; ───────────────────────── bencode ───────────────────────── - -(defn benc - "Encode `x` (integer, string, keyword, sequential, or map) to a bencode string." - [x] - (cond - (integer? x) (str "i" x "e") - (string? x) (str (count x) ":" x) - (keyword? x) (benc (name x)) - (symbol? x) (benc (name x)) - (map? x) (let [ks (sort (fn [a b] (compare (name a) (name b))) (keys x))] - (str "d" (apply str (mapcat (fn [k] [(benc (name k)) (benc (get x k))]) ks)) "e")) - (sequential? x) (str "l" (apply str (map benc x)) "e") - (nil? x) "le" - :else (throw (ex-info "bencode: cannot encode" {:value x})))) - -(defn encode [x] (benc x)) - -; A reader buffers bytes from a janet.net connection (or a preloaded string for -; tests) and refills via janet.net/read. -(defn reader [conn buf] (atom {:conn conn :buf (or buf "") :pos 0})) - -(defn- rd-ensure [r n] - (loop [] - (let [{:keys [conn buf pos]} @r] - (when (< (count buf) (+ pos n)) - (let [chunk (janet.net/read conn 4096)] - (when (nil? chunk) (throw (ex-info "eof" {}))) - (swap! r assoc :buf (str buf (str chunk))) - (recur)))))) - -(defn- take-n [r n] - (rd-ensure r n) - (let [{:keys [buf pos]} @r] - (swap! r assoc :pos (+ pos n)) - (subs buf pos (+ pos n)))) - -(defn- take-ch [r] (take-n r 1)) - -(def ^:private digits #{"0" "1" "2" "3" "4" "5" "6" "7" "8" "9"}) - -(defn decode - "Read one bencode value from reader `r`. Throws on EOF. Dict keys come back as - strings; the top-level nREPL message is a dict (map)." - [r] - (let [c (take-ch r)] - (cond - (= c "i") (loop [acc ""] (let [d (take-ch r)] (if (= d "e") (janet/scan-number acc) (recur (str acc d))))) - (= c "l") (loop [out []] (let [v (decode r)] (if (= v ::end) out (recur (conj out v))))) - (= c "d") (loop [out {}] (let [k (decode r)] (if (= k ::end) out (recur (assoc out k (decode r)))))) - (= c "e") ::end - (contains? digits c) - (loop [acc c] (let [d (take-ch r)] (if (= d ":") (take-n r (janet/scan-number acc)) (recur (str acc d))))) - :else (throw (ex-info "bad bencode byte" {:byte c}))))) - -;; ───────────────────────── server ───────────────────────── - -(def version "0.1.0") - -(def ^:private session-counter (atom 0)) -(defn- new-session [] - (str "jolt-" (swap! session-counter inc) "-" (janet.math/floor (* 1000000 (janet.math/random))))) - -(defn- resp-for - "Build a response by echoing the request's id/session (an nREPL requirement)." - [msg extra] - (assoc extra "session" (get msg "session" "none") "id" (get msg "id" "unknown"))) - -; Jolt resolves a function body's unqualified symbols against the *dynamic* -; current-ns, not the function's home ns. So evaluating user code (which switches -; ns) would break jolt.nrepl's own later symbol lookups. eval-in-ns confines the -; switch: it evaluates one form in `ns-str` and ALWAYS restores current-ns to -; jolt.nrepl before returning, reporting the form's value/error and resulting ns. -; It uses only special forms (in-ns/eval/the-ns/try) + keywords, so it resolves -; regardless of the ambient ns. -(defn- eval-in-ns [ns-str form] - (in-ns (symbol ns-str)) - ; Bind the value before reading the ns: jolt evaluates map-literal values - ; right-to-left, so the result ns must be captured *after* eval runs any in-ns. - (let [result (try (let [v (eval form)] {:val v :ns (:name (the-ns))}) - (catch Throwable e {:err e :ns (:name (the-ns))}))] - (in-ns 'jolt.nrepl) - result)) - -(defn- eval-handler [server msg send!] - ; current-ns is global ctx state shared by all fibers, so set the eval ns - ; explicitly each time: requested :ns, else the session's last ns, else user. - ; `respond` / `flush-out` are locals (lexical, ns-independent) on purpose. - (let [code (get msg "code" "") - out-buf (janet/buffer "") - old-out (janet/dyn :out) - respond (fn [extra] (send! (assoc extra "session" (get msg "session" "none") - "id" (get msg "id" "unknown")))) - flush-out (fn [] (when (pos? (count out-buf)) - (respond {"out" (str out-buf)}) - (janet.buffer/clear out-buf)))] - (try - (do - (janet/setdyn :out out-buf) - (loop [forms (seq (read-string (str "[" code "]"))) - cur-ns (or (get msg "ns") (:eval-ns @server) "user")] - (when forms - (let [{:keys [val ns err]} (eval-in-ns cur-ns (first forms))] - (flush-out) - (swap! server assoc :eval-ns ns) - (when err (throw err)) - (respond {"ns" ns "value" (pr-str val)}) - (recur (next forms) ns)))) - (janet/setdyn :out old-out) - (respond {"status" ["done"]})) - (catch Throwable e - (janet/setdyn :out old-out) - (flush-out) - (respond {"err" (str e "\n")}) - (respond {"ex" "class jolt/Exception" - "root-ex" "class jolt/Exception" - "status" ["eval-error"]}) - (respond {"status" ["done"]}))))) - -(def ^:private describe-ops - {"clone" {} "close" {} "describe" {} "eval" {} "load-file" {} - "ls-sessions" {} "interrupt" {} "eldoc" {}}) - -(defn- dispatch [server msg send!] - (case (get msg "op") - "clone" (let [id (new-session)] - (swap! server update :sessions conj id) - (send! (resp-for msg {"new-session" id "status" ["done"]}))) - "describe" (send! (resp-for msg {"ops" describe-ops - "versions" {"jolt" {"version-string" version} - "nrepl" {"version-string" version}} - "status" ["done"]})) - "eval" (eval-handler server msg send!) - "load-file" (eval-handler server (assoc msg "code" (get msg "file" "")) send!) - "close" (do (swap! server update :sessions disj (get msg "session")) - (send! (resp-for msg {"status" ["done" "session-closed"]}))) - "ls-sessions" (send! (resp-for msg {"sessions" (vec (:sessions @server)) "status" ["done"]})) - "interrupt" (send! (resp-for msg {"status" ["done"]})) - "eldoc" (send! (resp-for msg {"status" ["done" "no-eldoc"]})) - (send! (resp-for msg {"status" ["error" "unknown-op" "done"]})))) - -(defn- handle-conn [server conn] - (let [r (reader conn nil) - send! (fn [resp] (janet.net/write conn (encode resp)))] - (try - (loop [] - (let [msg (decode r)] - (when (map? msg) - (try (dispatch server msg send!) - (catch Throwable e - (send! (resp-for msg {"err" (str e "\n") "status" ["done"]})))) - (recur)))) - (catch Throwable _ nil)) - (try (janet.net/close conn) (catch Throwable _ nil)))) - -; We run the accept loop ourselves with janet.net/accept rather than passing a -; handler to janet.net/server: Janet's built-in accept loop arity-checks the -; handler, which a Jolt closure doesn't satisfy. janet.ev/call schedules each -; connection (and the loop itself) on a fiber. -(defn- accept-loop [server] - (loop [] - (let [conn (try (janet.net/accept (:sock @server)) (catch Throwable _ nil))] - (when conn - (janet.ev/call handle-conn server conn) - (recur))))) - -(defn start-server! - "Start an nREPL server. opts: :host (default \"127.0.0.1\"), :port (default - 7888). Returns a server handle (an atom). Non-blocking — connections are served - on the event loop." - [opts] - (let [host (get opts :host "127.0.0.1") - port (get opts :port 7888) - sock (janet.net/server host (str port)) - server (atom {:sessions #{} :host host :port port :sock sock :eval-ns "user"})] - (janet.ev/call accept-loop server) - server)) - -(defn stop-server! - "Stop accepting new connections." - [server] - (when-let [sock (:sock @server)] (janet.net/close sock)) - server) - -;; ───────────────────────── client ───────────────────────── - -(defn connect - "Connect to an nREPL server. opts: :host (default \"127.0.0.1\"), :port - (default 7888). Returns a client handle." - [opts] - (let [conn (janet.net/connect (get opts :host "127.0.0.1") (str (get opts :port 7888)))] - {:conn conn :reader (reader conn nil)})) - -(defn send-msg [client msg] (janet.net/write (:conn client) (encode msg))) -(defn read-msg [client] (decode (:reader client))) - -(defn- status-done? [resp] - (when-let [st (get resp "status")] - (and (sequential? st) (some (fn [s] (= "done" (str s))) st)))) - -(defn request - "Send `msg` (a map with at least an \"op\") and collect responses until one - carries the \"done\" status. Returns the vector of responses." - [client msg] - (send-msg client msg) - (loop [out []] - (let [resp (read-msg client) - out (conj out resp)] - (if (status-done? resp) out (recur out))))) - -(defn client-clone - "Send a clone op; return the new session id." - [client] - (some (fn [r] (get r "new-session")) (request client {"op" "clone"}))) - -(defn client-eval - "Eval `code`; returns the responses. Pass `session` to eval in a cloned session." - ([client code] (request client {"op" "eval" "code" code})) - ([client code session] (request client {"op" "eval" "code" code "session" session}))) - -(defn client-close [client] (janet.net/close (:conn client))) diff --git a/src/jolt/jolt/png.clj b/src/jolt/jolt/png.clj deleted file mode 100644 index ecb72eb..0000000 --- a/src/jolt/jolt/png.clj +++ /dev/null @@ -1,35 +0,0 @@ -; Jolt Standard Library: jolt.png -; -; Write PNG images from Clojure. Build an image, push RGB pixels in row-major -; order (top row first), then write to disk: -; -; (require '[jolt.png :as png]) -; (let [img (png/image w h)] -; (doseq [y (range h)] -; (doseq [x (range w)] -; (png/put! img r g b))) ; r g b are ints 0-255 -; (png/write img w h "out.png")) -; -; The byte-level encoding (filtering, stored-DEFLATE/zlib, CRC32) runs in the -; host (the Janet `png` module, reached via the `janet.*` bridge): per-byte work -; in the overlay is far too slow, so the overlay only produces pixels and the -; host encodes them in one pass. -(ns jolt.png) - -(defn image - "A blank w×h RGB pixel sink (a host byte buffer). Push exactly w*h pixels with - put!, in row-major / top-row-first order, then write." - [w h] - (janet.buffer/new (* w h 3))) - -(defn put! - "Append one RGB pixel — each of r g b an int in 0-255 — to the image. Returns - the image so calls can be threaded." - [img r g b] - (janet.buffer/push-byte img r g b) - img) - -(defn write - "Encode the filled w×h image as a PNG and write it to path. Returns path." - [img w h path] - (janet.png/write path w h img)) diff --git a/src/jolt/jolt/shell.clj b/src/jolt/jolt/shell.clj deleted file mode 100644 index bb2caf1..0000000 --- a/src/jolt/jolt/shell.clj +++ /dev/null @@ -1,12 +0,0 @@ -; Jolt Standard Library: jolt.shell -; Shell command execution via Janet's os/shell. - -(defn sh - [& args] - (let [cmd (apply str (interpose " " args)) - result (os/shell cmd)] - {:exit (result 0) :out (result 1) :err (result 2)})) - -(defn shell - [& args] - (:out (apply sh args)))