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
|
|
@ -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