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:
parent
c18f8087f0
commit
33eff7c7d8
112 changed files with 970 additions and 1621 deletions
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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)))
|
||||
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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))]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)))
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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)]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue