From e611034550a476ddfd7b483f55da9df6cd2b621e Mon Sep 17 00:00:00 2001 From: Yogthos Date: Sun, 7 Jun 2026 00:38:02 -0400 Subject: [PATCH] core: move if-let/if-some/when-some/while/dotimes macros to overlay MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 3 batch 2 (jolt-1j0), 5 user-facing macros as defmacro + syntax-quote. The conditional-binding macros use the auto-gensym temp# idiom so the name binds only in the taken branch (the else/empty branch sees the outer scope, carrying forward the earlier scope fix). when-let stays in Janet for now — 20-coll uses it, so it must remain available before the macro tier loads. Note: gensym in a macro body resolves to Janet's 0-arity builtin, so explicit (gensym "prefix") fails — use template auto-gensym (foo#) instead. conformance 228/228 x3, full suite green (incl. the if-let scope regression spec, now exercising the overlay macro), clojure-test-suite 3930, bench A/B flat. --- jolt-core/clojure/core/30-macros.clj | 27 +++++++++++ src/jolt/core.janet | 68 +--------------------------- test/spec/macros-spec.janet | 11 ++++- 3 files changed, 38 insertions(+), 68 deletions(-) diff --git a/jolt-core/clojure/core/30-macros.clj b/jolt-core/clojure/core/30-macros.clj index 08bba37..c34a509 100644 --- a/jolt-core/clojure/core/30-macros.clj +++ b/jolt-core/clojure/core/30-macros.clj @@ -16,3 +16,30 @@ ;; 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]] + (let [form (bindings 0) tst (bindings 1)] + `(let [temp# ~tst] + (if temp# (let [~form temp#] ~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) ~else)))) + +(defmacro when-some [bindings & body] + (let [form (bindings 0) tst (bindings 1)] + `(let [temp# ~tst] + (if (some? temp#) (let [~form temp#] ~@body) nil)))) + +(defmacro while [test & body] + `(loop [] (when ~test ~@body (recur)))) + +(defmacro dotimes [bindings & body] + (let [i (bindings 0) n (bindings 1)] + `(let [n# ~n] + (loop [~i 0] + (when (< ~i n#) ~@body (recur (inc ~i))))))) diff --git a/src/jolt/core.janet b/src/jolt/core.janet index 732c9db..d8d2aa2 100644 --- a/src/jolt/core.janet +++ b/src/jolt/core.janet @@ -2142,17 +2142,6 @@ # temp around the if; the name is rebound to the temp inside the taken branch only. (defn- sym* [name] {:jolt/type :symbol :ns nil :name name}) -(defn core-if-let - "Macro: (if-let [binding val-expr] then else?)" - [bindings then-form & else-forms] - (def form-sym (in bindings 0)) - (def val-form (in bindings 1)) - (def temp (gensym "if_let__")) - @[(sym* "let*") @[temp val-form] - @[(sym* "if") temp - @[(sym* "let*") @[form-sym temp] then-form] - ;else-forms]]) - (defn core-when-let "Macro: (when-let [binding val-expr] & body)" [bindings & body] @@ -2164,28 +2153,6 @@ @[(sym* "let*") @[form-sym temp] @[(sym* "do") ;body]] nil]]) -(defn core-if-some - "Macro: (if-some [binding val-expr] then else?)" - [bindings then-form & else-forms] - (def form-sym (in bindings 0)) - (def val-form (in bindings 1)) - (def temp (gensym "if_some__")) - @[(sym* "let*") @[temp val-form] - @[(sym* "if") @[(sym* "some?") temp] - @[(sym* "let*") @[form-sym temp] then-form] - ;else-forms]]) - -(defn core-when-some - "Macro: (when-some [binding val-expr] & body)" - [bindings & body] - (def form-sym (in bindings 0)) - (def val-form (in bindings 1)) - (def temp (gensym "when_some__")) - @[(sym* "let*") @[temp val-form] - @[(sym* "if") @[(sym* "some?") temp] - @[(sym* "let*") @[form-sym temp] @[(sym* "do") ;body]] - nil]]) - (defn core-doto "Macro: (doto obj (method args)...) → let obj, call methods, return obj" [obj & forms] @@ -2229,34 +2196,6 @@ (build (tuple/slice cls 2))])))) @[{:jolt/type :symbol :ns nil :name "let*"} @[g expr] (build clauses)]) -(defn core-dotimes - "Macro: (dotimes [sym n] & body) -> loop from 0 to n-1" - [bindings & body] - (def sym (in bindings 0)) - (def n-form (in bindings 1)) - (def i (gensym)) - @[{:jolt/type :symbol :ns nil :name "let*"} - @[i n-form] - @[{:jolt/type :symbol :ns nil :name "loop*"} - @[sym 0] - @[{:jolt/type :symbol :ns nil :name "if"} - @[{:jolt/type :symbol :ns nil :name "<"} sym i] - @[{:jolt/type :symbol :ns nil :name "do"} - ;body - @[{:jolt/type :symbol :ns nil :name "recur"} - @[{:jolt/type :symbol :ns nil :name "inc"} sym]]] - nil]]]) - -(defn core-while - "Macro: (while test & body) -> loop while test is truthy" - [test & body] - @[{:jolt/type :symbol :ns nil :name "loop*"} - @[] - @[{:jolt/type :symbol :ns nil :name "when"} - test - @[{:jolt/type :symbol :ns nil :name "do"} ;body] - @[{:jolt/type :symbol :ns nil :name "recur"}]]]) - (defn core-for "Macro: (for [binding-form coll :when test :let [bindings]] body) List comprehension. Basic support for :when and :let." @@ -3836,14 +3775,9 @@ "when" core-when "when-not" core-when-not "when-first" core-when-first - "if-let" core-if-let "when-let" core-when-let - "if-some" core-if-some - "when-some" core-when-some "doto" core-doto "condp" core-condp - "dotimes" core-dotimes - "while" core-while "->" core-thread-first "->>" core-thread-last "some->" core-some-> @@ -3932,7 +3866,7 @@ (defn core-macro-names "Set of core binding names that are macros." [] - @{"and" true "or" true "cond" true "case" true "for" true "when" true "when-not" true "if-let" true "when-let" true "if-some" true "when-some" true "doto" true "defn" true "defn-" true "declare" true "fn" true "let" true "loop" true "defrecord" true "defprotocol" true "extend-type" true "extend-protocol" true "extend" true "reify" true "proxy" true "definterface" true "binding" true "lazy-seq" true "lazy-cat" true "when-first" true "condp" true "dotimes" true "while" true "some->" true "some->>" true "cond->" true "cond->>" true "as->" true "->" true "->>" true "letfn" true "doseq" true "delay" true "assert" true "future" true}) + @{"and" true "or" true "cond" true "case" true "for" true "when" true "when-not" true "when-let" true "doto" true "defn" true "defn-" true "declare" true "fn" true "let" true "loop" true "defrecord" true "defprotocol" true "extend-type" true "extend-protocol" true "extend" true "reify" true "proxy" true "definterface" true "binding" true "lazy-seq" true "lazy-cat" true "when-first" true "condp" true "some->" true "some->>" true "cond->" true "cond->>" true "as->" true "->" true "->>" true "letfn" true "doseq" true "delay" true "assert" true "future" true}) (def init-core! (fn [& args] diff --git a/test/spec/macros-spec.janet b/test/spec/macros-spec.janet index 551e51d..f55a98f 100644 --- a/test/spec/macros-spec.janet +++ b/test/spec/macros-spec.janet @@ -34,4 +34,13 @@ ["if-not no else" "nil" "(if-not true :then)"] ["if-not no else hit" ":then" "(if-not false :then)"] ["comment -> nil" "nil" "(comment a b c)"] - ["comment in do" "42" "(do (comment ignored) 42)"]) + ["comment in do" "42" "(do (comment ignored) 42)"] + ["if-let then" "6" "(if-let [x 5] (inc x) :none)"] + ["if-let else" ":none" "(if-let [x nil] (inc x) :none)"] + ["if-let else scope" "9" "(let [x 9] (if-let [x nil] :t x))"] + ["if-some zero" "1" "(if-some [x 0] (inc x) :none)"] + ["if-some nil" ":none" "(if-some [x nil] x :none)"] + ["when-some multi" "14" "(when-some [x 7] (inc x) (* x 2))"] + ["when-some nil" "nil" "(when-some [x nil] x)"] + ["while loop" "3" "(let [a (atom 0)] (while (< @a 3) (swap! a inc)) @a)"] + ["dotimes sum" "10" "(let [a (atom 0)] (dotimes [i 5] (swap! a + i)) @a)"])