core: move doseq to the syntax tier

Same shortcut as the prior Janet macro — realize a for comprehension with count
for side effects, return nil — so for keeps handling :when/:let/:while and multiple
bindings. Lives in 00-syntax because the analyzer uses doseq (analyze-try). Added
:when/:while/:let/returns-nil regression cases.

conformance 228x3, fixpoint, clojure-test-suite 3930, full suite green.
This commit is contained in:
Yogthos 2026-06-07 16:40:24 -04:00
parent 646744ca1d
commit 40b0f525f3
3 changed files with 12 additions and 10 deletions

View file

@ -95,3 +95,9 @@
(first cls)
`(if ~(mk-test (first cls)) ~(nth cls 1) ~(build (drop 2 cls))))))]
`(let* [~g ~expr] ~(build clauses))))
;; doseq runs body for side effects across the bindings, returning nil. Same
;; shortcut as the prior Janet macro: realize a `for` comprehension with count
;; (for handles :when/:let/:while and multiple bindings).
(defmacro doseq [bindings & body]
`(do (count (for ~bindings (do ~@body nil))) nil))

View file

@ -2364,13 +2364,6 @@
# doseq — like `for` but eager and returns nil. Reuse `for`, force realization
# with `count`, discard the result.
(defn core-doseq [bindings & body]
(def for-body @[{:jolt/type :symbol :ns nil :name "do"} ;body nil])
@[{:jolt/type :symbol :ns nil :name "do"}
@[{:jolt/type :symbol :ns nil :name "count"}
@[{:jolt/type :symbol :ns nil :name "for"} bindings for-body]]
nil])
# assert — (assert x) / (assert x message). Throws when x is falsy.
# resolve stub — returns nil (symbols not found in Jolt's clojure.core)
@ -3174,7 +3167,6 @@
"pop" core-pop
"trampoline" core-trampoline
"format" core-format
"doseq" core-doseq
"first" core-first
"rest" core-rest
"next" core-next
@ -3562,7 +3554,7 @@
(defn core-macro-names
"Set of core binding names that are macros."
[]
@{"for" true "when-let" true "defn" true "defn-" 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 "lazy-seq" true "lazy-cat" true "doseq" true})
@{"for" true "when-let" true "defn" true "defn-" 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 "lazy-seq" true "lazy-cat" true})
(def init-core!
(fn [& args]

View file

@ -69,7 +69,11 @@
["for :while" "[0 1 2]" "(for [x (range 10) :while (< x 3)] x)"]
["for :let" "[0 1 4]" "(for [x (range 3) :let [sq (* x x)]] sq)"]
["doseq side-effect" "6" "(let [a (atom 0)] (doseq [x [1 2 3]] (swap! a (fn [v] (+ v x)))) @a)"]
["doseq nested" "4" "(let [c (atom 0)] (doseq [x [1 2] y [10 20]] (swap! c inc)) @c)"])
["doseq nested" "4" "(let [c (atom 0)] (doseq [x [1 2] y [10 20]] (swap! c inc)) @c)"]
["doseq :when" "[1 3]" "(let [a (atom [])] (doseq [x [1 2 3] :when (odd? x)] (swap! a conj x)) @a)"]
["doseq :while" "6" "(let [a (atom 0)] (doseq [x (range 10) :while (< x 4)] (swap! a + x)) @a)"]
["doseq :let" "[0 1 4]" "(let [a (atom [])] (doseq [x (range 3) :let [sq (* x x)]] (swap! a conj sq)) @a)"]
["doseq returns nil" "nil" "(doseq [x [1 2 3]] x)"])
(defspec "control / threading"
["->" "6" "(-> 1 inc (+ 4))"]