# 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)"]
  ["case symbol const"  ":s"     "(case 'foo foo :s :default)"]
  ["case vector const"  ":v"     "(case [1 2] [1 2] :v :default)"]
  ["case map const"     ":m"     "(case {:a 1} {:a 1} :m :default)"]
  ["case list const"    ":l"     "(case '(a b) (quote (a b)) :l :default)"]
  ["case keyword"       ":k"     "(case :x :x :k :default)"])

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

# Regression: if-let/when-let/if-some/when-some bind the name ONLY in the
# then/body branch. The else branch (and a falsy when-let body, which there is
# none of) must see the surrounding scope, not the binding — so the else of
# (let [x 5] (if-let [x nil] ...)) sees x=5, like Clojure. (Previously the macros
# wrapped the whole `if` in the binding's let*, leaking it into the else.)
(defspec "control / conditional-binding scope"
  ["if-let else sees outer"    "5"   "(let [x 5] (if-let [x nil] :then x))"]
  ["if-let then binds"         "7"   "(let [x 5] (if-let [x 7] x :else))"]
  ["if-some else sees outer"   "5"   "(let [x 5] (if-some [x nil] :then x))"]
  ["if-some binds false"       "false" "(if-some [x false] x :else)"]
  ["when-let else via or"      "5"   "(let [x 5] (or (when-let [x nil] x) x))"]
  ["when-let multi-form body"  "14"  "(when-let [x 7] (inc x) (* x 2))"]
  ["if-let in fn param"        "9"   "((fn [xs] (if-let [xs nil] :then xs)) 9)"]
  ["when-some binds zero"      "1"   "(when-some [x 0] (inc x))"]
  ["if-let evals test once"    "1"   "(let [c (atom 0)] (if-let [v (do (swap! c inc) :v)] @c :none))"])

# Regression: loop/recur lowering (jolt-v28u). The backend lowers tail loop/recur
# to a Janet while + state vars with a fresh per-iteration `let` rebinding of the
# loop names. The let rebinding is load-bearing: a closure created in the body
# must capture THAT iteration's value (Clojure semantics), not a shared mutable
# var — Janet closures capture vars by reference, so a naive while+var would give
# [3 3 3]. recur reads the (immutable) iteration bindings and writes the state
# vars, so cross-referencing args don't clobber (swap, fib).
(defspec "control / loop lowering (jolt-v28u)"
  ["closure captures per-iter binding" "[0 1 2]"
   "(mapv (fn [g] (g)) (loop [i 0 fs []] (if (< i 3) (recur (inc i) (conj fs (fn [] i))) fs)))"]
  ["fib via loop"          "55"     "(loop [a 0 b 1 i 0] (if (= i 10) a (recur b (+ a b) (inc i))))"]
  ["recur args no clobber" "[2 1]"  "(loop [a 1 b 2 n 0] (if (= n 1) [a b] (recur b a (inc n))))"]
  ["nested loops"          "9"      "(loop [i 0 s 0] (if (= i 3) s (recur (inc i) (loop [j 0 t s] (if (= j 3) t (recur (inc j) (inc t)))))))"]
  ["loop sequential init"  "12"     "(loop [a 1 b (+ a 10)] (+ a b))"]
  ["recur through let"     "6"      "(loop [i 0 acc 0] (let [x (* i 2)] (if (< i 3) (recur (inc i) (+ acc x)) acc)))"]
  ["fn-arity recur intact" "15"     "((fn f [n acc] (if (zero? n) acc (recur (dec n) (+ acc n)))) 5 0)"])

(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)"]
  ["for :let+:when"     "[4 6 8]" "(for [x (range 5) :let [y (* x 2)] :when (> y 3)] y)"]
  ["for multi :when"    "[[1 :a] [1 :b]]" "(for [x [0 1] :when (odd? x) y [:a :b]] [x y])"]
  ["for destructure"    "[3 7]"   "(for [[a b] [[1 2] [3 4]]] (+ a b))"]
  ["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 :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))"]
  ["-> 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))"]
  ["doto returns subject" "5"    "(let [a (doto (atom 0) (reset! 5))] @a)"])
