self-host: gate the analyzer build on the kernel tier; move case/cond-> to the overlay
The real bug behind the case/cond-> fixpoint regression wasn't gensym — it was build ordering. A top-level defn in a pre-kernel tier (the fresh-sym helper these macros need) gets compiled in compile mode, and that lazily builds the self-hosted analyzer via ensure-analyzer. 00-syntax loads before 00-kernel, so the analyzer was built against a core missing mapv/second/peek/...: its references to them were interned as forward-ref nil cells in jolt.analyzer. Those nil cells then shadowed the real clojure.core defs when the analyzer rebuilt itself (stage2), so the analyzer's own mapv calls went to nil — 'Cannot call nil as a function'. Only variadic-heavy paths (juxt+mapv) surfaced it. build-compiler! already documented that the kernel must be loaded first; nothing enforced it. Gate ensure-analyzer on a :kernel-ready? flag set after the kernel tier loads. A pre-kernel compile now falls back to the interpreter (compile-and-eval already handles that) instead of building the analyzer too early. fresh-sym ends up interpreted during 00-syntax load, which is fine. With ordering fixed, case and cond-> move to 00-syntax (they need a real gensym, so syntax-quote auto-gensym alone won't do). conformance 228x3, fixpoint stage1==2==3, clojure-test-suite 3930, full suite green, bench flat.
This commit is contained in:
parent
d0d48f0ebd
commit
646744ca1d
4 changed files with 58 additions and 58 deletions
|
|
@ -59,3 +59,39 @@
|
|||
;; Forward declaration is a no-op on Jolt — the compiler resolves forward refs via
|
||||
;; pending cells (matching the prior Janet macro).
|
||||
(defmacro declare [& syms] `(do))
|
||||
|
||||
;; A fresh jolt symbol inside a macro body (a bare (gensym) returns a Janet 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))))
|
||||
|
||||
;; cond->: thread expr through each (test form) pair, only when the test is truthy.
|
||||
;; Linear nested let*, a distinct fresh symbol per step.
|
||||
(defmacro cond-> [expr & clauses]
|
||||
(let [step (fn step [prev cls]
|
||||
(if (empty? cls)
|
||||
prev
|
||||
(let [t (first cls)
|
||||
f (nth cls 1)
|
||||
gn (fresh-sym)
|
||||
call (if (seq? f) `(~(first f) ~prev ~@(rest f)) `(~f ~prev))]
|
||||
`(let* [~gn (if ~t ~call ~prev)] ~(step gn (drop 2 cls))))))
|
||||
g0 (fresh-sym)]
|
||||
`(let* [~g0 ~expr] ~(step g0 clauses))))
|
||||
|
||||
;; case: nested =/or tests (no jump table). Test constants are NOT evaluated —
|
||||
;; symbols and list constants are quoted; a list in test position is a set (or).
|
||||
(defmacro case [expr & clauses]
|
||||
(let [g (fresh-sym)
|
||||
mk-const (fn [c] (if (or (symbol? c) (seq? c)) `(quote ~c) c))
|
||||
mk-test (fn [c]
|
||||
(if (seq? c)
|
||||
`(or ~@(map (fn [v] `(= ~g ~(mk-const v))) c))
|
||||
`(= ~g ~(mk-const c))))
|
||||
build (fn build [cls]
|
||||
(if (empty? cls)
|
||||
nil
|
||||
(if (empty? (rest cls))
|
||||
(first cls)
|
||||
`(if ~(mk-test (first cls)) ~(nth cls 1) ~(build (drop 2 cls))))))]
|
||||
`(let* [~g ~expr] ~(build clauses))))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue