Chez: runtime eval / load-string / defmacro on the zero-Janet spine

The compiler image is already resident at runtime on the Chez spine, so eval
and load-string are just wiring: make them clojure.core functions instead of
analyzer special forms.

- eval / load-string are now functions, not special forms. Dropped "eval" from
  the host-contract special-symbol lists so it resolves as an ordinary var, and
  def-var! both in compile-eval.ss. eval takes an already-read form (e.g. from
  quote/list) and compiles+evals it in the current ns; load-string reads every
  form from a source string and evals each, returning the last.
- Runtime defmacro: jolt-compile-eval-form intercepts a (defmacro ...) form
  before analysis, defs the expander fn + mark-macro!s the var, exactly as
  emit-image.ss does at build time. The two helpers (macro-form? / defmacro->fn)
  move to compile-eval.ss and emit-image.ss reuses them.
- Top-level (do ...) is now unrolled form-by-form, like Clojure, so a defmacro
  or def in an earlier subform is visible (macro flag set / var interned) before
  a later subform is analyzed. This is what makes multi-form -e with a macro work.

Seed is byte-identical (no source references eval), so no re-mint; bootstrap-test
still passes. Zero-Janet corpus 2534 -> 2544 (eval/load-string cases now run),
0 new divergences; floor raised. Prelude corpus, JVM cert, full Janet gate green.

jolt-r8ku
This commit is contained in:
Yogthos 2026-06-20 12:14:08 -04:00
parent a8b61283b3
commit a23095b502
5 changed files with 112 additions and 41 deletions

View file

@ -54,17 +54,97 @@
(if (pair? (cdr items)) (cddr items) '()))) (if (pair? (cdr items)) (cddr items) '())))
(else (for-each (lambda (x) (ce-scan-requires! x ns)) items)))))))) (else (for-each (lambda (x) (ce-scan-requires! x ns)) items))))))))
;; Source string -> Scheme source string (read -> analyze -> emit, all on Chez). ;; Already-read FORM -> Scheme source string (analyze -> emit on Chez).
;; `ns` is the compile namespace unqualified symbols resolve against. ;; `ns` is the compile namespace unqualified symbols resolve against.
(define (jolt-analyze-emit src ns) (define (jolt-analyze-emit-form form ns)
(let* ((form (jolt-ce-read src))) (ce-scan-requires! form ns)
(ce-scan-requires! form ns) (let* ((ctx (make-analyze-ctx ns))
(let* ((ctx (make-analyze-ctx ns)) (ir (jolt-ce-analyze ctx form)))
(ir (jolt-ce-analyze ctx form))) (jolt-ce-emit ir)))
(jolt-ce-emit ir))))
;; Source string -> value (compile on Chez, then eval the emitted Scheme in the ;; Source string -> Scheme source string (read then analyze -> emit, all on Chez).
(define (jolt-analyze-emit src ns)
(jolt-analyze-emit-form (jolt-ce-read src) ns))
;; --- runtime defmacro (jolt-r8ku) -------------------------------------------
;; Shared with emit-image.ss (loaded after this). A defmacro lowers to a def of
;; its expander fn + a macro flag, exactly as the prelude emits build-time macros.
;; Is `f` a (defmacro ...) / (definline ...) form?
(define (ce-macro-form? f)
(and (cseq? f) (cseq-list? f)
(let ((items (seq->list f)))
(and (pair? items) (symbol-t? (car items))
(let ((h (symbol-t-name (car items))))
(or (string=? h "defmacro") (string=? h "definline")))))))
;; (defmacro NAME [docstring] [attr-map] params body...) -> (values "NAME" (fn ...)).
;; Strips a leading docstring (native string) + attr-map (a non-symbol pmap), then
;; re-heads the rest with `fn` so a destructured macro arglist desugars. Emits the
;; BARE fn (the caller wraps it in def-var! + mark-macro!), never a (def NAME ...) —
;; interning NAME would make require skip the real macro (jolt-r9lm).
(define (ce-defmacro->fn f)
(let* ((items (seq->list f))
(name-sym (cadr items))
(after-name (cddr items))
(a1 (if (and (pair? after-name) (string? (car after-name)))
(cdr after-name) after-name))
(after-meta (if (and (pair? a1) (pmap? (car a1)))
(cdr a1) a1))
(fn-sym (jolt-symbol #f "fn")))
(values (symbol-t-name name-sym)
(apply jolt-list (cons fn-sym after-meta)))))
;; A bare top-level (do ...) form — head is the unqualified `do` symbol.
(define (ce-top-do? form)
(and (cseq? form) (cseq-list? form)
(let ((h (seq-first form)))
(and (symbol-t? h) (jolt-nil? (hc-sym-ns h))
(string=? (symbol-t-name h) "do")))))
;; Compile + eval ONE already-read form in compile ns `ns`; returns the value.
;; A top-level (do ...) is UNROLLED — each subform compiled+eval'd in turn, like
;; Clojure's top-level do — so a runtime defmacro/def in an earlier subform is
;; visible (macro flag set, var interned) before a later subform is analyzed.
(define (jolt-compile-eval-form form ns)
(cond
((ce-top-do? form)
(let loop ((fs (cdr (seq->list form))) (result jolt-nil))
(if (null? fs)
result
(loop (cdr fs) (jolt-compile-eval-form (car fs) ns)))))
;; runtime defmacro: def the expander fn + mark the var a macro so subsequent
;; forms expand it (hc-macro? reads var-macro-table). Mirrors emit-image.ss
;; ei-emit-ns and the Janet seed eval-defmacro.
((ce-macro-form? form)
(let-values (((nm fn-form) (ce-defmacro->fn form)))
(def-var! ns nm (jolt-compile-eval-form fn-form ns))
(mark-macro! ns nm)
jolt-nil))
(else
(eval (read (open-input-string (jolt-analyze-emit-form form ns)))
(interaction-environment)))))
;; Source string -> value (read one form, compile + eval on Chez, in the
;; top-level environment where rt.ss's runtime procedures live). ;; top-level environment where rt.ss's runtime procedures live).
(define (jolt-compile-eval src ns) (define (jolt-compile-eval src ns)
(eval (read (open-input-string (jolt-analyze-emit src ns))) (jolt-compile-eval-form (jolt-ce-read src) ns))
(interaction-environment)))
;; clojure.core/load-string: read every form from the source string and compile+
;; eval each in the current ns, returning the last value (nil for blank input).
;; Mirrors src/jolt/api.janet load-string (the parse-next loop). jolt-r8ku.
(define (jolt-load-string s)
(let loop ((src s) (result jolt-nil))
(let ((pn (jolt-parse-next src)))
(if (jolt-nil? pn)
result
(loop (jolt-nth pn 1)
(jolt-compile-eval-form (jolt-nth pn 0) (chez-current-ns)))))))
;; eval / load-string are FUNCTIONS on the spine (the compiler image is resident
;; at runtime). eval takes an already-read FORM (e.g. from quote / list); it and
;; load-string compile+eval in the current ns. eval is removed from the analyzer's
;; special-symbol lists (host-contract.ss) so it resolves as an ordinary core var.
(def-var! "clojure.core" "eval"
(lambda (form) (jolt-compile-eval-form form (chez-current-ns))))
(def-var! "clojure.core" "load-string" jolt-load-string)

View file

@ -30,31 +30,8 @@
(and (pair? items) (symbol-t? (car items)) (and (pair? items) (symbol-t? (car items))
(string=? (symbol-t-name (car items)) "ns"))))) (string=? (symbol-t-name (car items)) "ns")))))
;; Is `f` a (defmacro ...) / (definline ...) form? ;; ei-macro-form? / ei-defmacro->fn moved to compile-eval.ss (ce-macro-form? /
(define (ei-macro-form? f) ;; ce-defmacro->fn, loaded before this) — shared with the runtime defmacro spine.
(and (cseq? f) (cseq-list? f)
(let ((items (seq->list f)))
(and (pair? items) (symbol-t? (car items))
(let ((h (symbol-t-name (car items))))
(or (string=? h "defmacro") (string=? h "definline")))))))
;; (defmacro NAME [docstring] [attr-map] params body...) -> (values "NAME" (fn ...)).
;; Mirrors driver.janet defmacro->fn: strip a leading docstring (native string) and
;; an attr-map (a pmap that isn't a symbol), then re-head the rest with `fn` so a
;; destructured macro arglist desugars before lowering. We emit the BARE fn (the
;; caller wraps it in def-var! + mark-macro!), never a (def NAME ...) — interning
;; NAME would make require skip the real macro (jolt-r9lm).
(define (ei-defmacro->fn f)
(let* ((items (seq->list f))
(name-sym (cadr items))
(after-name (cddr items))
(a1 (if (and (pair? after-name) (string? (car after-name)))
(cdr after-name) after-name))
(after-meta (if (and (pair? a1) (pmap? (car a1)))
(cdr a1) a1))
(fn-sym (jolt-symbol #f "fn")))
(values (symbol-t-name name-sym)
(apply jolt-list (cons fn-sym after-meta)))))
;; Cross-compile one namespace's source to a list of guard-wrapped Scheme strings. ;; Cross-compile one namespace's source to a list of guard-wrapped Scheme strings.
;; Mirrors driver.janet emit-ns-forms-list/emit-core-prelude + emit-form-scheme. ;; Mirrors driver.janet emit-ns-forms-list/emit-core-prelude + emit-form-scheme.
@ -70,8 +47,8 @@
(ce-scan-requires! f ns-name) (ce-scan-requires! f ns-name)
(cond (cond
((ei-ns-form? f) (loop (cdr forms) acc)) ((ei-ns-form? f) (loop (cdr forms) acc))
((ei-macro-form? f) ((ce-macro-form? f)
(let-values (((nm fn-form) (ei-defmacro->fn f))) (let-values (((nm fn-form) (ce-defmacro->fn f)))
(let ((scm (guard (e (#t #f)) (let ((scm (guard (e (#t #f))
(let ((ctx (make-analyze-ctx ns-name))) (let ((ctx (make-analyze-ctx ns-name)))
(jolt-ce-emit (jolt-ce-analyze ctx fn-form)))))) (jolt-ce-emit (jolt-ce-analyze ctx fn-form))))))

View file

@ -105,9 +105,12 @@
;; --- special forms ---------------------------------------------------------- ;; --- special forms ----------------------------------------------------------
;; Mirrors host_iface special-names + interop-head? — forms the analyzer marks ;; Mirrors host_iface special-names + interop-head? — forms the analyzer marks
;; uncompilable (the handled specials are dispatched in analyze-list BEFORE this). ;; uncompilable (the handled specials are dispatched in analyze-list BEFORE this).
;; `eval` is NOT here: it is a clojure.core FUNCTION on the spine (compile-eval.ss
;; def-var!s it), so it must resolve as an ordinary var, not punt (jolt-r8ku).
;; `defmacro` stays special — the spine intercepts it before analysis.
(define hc-special-names (define hc-special-names
'("quote" "syntax-quote" "unquote" "unquote-splicing" "do" "if" "def" '("quote" "syntax-quote" "unquote" "unquote-splicing" "do" "if" "def"
"defmacro" "fn*" "let*" "loop*" "recur" "throw" "try" "set!" "eval" "new" "defmacro" "fn*" "let*" "loop*" "recur" "throw" "try" "set!" "new"
"." "gen-class" "monitor-enter" "monitor-exit" "letfn")) "." "gen-class" "monitor-enter" "monitor-exit" "letfn"))
(define (hc-interop-head? name) (define (hc-interop-head? name)
(let ((n (string-length name))) (let ((n (string-length name)))
@ -180,7 +183,7 @@
;; clojure.core / the compile ns; a foo# auto-gensym is stable within one `. ;; clojure.core / the compile ns; a foo# auto-gensym is stable within one `.
(define hc-special-symbols (define hc-special-symbols
'("quote" "syntax-quote" "unquote" "unquote-splicing" "do" "if" "def" '("quote" "syntax-quote" "unquote" "unquote-splicing" "do" "if" "def"
"defmacro" "fn*" "let*" "loop*" "recur" "throw" "try" "set!" "var" "eval" "defmacro" "fn*" "let*" "loop*" "recur" "throw" "try" "set!" "var"
"new" ".")) "new" "."))
(define (hc-special-symbol? nm) (and (member nm hc-special-symbols) #t)) (define (hc-special-symbol? nm) (and (member nm hc-special-symbols) #t))

View file

@ -36,7 +36,18 @@
["(case 3 1 :a 2 :b :other)" ":other"] ["(case 3 1 :a 2 :b :other)" ":other"]
["(reduce + (vals (reduce (fn [m k] (assoc m k (* k k))) {} [1 2 3])))" "14"] ["(reduce + (vals (reduce (fn [m k] (assoc m k (* k k))) {} [1 2 3])))" "14"]
["(map inc [1 2 3])" "(2 3 4)"] ["(map inc [1 2 3])" "(2 3 4)"]
["nil" ""]]) ["nil" ""]
# jolt-r8ku: runtime eval / load-string / defmacro on the Chez spine.
["(eval (quote (+ 1 2)))" "3"]
["(eval (list (quote +) 1 2 3))" "6"]
["(eval (quote (let [a 2 b 3] (* a b))))" "6"]
["(load-string \"(+ 1 2)\")" "3"]
["(load-string \"(def y 5) (* y y)\")" "25"]
["(load-string \"\")" ""]
["(map eval [(quote (+ 1 1)) (quote (* 3 3))])" "(2 9)"]
["(defmacro add1 [x] (list (quote +) x 1)) (add1 10)" "11"]
["(defmacro twice [x] `(do ~x ~x)) (twice (+ 2 3))" "5"]
["(defmacro m [x] `(+ ~x 1)) (m (m (m 0)))" "3"]])
(each [src want] cases (each [src want] cases
(def [code out err] (joltc src)) (def [code out err] (joltc src))

View file

@ -166,7 +166,7 @@
# Regression floor: raise as the Chez-hosted compiler closes gaps. The gate fails # Regression floor: raise as the Chez-hosted compiler closes gaps. The gate fails
# on any NEW divergence or if pass drops below the floor. Strided runs scale to 0. # on any NEW divergence or if pass drops below the floor. Strided runs scale to 0.
(def base-floor (scan-number (or (os/getenv "JOLT_CHEZ_ZJ_FLOOR") "2534"))) (def base-floor (scan-number (or (os/getenv "JOLT_CHEZ_ZJ_FLOOR") "2544")))
(def floor (if (os/getenv "JOLT_CORPUS_LIMIT") 0 base-floor)) (def floor (if (os/getenv "JOLT_CORPUS_LIMIT") 0 base-floor))
(when (or (> (length diverged) 0) (< pass floor)) (when (or (> (length diverged) 0) (< pass floor))
(printf "REGRESSION: pass %d < floor %d or %d new divergence(s)" pass floor (length diverged))) (printf "REGRESSION: pass %d < floor %d or %d new divergence(s)" pass floor (length diverged)))