Scrub dangling Janet references; drop dead Janet-coupled files

Rephrase comments that pointed at deleted Janet files (emit.janet, the seed
sources, 'the Janet back end punts ...') to present-tense descriptions of the
Chez behavior. Comment/docstring-only; the self-host fixpoint is unchanged
(comments don't affect the compiled seed).

Delete five files that were Janet-host shims with no Chez path: clojure.java.io
(provided natively by host/chez/io.ss), and jolt.{nrepl,png,interop,shell}
(the janet.* bridge, os/shell, janet.net — none exist on Chez).

jolt-cf1q.6
This commit is contained in:
Yogthos 2026-06-21 12:01:04 -04:00
parent 750ce05716
commit 48e2ef5910
53 changed files with 418 additions and 675 deletions

View file

@ -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?

View file

@ -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))))

View file

@ -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))

View file

@ -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:

View file

@ -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)

View file

@ -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)))

View file

@ -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))))

View file

@ -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

View file

@ -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

View file

@ -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 <fn>) +
@ -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

View file

@ -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)

View file

@ -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

View file

@ -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)))

View file

@ -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 ""))

View file

@ -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"))

View file

@ -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)

View file

@ -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.

View file

@ -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))

View file

@ -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/

View file

@ -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))

View file

@ -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

View file

@ -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)))

View file

@ -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)))

View file

@ -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.

View file

@ -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

View file

@ -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

View file

@ -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 '()))

View file

@ -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)))

View file

@ -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)) "#<transient vector>" "#<transient map>"))

View file

@ -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 <arglist-vec> _) 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)))

View file

@ -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

View file

@ -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)))

View file

@ -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")

View file

@ -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.

View file

@ -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

View file

@ -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

View file

@ -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)))

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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))

View file

@ -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)

View file

@ -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]

View file

@ -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 <vec> _) 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)}

View file

@ -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)

View file

@ -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})

View file

@ -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.

View file

@ -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)]

View file

@ -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)))))

View file

@ -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))))

View file

@ -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)))

View file

@ -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))

View file

@ -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)))