test(spec): control-flow, functions, destructuring — fix loop destructuring

Add spec suites for conditionals/logic/let/loop/iteration/threading, functions
(definition, application, combinators), and destructuring across let/fn/loop/
doseq/for. Bug caught and fixed: loop* assumed simple-symbol bindings, so
(loop [[a b] [1 2]] ...) errored; it now destructures each binding via
destructure-bind, supporting patterns in loop bindings (and recur). jpm test green.
This commit is contained in:
Yogthos 2026-06-05 00:16:15 -04:00
parent 68d6b1d6c1
commit 50d63d896f
4 changed files with 136 additions and 4 deletions

View file

@ -788,19 +788,20 @@
"loop*" (let [bind-vec (in form 1) "loop*" (let [bind-vec (in form 1)
body (tuple/slice form 2) body (tuple/slice form 2)
init-vals @[] init-vals @[]
sym-names @[]] patterns @[]]
(var i 0) (var i 0)
(while (< i (length bind-vec)) (while (< i (length bind-vec))
(array/push init-vals (eval-form ctx bindings (bind-vec (+ i 1)))) (array/push init-vals (eval-form ctx bindings (bind-vec (+ i 1))))
(array/push sym-names ((bind-vec i) :name)) # keep the binding form (symbol OR destructuring pattern)
(array/push patterns (bind-vec i))
(+= i 2)) (+= i 2))
(var loop-fn nil) (var loop-fn nil)
(set loop-fn (fn [& args] (set loop-fn (fn [& args]
(var loop-bindings @{}) (var loop-bindings @{})
(table/setproto loop-bindings bindings) (table/setproto loop-bindings bindings)
(var j 0) (var j 0)
(each sn sym-names (each pat patterns
(bind-put loop-bindings sn (args j)) (destructure-bind ctx loop-bindings pat (in args j))
(++ j)) (++ j))
(put loop-bindings :jolt/loop-fn loop-fn) (put loop-bindings :jolt/loop-fn loop-fn)
(var result nil) (var result nil)

View file

@ -0,0 +1,62 @@
# Specification: control flow & binding forms.
(use ../support/harness)
(defspec "control / conditionals"
["if true" "1" "(if true 1 2)"]
["if false" "2" "(if false 1 2)"]
["if nil is false" "2" "(if nil 1 2)"]
["if no else" "nil" "(if false 1)"]
["when true" "3" "(when true 1 2 3)"]
["when false" "nil" "(when false 1)"]
["when-not" "1" "(when-not false 1)"]
["cond" ":b" "(cond false :a true :b :else :c)"]
["cond :else" ":c" "(cond false :a false :b :else :c)"]
["cond no match" "nil" "(cond false :a)"]
["condp" "\"two\"" "(condp = 2 1 \"one\" 2 \"two\" \"other\")"]
["case" ":b" "(case 2 1 :a 2 :b :default)"]
["case default" ":d" "(case 9 1 :a 2 :b :d)"]
["case multi" ":ab" "(case 2 (1 2) :ab 3 :c)"])
(defspec "control / logic"
["and all true" "3" "(and 1 2 3)"]
["and short circuits" "nil" "(and 1 nil 3)"]
["and empty" "true" "(and)"]
["or first truthy" "1" "(or nil 1 2)"]
["or all false" "false" "(or nil false)"]
["or empty" "nil" "(or)"]
["not" "false" "(not true)"])
(defspec "control / let & loop"
["let" "3" "(let [a 1 b 2] (+ a b))"]
["let sequential" "3" "(let [a 1 b (+ a 2)] b)"]
["let shadowing" "2" "(let [a 1] (let [a 2] a))"]
["letfn mutual" "true" "(letfn [(ev? [n] (if (zero? n) true (od? (dec n)))) (od? [n] (if (zero? n) false (ev? (dec n))))] (ev? 10))"]
["loop/recur" "15" "(loop [i 1 acc 0] (if (> i 5) acc (recur (inc i) (+ acc i))))"]
["when-let" "2" "(when-let [x 1] (inc x))"]
["when-let nil" "nil" "(when-let [x nil] (inc x))"]
["if-let" "2" "(if-let [x 1] (inc x) :none)"]
["if-let else" ":none" "(if-let [x nil] (inc x) :none)"]
["if-some zero" "1" "(if-some [x 0] (inc x) :none)"]
["when-some nil" "nil" "(when-some [x nil] x)"])
(defspec "control / iteration"
["dotimes side-effect" "5" "(let [a (atom 0)] (dotimes [i 5] (swap! a inc)) @a)"]
["while" "5" "(let [a (atom 0)] (while (< @a 5) (swap! a inc)) @a)"]
["for" "[0 1 2]" "(for [x (range 3)] x)"]
["for nested" "[[0 :a] [0 :b] [1 :a] [1 :b]]" "(for [x (range 2) y [:a :b]] [x y])"]
["for :when" "[0 2 4]" "(for [x (range 6) :when (even? x)] x)"]
["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)"])
(defspec "control / threading"
["->" "6" "(-> 1 inc (+ 4))"]
["-> with forms" "[1 2 3]" "(-> [] (conj 1) (conj 2) (conj 3))"]
["->>" "9" "(->> [1 2 3] (map inc) (reduce +))"]
["as->" "2" "(as-> [0 1] x (map inc x) (reverse x) (first x))"]
["some->" "2" "(some-> 1 inc)"]
["some-> nil stops" "nil" "(some-> nil inc)"]
["some->>" "[2 3]" "(some->> [1 2] (map inc))"]
["cond->" "2" "(cond-> 1 true inc false inc)"]
["cond->>" "[1 2]" "(cond->> [2] true (cons 1))"])

View file

@ -0,0 +1,32 @@
# Specification: destructuring (in let, fn, loop, doseq, for).
(use ../support/harness)
(defspec "destructure / sequential"
["basic vector" "3" "(let [[a b] [1 2]] (+ a b))"]
["skip with _" "3" "(let [[_ b] [1 2]] (+ b 1))"]
["rest with &" "[3 4]" "(let [[a & more] [1 3 4]] more)"]
[":as whole" "[1 2]" "(let [[a :as v] [1 2]] v)"]
["nested" "3" "(let [[[a b]] [[1 2]]] (+ a b))"]
["fewer values nil" "nil" "(let [[a b c] [1 2]] c)"]
["over a list" "1" "(let [[a] (list 1 2)] a)"]
["over a seq" "2" "(let [[a b] (rest [9 1 2])] b)"]
["string chars" "\\a" "(let [[a] (seq \"ab\")] a)"])
(defspec "destructure / associative"
["keys" "3" "(let [{:keys [a b]} {:a 1 :b 2}] (+ a b))"]
[":as map" "{:a 1}" "(let [{:as m} {:a 1}] m)"]
[":or default" "9" "(let [{:keys [a] :or {a 9}} {}] a)"]
[":or present" "1" "(let [{:keys [a] :or {a 9}} {:a 1}] a)"]
["explicit binding" "1" "(let [{x :a} {:a 1}] x)"]
["nested map" "2" "(let [{{b :b} :a} {:a {:b 2}}] b)"]
["keys + as" "[1 {:a 1}]" "(let [{:keys [a] :as m} {:a 1}] [a m])"]
["map in vector" "1" "(let [[{:keys [a]}] [{:a 1}]] a)"])
(defspec "destructure / in forms"
["fn params" "3" "((fn [[a b]] (+ a b)) [1 2])"]
["fn map param" "1" "((fn [{:keys [a]}] a) {:a 1})"]
["defn destructure" "3" "(do (defn f [[a b]] (+ a b)) (f [1 2]))"]
["loop destructure" "3" "(loop [[a b] [1 2]] (+ a b))"]
["doseq destructure" "12" "(let [s (atom 0)] (doseq [[k v] {:a 4 :b 8}] (swap! s (fn [x] (+ x v)))) @s)"]
["for destructure" "[3 7]" "(for [[a b] [[1 2] [3 4]]] (+ a b))"]
["& rest in fn" "[2 3]" "((fn [a & more] more) 1 2 3)"])

View file

@ -0,0 +1,37 @@
# Specification: functions & higher-order combinators.
(use ../support/harness)
(defspec "functions / definition"
["fn literal" "3" "((fn [a b] (+ a b)) 1 2)"]
["fn shorthand" "3" "(#(+ %1 %2) 1 2)"]
["fn shorthand %" "2" "(#(inc %) 1)"]
["defn" "5" "(do (defn f [x] (+ x 2)) (f 3))"]
["multi-arity" "[1 5]" "(do (defn f ([x] x) ([x y] (+ x y))) [(f 1) (f 2 3)])"]
["variadic" "[1 2 3]" "(do (defn f [& xs] xs) (f 1 2 3))"]
["variadic with fixed" "[1 [2 3]]" "(do (defn f [a & xs] [a xs]) (f 1 2 3))"]
["closure captures" "8" "(do (defn adder [n] (fn [x] (+ x n))) ((adder 5) 3))"]
["recursion" "120" "(do (defn fact [n] (if (< n 2) 1 (* n (fact (dec n))))) (fact 5))"]
["named fn self-ref" "120" "((fn fact [n] (if (< n 2) 1 (* n (fact (dec n))))) 5)"])
(defspec "functions / application"
["apply" "6" "(apply + [1 2 3])"]
["apply with leading" "10" "(apply + 1 2 [3 4])"]
["apply keyword" "1" "(apply :a [{:a 1}])"]
["partial" "7" "((partial + 5) 2)"]
["partial multi" "10" "((partial + 1 2) 3 4)"]
["comp" "4" "((comp inc inc) 2)"]
["comp order" "5" "((comp inc (fn [x] (* x 2))) 2)"]
["comp identity" "3" "((comp) 3)"]
["complement" "true" "((complement even?) 3)"]
["constantly" "5" "((constantly 5) 1 2 3)"]
["identity" "7" "(identity 7)"])
(defspec "functions / combinators"
["juxt" "[1 3]" "((juxt first last) [1 2 3])"]
["fnil" "1" "((fnil inc 0) nil)"]
["fnil passes value" "6" "((fnil inc 0) 5)"]
["every-pred true" "true" "((every-pred pos? even?) 4)"]
["every-pred false" "false" "((every-pred pos? even?) 3)"]
["some-fn" "true" "((some-fn even? neg?) 3 4)"]
["memoize" "2" "(do (def c (atom 0)) (def f (memoize (fn [x] (swap! c inc) x))) (f 1) (f 1) (f 2) @c)"]
["trampoline" "10" "(trampoline (fn f [n acc] (if (zero? n) acc (fn [] (f (dec n) (+ acc 2))))) 5 0)"])