core: move if-let/if-some/when-some/while/dotimes macros to overlay

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.
This commit is contained in:
Yogthos 2026-06-07 00:38:02 -04:00
parent 330a3a23d9
commit e611034550
3 changed files with 38 additions and 68 deletions

View file

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

View file

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

View file

@ -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)"])