macroexpand-first analyzer order; one macro path; defmacro/letfn fixes
The analyzer checked special forms before expanding macros, the reverse of the canonical read -> macroexpand -> analyze order (Clojure/CLJS analyze-seq). Move macroexpansion to the front of analyze-list. Knock-on fixes: - letfn was both a (broken) macro expanding to let* AND a primitive special (analyze-letfn, proper letrec*). Macroexpand-first surfaced the macro, breaking mutual recursion; remove the macro, keep letfn a primitive. - defmacro is now compiled by the analyzer (a :set-var-style :defmacro node that defs the expander fn via the fn macro — so destructuring arglists desugar — and marks the var a macro), so a non-top-level (when … (defmacro …)) works. The runtime spine's separate top-level defmacro interception is removed: one path. SCI load 162 -> 202/218.
This commit is contained in:
parent
e6ee17b055
commit
f18ae3bd46
8 changed files with 402 additions and 383 deletions
|
|
@ -117,14 +117,9 @@
|
|||
result
|
||||
(let ((r (jolt-compile-eval-form (car fs) cur)))
|
||||
(loop (cdr fs) r (chez-current-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.
|
||||
((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))
|
||||
;; defmacro is compiled like any other form — the analyzer lowers it to a def
|
||||
;; of the expander fn + (mark-macro! …) so subsequent forms expand it. One
|
||||
;; macro-expansion path (no separate spine interception).
|
||||
(else
|
||||
(eval (read (open-input-string (jolt-analyze-emit-form form ns)))
|
||||
(interaction-environment)))))
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
;; run-sci.ss — SCI conformance: load borkdude/sci's own source (vendor/sci) through
|
||||
;; joltc and require its forms to compile+eval. A real-world Clojure-compatibility
|
||||
;; stress test. Pure Chez, no Janet. Floor-gated like the corpus: a regression below
|
||||
;; the floor (or the count today, 196/218) fails. Raise the floor as host gaps close
|
||||
;; the floor (or the count today, 202/218) fails. Raise the floor as host gaps close
|
||||
;; (the tail is genuine gaps — set! on vars, some macro/def shapes).
|
||||
;;
|
||||
;; chez --script host/chez/run-sci.ss
|
||||
;; JOLT_SCI_FLOOR=N override the floor (default 196)
|
||||
;; JOLT_SCI_FLOOR=N override the floor (default 202)
|
||||
;; SCI_VERBOSE=1 print each failing form's error
|
||||
(import (chezscheme))
|
||||
|
||||
|
|
@ -74,7 +74,7 @@
|
|||
load-order)
|
||||
|
||||
(printf "\nSCI load: ~a/~a forms ok (~a fail)\n" total-ok (+ total-ok total-fail) total-fail)
|
||||
(define floor (let ((s (getenv "JOLT_SCI_FLOOR"))) (if s (string->number s) 196)))
|
||||
(define floor (let ((s (getenv "JOLT_SCI_FLOOR"))) (if s (string->number s) 202)))
|
||||
(when (< total-ok floor)
|
||||
(printf "REGRESSION: ~a forms loaded < floor ~a\n" total-ok floor))
|
||||
(flush-output-port)
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -225,10 +225,10 @@
|
|||
|
||||
;; Build the fn* form via a template (a reader-list array): cons/list in a macro
|
||||
;; body produce a plist the evaluator can't call as a form.
|
||||
(defmacro letfn [fnspecs & body]
|
||||
(let [binds (reduce (fn [acc spec] (conj (conj acc (first spec)) `(fn* ~@(rest spec))))
|
||||
[] fnspecs)]
|
||||
`(let* [~@binds] ~@body)))
|
||||
;; letfn is a primitive special form (analyze-letfn -> letrec*), not a macro: its
|
||||
;; fns are mutually recursive, which a (let* …) expansion cannot express. Defining
|
||||
;; it as a macro would shadow the special once macroexpansion runs first (the
|
||||
;; canonical order), so it is intentionally NOT a macro here.
|
||||
|
||||
;; Dynamic binding: install a thread-binding frame of var->value (array-map keeps
|
||||
;; var-get happy, unlike a phm), restore on exit.
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@
|
|||
|
||||
(def ^:private handled
|
||||
#{"quote" "if" "do" "def" "fn*" "let*" "loop*" "recur" "throw" "try"
|
||||
"syntax-quote" "var" "letfn" "set!"})
|
||||
"syntax-quote" "var" "letfn" "set!" "defmacro"})
|
||||
|
||||
(defn- uncompilable [why]
|
||||
(throw (str "jolt/uncompilable: " why)))
|
||||
|
|
@ -326,6 +326,23 @@
|
|||
;; (set! *var* val): set the var's innermost thread binding, else its root
|
||||
;; (jolt-var-set). A local target is a deftype mutable field — not yet
|
||||
;; supported (jolt binds fields immutably); an interop (.-field) target too.
|
||||
;; A defmacro that is not top-level (the spine intercepts those) — e.g. one
|
||||
;; produced by a macro like (when … (defmacro …)). Lower it the way the spine
|
||||
;; does: def the expander fn, then mark the var a macro at runtime so later
|
||||
;; forms expand it. Strip a leading docstring / attr-map, as defmacro allows.
|
||||
"defmacro" (let [name-sym (nth items 1)
|
||||
nm (form-sym-name name-sym)
|
||||
cur (compile-ns ctx)
|
||||
after (drop 2 items)
|
||||
after (if (string? (first after)) (rest after) after)
|
||||
after (if (form-map? (first after)) (rest after) after)
|
||||
;; build (fn params body…) and analyze it through the fn MACRO
|
||||
;; so a destructuring macro arglist desugars (the fn* primitive
|
||||
;; would not), then def it and mark the var a macro.
|
||||
fn-form (cons (symbol "fn") after)]
|
||||
(host-intern! ctx cur nm)
|
||||
{:op :defmacro :ns cur :name nm
|
||||
:fn (analyze ctx fn-form env)})
|
||||
"set!" (let [target (nth items 1)]
|
||||
(when-not (form-sym? target) (uncompilable "set! of a non-symbol target"))
|
||||
(when (local? env (form-sym-name target)) (uncompilable "set! of a local"))
|
||||
|
|
@ -441,6 +458,13 @@
|
|||
hname (when (and (form-sym? head) (nil? (form-sym-ns head))) (form-sym-name head))
|
||||
shadowed (and hname (local? env hname))]
|
||||
(cond
|
||||
;; Canonical order (Clojure/CLJS analyze-seq): macroexpand FIRST, then
|
||||
;; dispatch special forms / interop / invoke. Expanding before the
|
||||
;; special-form check means a head that is a macro always expands — even
|
||||
;; one whose name is also in the special-form set — matching reference
|
||||
;; read -> macroexpand -> analyze. A local shadows both.
|
||||
(and (form-sym? head) (not shadowed) (form-macro? ctx head))
|
||||
(analyze ctx (form-expand-1 ctx form) env)
|
||||
(and hname (not shadowed) (contains? handled hname))
|
||||
(analyze-special ctx hname items env)
|
||||
(and hname (not shadowed) (method-head? hname))
|
||||
|
|
@ -460,8 +484,6 @@
|
|||
(analyze-field ctx hname items env)
|
||||
(and hname (not shadowed) (form-special? hname))
|
||||
(uncompilable (str "special form " hname))
|
||||
(and (form-sym? head) (not shadowed) (form-macro? ctx head))
|
||||
(analyze ctx (form-expand-1 ctx form) env)
|
||||
:else
|
||||
;; stamp the list form's source offset onto the :invoke (jolt-fqy)
|
||||
;; so the success checker can report file:line:col. nil when the
|
||||
|
|
|
|||
|
|
@ -379,6 +379,11 @@
|
|||
:the-var (str "(jolt-var " (chez-str-lit (:ns node)) " " (chez-str-lit (:name node)) ")")
|
||||
;; (set! *var* val) -> set the var's innermost binding (else root); returns val.
|
||||
:set-var (str "(jolt-var-set " (emit (:the-var node)) " " (emit (:val node)) ")")
|
||||
;; a non-top-level defmacro -> def the expander fn + mark the var a macro at
|
||||
;; runtime (the spine does the same for top-level forms).
|
||||
:defmacro (str "(begin (def-var! " (chez-str-lit (:ns node)) " " (chez-str-lit (:name node)) " "
|
||||
(emit (:fn node)) ") (mark-macro! " (chez-str-lit (:ns node)) " "
|
||||
(chez-str-lit (:name node)) ") jolt-nil)")
|
||||
:host (throw (ex-info (str "emit: unsupported host ref `" (:name node) "`") {}))
|
||||
:host-static (str "(host-static-ref " (chez-str-lit (:class node)) " "
|
||||
(chez-str-lit (:member node)) ")")
|
||||
|
|
|
|||
|
|
@ -97,6 +97,7 @@
|
|||
:ret (f (get node :ret)))
|
||||
(= op :throw) (assoc node :expr (f (get node :expr)))
|
||||
(= op :set-var) (assoc node :val (f (get node :val)))
|
||||
(= op :defmacro) (assoc node :fn (f (get node :fn)))
|
||||
(= op :invoke) (assoc node :fn (f (get node :fn))
|
||||
:args (mapv f (get node :args)))
|
||||
(= op :vector) (assoc node :items (mapv f (get node :items)))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue