Clean up codebase: rename stdlib layer, strip porting residue, fix tooling

Rename src/jolt -> stdlib (the runtime-loaded layer; jolt-core stays the
seed-baked layer) and update the loader / emit-image / doc paths. Drop dead
code: the spike/ experiments, the duplicate clojuredocs-export.edn (json moves
to tools/), the Janet-era jolt.http binding, and the orphaned
persistent_vector.clj whose ns/path didn't even match.

Strip porting residue from comments and docstrings across host/chez, jolt-core,
stdlib, tests, and docs: internal issue ids, "Phase N" markers, and the "vs
Janet" historical exposition, leaving present-tense descriptions and the real
JVM-Clojure semantic contrasts. Same pass over the corpus suite labels. The seed
is unchanged (docstrings/comments aren't emitted), so the self-host fixpoint and
corpus are untouched.

Port tools/spec_coverage.py off the dead janet probe to bin/joltc and regenerate
coverage.md; drop the dead :host/janet rule from certify.clj and regenerate the
conformance profile. Add docs/host-interop.md (the JVM shims and how to register
your own host class from a library) and a writing-style note in CLAUDE.md.

Stabilize the four racy concurrency corpus cases (future-cancel and agent
send/send-off): give the future a sleeping body and the agent a slow action, so
cancel reliably catches an in-flight future and deref reliably reads the
pre-update snapshot. They certify deterministically now, so drop their :flaky
allowlist entries and the orphaned legend.
This commit is contained in:
Yogthos 2026-06-22 22:18:00 -04:00
parent c18f8087f0
commit 33eff7c7d8
112 changed files with 970 additions and 1621 deletions

View file

@ -25,7 +25,7 @@
(throw (str "zero? requires a number, got: " x)))))
;; pos? checks number? explicitly: this tier is recompiled by the staged pass,
;; where a bare (> x 0) emits the native janet op that happily orders strings
;; where a bare (> x 0) emits the native op that happily orders strings
;; (the documented native-ops relaxation) — the guard keeps Clojure's throw.
(def pos?
(fn* pos? [x]
@ -111,7 +111,7 @@
;; of 00-syntax has loaded, so using them here is fine.
(defmacro ns [nm & clauses]
;; ^{:map} metadata on the ns name reads as a (with-meta sym {...}) form, not an
;; annotated symbol (jolt-8w2). Real libraries put :author/:doc there
;; annotated symbol. Real libraries put :author/:doc there
;; (clojure.tools.logging), so unwrap to the bare symbol; jolt does not track
;; namespace metadata, so the map is dropped.
(let [nm (if (and (seq? nm) (= 'with-meta (first nm))) (second nm) nm)
@ -152,7 +152,7 @@
;; Forward declaration interns unbound vars (Clojure semantics). The interpreter
;; resolves forward refs lazily either way, but the COMPILER classifies globals at
;; compile time: without the var, a declared name that collides with a Janet root
;; compile time: without the var, a declared name that collides with a host root
;; binding (parse, hash, …) would compile to the host fn instead of the var.
(defmacro declare [& syms]
`(do ~@(map (fn* [s] `(def ~s)) syms)))
@ -309,8 +309,8 @@
(if (seq ps)
(if (symbol? (first ps))
(go (next ps) (conj nps (first ps)) lets)
;; bare (gensym) here is Janet's (a Janet symbol the destructurer
;; rejects); round-trip through str for a jolt symbol.
;; a bare (gensym) returns a host symbol the destructurer rejects;
;; round-trip through str for a jolt symbol.
(let [g (symbol (str (gensym)))]
(go (next ps) (conj nps g) (conj (conj lets (first ps)) g))))
[nps lets]))
@ -348,18 +348,18 @@
body (if (and (seq body) (map? (first body)) (not (symbol? (first body))))
(rest body) body)
;; ^{:map} metadata on the name reads as a (with-meta sym …) form, not an
;; annotated symbol (jolt-8w2). def attaches the metadata, but fn needs a
;; annotated symbol. def attaches the metadata, but fn needs a
;; bare symbol, so unwrap it for the fn name.
fn-only-name (if (symbol? fn-name) fn-name (first (rest fn-name)))]
;; pass the name through to fn: the compiled fn's janet name carries it,
;; so stack traces read app.deep/level3 instead of a gensym (jolt-2o7.1)
;; pass the name through to fn: the compiled fn's host name carries it,
;; so stack traces read app.deep/level3 instead of a gensym
`(def ~fn-name (fn ~fn-only-name ~@body))))
;; Jolt doesn't enforce privacy, so defn- is just defn (matching how Clojure's own
;; defn- delegates to defn with :private metadata).
(defmacro defn- [fn-name & body] `(defn ~fn-name ~@body))
;; A fresh jolt symbol inside a macro body (a bare (gensym) returns a Janet symbol
;; A fresh jolt symbol inside a macro body (a bare (gensym) returns a host symbol
;; the destructurer rejects). This defn compiles fine: by the time a tier triggers
;; the analyzer build the kernel is in place (the build is gated until then).
(defn- fresh-sym [] (symbol (str (gensym))))
@ -422,9 +422,9 @@
;; Per binding group: :when wraps the inner form in (if test (list inner) []) so
;; mapcat drops it when false; :let wraps it in a let*; :while wraps the coll in
;; take-while. The last group with no modifiers is a plain map (no flatten needed).
;; Faithful port of the prior Janet macro (single body expr). The body uses only
;; kernel/seed fns so it runs at analyzer-build time. `fn` (not fn*) carries the
;; binding so destructuring forms work.
;; Single body expr. The body uses only kernel/seed fns so it runs at
;; analyzer-build time. `fn` (not fn*) carries the binding so destructuring forms
;; work.
(defmacro for [bindings body]
(let [scan (fn scan [bvec i bind coll mods]
(if (and (< i (count bvec)) (keyword? (nth bvec i)))
@ -471,9 +471,9 @@
(build 0 (parse-groups bindings 0 []))
body)))
;; doseq runs body for side effects across the bindings, returning nil. Same
;; shortcut as the prior Janet macro: realize a `for` comprehension with count
;; (for handles :when/:let/:while and multiple bindings).
;; doseq runs body for side effects across the bindings, returning nil. Realizes
;; a `for` comprehension with count (for handles :when/:let/:while and multiple
;; bindings).
(defmacro doseq [bindings & body]
`(do (count (for ~bindings (do ~@body nil))) nil))
@ -489,7 +489,7 @@
;; lazy-seq / lazy-cat live here (not 30-macros) because the seq/coll tiers use
;; them and compile-as-they-load: the macro must be registered before those tiers
;; or (lazy-seq …) compiles to a call of the macro-as-function and leaks its
;; expansion at runtime (jolt-r81). They use only seed fns (make-lazy-seq/
;; expansion at runtime. They use only seed fns (make-lazy-seq/
;; coll->cells/concat) + map, all available from the start.
;; lazy-seq defers its body: make-lazy-seq holds a thunk that realizes the body
;; to cells when forced. lazy-cat wraps each coll in a lazy-seq and concats.

View file

@ -3,14 +3,14 @@
;; mode these self-host through the now-built analyzer (interpreted otherwise).
;;
;; Migration rule for adding fns here: the fn must (1) NOT be in
;; compiler/core-renames (that map emits core-X Janet symbols directly), (2) have
;; no internal Janet callers of its core-X binding, and (3) NOT be used by the
;; compiler/core-renames (that map emits core-X symbols directly), (2) have
;; no internal callers of its core-X binding, and (3) NOT be used by the
;; self-hosted compiler (jolt-core/jolt/*.clj). Compiler-facing structural fns go
;; in the kernel tier (00-kernel) instead — see its header.
;; Volatiles (moved up from 20-coll: this tier's transducers use them, and the
;; analyzer now ERRORS on unresolved forward references — jolt-2o7.3). The
;; constructor (volatile!) stays native; these are pure over ref-put!/get.
;; Volatiles (this tier's transducers use them, and the analyzer ERRORS on
;; unresolved forward references). The constructor (volatile!) stays native;
;; these are pure over ref-put!/get.
(defn vreset! [vol newval]
(jolt.host/ref-put! vol :val newval) newval)
(defn vswap! [vol f & args]
@ -21,7 +21,7 @@
(defn fnext [coll] (first (next coll)))
(defn nnext [coll] (next (next coll)))
;; Canonical Clojure defs: pure first/next/loop/recur, no Janet realize-for-iteration.
;; Canonical Clojure defs: pure first/next/loop/recur.
(defn last [s]
(if (next s) (recur (next s)) (first s)))

View file

@ -4,7 +4,7 @@
;; redefinable. Loaded after the seq tier; self-hosted in compile mode.
;;
;; Same migration rule as the seq tier (see 10-seq.clj): not in core-renames, no
;; internal Janet callers, not used by the self-hosted compiler.
;; internal callers, not used by the self-hosted compiler.
;; Tiny leaves first — fns below in this tier (and 25-sorted) use them.
(defn some? [x] (not (nil? x)))
@ -20,7 +20,7 @@
;; overlay versions cost an extra call layer per element (seq-pipe bench 4x).
;; Variadic bit ops — canonical Clojure arities folding the binary host op
;; (__bit-* seams). 2-arg call sites still compile to the native janet op via
;; (__bit-* seams). 2-arg call sites still compile to the native op via
;; the backend's native-ops table, so the binary fast path is unchanged.
(defn bit-and
([x y] (__bit-and x y))
@ -87,8 +87,8 @@
;; Distinct keys are recorded in a side vector so the buckets can be frozen in
;; place (no second map rebuild). A bucket's FIRST element is stored as a cheap
;; persistent [x]; only the second element promotes it to a transient — so an
;; all-singletons grouping pays no transient alloc and matches the old cost,
;; while any bucket that actually grows rides the O(1) push.
;; all-singletons grouping pays no transient alloc, while any bucket that
;; actually grows rides the O(1) push.
(defn group-by [f coll]
(let [tm (transient {})
ks (reduce (fn [ks x]
@ -355,7 +355,7 @@
(make-hierarchy) (partition 2 deriv-seq))
h))))
;; --- Stage 3 tier shrink: pure-over-core leaves moved off the host primitives ----
;; --- pure-over-core leaves expressed off the host primitives -----------------
;; Representation predicates over the overlay's own predicates.
(defn sequential? [x] (or (vector? x) (seq? x)))
@ -364,7 +364,7 @@
(or (vector? x) (map? x) (set? x) (list? x) (string? x)))
(defn indexed? [x] (vector? x))
;; sorted? is defined by the next tier (25-sorted) — declared here so this
;; tier compiles (forward references are analysis errors now, jolt-2o7.3).
;; tier compiles (forward references are analysis errors).
(declare sorted?)
(defn reversible? [x] (or (vector? x) (sorted? x)))
@ -377,7 +377,7 @@
(defn infinite? [x] (and (number? x) (or (= x ##Inf) (= x ##-Inf))))
;; qualified-/simple- keyword?/symbol? moved above qualified-ident? (forward
;; references are analysis errors now — jolt-2o7.3).
;; references are analysis errors).
;; realized?: defined on the pending types only (delay/lazy-seq/future read
@ -453,7 +453,7 @@
(defn println-str [& xs] (__with-out-str (fn* [] (apply println xs))))
(defn prn-str [& xs] (__with-out-str (fn* [] (apply prn xs))))
;; --- Phase 2 leaf batch 4 (jolt-ded): over the rand / sort host seams --------
;; --- leaves over the rand / sort host seams ----------------------------------
;; Canonical truncation toward zero via int (the kernel fn floored, which is
;; wrong for a negative n).
@ -549,11 +549,11 @@
;; file-seq: the tree of paths under root (root included), directories walked
;; via the host dir primitives. Paths (strings), not File objects. (Lives below
;; tree-seq: forward references are analysis errors now — jolt-2o7.3.)
;; tree-seq: forward references are analysis errors.)
(defn file-seq [root]
(if (__file? root)
;; java.io.File tree: walk via the File method surface so leaves are File
;; values callers can invoke .isFile/.getName/slurp on (jolt-hjw).
;; values callers can invoke .isFile/.getName/slurp on.
(tree-seq (fn [f] (.isDirectory f)) (fn [f] (seq (.listFiles f))) root)
(tree-seq __dir? __list-dir root)))
@ -587,10 +587,6 @@
;; No ratio type on Jolt, so rationalize is identity.
(defn rationalize [x] x)
;; trampoline: repeatedly calls f with args until a non-function result.
;; rand-int: random integer in [0, n). Uses Janet math/random.
;; 0-arg: a stateful transducer (tracks [seen? prev] in a volatile, so no sentinel
;; value is needed). 1-arg: eager dedupe of consecutive equal elements.
(defn dedupe
@ -625,8 +621,7 @@
;; canonical Clojure 1.11 shape (core.clj seq-to-map-for-destructuring):
;; even pairs build a map (later keys win, as createAsIfByAssoc), a SINGLE
;; element is returned as-is (the trailing-map calling convention), and an
;; unpaired key past pairs throws. The old jolt version silently dropped the
;; trailing element, losing (f {:b 2}) kwargs calls.
;; unpaired key past pairs throws.
(defn seq-to-map-for-destructuring [s]
(if (next s)
(loop [m {} xs (seq s)]
@ -637,8 +632,8 @@
m))
(if (seq s) (first s) {})))
;; Phase 4 (jolt-1j0): host-coupled fns that are pure logic over existing core
;; primitives, so they need no new jolt.host surface.
;; Host-coupled fns that are pure logic over existing core primitives, so they
;; need no new jolt.host surface.
;; vary-meta: f applied to obj's metadata (+ extra args), reattached. meta and
;; with-meta are the irreducible host primitives; vary-meta is just their compose.
@ -650,9 +645,8 @@
(apply str (map (fn [c] (if (= c \-) \_ c)) (seq (str s)))))
;; reduce-kv over a map (k v) or vector (index v). Both branches go through reduce,
;; so reduced short-circuits — and the vector path indexes correctly. (The prior
;; Janet version saw a pvec as a table and folded over its internal keys; it also
;; ignored reduced.) nil folds to init, matching Clojure.
;; so reduced short-circuits — and the vector path indexes correctly. nil folds
;; to init, matching Clojure.
(defn reduce-kv [f init coll]
(cond
(vector? coll) (reduce (fn [acc i] (f acc i (nth coll i))) init (range (count coll)))
@ -660,7 +654,7 @@
(nil? coll) init
:else (throw (str "reduce-kv not supported on: " coll))))
;; ex-info accessors. The Janet constructor (ex-info) stays — it builds the tagged
;; ex-info accessors. The constructor (ex-info) stays native — it builds the tagged
;; value and wires into throw — but the value exposes :jolt/type/:message/:data/
;; :cause via get, so the accessors are pure over get. A thrown non-ex-info arrives
;; wrapped as {:jolt/type :jolt/exception :value v}; unwrap that first.
@ -724,7 +718,7 @@
(when (< i (count vars))
(var-set (nth vars i) (nth saved i))
(recur (inc i))))))))
;; Jolt has no chunked seqs (Phase 5 territory), so this is always false.
;; Jolt has no chunked seqs, so this is always false.
(defn chunked-seq? [x] false)
;; Atom peripheral operations. atom/swap!/reset!/deref stay native — the compiler
@ -733,7 +727,7 @@
;; slot; add-watch/remove-watch/set-validator! mutate the atom (or its watches
;; sub-table) through the one host primitive jolt.host/ref-put! — the minimal
;; mutation kernel the overlay can't express over core fns (a nil value removes the
;; key). compare-and-set! compares by value, matching the prior Janet behavior.
;; key). compare-and-set! compares by value.
(defn swap-vals! [a f & args]
(let [old (deref a)] [old (apply swap! a f args)]))
(defn reset-vals! [a newval]
@ -777,7 +771,7 @@
(jolt.host/ref-put! target (nth idxs+val (- n 2)) val)
val))
;; --- Phase 2 leaf batch (jolt-ded): fn combinators + host-free stubs ---------
;; --- fn combinators + host-free stubs ----------------------------------------
(defn complement
"Takes a fn f and returns a fn that takes the same arguments as f, has the
@ -785,8 +779,7 @@
[f]
(fn [& args] (not (apply f args))))
;; Canonical Clojure fnil: patches only the FIRST 1-3 arguments (the old Janet
;; kernel patched every position it had a default for, which Clojure does not).
;; Canonical Clojure fnil: patches only the FIRST 1-3 arguments.
(defn fnil
([f x]
(fn [a & args] (apply f (if (nil? a) x a) args)))
@ -818,7 +811,7 @@
(let [t (:test (meta v))]
(if t (do (t) :ok) :no-test)))
;; --- Phase 2 leaf batch 2 (jolt-ded): canonical Clojure ports ----------------
;; --- canonical Clojure ports -------------------------------------------------
;; key/val/find first — merge-with and memoize below use them.
;; Strict, as in Clojure: an entry is what (seq m) yields (a host tuple), NOT
@ -882,8 +875,7 @@
(recur nxt (next ks))))
m)))))
;; find-based, so nil RESULTS are cached too (the old kernel fn re-computed
;; them); args canonicalize as a collection key.
;; find-based, so nil RESULTS are cached too; args canonicalize as a collection key.
(defn memoize [f]
(let [mem (atom (hash-map))]
(fn [& args]
@ -920,11 +912,9 @@
(defn reverse [coll] (reduce conj (list) coll))
;; --- Phase 2 leaf batch 3 (jolt-ded) -----------------------------------------
;; An empty coll of the same category; sorted colls keep their comparator (the
;; value's own :empty op). Strings and scalars are nil, as in Clojure; a lazy
;; seq empties to () (the old kernel fn returned a host table for it).
;; seq empties to ().
(defn empty [coll]
(cond
(nil? coll) nil
@ -948,8 +938,6 @@
(assoc m k (apply f (get m k) args)))))]
(up m ks f args)))
;; --- jolt-brh: the last missing-portable vars --------------------------------
;; jolt keywords have no intern table (any keyword "exists"), so find-keyword
;; always finds — babashka makes the same call.
(defn find-keyword
@ -962,7 +950,7 @@
;; 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).
;; goes through IFn dispatch.
(defn comp
([] identity)
([f] f)
@ -977,17 +965,15 @@
([x y z & args] (f (apply g x y z args)))))
([f g & fs] (reduce comp (comp f g) fs)))
;; Canonical IFn set (jolt-1vx): fns, keywords, symbols, maps (sorted incl.),
;; Canonical IFn set: fns, keywords, symbols, maps (sorted incl.),
;; sets, vectors, and vars — NOT lists ((ifn? '(1 2)) is false in Clojure).
;; Mutable-mode caveat: vectors and lists share the array representation
;; there, so vector? can't separate them and lists read as ifn?.
(defn ifn? [x]
(or (fn? x) (keyword? x) (symbol? x) (map? x) (set? x) (vector? x) (var? x)))
;; Auto-promoting (') and unchecked arithmetic. Jolt numbers don't overflow,
;; so all of these are the checked ops; fixed arities mirror Clojure's
;; signatures. unchecked-divide-int goes through quot, so dividing by zero
;; throws as on the JVM (the old seed fn silently truncated infinity).
;; throws as on the JVM.
(def +' +)
(def -' -)
(def *' *)
@ -1079,7 +1065,7 @@
(defn unchecked-float [x] (double x))
(defn unchecked-double [x] (double x))
;; --- transduce / into / eduction (seed-shrink round 5) ---------------------
;; --- transduce / into / eduction ---------------------------------------------
;; Canonical transduce: build the stacked rf once, reduce (which honors
;; `reduced` and steps lazy seqs incrementally), then run the completion arity.
(defn transduce
@ -1089,9 +1075,9 @@
(xf (reduce xf init coll)))))
;; 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).
;; through the overlay call layers — same lesson as even?/odd?).
;; eduction is EAGER on jolt (documented divergence, as before): the composed
;; eduction is EAGER on jolt (documented divergence): the composed
;; xforms applied to coll, realized into a vector.
(defn eduction [& args]
(let [coll (last args)
@ -1102,7 +1088,7 @@
(defn ->Eduction [xform coll] (into [] xform coll))
;; --- JVM-shape stubs and trivial shells (seed-shrink batch 2) --------------
;; --- JVM-shape stubs and trivial shells --------------------------------------
;; Pure compositions or documented jolt stubs; the host keeps nothing.
(defn enumeration-seq [e] (seq e))
(defn iterator-seq [i] (seq i))

View file

@ -1,4 +1,4 @@
;; clojure.core — sorted collections tier (stage 3, jolt-0lj).
;; clojure.core — sorted collections tier.
;;
;; A sorted-map / sorted-set is a tagged host table
;; {:jolt/type :jolt/sorted-map|:jolt/sorted-set
@ -10,8 +10,7 @@
;; The tree is a left-leaning-free red-black tree — Rich Hickey's algorithm,
;; ported from the ClojureScript PersistentTreeMap (cljs.core: tree-map-add /
;; balance-left / balance-right / tree-map-append / balance-*-del). assoc / get /
;; dissoc / contains are O(log n); the old sorted-VECTOR rep was O(n) per assoc,
;; O(n^2) to build (jolt-684u's sibling, jolt-0hbr). cljs uses BlackNode/RedNode
;; dissoc / contains are O(log n). cljs uses BlackNode/RedNode
;; deftypes, but this tier loads before 30-macros (no deftype), so a node is a
;; plain vector [color k v left right] (color :red/:black; left/right node|nil)
;; and the methods become functions — the algorithm is identical.

View file

@ -1,6 +1,6 @@
;; clojure.core — macro tier. Macros expressed in Clojure (defmacro + syntax-quote)
;; rather than as hand-built Janet form-transformers. Loaded after the fn tiers,
;; so a macro here may use any already-frozen core fn/macro.
;; clojure.core — macro tier. Macros expressed in Clojure (defmacro + syntax-quote).
;; Loaded after the fn tiers, so a macro here may use any already-frozen core
;; fn/macro.
;;
;; 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/
@ -34,7 +34,7 @@
(defmacro defmethod [mm dispatch-val & fn-tail]
`(defmethod-setup (quote ~mm) ~dispatch-val (fn ~@fn-tail)))
;; Multimethod table ops (tier 6c): a multimethod's method table lives on its
;; Multimethod table ops: a multimethod's method table lives on its
;; VAR (the value is just the dispatch closure), so these pass the name quoted
;; to ctx-capturing setups — the same shape as defmulti/defmethod above.
(defmacro prefer-method [mm dval-a dval-b]
@ -48,7 +48,7 @@
;; methods/get-method take the multimethod VALUE (Clojure semantics); the setup
;; maps it back to its var via the registry, so a bare multifn ref works from a
;; compiled fn in any namespace (jolt multimethod table-visibility fix).
;; compiled fn in any namespace.
(defmacro get-method [mm dval]
`(get-method-setup ~mm ~dval))
@ -75,7 +75,7 @@
`(do ~x ~@body))
;; defonce: define name only if it isn't already bound to a non-nil root;
;; returns the existing var untouched otherwise (matching the prior arm).
;; returns the existing var untouched otherwise.
;; time: evaluate expr, print the elapsed wall-clock, return the value.
;; current-time-ms is the host's monotonic clock.
(defmacro time [expr]
@ -164,14 +164,12 @@
(loop [~i 0]
(when (< ~i n#) ~@body (recur (inc ~i)))))))
;; A fresh jolt symbol inside a macro body: (gensym) here resolves to Janet's
;; builtin (a Janet symbol the destructurer rejects), so round-trip through str.
;; A fresh jolt symbol inside a macro body: a bare (gensym) returns a host symbol
;; the destructurer rejects, so round-trip through str.
(defn- fresh-sym [] (symbol (str (gensym))))
;; Lazy-safe: take only the head via first (Clojure uses (seq coll), but Jolt's
;; eager seq would realize an infinite coll like (repeat nil) and hang). Matches
;; the prior Janet behavior; the nil/false-head distinction waits on Phase 5
;; laziness.
;; eager seq would realize an infinite coll like (repeat nil) and hang).
(defmacro when-first [bindings & body]
(let [x (bindings 0) coll (bindings 1)]
`(when-let [~x (first ~coll)] ~@body)))
@ -289,7 +287,7 @@
;; a seq of field keywords; spliced into a vector LITERAL below ([~@…]) so
;; the analyzer sees a vector form, not a runtime pvec value.
field-kws (map (fn [f] (keyword (name f))) fields)
;; per-field TYPE HINT (jolt-3ko): ^Vec3 origin -> "Vec3" (a record type
;; per-field TYPE HINT: ^Vec3 origin -> "Vec3" (a record type
;; name), ^:num x -> "num", else nil. Lets the inference know a field's
;; exact type up front, so reading it back carries that type (not :any) —
;; the key to fast nested-record code. Spliced as a vector literal too.
@ -298,7 +296,7 @@
(and mt (:num mt)) "num"
:else nil)))
fields)
;; per-field MUTABILITY (jolt-c3q): ^:unsynchronized-mutable / ^:volatile-
;; per-field MUTABILITY: ^:unsynchronized-mutable / ^:volatile-
;; mutable marks a field set!-able. A type with any mutable field opts out
;; of the immutable shape-rec layout and uses the mutable table form, so
;; set! can mutate it (the ctor reads this vector). Spliced as a literal.
@ -309,7 +307,7 @@
fields)
;; mutable field symbols (^:unsynchronized-mutable / ^:volatile-mutable):
;; (set! field v) in a method body lowers to (set! (.-field inst) v), the
;; in-place field write the analyzer compiles to jolt-set-field! (jolt-c3q).
;; in-place field write the analyzer compiles to jolt-set-field!.
mutable-syms (map first (filter second (map vector fields field-muts)))
mutable? (fn [s] (boolean (some (fn [m] (= m s)) mutable-syms)))
rewrite-set (fn rw [inst form]
@ -359,7 +357,7 @@
{} sigs)]
`(do
(def ~pname (make-protocol ~(name pname) ~methods))
;; register method var-keys for devirtualization (jolt-41m); the inference
;; register method var-keys for devirtualization; the inference
;; reads this (via infer-unit!) to resolve a protocol call on a known record
(register-protocol-methods! ~(name pname) [~@(map (fn [s] (name (first s))) sigs)])
~@(map (fn [sig]
@ -431,8 +429,7 @@
`(do ~@(map (fn [g] `(extend-type ~(first g) ~psym ~@(rest g)))
(group-by-head type-impls))))
;; extend (the fn form) is not supported — stub to nil, as before.
;; extend is a real FUNCTION now — defined above extend-type.
;; extend is a real FUNCTION — defined above extend-type.
;; JVM proxies are unsupported.
(defmacro proxy [& args] nil)
;; definterface is JVM-only; bind the name to a marker and return the name (not a
@ -474,7 +471,7 @@
(let [argv (nth spec 1)
inst (first argv)
;; hint `this` with the record type so the inference
;; types it (jolt-3ko) and its field reads bare-index
;; types it and its field reads bare-index
;; instead of going through the runtime tag guard.
hinted (assoc argv 0 (vary-meta inst assoc :tag (name name-sym)))
binds (vec (mapcat (fn [f] [f `(get ~inst ~(keyword (name f)))]) fields))]
@ -492,8 +489,8 @@
;; lazy-seq / lazy-cat moved to the 00-syntax tier: the seq/coll tiers (10-seq,
;; 20-coll) use lazy-seq, and in compile mode a tier's forms are compiled as it
;; loads — so the macro must be registered BEFORE those tiers, else (lazy-seq …)
;; compiles as a call to the macro-as-function and leaks its expansion at runtime
;; (jolt-r81). They only need seed fns (make-lazy-seq/coll->cells/concat).
;; compiles as a call to the macro-as-function and leaks its expansion at runtime.
;; They only need seed fns (make-lazy-seq/coll->cells/concat).
;; memfn: a fn wrapping a method call, (memfn toUpperCase) => #(.toUpperCase %).
;; The method symbol is rewritten to jolt's .method call sugar; extra arg names

View file

@ -105,7 +105,7 @@
;; --- 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 — the §6.3 laziness counters depend on this.
;; minimally — the laziness counters depend on this.
;; (A take/nthrest form is correct but over-realizes.)
(defn partition-all
([n]
@ -139,7 +139,7 @@
(cons (take n s) (go (nthrest s step))))))]
(go coll))))
;; --- Phase 2 leaf batch 3 (jolt-ded): canonical lazy + transducer arities ----
;; --- canonical lazy + transducer arities -------------------------------------
(defn interpose
([sep]
@ -176,7 +176,7 @@
(when-let [s (seq coll)]
(cons (first s) (take-nth n (drop n s)))))))
;; --- pmap family (jolt-oeu): parallel map over real-thread futures ----------
;; --- pmap family: parallel map over real-thread futures ----------------------
;; Each element's work runs on its own OS thread with SNAPSHOT semantics
;; (futures marshal captured state — pure fns only, mutations don't propagate
;; back). All futures are spawned up front (doall), then derefed in order:

View file

@ -1,4 +1,4 @@
;; clojure.core — IO tier: the *in* reader family (jolt-0d9).
;; clojure.core — IO tier: the *in* reader family.
;;
;; *in* is a dynamic var holding a READER: a plain map whose two ops close
;; over their source — :read-line-fn (next line, newline
@ -135,7 +135,7 @@
(when line
(cons line (line-seq rdr)))))))
;; --- print-method (jolt-g1r) ------------------------------------------------
;; --- print-method ------------------------------------------------
;; Canonical dispatch (clojure/core.clj 3693): the :type metadata when it's a
;; keyword, else the value's type. On jolt, type is the keyword tag for
;; builtins and the deftype name SYMBOL for records — so a record method is

View file

@ -1,8 +1,8 @@
(ns jolt.analyzer
"Portable Clojure analyzer: reader form -> host-neutral IR (see jolt.ir).
Pure jolt-core depends only on the host contract (jolt.host) and IR
constructors (jolt.ir), never on Janet. The contract fns are referred unqualified
Depends only on the host contract (jolt.host) and IR
constructors (jolt.ir). The contract fns are referred unqualified
(host form predicates are `form-*` to avoid colliding with clojure.core), so the
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.
@ -50,7 +50,7 @@
(defn- add-locals [env names] (update env :locals #(reduce conj % names)))
(defn- with-recur [env name] (assoc env :recur name))
;; Type hints (jolt-94n). The reader keeps ^hint metadata on the binding symbol.
;; Type hints. The reader keeps ^hint metadata on the binding symbol.
;; Two hints resolve to the :struct fast path (a constant-keyword lookup skips
;; the :jolt/type guard and emits a bare get): ^:struct (a plain struct/record
;; map) and ^TypeName where TypeName is a defrecord/deftype (its instances are
@ -138,7 +138,7 @@
;; param an ordinary positional slot (holding the collected seq), so recur
;; is a self-call carrying the rest seq directly — Clojure semantics.
;; The recur target doubles as the COMPILED FN'S NAME, which is what a
;; janet stack trace shows — so carry the Clojure ns/fn-name (jolt-2o7.1):
;; host stack trace shows — so carry the Clojure ns/fn-name:
;; an error inside app.deep/level3 traces as _r$app.deep/level3--N
;; (report-error demangles the _r$/--N wrapper). gen-name's counter
;; keeps recur targets unique per compilation unit.
@ -215,7 +215,7 @@
;; the arity :rest key above). Assoc'ing them nil-when-absent would give the
;; node a nil-valued key, which makes it a phm in jolt's map representation
;; and forces the back end to densify it (norm-node) before reading :op — the
;; map-nil-representation trap Phase 2 cleaned up for def/fn/arity nodes. The
;; map-nil-representation trap, also avoided for def/fn/arity nodes. The
;; back end reads each key with a nil-safe (node :k) and gates on it, so an
;; absent key is indistinguishable from a present-nil one.
(let [n {:op :try :body (analyze-seq ctx @body env)}
@ -282,13 +282,13 @@
(let [nm (form-sym-name name-sym)
cur (compile-ns ctx)
;; (def name docstring value): docstring is form 2, value form 3.
;; Matches the interpreter; without this the docstring was taken
;; as the value and the real init dropped (jolt-6ym).
;; Matches the interpreter; otherwise the docstring is taken as
;; the value and the real init dropped.
has-doc (and (> (count items) 3) (string? (nth items 2)))
val-form (nth items (if has-doc 3 2))
base0 (or (form-sym-meta name-sym) {})
;; resolve a ^Type hint to its canonical class name at def
;; time (jolt-a1ir), as the JVM compiler does: ^String ->
;; time, as the JVM compiler does: ^String ->
;; java.lang.String. A record/unknown hint is left untouched.
tag (get base0 :tag)
tag-name (cond (form-sym? tag) (form-sym-name tag)
@ -365,7 +365,7 @@
:else (uncompilable "set! of an unsupported target")))
(uncompilable (str "special form " op))))
;; Host interop method call (jolt-0kf5). `(.method target arg*)` — a head that
;; Host interop method call. `(.method target arg*)` — a head that
;; starts with "." but not ".-" (field access stays punted). Analyzes to a
;; :host-call node; the Chez back end lowers it to a jolt-host-call dispatch.
(defn- method-head? [nm]
@ -391,11 +391,11 @@
;; `(Class. args*)` and `(new Class args*)` -> a :host-new node carrying the class
;; token and the analyzed args. The Chez back end lowers it to a runtime
;; constructor dispatch (jolt-avt6).
;; constructor dispatch.
(defn- analyze-ctor [ctx class args env]
(host-new class (mapv #(analyze ctx % env) args)))
;; jolt.ffi/__cfn (jolt-ffi): the low-level foreign-function form a jolt library
;; jolt.ffi/__cfn: the low-level foreign-function form a jolt library
;; uses (via the jolt.ffi/foreign-fn macro) to bind native code. Shape:
;; (jolt.ffi/__cfn "c_symbol" [:argtype ...] :rettype) ; non-blocking
;; (jolt.ffi/__cfn "c_symbol" [:argtype ...] :rettype :blocking) ; may block
@ -417,8 +417,7 @@
;; 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 Chez back end dispatches it through record-method-dispatch
;; (jolt-kuic).
;; it as a field). The Chez back end dispatches it through record-method-dispatch.
(defn- analyze-dot [ctx items env]
(when (< (count items) 3)
(throw (str "Malformed (. target member ...) form")))
@ -457,15 +456,15 @@
(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 Chez back end
;; lowers it to a runtime static dispatch (jolt-avt6).
;; lowers it to a runtime static dispatch.
(host-static ns nm)))
:else (let [r (resolve-global ctx form)]
(case (:kind r)
:var (var-ref (:ns r) (:name r))
:host (host-ref (:name r))
;; :unresolved — previously emitted a var-ref that auto-interned
;; an UNBOUND var, so a typo'd symbol died later as 'Cannot call
;; nil as a function' with no hint which symbol (jolt-2o7.3).
;; :unresolved — emitting a var-ref here would auto-intern an
;; UNBOUND var, so a typo'd symbol would die later as 'Cannot call
;; nil as a function' with no hint which symbol.
;; Punt to the interpreter: its resolver raises Clojure's
;; 'Unable to resolve symbol' when the form actually runs (at
;; eval for top-level forms, at call for fn bodies). A punt
@ -524,7 +523,7 @@
(and hname (not shadowed) (form-special? hname))
(uncompilable (str "special form " hname))
:else
;; stamp the list form's source offset onto the :invoke (jolt-fqy)
;; stamp the list form's source offset onto the :invoke
;; so the success checker can report file:line:col. nil when the
;; reader did not record it (synthetic/macro-built forms).
(let [n (invoke (analyze ctx head env)

View file

@ -1,25 +1,12 @@
(ns jolt.backend-scheme
"Portable Clojure IR -> Chez Scheme emitter (Chez Phase 3, jolt-cf1q.4).
"Lowers the host-neutral IR (jolt.ir) to Chez Scheme source text.
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 the bootstrap spine.
Output is a STRING of Scheme source; `host/compile` on Chez is `(eval (read
...))`. 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.
INCREMENT 2 (jolt-7jvp): collection literals (vector/map/set, emit-ordered) +
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 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+)."
The analyzer produces IR; this emitter turns each IR op into a string of Scheme
source, which the host compiles with (eval (read ...)). It depends only on
clojure.core and clojure.string, so once cross-compiled it runs on Chez and can
emit its own code the bootstrap spine. Quoted forms are walked through the
portable jolt.host form-* contract, the same seam the analyzer uses, so the
emitter never touches a concrete host representation directly."
(:require [clojure.string :as str]
[jolt.host :refer [form-sym? form-sym-name form-sym-ns form-sym-meta
form-list? form-vec? form-map? form-set? form-char?
@ -83,13 +70,13 @@
(def ^:private supported-host-methods #{"isDirectory" "listFiles"})
;; Native-op Scheme procedures that return a genuine Scheme boolean (#t/#f), so an
;; :if test built from them needs no jolt-truthy? wrapper (jolt-nkcb).
;; :if test built from them needs no jolt-truthy? wrapper.
(def ^:private bool-returning-ops
#{"<" "<=" ">" ">=" "jolt=" "jolt-not"
"jolt-even?" "jolt-odd?" "jolt-pos?" "jolt-neg?"
"jolt-zero?" "jolt-empty?" "jolt-contains?"})
;; PRELUDE MODE (inc 3d). The default (subset) mode rejects any clojure.core ref
;; PRELUDE MODE. The default (subset) mode rejects any clojure.core ref
;; that isn't a native-op — a clean "out of subset" signal for user-facing `-e`.
;; When emitting clojure.core ITSELF as a prelude, core fns reference each other
;; constantly; those lower to var-deref (resolved at runtime).
@ -131,7 +118,7 @@
(declare emit)
;; A Chez string literal (jolt-x0os). Every char outside printable ASCII becomes a
;; A Chez string literal. Every char outside printable ASCII becomes a
;; codepoint hex escape \x<cp>; ; the named escapes (\n \t \r \" \\) match what
;; Chez's reader accepts. For pure printable ASCII this is byte-identical to %j.
(defn- char-escape [cp]
@ -151,7 +138,7 @@
(cond
(nil? v) "jolt-nil"
(boolean? v) (if v "#t" "#f")
;; Numeric tower (jolt-n6al): emit a literal Chez re-reads as the SAME number.
;; Numeric tower: emit a literal Chez re-reads as the SAME number.
;; Exact integers -> "42", exact ratios -> "1/2" (str renders both faithfully);
;; a flonum must carry a decimal point/exponent or Chez reads it back as exact,
;; so a whole flonum (str drops its .0) gets ".0" appended. ##Inf/##-Inf/##NaN
@ -169,9 +156,9 @@
(str "(keyword " (chez-str-lit kns) " " (chez-str-lit (name v)) ")")
(str "(keyword #f " (chez-str-lit (name v)) ")"))
;; char literal -> (integer->char <codepoint>). Get the codepoint via the host
;; contract (form-char-code), NOT (get v :ch): on Janet a char is a struct with
;; a :ch field, but on Chez (the self-hosted spine) it's a native char, so the
;; struct-field read returns nil and emits (integer->char) with no arg.
;; contract (form-char-code), NOT (get v :ch): on Chez (the self-hosted spine)
;; a char is a native char, so a struct-field read returns nil and would emit
;; (integer->char) with no arg.
(form-char? v) (str "(integer->char " (form-char-code v) ")")
:else (throw (ex-info (str "emit-const: unsupported literal " (pr-str v)) {}))))
@ -211,7 +198,7 @@
(str "(let* (" binds ") " (build tmps) ")"))
(build strs)))
;; Quoted literals (jolt-u8j7). A :quote node's :form is the RAW reader form;
;; Quoted literals. A :quote node's :form is the RAW reader form;
;; 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
@ -417,7 +404,7 @@
:else
(invoke))))
;; try/catch/finally (jolt-vcsl). throw raises the jolt value RAW (jolt-throw =
;; try/catch/finally. throw raises the jolt value RAW (jolt-throw =
;; Scheme `raise`); catch lowers to `guard` with an `else` clause (the IR drops
;; the class), finally to `dynamic-wind`'s after-thunk (runs on success, catch and
;; escape — Clojure finally semantics). Both keys optional on the node.
@ -496,7 +483,7 @@
;; a namespace value spliced into a form (~*ns*) -> reconstruct by name.
:the-ns (str "(intern-ns! " (chez-str-lit (:name node)) ")")
;; (.method target arg*) -> jolt-host-call for an rt-shimmed method, else
;; record-method-dispatch (a reify/record protocol method, jolt-jgoc).
;; record-method-dispatch (a reify/record protocol method).
:host-call (let [m (:method node)
target (emit (:target node))
args (map emit (:args node))]

View file

@ -23,8 +23,8 @@
;; A runtime primitive (cons, +, get, apply, …) the back end maps to the host RT.
(defn rt [name] {:op :rt :name name})
;; A name that resolves only via the host's own environment (e.g. + or int? on
;; Janet) — the back end emits a host-appropriate reference.
;; A name that resolves only via the host's own environment (e.g. + or int?) —
;; the back end emits a host-appropriate reference.
(defn host-ref [name] {:op :host :name name})
;; A qualified static reference to a host class member, `Class/member` (e.g.
@ -72,7 +72,7 @@
(defn op [node] (:op node))
;; ---------------------------------------------------------------------------
;; Structural recursion over IR child nodes (jolt-26dm / phase 3a).
;; Structural recursion over IR child nodes.
;;
;; A tree-rewriting pass recurses into each op's child NODE positions and
;; rebuilds the node; this combinator does that one place, so the per-op child

View file

@ -1,11 +1,11 @@
(ns jolt.passes
"IR optimization passes (nanopass-lite, jolt-2om) + the inference/checking
"IR optimization passes (nanopass-lite) + the inference/checking
driver. Façade over three weakly-coupled namespaces, loaded with the compiler:
jolt.passes.fold const-fold (always-on) + the shared const-shape predicate.
jolt.passes.inline inline + flatten-lets + scalar-replace (direct-link only).
jolt.passes.types collection-type inference + success-type checking
(RFC 0006) + the inter-procedural driver API (jolt-767).
(RFC 0006) + the inter-procedural driver API.
run-passes (below) is the single entry the back end applies to every analyzed
form. The driver/checker fns the back end looks up by name (check-form,
@ -25,18 +25,18 @@
(defn run-passes
"All passes, in order. The back end applies this to every analyzed form. When
inlining is enabled for the unit (user code under direct-linking, jolt-87f),
inlining is enabled for the unit (user code under direct-linking),
run inline + flatten + scalar-replace + const-fold to a capped fixpoint
inlining exposes map literals to lookups, scalar-replace collapses them, which
may expose more then a collection-type inference pass (jolt-99x, optionally
may expose more then a collection-type inference pass (optionally
also emitting success diagnostics) that auto-drops the lookup guard where the
type is proven. Otherwise (core + bootstrap) just const-fold, as before."
[node ctx]
(if (inline-enabled? ctx)
(let [_ (set-rec-shapes! (record-shapes ctx)) ;; record ctor fold (jolt-15jq)
(let [_ (set-rec-shapes! (record-shapes ctx)) ;; record ctor fold
;; resolve ^Record param hints (incl. defrecord/extend-type method
;; `this`) to bare field reads per-form, not only under whole-program
;; (jolt-3ko). Same shapes the inline pass uses.
;; `this`) to bare field reads per-form, not only under whole-program.
;; Same shapes the inline pass uses.
_ (set-record-shapes! (record-shapes ctx))
opt (loop [i 0 n (const-fold node)]
(reset! dirty false)
@ -45,6 +45,6 @@
(recur (inc i) n2)
n2)))]
;; a final const-fold after inference propagates any predicate folded to a
;; constant (jolt-wcw), collapsing the `if` it gates to the taken branch.
;; constant, collapsing the `if` it gates to the taken branch.
(const-fold (run-inference opt)))
(const-fold node)))

View file

@ -1,6 +1,6 @@
(ns jolt.passes.inline
"Inlining + flatten-lets + scalar-replace (AOT escape analysis). These run only
when host/inline-enabled? (user code opted into direct-linking, jolt-87f); they
when host/inline-enabled? (user code opted into direct-linking); they
share the alpha-rename invariant (every spliced binder is made globally fresh)
and the `dirty` fixpoint flag. Portable Clojure (compiler-tier)."
(:require [jolt.host :refer [inline-ir]]
@ -18,10 +18,10 @@
;; per unit by run-passes (set-rec-shapes!) before the fixpoint so scalar-replace
;; can recognize a (->Rec ..) call and map its positional args to declared fields
;; — the record analogue of the inline keys a map literal already carries in the
;; IR (jolt-15jq).
;; IR.
(def ^:private rec-shapes (atom {}))
(defn set-rec-shapes!
"Install the record-ctor shape registry the record fold consults (jolt-15jq)."
"Install the record-ctor shape registry the record fold consults."
[m] (reset! rec-shapes (or m {})))
(def ^:private fresh-counter (atom 0))
@ -31,7 +31,7 @@
(str base "__il" n)))
;; ---------------------------------------------------------------------------
;; Inlining (jolt-87f). The back end stashes {:params [..] :body ir} on the var
;; Inlining. The back end stashes {:params [..] :body ir} on the var
;; cell of each single-fixed-arity defn compiled under :inline?; here we splice
;; that body at a call site. To stay capture-safe we ALPHA-RENAME the body —
;; every param and every inner let-bound name becomes a globally fresh name —
@ -89,7 +89,7 @@
(= op :local) (let [r (get env (get node :name))]
;; carry the param's ^:struct hint onto a let-bound fresh
;; local, so lookups inside the inlined body keep the bare
;; (no-guard) path (jolt-dad). The param hint asserts the
;; (no-guard) path. The param hint asserts the
;; arg is a struct; inlining doesn't change that contract.
(if r
(if (and (= :local (get r :op)) (get node :hint) (not (get r :hint)))
@ -256,7 +256,7 @@
;; forward ref: a record ctor (allocating an immutable struct from its args) is
;; side-effect-free, so pure? treats (->Rec pure-args..) as pure — which lets a
;; nested record (a Ray holding a Vec3) fold bottom-up (jolt-15jq).
;; nested record (a Ray holding a Vec3) fold bottom-up.
(declare ctor-shape)
(defn- pure?
@ -427,7 +427,7 @@
(= op :def) (local-escapes? (get node :init) nm)
:else true)))
;; --- record constructors as foldable struct sources (jolt-15jq) -------------
;; --- record constructors as foldable struct sources -------------------------
;; A record ctor (->Rec a b ..) is a positional struct: the registry maps its
;; ctor key ("ns/->Name", exactly how the IR names the call head) to the DECLARED
;; field order. A field read on a non-escaping ctor folds to the matching arg,

View file

@ -1,14 +1,14 @@
(ns jolt.passes.types
"Collection-type inference (jolt-99x) and success-type checking (RFC 0006).
"Collection-type inference and success-type checking (RFC 0006).
A forward, soft-typing pass (simplified HM: monovariant, never-fails, lattice
top = :any) that types expressions and reuses the SAME walk as a loose success
checker. Also the inter-procedural driver API (jolt-767) the back end calls to
top = :any) that types expressions and reuses the same walk as a loose success
checker. Also the inter-procedural driver API the back end calls to
propagate param types across a unit / the whole program. Weakly coupled to the
IR-rewriting passes shares only the const-shape predicate (jolt.passes.fold)."
(:require [jolt.passes.fold :refer [scalar-const?]]))
;; ---------------------------------------------------------------------------
;; Collection-type inference (jolt-99x), Phase 0: intra-procedural. A forward,
;; Collection-type inference, intra-procedural. A forward,
;; soft-typing-style pass (simplified HM: monovariant, never-fails, lattice top
;; = :any) that types expressions from literals/arithmetic and flows the type
;; through let bindings and if-joins. Where a keyword-lookup subject is PROVEN a
@ -39,7 +39,7 @@
(defn- mk-set [t] {:set (if t t :any)})
(defn- mk-struct [fs] {:struct fs})
;; Bounded union types (RFC 0006 / jolt-pz5). A union {:union #{T...}} records
;; Bounded union types (RFC 0006). A union {:union #{T...}} records
;; that a value is provably one of a small, fixed set of SCALAR types — what
;; differing if-branches used to collapse to :any. It exists so the success
;; checker can reject a use where EVERY member is in the op's error domain
@ -86,7 +86,7 @@
(and (struct-type? a) (struct-type? b))
(let [merged (mk-struct (merge-fields (sfields a) (sfields b)))]
;; joining two values of the SAME complete shape preserves it — the
;; merged struct has the same key set (jolt-t34 R2). Different shapes
;; merged struct has the same key set. Different shapes
;; (or an incomplete side) drop it, as the layout is no longer proven.
(if (and (get a :shape) (= (get a :shape) (get b :shape)))
(assoc merged :shape (get a :shape))
@ -94,7 +94,7 @@
(and (vec-type? a) (vec-type? b)) (mk-vec (join-t (velem a) (velem b)))
(and (set-type? a) (set-type? b)) (mk-set (join-t (selem a) (selem b)))
;; differing kinds: form a scalar union when both sides reduce to scalars
;; (or scalar unions); anything compound on either side stays :any (jolt-pz5)
;; (or scalar unions); anything compound on either side stays :any
:else (let [ma (cond (union-type? a) (umembers a) (scalar-t? a) #{a} :else nil)
mb (cond (union-type? b) (umembers b) (scalar-t? b) #{b} :else nil)]
(if (and ma mb) (union-of (reduce conj ma mb)) :any))))
@ -108,25 +108,25 @@
(struct-type? t)
;; capping truncates VALUES below depth d, but the KEY SET is unchanged, so
;; a complete :shape survives — keep it so nested/container field reads can
;; still bare-index (jolt-t34 R2). cap recurses into fields, so a nested
;; still bare-index. cap recurses into fields, so a nested
;; shaped value (a vec3 inside a hit-info) keeps its own :shape too.
(let [capped (mk-struct (reduce (fn [m k] (assoc m k (cap (get (sfields t) k) (dec d))))
{} (keys (sfields t))))
;; the record :type tag (and :shape) are independent of field-value
;; depth, so they survive truncation — a record read from a deep
;; container keeps its identity, so devirtualization (jolt-41m),
;; record? folding, and the record fast path still fire on it.
;; container keeps its identity, so devirtualization, record? folding,
;; and the record fast path still fire on it.
capped (if (get t :shape) (assoc capped :shape (get t :shape)) capped)
capped (if (get t :type) (assoc capped :type (get t :type)) capped)]
capped)
(vec-type? t) (mk-vec (cap (velem t) (dec d)))
(set-type? t) (mk-set (cap (selem t) (dec d)))
:else t))
;; raw-get-safe (a Janet struct / record): a struct type. The field type of key
;; raw-get-safe (a struct / record): a struct type. The field type of key
;; k, if known, else :any.
(defn- struct-safe? [t] (struct-type? t))
(defn- field-type [t k] (if (struct-type? t) (get (sfields t) k :any) :any))
;; Shape (hidden class, jolt-t34). A struct type built from a map LITERAL carries
;; Shape (hidden class). A struct type built from a map LITERAL carries
;; its complete layout — :shape, the canonical (str-sorted) key vector. The back
;; end represents such a map as a shape tuple and reads a field by bare index.
;; A struct type from a JOIN or from field-access inference has no :shape
@ -141,7 +141,7 @@
;; a lookup whose SUBJECT is that node — this is what makes nested access work:
;; (:direction ray) is tagged struct, so (:r (:direction ray)) drops its guard.
;; tag a lookup subject as a struct, carrying the complete shape when known
;; (so the back end bare-indexes) — jolt-t34
;; (so the back end bare-indexes).
(defn- mark-struct [node t]
(let [n (assoc node :hint :struct)]
(if (get t :shape) (assoc n :shape (get t :shape)) n)))
@ -159,7 +159,7 @@
"bit-and" "bit-or" "bit-xor" "count"})
(def ^:private vector-ret-fns #{"vec" "vector" "mapv" "filterv" "subvec"})
;; Inter-procedural state (jolt-767, Phase 1). The Janet orchestrator (backend
;; Inter-procedural state. The orchestrator (backend
;; infer-unit!) drives a whole-unit fixpoint: before typing a fn body it installs
;; the current return-type estimates of all unit fns here, and after typing it
;; reads back the call sites this body made (callee + inferred arg types) to
@ -168,12 +168,12 @@
(def ^:private calls-box (atom [])) ;; collected [ "ns/name" [arg-types...] ]
(def ^:private escapes-box (atom #{})) ;; var-keys used as a VALUE (not a call head)
(def ^:private diag-box (atom [])) ;; success-type-check diagnostics (RFC 0006)
;; jolt-d6u: a var reference's VALUE type — a fn var is :truthy (non-nil), a def
;; a var reference's VALUE type — a fn var is :truthy (non-nil), a def
;; var carries its inferred init type (e.g. a color table -> {:vec :struct-map}).
;; The orchestrator populates this from sealed (opt-mode) cell roots + def inits.
(def ^:private vtype-box (atom {})) ;; "ns/name" -> value type
;; User-function error domains (jolt-zo1), opt-in. As the checker walks defs it
;; User-function error domains, opt-in. As the checker walks defs it
;; registers each non-redefinable single-fixed-arity user fn's {:params :body}
;; here, keyed "ns/name". At a later call site (strict mode only) the body is
;; re-checked with ONE parameter bound to its concrete argument type — if that
@ -181,17 +181,17 @@
;; provably wrong and the CALL is reported. Module state, like rtenv-box: a def
;; must precede its call (the same closed-world ordering RFC 0005 assumes).
(def ^:private user-sig-box (atom {})) ;; "ns/name" -> {:params [..] :body ir}
;; jolt-t34: a record constructor's return shape. "ns/->Name" -> [field-kw ...]
;; a record constructor's return shape. "ns/->Name" -> [field-kw ...]
;; in DECLARED order (the runtime lays records out in declared field order, so
;; the back end bare-indexes by that order). A call (->Point a b) types as a
;; struct of this shape, so field reads on the result bare-index — declared
;; shapes are clean fuel: a lookup, not fragile inference.
(def ^:private record-shapes-box (atom {}))
;; jolt-41m: protocol-method registry "ns/method" -> [proto method], for
;; protocol-method registry "ns/method" -> [proto method], for
;; devirtualizing a protocol call whose receiver is a known record type.
(def ^:private protocol-methods-box (atom {}))
;; jolt-3ko: build a record's struct TYPE from its registry entry, resolving each
;; build a record's struct TYPE from its registry entry, resolving each
;; field's declared type hint. A field tagged with a record type (its ctor-key)
;; recurses, so a Vec3 stored in a Ray field reads back as Vec3 — not :any —
;; which is what lets nested-record code prove its reads. Depth-bounded so a
@ -211,7 +211,7 @@
(field-type-from-tag (when tags (nth tags i)) (dec depth))))
{} (range (count fields)))]
(assoc (mk-struct fmap) :shape (vec fields) :type (get rs :type))))
;; jolt-t34: whether to shape generic const-key MAP literals (opt-in, JOLT_SHAPE).
;; whether to shape generic const-key MAP literals (opt-in, JOLT_SHAPE).
;; Records are shaped regardless; maps only when this is on.
(def ^:private map-shapes-box (atom false))
(def ^:private checking-box (atom #{})) ;; keys mid-recheck — cycle guard
@ -238,10 +238,10 @@
;; a user fn whose return type the fixpoint has estimated
(= op :var) (let [rs (get @record-shapes-box (var-key fnode))]
(if rs
;; record ctor -> struct of declared shape (jolt-t34); :shape
;; record ctor -> struct of declared shape; :shape
;; is the DECLARED field order the back end indexes by, :type
;; the record tag (devirt), and field types come from the
;; declared hints so nested records stay typed (jolt-3ko)
;; declared hints so nested records stay typed
(record-type-from-entry rs type-depth)
(let [r (get @rtenv-box (var-key fnode))]
(if r r (let [nm (and (= "clojure.core" (get fnode :ns)) (get fnode :name))]
@ -255,7 +255,7 @@
:else :any))
:else :any)))
;; Predicate folding (jolt-wcw): a type predicate whose argument's type is
;; Predicate folding: a type predicate whose argument's type is
;; PROVEN folds to a compile-time boolean. Only the precise tags are folded —
;; :num/:str/:kw mean exactly that scalar, and a record carries its defrecord
;; :type tag. NOT folded: vector?/set?/map?, because the :vec tag conflates a
@ -289,8 +289,8 @@
(declare infer)
;; HOFs that apply their fn arg to the ELEMENTS of a collection (jolt-d6u,
;; Phase 3). :epos is which param of the fn receives an element. reduce is
;; HOFs that apply their fn arg to the ELEMENTS of a collection. :epos is which
;; param of the fn receives an element. reduce is
;; handled separately (its arity changes the coll position, and its closure
;; also takes an accumulator).
(def ^:private hof-table
@ -353,7 +353,7 @@
base (when struct?
(cap (mk-struct (reduce (fn [m r] (assoc m (nth r 3) (nth r 2))) {} res)) type-depth))
;; a literal is a COMPLETE shape: carry its sorted key vector so the
;; back end can lay it out and bare-index lookups (jolt-t34)
;; back end can lay it out and bare-index lookups
shp (when (and @map-shapes-box base (struct-type? base)) (shape-order (keys (sfields base))))
t (if base (if shp (assoc base :shape shp) base) :any)
node' (assoc node :pairs (mapv (fn [r] [(nth r 0) (nth r 1)]) res))]
@ -393,7 +393,7 @@
args (get node :args)
n (count args)]
(cond
;; predicate folding (jolt-wcw): a type predicate over a single,
;; predicate folding: a type predicate over a single,
;; side-effect-free argument whose type PROVES the answer becomes a
;; boolean constant — eliminating the call, and (once const-fold runs
;; after inference) collapsing any `if` it gates. Falls through to the
@ -427,7 +427,7 @@
dr (when (= n 3) (infer (nth args 2) tenv))]
[(if dr (join ft (nth dr 0)) ft)
(assoc node :args (if dr [msub (nth kr 1) (nth dr 1)] [msub (nth kr 1)]))])
;; reduce over a typed vector with a fn-literal (jolt-d6u): seed the
;; reduce over a typed vector with a fn-literal: seed the
;; closure's accumulator (param 0) to the init type and its element
;; (param 1) to the vector's element type, so its body — and any calls
;; it makes — see those types.
@ -471,7 +471,7 @@
fnode' (if iscall-var fnode (nth fr 1))
;; the callee's value type: a var's from vtype-box (a fn is
;; :truthy, a def carries its inferred type), else the inferred
;; type of the callee expression (jolt-wwy)
;; type of the callee expression
callee-t (if iscall-var (get @vtype-box (var-key fnode)) (nth fr 0))
ares (mapv (fn [a] (infer a tenv)) args)]
(when iscall-var
@ -482,7 +482,7 @@
(when @checking?
(let [ats (mapv (fn [r] (nth r 0)) ares) pos (get node :pos)]
(when cn (check-invoke cn args ats pos))
;; calling a provably non-function (jolt-wwy)
;; calling a provably non-function
(when (not-callable? callee-t)
(swap! diag-box conj
{:op :call :type (type-name callee-t) :pos pos
@ -490,7 +490,7 @@
(when (and @strict-box iscall-var)
(let [k (var-key fnode) usig (get @user-sig-box k)]
(when usig (check-user-call k usig ats pos))))))
;; devirtualization (jolt-41m): a protocol-method call whose receiver
;; devirtualization: a protocol-method call whose receiver
;; (arg 0) is a known record type resolves to a direct method call.
;; Annotate the node with [type-tag proto method]; the back end looks
;; up the impl at emit time and calls it directly, skipping the
@ -517,7 +517,7 @@
[(nth br 0) (assoc node :bindings (nth res 1) :body (nth br 1))])
(= op :loop)
;; conservative + sound: loop bindings join across recur, which we don't
;; track in Phase 0, so they stay :any. Still descend to annotate any
;; track here, so they stay :any. Still descend to annotate any
;; known-type lookups inside the body.
[:any (assoc node
:bindings (mapv (fn [b] [(nth b 0) (nth (infer (nth b 1) tenv) 1)]) (get node :bindings))
@ -531,7 +531,7 @@
;; (:phints, name -> ctor-key) — then seed it to that record type so field
;; reads off it bare-index per-form, not only under whole-program. This is
;; what makes a protocol method's `this` (hinted by defrecord/extend-type)
;; read its fields without the runtime tag guard (jolt-3ko).
;; read its fields without the runtime tag guard.
[:any (assoc node :arities
(mapv (fn [a]
(let [phm (reduce (fn [m pr] (assoc m (nth pr 0) (nth pr 1)))
@ -565,7 +565,7 @@
;; cases; lenient ops ((get 5 :k) -> nil, (:k 5) -> nil) are NOT listed.
;; concrete non-numbers: arithmetic provably throws on these. A union is in the
;; error domain only when EVERY member is (jolt-pz5) — if any member is an
;; error domain only when EVERY member is — if any member is an
;; accepted type the call is accepted (no false positive).
(defn- not-number? [t]
(if (union-type? t)
@ -581,7 +581,7 @@
(every? not-seqable? (umembers t))
(or (= t :num) (= t :kw))))
;; concrete non-callable values (jolt-wwy): calling them throws "Cannot call X
;; concrete non-callable values: calling them throws "Cannot call X
;; as a function". Only :num and :str — keywords/maps/vectors/sets are IFn,
;; :truthy/:any/:nil are ambiguous (accepted). A union is non-callable only when
;; every member is.
@ -615,7 +615,7 @@
(defn- check-invoke
"If node is a core-op call whose argument type is provably in the error domain,
conj a diagnostic. arg-types is the vector of inferred argument types; pos is
the call form's source offset (jolt-fqy), carried into each diagnostic."
the call form's source offset, carried into each diagnostic."
[cn args arg-types pos]
(cond
(contains? num-ops cn)
@ -638,7 +638,7 @@
", but argument 1 is " (type-name t))})))
:else nil))
;; --- user-function error domains (jolt-zo1), opt-in --------------------------
;; --- user-function error domains, opt-in -------------------------------------
(defn- all-any-env
"tenv binding every param name to :any (the all-ambiguous baseline)."
[params]
@ -677,7 +677,7 @@
(defn- check-user-call
"Strict mode: report a call to a registered user fn that provably throws
either a WRONG ARITY (the registered fn has one fixed arity, so a different
arg count always throws, jolt-wwy) or an argument whose concrete type the body
arg count always throws) or an argument whose concrete type the body
rejects. For the latter, re-check the body with ONLY that parameter bound to
its arg type (others :any); a diagnostic the all-:any body did not already
have means the argument alone is provably wrong. Monotonic binding a
@ -715,13 +715,13 @@
nil (range npar)))))
(reset! checking-box prev))))
;; --- Inter-procedural driver API (jolt-767) consumed by the back end --------
;; --- Inter-procedural driver API consumed by the back end -------------------
(defn set-rtenv!
"Install the current return-type estimates (a map \"ns/name\" -> type) used to
type call results during the fixpoint."
[m] (reset! rtenv-box m))
;; jolt-t34: install record-ctor shapes ("ns/->Name" -> [field-kw ...]) and the
;; install record-ctor shapes ("ns/->Name" -> [field-kw ...]) and the
;; map-shaping flag (opt-in JOLT_SHAPE), both read by infer.
(defn set-record-shapes! [m] (reset! record-shapes-box (or m {})))
(defn set-protocol-methods! [m] (reset! protocol-methods-box (or m {})))
@ -729,7 +729,7 @@
(defn set-vtypes!
"Install var VALUE types (a map \"ns/name\" -> type): fn vars are :truthy
(non-nil), def vars carry their inferred init type (jolt-d6u)."
(non-nil), def vars carry their inferred init type."
[m] (reset! vtype-box m))
(defn join-types
@ -747,7 +747,7 @@
usable in normal builds (the decoupled checking path).
With strict? true, also reports calls to registered user functions whose
concrete argument types provably make the body throw (jolt-zo1, opt-in,
concrete argument types provably make the body throw (opt-in,
closed-world). user-sig-box accumulates registered defs across forms, so a
def must precede its call the same ordering RFC 0005 already assumes."
([node] (check-form node false))
@ -810,7 +810,7 @@
propagates to a fn's callees DURING inference not only at the final re-emit
(reinfer-def). Without it a hinted param with no callers stays :any through the
fixpoint, so a field read off it (e.g. (:origin ^Ray r)) never tells a shared
callee its arg is a Vec3 (jolt-3ko)."
callee its arg is a Vec3."
[params phints]
(let [m (reduce (fn [acc pr] (assoc acc (nth pr 0) (nth pr 1))) {} phints)]
(mapv (fn [nm]

View file

@ -1,22 +1,16 @@
(ns jolt.reader
"Portable Clojure reader: source text -> reader forms (Chez Phase 3, jolt-cf1q.4).
"Reads Clojure source text into reader forms.
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 to drive compile-from-source.
The lexing and parsing 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 can't write
a {:jolt/type :symbol} literal it would parse as a tagged reader form and
the concrete form representation belongs to the host. The analyzer uses the same
split. Once cross-compiled this runs on Chez to drive compile-from-source.
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/
true/false), keywords, strings, numbers (sign/hex/radix/ratio/fractional/
exponent, trailing N/M), characters. Collections, quote/deref/meta, and dispatch
(#) land in 5b/5c (they throw not-yet-ported so a hit is loud)."
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."
(:require [clojure.string :as str]
[jolt.host :refer [form-make-symbol form-make-char form-char-from-name
form-scan-number form-make-list form-make-vector
@ -213,8 +207,8 @@
[:form (form-make-vector items) end]))
;; Map: pair up keys and values, skipping comments/#_ in either slot while keeping
;; the pending key (dropping both desyncs the pairing). Splice in a map slot lands
;; in inc 5c; here a key/value is always a single :form (or :skip).
;; the pending key (dropping both desyncs the pairing). A key/value is always a
;; single :form (or :skip) — splice in a map slot is not supported.
(defn- read-map* [s pos]
(loop [pos (inc pos) kvs []]
(let [pos (skip-whitespace s pos)]