diff --git a/jolt-core/clojure/core/30-macros.clj b/jolt-core/clojure/core/30-macros.clj index ffea3b3..1379e63 100644 --- a/jolt-core/clojure/core/30-macros.clj +++ b/jolt-core/clojure/core/30-macros.clj @@ -12,27 +12,25 @@ (defmacro comment [& body] nil) -;; Single arglist (Jolt defmacro is single-arity); the optional else is the head of -;; a plain rest param — (first else) is the else form or nil. (A `& [else]`-style -;; rest-destructure reads the same but the analyzer can't yet compile destructuring -;; in a rest param, which would force this expander to stay interpreted — jolt-f79.) -(defmacro if-not [test then & else] - `(if (not ~test) ~then ~(first else))) +;; Single arglist (Jolt defmacro is single-arity); the optional else defaults nil +;; via rest-destructuring. +(defmacro if-not [test then & [else]] + `(if (not ~test) ~then ~else)) ;; Conditional binding macros: the name is bound ONLY in the taken branch (the ;; auto-gensym temp# tests the value; the else/empty branch sees the surrounding ;; scope). temp# is a single template-local gensym — referenced twice, same symbol. -(defmacro if-let [bindings then & else] +(defmacro if-let [bindings then & [else]] (let [form (bindings 0) tst (bindings 1)] `(let [temp# ~tst] - (if temp# (let [~form temp#] ~then) ~(first else))))) + (if temp# (let [~form temp#] ~then) ~else)))) ;; when-let lives in 00-syntax (not here): 20-coll uses it, which loads before this tier. -(defmacro if-some [bindings then & else] +(defmacro if-some [bindings then & [else]] (let [form (bindings 0) tst (bindings 1)] `(let [temp# ~tst] - (if (some? temp#) (let [~form temp#] ~then) ~(first else))))) + (if (some? temp#) (let [~form temp#] ~then) ~else)))) (defmacro when-some [bindings & body] (let [form (bindings 0) tst (bindings 1)] @@ -93,9 +91,8 @@ (partition 2 clauses))] `(let [~g ~expr ~@(thread-binds g steps)] ~(if (empty? steps) g (last steps))))) -(defmacro assert [x & message] - (let [m (first message) - msg (if m m (str "Assert failed: " (pr-str x)))] +(defmacro assert [x & [message]] + (let [msg (if message message (str "Assert failed: " (pr-str x)))] `(when-not ~x (throw (ex-info ~msg {}))))) (defmacro delay [& body] diff --git a/src/jolt/api.janet b/src/jolt/api.janet index c172a1b..fe10f27 100644 --- a/src/jolt/api.janet +++ b/src/jolt/api.janet @@ -14,18 +14,21 @@ (import ./stdlib_embed :as stdlib-embed) (import ./host_iface :as host) -# A defmacro expander compiles to a native fn (built as (fn* args body...) and run +# A defmacro expander compiles to a native fn (built as (fn args body...) and run # through the self-hosted pipeline) so macro expansion is COMPILED code, zero runtime # cost — instead of an interpreted closure, mirroring Clojure (macros are ordinary -# compiled fns). Returns nil when the analyzer isn't built yet (the early macros, -# expanded WHILE the analyzer is being bootstrapped) or the body isn't compilable; in -# that case defmacro keeps an interpreted closure, and backend/recompile-macros! -# replaces it with a compiled expander once the analyzer comes alive (staged -# bootstrap — the interpreter is a build-time crutch, gone by steady state). +# compiled fns). Wrapped in the `fn` MACRO (not the `fn*` primitive) so a destructured +# macro arglist — `[a & [b]]`, `[& {:keys [x]}]`, nested — desugars before lowering; +# raw fn* punts on a destructuring rest param. Returns nil when the analyzer isn't +# built yet (the early macros, expanded WHILE the analyzer is being bootstrapped) or +# the body isn't compilable; in that case defmacro keeps an interpreted closure, and +# backend/recompile-macros! replaces it with a compiled expander once the analyzer +# comes alive (staged bootstrap — the interpreter is a build-time crutch, gone by +# steady state). (set macro-compile-hook (fn [ctx args-form body] (backend/try-compile-fn ctx - (array/concat @[{:jolt/type :symbol :ns nil :name "fn*"} args-form] body)))) + (array/concat @[{:jolt/type :symbol :ns nil :name "fn"} args-form] body)))) (defn normalize-pvecs "Deep-convert any sequential (pvec/tuple/array) to a Janet tuple. Test helper diff --git a/src/jolt/backend.janet b/src/jolt/backend.janet index 357e444..9352517 100644 --- a/src/jolt/backend.janet +++ b/src/jolt/backend.janet @@ -373,7 +373,10 @@ (def r (protect (eval (compiled 1) (comp/ctx-janet-env ctx)))) (when (r 0) (r 1))))) -(def- fn*-sym {:jolt/type :symbol :ns nil :name "fn*"}) +# Wrap expanders in the `fn` MACRO, not the `fn*` primitive: `fn` desugars a +# destructured macro arglist (`[a & [b]]`, `[& {:keys [x]}]`) before lowering, +# whereas raw fn* punts on a destructuring rest param. +(def- fn-sym {:jolt/type :symbol :ns nil :name "fn"}) (defn recompile-macros! "Staged-bootstrap second pass: once the self-hosted analyzer is alive, replace @@ -398,7 +401,7 @@ (not (v :macro-uses-env))) (def [args-form body] (v :macro-src)) (def compiled - (try-compile-fn ctx (array/concat @[fn*-sym args-form] body))) + (try-compile-fn ctx (array/concat @[fn-sym args-form] body))) (when compiled (bind-root v compiled) (put v :macro-compiled true)