diff --git a/src/jolt/evaluator.janet b/src/jolt/evaluator.janet index 21851c8..3f252c8 100644 --- a/src/jolt/evaluator.janet +++ b/src/jolt/evaluator.janet @@ -788,19 +788,20 @@ "loop*" (let [bind-vec (in form 1) body (tuple/slice form 2) init-vals @[] - sym-names @[]] + patterns @[]] (var i 0) (while (< i (length bind-vec)) (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)) (var loop-fn nil) (set loop-fn (fn [& args] (var loop-bindings @{}) (table/setproto loop-bindings bindings) (var j 0) - (each sn sym-names - (bind-put loop-bindings sn (args j)) + (each pat patterns + (destructure-bind ctx loop-bindings pat (in args j)) (++ j)) (put loop-bindings :jolt/loop-fn loop-fn) (var result nil) diff --git a/test/spec/control-flow-spec.janet b/test/spec/control-flow-spec.janet new file mode 100644 index 0000000..81519fc --- /dev/null +++ b/test/spec/control-flow-spec.janet @@ -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))"]) diff --git a/test/spec/destructuring-spec.janet b/test/spec/destructuring-spec.janet new file mode 100644 index 0000000..f33441e --- /dev/null +++ b/test/spec/destructuring-spec.janet @@ -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)"]) diff --git a/test/spec/functions-spec.janet b/test/spec/functions-spec.janet new file mode 100644 index 0000000..ef7c24e --- /dev/null +++ b/test/spec/functions-spec.janet @@ -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)"])