diff --git a/src/jolt/core.janet b/src/jolt/core.janet index 71e13f1..6e6d2f0 100644 --- a/src/jolt/core.janet +++ b/src/jolt/core.janet @@ -31,6 +31,10 @@ (defn core-even? [n] (= 0 (% n 2))) (defn core-odd? [n] (not= 0 (% n 2))) +(defn core-integer? [x] (and (number? x) (= x (math/floor x)))) +(defn core-boolean? [x] (or (= x true) (= x false))) +(defn core-list? [x] (and (array? x) (not (get x :jolt/type)))) + (defn core-empty? [coll] (if (nil? coll) true (if (set? coll) (= 0 (coll :cnt)) @@ -74,6 +78,10 @@ (defn core-max [& args] (apply max args)) (defn core-min [& args] (apply min args)) +(defn core-abs [x] (if (neg? x) (- 0 x) x)) +(defn core-rand [] (math/random)) +(defn core-rand-int [n] (math/floor (* (math/random) n))) + # ============================================================ # Comparison # ============================================================ @@ -210,11 +218,8 @@ (if (= 0 (length r)) nil r))) (defn core-cons [x coll] - (if (nil? coll) - @[x] - (if (tuple? coll) - (tuple/slice (tuple ;(array/insert (array/slice coll) 0 x))) - (array/insert coll 0 x)))) + "Returns a lazy-seq compatible cons cell [first, rest-thunk]." + @[x (fn [] coll)]) (defn core-seq [coll] (if (or (nil? coll) (and (or (tuple? coll) (array? coll)) (= 0 (length coll)))) @@ -284,10 +289,27 @@ # Sequence operations # ============================================================ +(defn- realize-for-iteration [c] + "If c is a lazy-seq, traverse and return all elements as an array. + Otherwise return c as-is. Warning: will loop on infinite lazy-seqs." + (if (lazy-seq? c) + (do + (var items @[]) + (var cur c) + (while (not (nil? (ls-first cur))) + (array/push items (ls-first cur)) + (set cur (ls-rest cur))) + items) + c)) + (defn core-map [f & colls] - (let [first-coll (colls 0) + (let [realized (map realize-for-iteration colls) + first-coll (realized 0) result (if (= 1 (length colls)) - (array ;(map f first-coll)) + (do + (var res @[]) + (each x first-coll (array/push res (f x))) + res) (do (var res @[]) (var idxs @{}) @@ -297,7 +319,7 @@ (var args @[]) (var i 0) (while (< i (length colls)) - (let [c (colls i) j (idxs i)] + (let [c (realized i) j (idxs i)] (if (>= j (length c)) (do (set done true) (break)) (array/push args (c j)))) @@ -341,19 +363,37 @@ (error "Wrong number of args passed to: reduce")))) (defn core-take [n coll] - (var result @[]) - (var i 0) - (var c (if (lazy-seq? coll) (realize-ls coll) coll)) - (while (and (< i n) (< i (length c))) - (array/push result (c i)) - (++ i)) - (if (tuple? c) (tuple/slice (tuple ;result)) result)) + (if (lazy-seq? coll) + (do + (var result @[]) + (var cur coll) + (var i 0) + (while (and (< i n) (not (nil? (ls-first cur)))) + (array/push result (ls-first cur)) + (set cur (ls-rest cur)) + (++ i)) + result) + (do + (var result @[]) + (var i 0) + (while (and (< i n) (< i (length coll))) + (array/push result (coll i)) + (++ i)) + (if (tuple? coll) (tuple/slice (tuple ;result)) result)))) (defn core-drop [n coll] - (var c (if (lazy-seq? coll) (realize-ls coll) coll)) - (if (tuple? c) - (tuple/slice c (min n (length c))) - (array/slice c (min n (length c))))) + (if (lazy-seq? coll) + (do + (var cur coll) + (var i 0) + (while (and (< i n) (ls-first cur)) + (set cur (ls-rest cur)) + (++ i)) + (if (nil? (ls-first cur)) nil cur)) + (do + (if (tuple? coll) + (tuple/slice coll (min n (length coll))) + (array/slice coll (min n (length coll))))))) (defn core-take-while [pred coll] (var result @[]) @@ -370,54 +410,130 @@ (tuple/slice c start) (array/slice c start))) +(defn coll->cells [c] + "Convert a seqable to lazy-seq cell chain: nil or [first, rest-thunk]. + If the value is a function, call it and use the result. + If the result is already a cell (array of [val, function]), return it directly." + (if (nil? c) nil + (if (function? c) + (let [r (c)] + (if (and (indexed? r) (= 2 (length r)) (function? (in r 1))) + r + (coll->cells r))) + (if (lazy-seq? c) (realize-ls c) + (if (indexed? c) + (if (= 0 (length c)) nil + (let [f (in c 0) + rest (if (> (length c) 1) + (if (tuple? c) (tuple/slice c 1) (array/slice c 1)) + nil)] + @[f (fn [] (coll->cells rest))])) + nil))))) + (defn core-concat [& colls] - (var result @[]) - (each c colls - (def c-realized (if (lazy-seq? c) (realize-ls c) c)) - (each x c-realized (array/push result x))) - result) + "Lazy concatenation — returns a lazy-seq that yields elements one at a time. + Supports self-referencing sequences like fib-seq." + (var cs (if (tuple? colls) (array/slice colls) colls)) + (var cur nil) + (defn next-cell [] + (var cell (coll->cells cur)) + (while (and (nil? cell) (not (nil? cs)) (> (length cs) 0)) + (set cur (in cs 0)) + (set cs (array/slice cs 1)) + (set cell (coll->cells cur))) + (if (nil? cell) nil + @[(in cell 0) + (fn [] + (set cur (in cell 1)) + (next-cell))])) + (make-lazy-seq next-cell)) (defn core-reverse [coll] - (var c (if (lazy-seq? coll) (realize-ls coll) coll)) - (var result @[]) - (var i (dec (length c))) - (while (>= i 0) - (array/push result (c i)) - (-- i)) - (if (tuple? c) (tuple/slice (tuple ;result)) result)) + (if (nil? coll) @[] + (if (lazy-seq? coll) + (do + (var result @[]) + (var cur coll) + (while (not (nil? (ls-first cur))) + (array/push result (ls-first cur)) + (set cur (ls-rest cur))) + (var reversed @[]) + (var i (dec (length result))) + (while (>= i 0) + (array/push reversed (in result i)) + (-- i)) + reversed) + (do + (var result @[]) + (var i (dec (length coll))) + (while (>= i 0) + (array/push result (coll i)) + (-- i)) + (if (tuple? coll) (tuple/slice (tuple ;result)) result))))) (defn core-nth "Return the nth element of a sequential collection." [coll idx &opt default] - (var c (if (lazy-seq? coll) (realize-ls coll) coll)) - (if (and (>= idx 0) (< idx (length c))) - (in c idx) - (if (nil? default) - (error (string "Index " idx " out of bounds, length: " (length c))) - default))) + (if (lazy-seq? coll) + (do + (var cur coll) + (var i 0) + (while (and (< i idx) (ls-first cur)) + (set cur (ls-rest cur)) + (++ i)) + (if (ls-first cur) (ls-first cur) + (if (nil? default) + (error (string "Index " idx " out of bounds")) + default))) + (do + (var c (if (lazy-seq? coll) (realize-ls coll) coll)) + (if (and (>= idx 0) (< idx (length c))) + (in c idx) + (if (nil? default) + (error (string "Index " idx " out of bounds, length: " (length c))) + default))))) (defn core-sort [coll] - (var c (if (lazy-seq? coll) (realize-ls coll) coll)) - (let [arr (if (tuple? c) (array/slice c) c) - sorted (sort arr)] - (if (tuple? c) (tuple/slice (tuple ;sorted)) sorted))) + (if (nil? coll) @[] + (if (lazy-seq? coll) + (do + (var items @[]) + (var cur coll) + (while (not (nil? (ls-first cur))) + (array/push items (ls-first cur)) + (set cur (ls-rest cur))) + (sort items)) + (let [arr (if (tuple? coll) (array/slice coll) coll) + sorted (sort arr)] + (if (tuple? coll) (tuple/slice (tuple ;sorted)) sorted))))) (defn core-sort-by [keyfn coll] + (if (nil? coll) (break @[])) (var c (if (lazy-seq? coll) (realize-ls coll) coll)) (let [arr (if (tuple? c) (array/slice c) c) sorted (sort-by keyfn arr)] (if (tuple? c) (tuple/slice (tuple ;sorted)) sorted))) (defn core-distinct [coll] - (var seen @{}) - (var result @[]) - (var c (if (lazy-seq? coll) (realize-ls coll) coll)) - (each x c - (if (nil? (seen x)) - (do - (put seen x true) - (array/push result x)))) - (if (tuple? c) (tuple/slice (tuple ;result)) result)) + (if (nil? coll) @[] + (if (lazy-seq? coll) + (do + (var seen @{}) + (var result @[]) + (var cur coll) + (while (not (nil? (ls-first cur))) + (let [x (ls-first cur)] + (if (nil? (seen x)) + (do (put seen x true) (array/push result x)))) + (set cur (ls-rest cur))) + result) + (do + (var seen @{}) + (var result @[]) + (each x coll + (if (nil? (seen x)) + (do (put seen x true) (array/push result x)))) + (if (tuple? coll) (tuple/slice (tuple ;result)) result))))) (defn core-group-by [f coll] (var result @{}) @@ -590,15 +706,18 @@ (defn core-lazy-seq [& body] @[{:jolt/type :symbol :ns nil :name "make-lazy-seq"} - @[{:jolt/type :symbol :ns nil :name "fn*"} [] ;body]]) + @[{:jolt/type :symbol :ns nil :name "fn*"} [] + @[{:jolt/type :symbol :ns nil :name "coll->cells"} + @[{:jolt/type :symbol :ns nil :name "do"} ;body]]]]) (defn core-lazy-cat [& colls] - "Macro: (lazy-cat & colls) — concatenate lazy sequences, wrapping each coll in lazy-seq." - (def result @[]) - (array/push result {:jolt/type :symbol :ns nil :name "concat"}) + "Macro: (lazy-cat & colls) — concatenate lazy sequences, wrapping each coll in lazy-seq. + concat is now lazy, so no outer make-lazy-seq wrapping is needed." + (def concat-form @[]) + (array/push concat-form {:jolt/type :symbol :ns nil :name "concat"}) (each c colls - (array/push result @[{:jolt/type :symbol :ns nil :name "lazy-seq"} c])) - result) + (array/push concat-form @[{:jolt/type :symbol :ns nil :name "lazy-seq"} c])) + concat-form) (defn core-set [coll] (apply core-hash-set (if (tuple? coll) (array/slice coll) coll))) @@ -727,31 +846,102 @@ # Atom # ============================================================ -(defn core-atom [val] - @{:jolt/type :jolt/atom :value val :watches @{}}) +(defn core-atom + "Create an atom. Accepts optional :validator fn and :meta map." + [val & opts] + (var atm @{:jolt/type :jolt/atom :value val :watches @{} :validator nil}) + (var i 0) + (while (< i (length opts)) + (case (opts i) + :validator (put atm :validator (opts (+ i 1))) + :meta (let [m (opts (+ i 1))] + (var meta-tab @{}) + (each k (keys m) (put meta-tab k (get m k))) + (table/setproto atm meta-tab) + (put atm :jolt/meta m))) + (+= i 2)) + atm) (defn core-atom? [x] (and (table? x) (= :jolt/atom (x :jolt/type)))) (defn core-deref [ref] (cond - # Jolt atom (and (table? ref) (= :jolt/atom (ref :jolt/type))) (ref :value) - # Jolt var (from types.janet) (and (table? ref) (= :jolt/var (ref :jolt/type))) (ref :root) - # default: return as-is ref)) +(defn- atom-validate + "Call validator on atm. Returns the value if valid, errors otherwise." + [atm val] + (let [v (atm :validator)] + (if v + (if (v val) val + (error "Validator rejected value")) + val))) + +(defn- atom-notify-watches + [atm old-val new-val] + (loop [[k w] :pairs (atm :watches)] + (w k atm old-val new-val))) + (defn core-reset! [atm val] - (put atm :value val) - val) + (let [old-val (atm :value)] + (atom-validate atm val) + (put atm :value val) + (atom-notify-watches atm old-val val) + val)) (defn core-swap! [atm f & args] - (let [new-val (apply f (atm :value) args)] - (put atm :value new-val) - new-val)) + (var old-val (atm :value)) + (var new-val (apply f old-val args)) + (atom-validate atm new-val) + (put atm :value new-val) + (atom-notify-watches atm old-val new-val) + new-val) + +(defn core-reset-vals! [atm val] + (let [old-val (atm :value)] + (atom-validate atm val) + (put atm :value val) + (atom-notify-watches atm old-val val) + [old-val val])) + +(defn core-swap-vals! [atm f & args] + (var old-val (atm :value)) + (var new-val (apply f old-val args)) + (atom-validate atm new-val) + (put atm :value new-val) + (atom-notify-watches atm old-val new-val) + [old-val new-val]) + +(defn core-compare-and-set! [atm old-val new-val] + (if (= old-val (atm :value)) + (do + (atom-validate atm new-val) + (put atm :value new-val) + (atom-notify-watches atm old-val new-val) + true) + false)) + +(defn core-set-validator! [atm validator-fn] + (put atm :validator validator-fn) + nil) + +(defn core-get-validator [atm] + (atm :validator)) + +(defn core-add-watch [atm key watch-fn] + (let [watches (atm :watches)] + (put watches key watch-fn) + atm)) + +(defn core-remove-watch [atm key] + (let [watches (atm :watches)] + (put watches key nil) + atm)) # ============================================================ # Threading macros (as regular functions? No, as macros in Clojure) @@ -773,6 +963,53 @@ (put gensym_counter :val (+ n 1)) {:jolt/type :symbol :ns nil :name (string prefix-string n)}) +(defn core-cond + "Macro: (cond test1 expr1 test2 expr2 ... :else default) + -> (if test1 expr1 (if test2 expr2 ...))" + [& clauses] + (defn build [cls] + (if (= 0 (length cls)) + nil + (let [t (first cls)] + (if (= :else t) + (if (> (length cls) 1) (in cls 1) nil) + (if (< (length cls) 2) + (error "cond requires an even number of forms") + (let [e (in cls 1)] + @[{:jolt/type :symbol :ns nil :name "if"} + t e + (build (tuple/slice cls 2))])))))) + (build clauses)) + +(defn core-case + "Macro: (case expr val1 result1 ... default) + Supports single values, lists of values (one-of-many), and symbols." + [expr & clauses] + (def g (gensym)) + (defn make-const [c] + (if (and (struct? c) (= :symbol (c :jolt/type))) + @[{:jolt/type :symbol :ns nil :name "quote"} c] + c)) + (defn make-test [c] + (if (array? c) + (let [or-args @[{:jolt/type :symbol :ns nil :name "or"}]] + (each v c + (array/push or-args @[{:jolt/type :symbol :ns nil :name "="} g (make-const v)])) + or-args) + @[{:jolt/type :symbol :ns nil :name "="} g (make-const c)])) + (defn build [cls] + (if (= 0 (length cls)) + nil + (if (= 1 (length cls)) + (first cls) + (let [c (first cls) + r (first (tuple/slice cls 1))] + @[{:jolt/type :symbol :ns nil :name "if"} + (make-test c) + r + (build (tuple/slice cls 2))])))) + @[{:jolt/type :symbol :ns nil :name "let*"} @[g expr] (build clauses)]) + (defn core-when "Macro: (when test & body) -> (if test (do body...))" [test & body] @@ -872,6 +1109,243 @@ (array/push result sym) result) +(defn core-if-not + "Macro: (if-not test then else?) -> (if (not test) then else?)" + [test then-form & else-forms] + @[{:jolt/type :symbol :ns nil :name "if"} + @[{:jolt/type :symbol :ns nil :name "not"} test] + then-form + ;else-forms]) + +(defn core-when-first + "Macro: (when-first [sym coll] & body) -> (when-let [sym (first coll)] body...)" + [bindings & body] + (def sym (in bindings 0)) + (def coll-form (in bindings 1)) + @[{:jolt/type :symbol :ns nil :name "when-let"} + @[sym @[{:jolt/type :symbol :ns nil :name "first"} coll-form]] + ;body]) + +(defn core-condp + "Macro: (condp pred expr clause1 val1 ... default)" + [pred expr & clauses] + (def g (gensym)) + (defn build [cls] + (if (= 0 (length cls)) + nil + (if (= 1 (length cls)) + (first cls) + (let [c (first cls) + v (first (tuple/slice cls 1))] + @[{:jolt/type :symbol :ns nil :name "if"} + (if (and (struct? c) (= :symbol (c :jolt/type)) (= ":>>" (c :name))) + @[v g] + @[pred c g]) + v + (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." + [bindings body] + (defn parse-groups [bvec] + (var groups @[]) + (var i 0) + (while (< i (length bvec)) + (def bind (bvec i)) + (def coll (bvec (+ i 1))) + (def mods @[]) + (+= i 2) + (while (and (< i (length bvec)) (keyword? (bvec i))) + (case (bvec i) + :when (do (array/push mods @[{:jolt/type :symbol :ns nil :name "when"} (bvec (+ i 1))]) (+= i 2)) + :let (do (array/push mods @[{:jolt/type :symbol :ns nil :name "let"} (bvec (+ i 1))]) (+= i 2)) + :while (do (+= i 2)) + (do (+= i 1)))) + (array/push groups @[bind coll mods])) + groups) + (defn wrap-mods [mods inner-form] + (if (= 0 (length mods)) + inner-form + (let [m (in mods (- (length mods) 1)) + rest-mods (array/slice mods 0 (- (length mods) 1)) + kind (get (m 0) :name)] + (wrap-mods rest-mods + (if (= kind "when") + @[{:jolt/type :symbol :ns nil :name "if"} (m 1) + @[{:jolt/type :symbol :ns nil :name "list"} inner-form] @[]] + @[{:jolt/type :symbol :ns nil :name "let*"} (m 1) inner-form]))))) + (defn build [group-idx groups] + (if (>= group-idx (length groups)) + body + (let [g (in groups group-idx) + my-bind (in g 0) + my-coll (in g 1) + my-mods (in g 2) + inner (build (+ group-idx 1) groups) + inner-form (wrap-mods my-mods inner) + is-last (= group-idx (- (length groups) 1)) + has-mods (> (length my-mods) 0)] + (if (and is-last (not has-mods)) + @[{:jolt/type :symbol :ns nil :name "map"} + @[{:jolt/type :symbol :ns nil :name "fn"} [my-bind] inner-form] + my-coll] + @[{:jolt/type :symbol :ns nil :name "mapcat"} + @[{:jolt/type :symbol :ns nil :name "fn"} [my-bind] inner-form] + my-coll])))) + (if (>= (length bindings) 2) + (build 0 (parse-groups bindings)) + body)) + +(defn core-thread-first + "Macro: (-> x & forms) — thread first" + [x & forms] + (if (= 0 (length forms)) x + (let [f (first forms) + rest-forms (tuple/slice forms 1)] + (if (array? f) + (apply core-thread-first [(let [arr (array/slice f)] + (array/insert arr 1 x) + arr) ;rest-forms]) + (apply core-thread-first [@[f x] ;rest-forms]))))) + +(defn core-thread-last + "Macro: (->> x & forms) — thread last" + [x & forms] + (if (= 0 (length forms)) x + (let [f (first forms) + rest-forms (tuple/slice forms 1)] + (if (array? f) + (apply core-thread-last [(let [arr (array/slice f)] + (array/push arr x) + arr) ;rest-forms]) + (apply core-thread-last [@[f x] ;rest-forms]))))) + +(defn core-some-> + "Macro: (some-> expr & forms) — thread first, stop at nil" + [expr & forms] + (if (= 0 (length forms)) expr + (let [f (first forms) + rest-forms (tuple/slice forms 1)] + @[{:jolt/type :symbol :ns nil :name "let*"} + @[{:jolt/type :symbol :ns nil :name "some->__x"} expr] + @[{:jolt/type :symbol :ns nil :name "if"} + @[{:jolt/type :symbol :ns nil :name "some?"} + {:jolt/type :symbol :ns nil :name "some->__x"}] + @[{:jolt/type :symbol :ns nil :name "let*"} + @[{:jolt/type :symbol :ns nil :name "some->__x"} + (if (array? f) + (let [arr (array/slice f)] + (array/insert arr 1 {:jolt/type :symbol :ns nil :name "some->__x"}) + arr) + @[f {:jolt/type :symbol :ns nil :name "some->__x"}])] + (apply core-some-> [{:jolt/type :symbol :ns nil :name "some->__x"} ;rest-forms])] + nil]]))) + +(defn core-some->> + "Macro: (some->> expr & forms) — thread last, stop at nil" + [expr & forms] + (if (= 0 (length forms)) expr + (let [f (first forms) + rest-forms (tuple/slice forms 1)] + @[{:jolt/type :symbol :ns nil :name "let*"} + @[{:jolt/type :symbol :ns nil :name "some->__x"} expr] + @[{:jolt/type :symbol :ns nil :name "if"} + @[{:jolt/type :symbol :ns nil :name "some?"} + {:jolt/type :symbol :ns nil :name "some->__x"}] + @[{:jolt/type :symbol :ns nil :name "let*"} + @[{:jolt/type :symbol :ns nil :name "some->__x"} + (if (array? f) + (let [arr (array/slice f)] + (array/push arr {:jolt/type :symbol :ns nil :name "some->__x"}) + arr) + @[f {:jolt/type :symbol :ns nil :name "some->__x"}])] + (apply core-some->> [{:jolt/type :symbol :ns nil :name "some->__x"} ;rest-forms])] + nil]]))) + +(defn core-cond-> + "Macro: (cond-> expr test form ...) — thread first only when test is true" + [expr & clauses] + (def g (gensym)) + (defn build [cls result-form] + (if (= 0 (length cls)) + result-form + (let [t (first cls) + f (in cls 1) + f-call (if (array? f) + (let [arr (array/slice f)] + (array/insert arr 1 result-form) + arr) + @[f result-form])] + (build (tuple/slice cls 2) + @[{:jolt/type :symbol :ns nil :name "if"} + t + f-call + result-form])))) + @[{:jolt/type :symbol :ns nil :name "let*"} @[g expr] (build clauses g)]) + +(defn core-cond->> + "Macro: (cond->> expr test form ...) — thread last only when test is true" + [expr & clauses] + (def g (gensym)) + (defn build [cls result-form] + (if (= 0 (length cls)) + result-form + (let [t (first cls) + f (in cls 1) + f-call (if (array? f) + (let [arr (array/slice f)] + (array/push arr result-form) + arr) + @[f result-form])] + (build (tuple/slice cls 2) + @[{:jolt/type :symbol :ns nil :name "if"} + t + f-call + result-form])))) + @[{:jolt/type :symbol :ns nil :name "let*"} @[g expr] (build clauses g)]) + +(defn core-as-> + "Macro: (as-> expr name & forms) — bind name to expr, thread through forms" + [expr name & forms] + (defn build [fs acc] + (if (= 0 (length fs)) + acc + (let [f (first fs)] + @[{:jolt/type :symbol :ns nil :name "let*"} + @[name acc] + (build (tuple/slice fs 1) f)]))) + (build forms expr)) + (defn core-push-thread-bindings [b] (push-thread-bindings b)) (defn core-pop-thread-bindings [] (pop-thread-bindings)) @@ -1228,6 +1702,9 @@ "neg?" core-neg? "even?" core-even? "odd?" core-odd? + "integer?" core-integer? + "boolean?" core-boolean? + "list?" core-list? "empty?" core-empty? "every?" core-every? "+" core-+ @@ -1241,6 +1718,9 @@ "quot" core-quot "max" core-max "min" core-min + "abs" core-abs + "rand" core-rand + "rand-int" core-rand-int "=" core-= "not=" core-not= "<" core-< @@ -1308,6 +1788,7 @@ "disj" core-disj "lazy-seq" core-lazy-seq "lazy-cat" core-lazy-cat + "coll->cells" coll->cells "make-lazy-seq" make-lazy-seq "str" core-str "name" core-name @@ -1357,16 +1838,38 @@ "deref" core-deref "reset!" core-reset! "swap!" core-swap! + "swap-vals!" core-swap-vals! + "reset-vals!" core-reset-vals! + "compare-and-set!" core-compare-and-set! + "set-validator!" core-set-validator! + "get-validator" core-get-validator + "add-watch" core-add-watch + "remove-watch" core-remove-watch "not" core-not "and" core-and "or" core-or + "cond" core-cond + "case" core-case + "for" core-for "when" core-when "when-not" core-when-not + "if-not" core-if-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-> + "some->>" core-some->> + "cond->" core-cond-> + "cond->>" core-cond->> + "as->" core-as-> "defn" core-defn "defn-" core-defn- "derive" core-derive @@ -1434,7 +1937,7 @@ (defn core-macro-names "Set of core binding names that are macros." [] - @{"and" true "or" 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 "comment" true "binding" true "lazy-seq" true "lazy-cat" true}) + @{"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 "comment" true "binding" true "lazy-seq" true "lazy-cat" true "if-not" true "when-first" true "condp" true "dotimes" true "while" true "some->" true "some->>" true "cond->" true "cond->>" true "as->" true "->" true "->>" true}) (def init-core! (fn [& args] diff --git a/src/jolt/evaluator.janet b/src/jolt/evaluator.janet index 031d45d..a70d7e4 100644 --- a/src/jolt/evaluator.janet +++ b/src/jolt/evaluator.janet @@ -261,14 +261,16 @@ (if (> (length form) 3) (eval-form ctx bindings (in form 3)) nil))) "def" (let [raw-name (in form 1) name-sym (unwrap-meta-name raw-name) - val (eval-form ctx bindings (in form 2)) # Check for ^:dynamic metadata dynamic? (and (array? raw-name) (> (length raw-name) 0) (sym-name? (first raw-name) "with-meta") (= :dynamic (last raw-name))) ns-name (ctx-current-ns ctx) - ns (ctx-find-ns ctx ns-name)] - (def v (ns-intern ns (name-sym :name) val)) + ns (ctx-find-ns ctx ns-name) + # Create var first (unbound) so self-referencing defs resolve + v (ns-intern ns (name-sym :name)) + val (eval-form ctx bindings (in form 2))] + (bind-root v val) (when dynamic? (put v :dynamic true)) (var-get v)) diff --git a/src/jolt/phm.janet b/src/jolt/phm.janet index 516948a..3952f0e 100644 --- a/src/jolt/phm.janet +++ b/src/jolt/phm.janet @@ -114,45 +114,61 @@ m) # ============================================================ -# LazySeq — realize-once lazy sequence +# LazySeq — cell-by-cell lazy sequence (Clojure-compatible) # ============================================================ +# Model: thunk returns nil (empty) or [first-val, rest-thunk] pair. +# Each step produces one element + thunk for the rest. +# Supports self-referencing sequences like fib-seq. (defn lazy-seq? "Check if x is a LazySeq." [x] (and (table? x) (= :jolt/lazy-seq (x :jolt/type)))) +(defn make-lazy-seq [thunk] + @{:jolt/type :jolt/lazy-seq :fn thunk :realized false :val nil}) + (defn realize-ls - "Force a LazySeq. Returns the realized value, caching it." + "Force a LazySeq cell. Returns nil (empty) or [first-val, rest-thunk]." [ls] (if (get ls :realized) (ls :val) - (let [v ((ls :fn)) - vf (if (lazy-seq? v) (realize-ls v) v)] + (let [v ((ls :fn))] (put ls :realized true) - (put ls :val vf) - vf))) + (put ls :val v) + v))) (defn ls-first [ls] - (let [val (realize-ls ls)] - (if (nil? val) nil - (if (and (indexed? val) (> (length val) 0)) (in val 0) nil)))) + (let [cell (realize-ls ls)] + (if (nil? cell) nil (in cell 0)))) (defn ls-rest [ls] - (let [val (realize-ls ls)] - (if (nil? val) nil - (if (indexed? val) (if (tuple? val) (tuple/slice val 1) (array/slice val 1)) nil)))) + (let [cell (realize-ls ls)] + (if (nil? cell) nil + (let [rt (in cell 1)] + (if (nil? rt) nil (make-lazy-seq rt)))))) (defn ls-seq [ls] - (let [val (realize-ls ls)] - (when val (if (tuple? val) (tuple/slice val) (if (array? val) (array/slice val) val))))) + (var result @[]) + (var cur ls) + (while (not (nil? cur)) + (let [cell (realize-ls cur)] + (if (nil? cell) (break)) + (array/push result (in cell 0)) + (let [rt (in cell 1)] + (set cur (if (nil? rt) nil (make-lazy-seq rt)))))) + (if (= 0 (length result)) nil result)) (defn ls-count [ls] - (let [val (realize-ls ls)] - (if (nil? val) 0 (length val)))) - -(defn make-lazy-seq [thunk] - @{:jolt/type :jolt/lazy-seq :fn thunk :realized false :val nil}) + (var cnt 0) + (var cur ls) + (while (not (nil? cur)) + (let [cell (realize-ls cur)] + (if (nil? cell) (break)) + (++ cnt) + (let [rt (in cell 1)] + (set cur (if (nil? rt) nil (make-lazy-seq rt)))))) + cnt) # ============================================================ # PersistentHashSet — backed by PersistentHashMap diff --git a/test/clojure-atom-test.janet b/test/clojure-atom-test.janet new file mode 100644 index 0000000..48c821c --- /dev/null +++ b/test/clojure-atom-test.janet @@ -0,0 +1,154 @@ +# Ported from clojure/test_clojure/atoms.clj + systematic atom tests +(use ../src/jolt/api) +(defn ct-eval [ctx s] (eval-string ctx s)) + +(print "Ported Atom Tests") + +# --- atom creation --- +(print "test atom creation...") +(let [ctx (init)] + (assert (= true (ct-eval ctx "(atom? (atom 0))")) "atom?") + (assert (= false (ct-eval ctx "(atom? nil)")) "atom? nil") + (assert (= false (ct-eval ctx "(atom? 42)")) "atom? number") + (assert (= false (ct-eval ctx "(atom? \"x\")")) "atom? string") + (assert (= 42 (ct-eval ctx "(deref (atom 42))")) "deref atom") + (assert (= 99 (ct-eval ctx "(let [a (atom 99)] @a)")) "@ deref macro")) +(print " ok") + +# --- deref on non-atoms --- +(print "test deref...") +(let [ctx (init)] + (assert (= 1 (ct-eval ctx "(deref 1)")) "deref non-atom passes through") + (assert (= "x" (ct-eval ctx "(deref \"x\")")) "deref string passes through") + (assert (= nil (ct-eval ctx "(deref nil)")) "deref nil")) +(print " ok") + +# --- reset! --- +(print "test reset!...") +(let [ctx (init)] + (assert (= :b (ct-eval ctx "(let [a (atom :a)] (reset! a :b))")) "reset! returns new") + (assert (= 42 (ct-eval ctx "(let [a (atom 0)] (reset! a 42) @a)")) "reset! updates value") + (assert (= true (ct-eval ctx "(= [1 1] (let [a (atom 0)] [(reset! a 1) @a]))")) "reset! returns new, value changed")) +(print " ok") + +# --- swap! --- +(print "test swap!...") +(let [ctx (init)] + (assert (= 1 (ct-eval ctx "(let [a (atom 0)] (swap! a inc))")) "swap! inc returns new") + (assert (= 2 (ct-eval ctx "(let [a (atom 0)] (swap! a + 2))")) "swap! + 2") + (assert (= 3 (ct-eval ctx "(let [a (atom 0)] (swap! a + 1 2) @a)")) "swap! + 1 2") + (assert (= 6 (ct-eval ctx "(let [a (atom 0)] (swap! a + 1 2 3) @a)")) "swap! + 1 2 3") + (assert (= 10 (ct-eval ctx "(let [a (atom 0)] (swap! a + 1 2 3 4) @a)")) "swap! + 1 2 3 4")) +(print " ok") + +# --- swap-vals! (returns [old new]) --- +(print "test swap-vals!...") +(let [ctx (init)] + (assert (= true (ct-eval ctx + "(= [0 1] (let [a (atom 0)] (swap-vals! a inc)))")) "swap-vals! inc") + (assert (= true (ct-eval ctx + "(= [1 2] (let [a (atom 1)] (swap-vals! a inc)))")) "swap-vals! inc from 1") + (assert (= 2 (ct-eval ctx + "(let [a (atom 1)] (swap-vals! a inc) @a)")) "swap-vals! updates value")) +(print " ok") + +# --- swap-vals! with extra args --- +(print "test swap-vals! arities...") +(let [ctx (init)] + (assert (= true (ct-eval ctx + "(= [0 1] (let [a (atom 0)] (swap-vals! a + 1)))")) "swap-vals! + 1") + (assert (= true (ct-eval ctx + "(= [1 3] (let [a (atom 0)] (swap-vals! a + 1) (swap-vals! a + 1 1)))")) "swap-vals! + 1 1") + (assert (= true (ct-eval ctx + "(= [3 6] (let [a (atom 0)] (swap-vals! a + 1) (swap-vals! a + 1 1) (swap-vals! a + 1 1 1)))")) "swap-vals! + 1 1 1") + (assert (= true (ct-eval ctx + "(= [6 10] (let [a (atom 0)] (swap-vals! a + 1) (swap-vals! a + 1 1) (swap-vals! a + 1 1 1) (swap-vals! a + 1 1 1 1)))")) "swap-vals! + 1 1 1 1")) +(print " ok") + +# --- reset-vals! (returns [old new]) --- +(print "test reset-vals!...") +(let [ctx (init)] + (assert (= true (ct-eval ctx + "(= [0 :b] (let [a (atom 0)] (reset-vals! a :b)))")) "reset-vals! returns old new") + (assert (= true (ct-eval ctx + "(= [:b 42] (let [a (atom 0)] (reset-vals! a :b) (reset-vals! a 42)))")) "reset-vals! chain") + (assert (= 42 (ct-eval ctx + "(let [a (atom 0)] (reset-vals! a :b) (reset-vals! a 42) @a)")) "reset-vals! updates value")) +(print " ok") + +# --- compare-and-set! --- +(print "test compare-and-set!...") +(let [ctx (init)] + (assert (= true (ct-eval ctx + "(let [a (atom 0)] (compare-and-set! a 0 1))")) "CAS true match") + (assert (= true (ct-eval ctx + "(= [true 1] (let [a (atom 0)] [(compare-and-set! a 0 1) @a]))")) "CAS true + value changed") + (assert (= true (ct-eval ctx + "(= [false 0] (let [a (atom 0)] [(compare-and-set! a 1 2) @a]))")) "CAS false no match") + (assert (= true (ct-eval ctx + "(= [false 1] (let [a (atom 0)] (compare-and-set! a 0 1) [(compare-and-set! a 0 2) @a]))")) "CAS false after change")) +(print " ok") + +# --- validator --- +(print "test validator...") +(let [ctx (init)] + (assert (= 42 (ct-eval ctx + "(let [a (atom 0 :validator pos?)] (reset! a 42) @a)")) "validator passes") + (assert (= true (ct-eval ctx + "(= false (try (let [a (atom 0 :validator pos?)] (reset! a -1) true) (catch Exception e false)))")) "validator blocks invalid reset!") + (assert (= true (ct-eval ctx + "(= false (try (let [a (atom 0 :validator pos?)] (swap! a (fn [x] -1)) true) (catch Exception e false)))")) "validator blocks invalid swap!") + (assert (= nil (ct-eval ctx "(set-validator! (atom 0) pos?)")) "set-validator! returns nil") + (assert (= nil (ct-eval ctx "(get-validator (atom 0))")) "get-validator nil default") + (assert (= true (ct-eval ctx + "(= even? (do (def a (atom 0)) (set-validator! a even?) (get-validator a)))")) "get-validator returns set fn")) +(print " ok") + +# --- watches --- +(print "test watches...") +(let [ctx (init)] + (assert (= true (ct-eval ctx + "(empty? (:watches (atom 0)))")) "atom starts with empty watches") + (assert (= true (ct-eval ctx + "(= [0 42] (let [a (atom 0) + w (atom nil)] + (add-watch a :my-key (fn [k ref old new] (reset! w [old new]))) + (swap! a + 42) + @w))")) "add-watch triggers on swap!") + (assert (= true (ct-eval ctx + "(= :unchanged (let [a (atom 0) + w (atom :unchanged)] + (add-watch a :x (fn [_ _ _ _] (reset! w :fired))) + (remove-watch a :x) + (swap! a inc) + @w))")) "remove-watch stops notification") + (assert (= true (ct-eval ctx + "(= 2 (let [a (atom 0)] + (add-watch a :foo (fn [_ _ _ _] nil)) + (add-watch a :bar (fn [_ _ _ _] nil)) + (count (:watches a))))")) "multiple watches") + (assert (= true (ct-eval ctx + "(= 0 (let [a (atom 0)] + (add-watch a :foo (fn [_ _ _ _] nil)) + (remove-watch a :foo) + (count (:watches a))))")) "remove-watch clears count") + (assert (= true (ct-eval ctx + "(= 2 (let [a (atom 0) + fired (atom [])] + (add-watch a :w1 (fn [_ _ o n] (swap! fired conj [:w1 o n]))) + (add-watch a :w2 (fn [_ _ o n] (swap! fired conj [:w2 o n]))) + (reset! a 99) + (count @fired)))")) "multiple watches both fire")) +(print " ok") + +# --- metadata on atoms --- +(print "test atom metadata...") +(let [ctx (init)] + (assert (= nil (ct-eval ctx "(meta (atom 0))")) "atom meta nil by default") + (assert (= true (ct-eval ctx + "(= {:foo \"bar\"} (meta (atom 0 :meta {:foo \"bar\"})))")) "atom with :meta") + (assert (= true (ct-eval ctx + "(= {:validated true} (meta (atom 0 :validator pos? :meta {:validated true})))")) "atom with validator and meta")) +(print " ok") + +(print "\nAll Ported Atom tests passed!") diff --git a/test/clojure-control-test.janet b/test/clojure-control-test.janet new file mode 100644 index 0000000..6dcf56c --- /dev/null +++ b/test/clojure-control-test.janet @@ -0,0 +1,151 @@ +# Ported from clojure/test_clojure/control.clj +(use ../src/jolt/api) +(defn ct-eval [ctx s] (eval-string ctx s)) + +(print "Ported Control Tests") + +# --- test-do --- +(print "test-do...") +(let [ctx (init)] + (assert (= nil (ct-eval ctx "(do)")) "do empty -> nil") + (assert (= 1 (ct-eval ctx "(do 1)")) "do returns last") + (assert (= 2 (ct-eval ctx "(do 1 2)")) "do returns last") + (assert (= 5 (ct-eval ctx "(do 1 2 3 4 5)")) "do returns last")) +(print " ok") + +# --- test-loop --- +(print "test-loop...") +(let [ctx (init)] + (assert (= 1 (ct-eval ctx "(loop [] 1)")) "loop body") + (assert (= 3 (ct-eval ctx "(loop [a 1] (if (< a 3) (recur (inc a)) a))")) "loop recur") + (assert (= true (ct-eval ctx + "(= [6 4 2] (loop [a () b [1 2 3]] + (if (seq b) + (recur (conj a (* 2 (first b))) (next b)) + a)))")) "loop accum list") + (assert (= true (ct-eval ctx + "(= [2 4 6] (loop [a [] b [1 2 3]] + (if (seq b) + (recur (conj a (* 2 (first b))) (next b)) + a)))")) "loop accum vector")) +(print " ok") + +# --- test-when --- +(print "test-when...") +(let [ctx (init)] + (assert (= 1 (ct-eval ctx "(when true 1)")) "when true") + (assert (= nil (ct-eval ctx "(when true)")) "when true no body") + (assert (= nil (ct-eval ctx "(when false)")) "when false") + (assert (= nil (ct-eval ctx "(when false 1)")) "when false with body")) +(print " ok") + +# --- test-when-not --- +(print "test-when-not...") +(let [ctx (init)] + (assert (= 1 (ct-eval ctx "(when-not false 1)")) "when-not false") + (assert (= nil (ct-eval ctx "(when-not true)")) "when-not true no body") + (assert (= nil (ct-eval ctx "(when-not false)")) "when-not false no body") + (assert (= nil (ct-eval ctx "(when-not true 1)")) "when-not true with body")) +(print " ok") + +# --- test-if-not --- +(print "test-if-not...") +(let [ctx (init)] + (assert (= 1 (ct-eval ctx "(if-not false 1)")) "if-not false") + (assert (= 1 (ct-eval ctx "(if-not false 1 2)")) "if-not false with else") + (assert (= nil (ct-eval ctx "(if-not true 1)")) "if-not true") + (assert (= 2 (ct-eval ctx "(if-not true 1 2)")) "if-not true with else")) +(print " ok") + +# --- test-when-let --- +(print "test-when-let...") +(let [ctx (init)] + (assert (= 1 (ct-eval ctx "(when-let [a 1] a)")) "when-let simple") + (assert (= 2 (ct-eval ctx "(when-let [[a b] '(1 2)] b)")) "when-let destructure") + (assert (= nil (ct-eval ctx "(when-let [a false] 1)")) "when-let false")) +(print " ok") + +# --- test-if-let --- +(print "test-if-let...") +(let [ctx (init)] + (assert (= 1 (ct-eval ctx "(if-let [a 1] a)")) "if-let simple") + (assert (= 2 (ct-eval ctx "(if-let [[a b] '(1 2)] b)")) "if-let destructure") + (assert (= nil (ct-eval ctx "(if-let [a false] 1)")) "if-let false") + (assert (= 1 (ct-eval ctx "(if-let [a false] a 1)")) "if-let false with else")) +(print " ok") + +# --- test-if-some --- +(print "test-if-some...") +(let [ctx (init)] + (assert (= 1 (ct-eval ctx "(if-some [a 1] a)")) "if-some simple") + (assert (= false (ct-eval ctx "(if-some [a false] a)")) "if-some false is some") + (assert (= nil (ct-eval ctx "(if-some [a nil] 1)")) "if-some nil") + (assert (= 3 (ct-eval ctx "(if-some [[a b] [1 2]] (+ a b))")) "if-some destructure")) +(print " ok") + +# --- test-when-some --- +(print "test-when-some...") +(let [ctx (init)] + (assert (= 1 (ct-eval ctx "(when-some [a 1] a)")) "when-some simple") + (assert (= 2 (ct-eval ctx "(when-some [[a b] [1 2]] b)")) "when-some destructure") + (assert (= false (ct-eval ctx "(when-some [a false] a)")) "when-some false is some") + (assert (= nil (ct-eval ctx "(when-some [a nil] 1)")) "when-some nil")) +(print " ok") + +# --- test-cond --- +(print "test-cond...") +(let [ctx (init)] + (assert (= nil (ct-eval ctx "(cond)")) "cond empty -> nil") + (assert (= nil (ct-eval ctx "(cond nil true)")) "cond nil -> nil") + (assert (= nil (ct-eval ctx "(cond false true)")) "cond false -> nil") + (assert (= 1 (ct-eval ctx "(cond true 1)")) "cond true -> 1") + (assert (= 3 (ct-eval ctx "(cond nil 1 false 2 true 3 true 4)")) "cond third branch") + (assert (= :b (ct-eval ctx "(cond false :a true :b)")) "cond skips false") + (assert (= :a (ct-eval ctx "(cond true :a true :b)")) "cond takes first true")) +(print " ok") + +# --- test-condp --- +(print "test-condp...") +(let [ctx (init)] + (assert (= :pass (ct-eval ctx "(condp = 1 1 :pass 2 :fail)")) "condp match first") + (assert (= :pass (ct-eval ctx "(condp = 1 2 :fail 1 :pass)")) "condp match second") + (assert (= :pass (ct-eval ctx "(condp = 1 2 :fail :pass)")) "condp default")) +(print " ok") + +# --- test-dotimes --- +(print "test-dotimes...") +(let [ctx (init)] + (assert (= nil (ct-eval ctx "(dotimes [n 1] n)")) "dotimes returns nil") + (assert (= 3 (ct-eval ctx + "(let [a (atom 0)] + (dotimes [n 3] (swap! a inc)) + @a)")) "dotimes 3 iterations") + (assert (= [0 1 2] (ct-eval ctx + "(let [a (atom [])] + (dotimes [n 3] (swap! a conj n)) + @a)")) "dotimes with index")) +(print " ok") + +# --- test-while --- +(print "test-while...") +(let [ctx (init)] + (assert (= nil (ct-eval ctx "(while nil 1)")) "while nil returns nil") + (assert (= true (ct-eval ctx + "(= [0 nil] + (let [a (atom 3) + w (while (pos? @a) (swap! a dec))] + [@a w]))")) "while dec to 0")) +(print " ok") + +# --- test-case --- +(print "test-case...") +(let [ctx (init)] + (assert (= :number (ct-eval ctx "(case 1 1 :number :default)")) "case match 1") + (assert (= :string (ct-eval ctx "(case \"foo\" \"foo\" :string :default)")) "case match string") + (assert (= :kw (ct-eval ctx "(case :zap :zap :kw :default)")) "case match keyword") + (assert (= :symbol (ct-eval ctx "(case 'pow pow :symbol :default)")) "case match symbol") + (assert (= :default (ct-eval ctx "(case 99 1 :number :default)")) "case default") + (assert (= :matched (ct-eval ctx "(case 2 (2 3 4) :matched :default)")) "case one-of-many")) +(print " ok") + +(print "\nAll Ported Control tests passed!") diff --git a/test/clojure-for-test.janet b/test/clojure-for-test.janet new file mode 100644 index 0000000..1443a76 --- /dev/null +++ b/test/clojure-for-test.janet @@ -0,0 +1,35 @@ +# Ported from clojure/test_clojure/for.clj +(use ../src/jolt/api) +(defn ct-eval [ctx s] (eval-string ctx s)) + +(print "Ported For Tests") + +# --- When --- +(print "test-for :when...") +(let [ctx (init)] + (assert (= true (ct-eval ctx + "(= (for [x (range 10) :when (odd? x)] x) + (quote (1 3 5 7 9)))")) "for :when odd?") + (assert (= true (ct-eval ctx + "(= (for [x (range 4) y (range 4) :when (odd? y)] [x y]) + (quote ([0 1] [0 3] [1 1] [1 3] [2 1] [2 3] [3 1] [3 3])))")) "for nested :when")) +(print " ok") + +# --- Let --- +(print "test-for :let...") +(let [ctx (init)] + (assert (= true (ct-eval ctx + "(= (for [x (range 3) y (range 3) :let [z (+ x y)] :when (odd? z)] [x y z]) + (quote ([0 1 1] [1 0 1] [1 2 3] [2 1 3])))")) "for :let :when")) +(print " ok") + +# --- Nesting --- +(print "test-for nesting...") +(let [ctx (init)] + (assert (= true (ct-eval ctx + "(= (for [x (quote (a b)) y (interpose x (quote (1 2))) z (list x y)] [x y z]) + (quote ([a 1 a] [a 1 1] [a a a] [a a a] [a 2 a] [a 2 2] + [b 1 b] [b 1 1] [b b b] [b b b] [b 2 b] [b 2 2])))")) "for nested interpose")) +(print " ok") + +(print "\nAll Ported For tests passed!") diff --git a/test/clojure-logic-test.janet b/test/clojure-logic-test.janet new file mode 100644 index 0000000..e82930d --- /dev/null +++ b/test/clojure-logic-test.janet @@ -0,0 +1,127 @@ +# Ported from clojure/test_clojure/logic.clj +(use ../src/jolt/api) +(defn ct-eval [ctx s] (eval-string ctx s)) + +(print "Ported Logic Tests (from clojure/test-clojure/logic.clj)") + +# --- test-if --- +(print "test-if: true/false/nil...") +(let [ctx (init)] + (assert (= :t (ct-eval ctx "(if true :t)")) "if true") + (assert (= :t (ct-eval ctx "(if true :t :f)")) "if true with else") + (assert (= nil (ct-eval ctx "(if false :t)")) "if false no else") + (assert (= :f (ct-eval ctx "(if false :t :f)")) "if false with else") + (assert (= nil (ct-eval ctx "(if nil :t)")) "if nil no else") + (assert (= :f (ct-eval ctx "(if nil :t :f)")) "if nil with else")) +(print " ok") + +(print "test-if: zero/empty is true...") +(let [ctx (init)] + (assert (= :t (ct-eval ctx "(if 0 :t :f)")) "0 is true") + (assert (= :t (ct-eval ctx "(if 0.0 :t :f)")) "0.0 is true") + (assert (= :t (ct-eval ctx "(if \"\" :t :f)")) "empty string is true") + (assert (= :t (ct-eval ctx "(if () :t :f)")) "empty list is true") + (assert (= :t (ct-eval ctx "(if [] :t :f)")) "empty vector is true") + (assert (= :t (ct-eval ctx "(if {} :t :f)")) "empty map is true") + (assert (= :t (ct-eval ctx "(if #{} :t :f)")) "empty set is true")) +(print " ok") + +(print "test-if: anything except nil/false is true...") +(let [ctx (init)] + (assert (= :t (ct-eval ctx "(if 42 :t :f)")) "42 is true") + (assert (= :t (ct-eval ctx "(if 1.2 :t :f)")) "1.2 is true") + (assert (= :t (ct-eval ctx "(if \"abc\" :t :f)")) "string is true") + (assert (= :t (ct-eval ctx "(if 'abc :t :f)")) "symbol is true") + (assert (= :t (ct-eval ctx "(if :kw :t :f)")) "keyword is true") + (assert (= :t (ct-eval ctx "(if '(1 2) :t :f)")) "list is true") + (assert (= :t (ct-eval ctx "(if [1 2] :t :f)")) "vector is true") + (assert (= :t (ct-eval ctx "(if {:a 1 :b 2} :t :f)")) "map is true") + (assert (= :t (ct-eval ctx "(if #{1 2} :t :f)")) "set is true")) +(print " ok") + +# --- test-nil-punning --- +(print "test-nil-punning...") +(let [ctx (init)] + (assert (= :yes (ct-eval ctx "(if (first []) :no :yes)")) "first [] nil") + (assert (= :yes (ct-eval ctx "(if (next [1]) :no :yes)")) "next [1] nil") + (assert (= :no (ct-eval ctx "(if (rest [1]) :no :yes)")) "rest [1] non-nil") + (assert (= :yes (ct-eval ctx "(if (seq nil) :no :yes)")) "seq nil") + (assert (= :yes (ct-eval ctx "(if (seq []) :no :yes)")) "seq [] nil") + (assert (= :no (ct-eval ctx "(if (lazy-seq nil) :no :yes)")) "lazy-seq nil non-nil") + (assert (= :no (ct-eval ctx "(if (lazy-seq []) :no :yes)")) "lazy-seq [] non-nil") + (assert (= :no (ct-eval ctx "(if (filter (fn [x] (> x 10)) [1 2 3]) :no :yes)")) "filter non-match non-nil") + (assert (= :no (ct-eval ctx "(if (map identity []) :no :yes)")) "map empty non-nil") + (assert (= :no (ct-eval ctx "(if (apply concat []) :no :yes)")) "apply concat [] non-nil") + (assert (= :no (ct-eval ctx "(if (concat) :no :yes)")) "concat empty non-nil") + (assert (= :no (ct-eval ctx "(if (concat []) :no :yes)")) "concat [] non-nil") + (assert (= :no (ct-eval ctx "(if (reverse nil) :no :yes)")) "reverse nil non-nil") + (assert (= :no (ct-eval ctx "(if (reverse []) :no :yes)")) "reverse [] non-nil") + (assert (= :no (ct-eval ctx "(if (sort nil) :no :yes)")) "sort nil non-nil") + (assert (= :no (ct-eval ctx "(if (sort []) :no :yes)")) "sort [] non-nil")) +(print " ok") + +# --- test-and --- +(print "test-and...") +(let [ctx (init)] + (assert (= true (ct-eval ctx "(and)")) "and empty") + (assert (= true (ct-eval ctx "(and true)")) "and true") + (assert (= nil (ct-eval ctx "(and nil)")) "and nil") + (assert (= false (ct-eval ctx "(and false)")) "and false") + (assert (= nil (ct-eval ctx "(and true nil)")) "and true nil") + (assert (= false (ct-eval ctx "(and true false)")) "and true false") + (assert (= "abc" (ct-eval ctx "(and 1 true :kw 'abc \"abc\")")) "and chain last") + (assert (= nil (ct-eval ctx "(and 1 true :kw nil 'abc \"abc\")")) "and chain nil") + (assert (= false (ct-eval ctx "(and 1 true :kw 'abc \"abc\" false)")) "and chain false")) +(print " ok") + +# --- test-or --- +(print "test-or...") +(let [ctx (init)] + (assert (= nil (ct-eval ctx "(or)")) "or empty") + (assert (= true (ct-eval ctx "(or true)")) "or true") + (assert (= nil (ct-eval ctx "(or nil)")) "or nil") + (assert (= false (ct-eval ctx "(or false)")) "or false") + (assert (= true (ct-eval ctx "(or nil false true)")) "or nil false true") + (assert (= 1 (ct-eval ctx "(or nil false 1 2)")) "or picks first truthy") + (assert (= "abc" (ct-eval ctx "(or nil false \"abc\" :kw)")) "or picks string") + (assert (= nil (ct-eval ctx "(or false nil)")) "or false nil -> nil") + (assert (= false (ct-eval ctx "(or nil false)")) "or nil false -> false") + (assert (= false (ct-eval ctx "(or nil nil nil false)")) "or chain to false") + (assert (= true (ct-eval ctx "(or nil true false)")) "or nil true false")) +(print " ok") + +# --- test-not --- +(print "test-not...") +(let [ctx (init)] + (assert (= true (ct-eval ctx "(not nil)")) "not nil") + (assert (= true (ct-eval ctx "(not false)")) "not false") + (assert (= false (ct-eval ctx "(not true)")) "not true") + (assert (= false (ct-eval ctx "(not 0)")) "not 0") + (assert (= false (ct-eval ctx "(not 0.0)")) "not 0.0") + (assert (= false (ct-eval ctx "(not 42)")) "not 42") + (assert (= false (ct-eval ctx "(not 1.2)")) "not 1.2") + (assert (= false (ct-eval ctx "(not \"\")")) "not empty string") + (assert (= false (ct-eval ctx "(not \"abc\")")) "not string") + (assert (= false (ct-eval ctx "(not 'abc)")) "not symbol") + (assert (= false (ct-eval ctx "(not :kw)")) "not keyword") + (assert (= false (ct-eval ctx "(not ())")) "not empty list") + (assert (= false (ct-eval ctx "(not '(1 2))")) "not list") + (assert (= false (ct-eval ctx "(not []))")) "not empty vector") + (assert (= false (ct-eval ctx "(not [1 2])")) "not vector") + (assert (= false (ct-eval ctx "(not {})")) "not empty map") + (assert (= false (ct-eval ctx "(not {:a 1 :b 2})")) "not map") + (assert (= false (ct-eval ctx "(not #{})")) "not empty set") + (assert (= false (ct-eval ctx "(not #{1 2})")) "not set")) +(print " ok") + +# --- test-some? --- +(print "test-some?...") +(let [ctx (init)] + (assert (= false (ct-eval ctx "(some? nil)")) "some? nil") + (assert (= true (ct-eval ctx "(some? false)")) "some? false") + (assert (= true (ct-eval ctx "(some? 0)")) "some? 0") + (assert (= true (ct-eval ctx "(some? \"abc\")")) "some? string") + (assert (= true (ct-eval ctx "(some? [])")) "some? empty vec")) +(print " ok") + +(print "\nAll Ported Logic tests passed!") diff --git a/test/clojure-macros-test.janet b/test/clojure-macros-test.janet new file mode 100644 index 0000000..506c7a6 --- /dev/null +++ b/test/clojure-macros-test.janet @@ -0,0 +1,63 @@ +# Ported from clojure/test_clojure/macros.clj +(use ../src/jolt/api) +(defn ct-eval [ctx s] (eval-string ctx s)) + +(print "Ported Macros Tests") + +# --- -> and ->> threading --- +(print "test -> and ->>...") +(let [ctx (init)] + (ct-eval ctx "(defmacro c [arg] (if (= 'b (first arg)) :foo :bar))") + (ct-eval ctx "(def a 2)") + (ct-eval ctx "(def b identity)") + (assert (= :foo (ct-eval ctx "(-> a b c)")) "-> threading") + (assert (= :foo (ct-eval ctx "(->> a b c)")) "->> threading")) +(print " ok") + +# --- some-> --- +(print "test some->...") +(let [ctx (init)] + (assert (= nil (ct-eval ctx "(some-> nil)")) "some-> nil") + (assert (= 0 (ct-eval ctx "(some-> 0)")) "some-> 0") + (assert (= -1 (ct-eval ctx "(some-> 1 (- 2))")) "some-> with form") + (ct-eval ctx "(defn const-nil [_] nil)") + (assert (= nil (ct-eval ctx "(some-> 1 const-nil (- 2))")) "some-> stop at nil")) +(print " ok") + +# --- some->> --- +(print "test some->>...") +(let [ctx (init)] + (assert (= nil (ct-eval ctx "(some->> nil)")) "some->> nil") + (assert (= 0 (ct-eval ctx "(some->> 0)")) "some->> 0") + (assert (= 1 (ct-eval ctx "(some->> 1 (- 2))")) "some->> with form") + (ct-eval ctx "(defn const-nil2 [_] nil)") + (assert (= nil (ct-eval ctx "(some->> 1 const-nil2 (- 2))")) "some->> stop at nil")) +(print " ok") + +# --- cond-> --- +(print "test cond->...") +(let [ctx (init)] + (assert (= 0 (ct-eval ctx "(cond-> 0)")) "cond-> single") + (assert (= -1 (ct-eval ctx "(cond-> 0 true inc true (- 2))")) "cond-> with tests") + (assert (= 0 (ct-eval ctx "(cond-> 0 false inc)")) "cond-> false test") + (assert (= -1 (ct-eval ctx "(cond-> 1 true (- 2) false inc)")) "cond-> mix")) +(print " ok") + +# --- cond->> --- +(print "test cond->>...") +(let [ctx (init)] + (assert (= 0 (ct-eval ctx "(cond->> 0)")) "cond->> single") + (assert (= 1 (ct-eval ctx "(cond->> 0 true inc true (- 2))")) "cond->> with tests") + (assert (= 0 (ct-eval ctx "(cond->> 0 false inc)")) "cond->> false test") + (assert (= 1 (ct-eval ctx "(cond->> 1 true (- 2) false inc)")) "cond->> mix")) +(print " ok") + +# --- as-> --- +(print "test as->...") +(let [ctx (init)] + (assert (= 0 (ct-eval ctx "(as-> 0 x)")) "as-> single") + (assert (= 1 (ct-eval ctx "(as-> 0 x (inc x))")) "as-> one form") + (assert (= 2 (ct-eval ctx "(as-> [0 1] x (map inc x) (reverse x) (first x))")) "as-> chain")) +(print " ok") + +(print "\nAll Ported Macros tests passed!") diff --git a/test/lazy-test.janet b/test/lazy-test.janet index cf9ac9d..4b065a5 100644 --- a/test/lazy-test.janet +++ b/test/lazy-test.janet @@ -42,14 +42,15 @@ (assert (= true (ct-eval ctx "(= [1 2 3] (distinct (lazy-seq [1 2 1 3 2])))")) "distinct lazy")) (print " ok") -(print "\n8: fib-seq via lazy-cat (self-referencing lazy-seq)...") +(print "8: fib-seq (deferred — eager core-map needs lazy upgrade)...") (let [ctx (init)] - (print " NOTE: self-referencing lazy-seqs currently trigger eager realization, causing infinite recursion.") - (print " This is a known limitation — our lazy-seq model forces the entire thunk at once.") - (print " Skipping fib-seq integration test for now.") - (print " When fixed, the test should assert:") - (print " (def fib-seq (lazy-cat [0 1] (map + (rest fib-seq) fib-seq)))") - (print " (= [0 1 1 2 3 5 8 13 21 34] (take 10 fib-seq))")) -(print " ok (deferred)") + (print " The cell-by-cell lazy-seq architecture is correct. concat, take,") + (print " drop, nth, first, rest all work with self-referencing patterns.") + (print " core-map still eagerly realizes all lazy-seq arguments, which") + (print " loops on infinite sequences. Making core-map return a lazy") + (print " result would complete the self-referencing lazy-seq story.") + (print " When fixed: (def fib-seq (lazy-cat [0 1] (map + (rest fib-seq) fib-seq)))") + (print " → (take 10 fib-seq) = [0 1 1 2 3 5 8 13 21 34]")) +(print " ok (deferred — core-map needs lazy upgrade)") (print "\nAll LazySeq tests passed!") diff --git a/test/logic-test.janet b/test/logic-test.janet new file mode 100644 index 0000000..0a8498c --- /dev/null +++ b/test/logic-test.janet @@ -0,0 +1,100 @@ +(use ../src/jolt/api) +(defn ct-eval [ctx s] (eval-string ctx s)) +(print "Ported Logic Tests (from clojure/test-clojure/logic.clj)") + +(print "1: test-if true/false/nil...") +(let [ctx (init)] + (assert (= :t (ct-eval ctx "(if true :t)")) "if true") + (assert (= :t (ct-eval ctx "(if true :t :f)")) "if true with else") + (assert (= nil (ct-eval ctx "(if false :t)")) "if false no else") + (assert (= :f (ct-eval ctx "(if false :t :f)")) "if false with else") + (assert (= nil (ct-eval ctx "(if nil :t)")) "if nil no else") + (assert (= :f (ct-eval ctx "(if nil :t :f)")) "if nil with else")) +(print " ok") + +(print "2: test-if zero/empty is true...") +(let [ctx (init)] + (assert (= true (ct-eval ctx "(= :t (if 0 :t :f))")) "0 is true") + (assert (= true (ct-eval ctx "(= :t (if 0.0 :t :f))")) "0.0 is true") + (assert (= true (ct-eval ctx "(= :t (if \"\" :t :f))")) "empty string is true") + (assert (= true (ct-eval ctx "(= :t (if () :t :f))")) "empty list is true") + (assert (= true (ct-eval ctx "(= :t (if [] :t :f))")) "empty vector is true") + (assert (= true (ct-eval ctx "(= :t (if {} :t :f))")) "empty map is true") + (assert (= true (ct-eval ctx "(= :t (if #{} :t :f))")) "empty set is true")) +(print " ok") + +(print "3: test-if anything except nil/false is true...") +(let [ctx (init)] + (assert (= true (ct-eval ctx "(= :t (if 42 :t :f))")) "42 is true") + (assert (= true (ct-eval ctx "(= :t (if 1.5 :t :f))")) "float is true") + (assert (= true (ct-eval ctx "(= :t (if \"abc\" :t :f))")) "string is true") + (assert (= true (ct-eval ctx "(= :t (if 'abc :t :f))")) "symbol is true") + (assert (= true (ct-eval ctx "(= :t (if :kw :t :f))")) "keyword is true") + (assert (= true (ct-eval ctx "(= :t (if '(1 2) :t :f))")) "list is true") + (assert (= true (ct-eval ctx "(= :t (if [1 2] :t :f))")) "vector is true") + (assert (= true (ct-eval ctx "(= :t (if {:a 1 :b 2} :t :f))")) "map is true") + (assert (= true (ct-eval ctx "(= :t (if #{1 2} :t :f))")) "set is true")) +(print " ok") + +(print "4: test-nil-punning...") +(let [ctx (init)] + (assert (= :yes (ct-eval ctx "(if (first []) :no :yes)")) "first [] nil") + (assert (= :yes (ct-eval ctx "(if (next [1]) :no :yes)")) "next [1] nil") + (assert (= :no (ct-eval ctx "(if (rest [1]) :no :yes)")) "rest [1] non-nil") + (assert (= :yes (ct-eval ctx "(if (seq nil) :no :yes)")) "seq nil") + (assert (= :yes (ct-eval ctx "(if (seq []) :no :yes)")) "seq [] nil") + (assert (= :no (ct-eval ctx "(if (lazy-seq nil) :no :yes)")) "lazy-seq nil non-nil") + (assert (= :no (ct-eval ctx "(if (lazy-seq []) :no :yes)")) "lazy-seq [] non-nil")) +(print " ok") + +(print "5: test-and...") +(let [ctx (init)] + (assert (= true (ct-eval ctx "(and)")) "and empty") + (assert (= true (ct-eval ctx "(and true)")) "and true") + (assert (= nil (ct-eval ctx "(and nil)")) "and nil") + (assert (= false (ct-eval ctx "(and false)")) "and false") + (assert (ct-eval ctx "(= \"abc\" (and 1 true :kw 'abc \"abc\"))") "and chain last") + (assert (= nil (ct-eval ctx "(and 1 true nil 'abc \"abc\")")) "and chain nil") + (assert (= false (ct-eval ctx "(and 1 true 'abc \"abc\" false)")) "and chain false")) +(print " ok") + +(print "6: test-or...") +(let [ctx (init)] + (assert (= nil (ct-eval ctx "(or)")) "or empty") + (assert (= true (ct-eval ctx "(or true)")) "or true") + (assert (= nil (ct-eval ctx "(or nil)")) "or nil") + (assert (= false (ct-eval ctx "(or false)")) "or false") + (assert (= true (ct-eval ctx "(or nil false true)")) "or nil false true") + (assert (= 1 (ct-eval ctx "(or nil false 1 2)")) "or picks first truthy") + (assert (ct-eval ctx "(= \"abc\" (or nil false \"abc\" :kw))") "or picks string") + (assert (= nil (ct-eval ctx "(or false nil)")) "or false nil -> nil") + (assert (= false (ct-eval ctx "(or nil false)")) "or nil false -> false") + (assert (= false (ct-eval ctx "(or nil nil nil false)")) "or chain to false") + (assert (= true (ct-eval ctx "(or nil true false)")) "or nil true false")) +(print " ok") + +(print "7: test-not...") +(let [ctx (init)] + (assert (= true (ct-eval ctx "(not nil)")) "not nil") + (assert (= true (ct-eval ctx "(not false)")) "not false") + (assert (= false (ct-eval ctx "(not true)")) "not true") + (assert (= false (ct-eval ctx "(not 0)")) "not 0") + (assert (= false (ct-eval ctx "(not 42)")) "not 42") + (assert (= false (ct-eval ctx "(not \"\")")) "not empty string") + (assert (= false (ct-eval ctx "(not \"abc\")")) "not string") + (assert (= false (ct-eval ctx "(not ())")) "not empty list") + (assert (= false (ct-eval ctx "(not [])")) "not empty vector") + (assert (= false (ct-eval ctx "(not {})")) "not empty map") + (assert (= false (ct-eval ctx "(not #{})")) "not empty set")) +(print " ok") + +(print "8: test-some?...") +(let [ctx (init)] + (assert (= false (ct-eval ctx "(some? nil)")) "some? nil") + (assert (= true (ct-eval ctx "(some? false)")) "some? false") + (assert (= true (ct-eval ctx "(some? 0)")) "some? 0") + (assert (= true (ct-eval ctx "(some? \"abc\")")) "some? string") + (assert (= true (ct-eval ctx "(some? [])")) "some? empty vec")) +(print " ok") + +(print "\nAll Ported Logic tests passed!") diff --git a/test/phase7-test.janet b/test/phase7-test.janet index ba01edc..cfacb73 100644 --- a/test/phase7-test.janet +++ b/test/phase7-test.janet @@ -7,7 +7,7 @@ (let [ls (ct-eval ctx "(lazy-seq (cons 1 (lazy-seq (cons 2 nil))))")] (assert (not (nil? ls)) "lazy-seq returns non-nil") (assert (= 1 (ct-eval ctx "(first (lazy-seq (cons 1 nil)))")) "first of lazy")) - (assert (= [1 2 3] (ct-eval ctx "(seq (lazy-seq [1 2 3]))")) "seq forces lazy") + (assert (= true (ct-eval ctx "(= [1 2 3] (seq (lazy-seq [1 2 3])))")) "seq forces lazy") (eval-string ctx "(def counter (atom 0))") (def val (ct-eval ctx "(let [ls (lazy-seq (do (swap! counter inc) [1 2 3]))] (seq ls) (seq ls) @counter)")) (assert (= 1 val) "realized once")) diff --git a/test/systematic-coverage.janet b/test/systematic-coverage.janet new file mode 100644 index 0000000..b73ec7a --- /dev/null +++ b/test/systematic-coverage.janet @@ -0,0 +1,212 @@ +# Systematic coverage tests for Clojure language features +(use ../src/jolt/api) +(defn ct-eval [ctx s] (eval-string ctx s)) + +(print "Systematic Coverage Tests") + +# --- Type Predicates --- +(print "1: type predicates...") +(let [ctx (init)] + (assert (= true (ct-eval ctx "(integer? 0)")) "integer? 0") + (assert (= true (ct-eval ctx "(integer? 1)")) "integer? 1") + (assert (= true (ct-eval ctx "(integer? -5)")) "integer? negative") + (assert (= false (ct-eval ctx "(integer? 1.5)")) "integer? float") + (assert (= false (ct-eval ctx "(integer? nil)")) "integer? nil") + (assert (= false (ct-eval ctx "(integer? \"abc\")")) "integer? string") + (assert (= true (ct-eval ctx "(boolean? true)")) "boolean? true") + (assert (= true (ct-eval ctx "(boolean? false)")) "boolean? false") + (assert (= false (ct-eval ctx "(boolean? 1)")) "boolean? falsy") + (assert (= false (ct-eval ctx "(boolean? nil)")) "boolean? nil") + (assert (= true (ct-eval ctx "(nil? nil)")) "nil? nil") + (assert (= false (ct-eval ctx "(nil? false)")) "nil? false") + (assert (= false (ct-eval ctx "(nil? 0)")) "nil? 0") + (assert (= false (ct-eval ctx "(nil? [])")) "nil? empty vec") + (assert (= false (ct-eval ctx "(some? nil)")) "some? nil") + (assert (= true (ct-eval ctx "(some? false)")) "some? false") + (assert (= true (ct-eval ctx "(some? 0)")) "some? 0") + (assert (= true (ct-eval ctx "(some? [])")) "some? empty vec") + (assert (= true (ct-eval ctx "(string? \"hello\")")) "string?") + (assert (= false (ct-eval ctx "(string? 42)")) "string? false") + (assert (= true (ct-eval ctx "(string? \"\")")) "string? empty") + (assert (= true (ct-eval ctx "(number? 42)")) "number? int") + (assert (= true (ct-eval ctx "(number? 3.14)")) "number? float") + (assert (= false (ct-eval ctx "(number? \"42\")")) "number? string") + (assert (= true (ct-eval ctx "(fn? inc)")) "fn? builtin") + (assert (= false (ct-eval ctx "(fn? 42)")) "fn? false") + (assert (= true (ct-eval ctx "(keyword? :foo)")) "keyword?") + (assert (= false (ct-eval ctx "(keyword? \"foo\")")) "keyword? false") + (assert (= true (ct-eval ctx "(symbol? 'abc)")) "symbol?") + (assert (= false (ct-eval ctx "(symbol? :abc)")) "symbol? false") + (assert (= true (ct-eval ctx "(map? {:a 1})")) "map?") + (assert (= false (ct-eval ctx "(map? [1 2])")) "map? false") + (assert (= true (ct-eval ctx "(set? #{1 2})")) "set?") + (assert (= false (ct-eval ctx "(set? [1 2])")) "set? false") + (assert (= true (ct-eval ctx "(seq? '(1 2))")) "seq? list") + (assert (= true (ct-eval ctx "(seq? [1 2])")) "seq? vector") + (assert (= true (ct-eval ctx "(coll? [1 2])")) "coll? vec") + (assert (= true (ct-eval ctx "(coll? '(1 2))")) "coll? list") + (assert (= true (ct-eval ctx "(coll? {})")) "coll? map") + (assert (= true (ct-eval ctx "(true? true)")) "true? true") + (assert (= false (ct-eval ctx "(true? 1)")) "true? 1") + (assert (= true (ct-eval ctx "(false? false)")) "false? false") + (assert (= false (ct-eval ctx "(false? nil)")) "false? nil") + (assert (= true (ct-eval ctx "(identical? 42 42)")) "identical? same value")) +(print " ok") + +# --- Number Predicates --- +(print "2: number predicates...") +(let [ctx (init)] + (assert (= true (ct-eval ctx "(zero? 0)")) "zero? 0") + (assert (= false (ct-eval ctx "(zero? 1)")) "zero? 1") + (assert (= false (ct-eval ctx "(zero? nil)")) "zero? nil") + (assert (= true (ct-eval ctx "(pos? 1)")) "pos?") + (assert (= false (ct-eval ctx "(pos? 0)")) "pos? 0") + (assert (= false (ct-eval ctx "(pos? -1)")) "pos? -1") + (assert (= true (ct-eval ctx "(neg? -1)")) "neg?") + (assert (= false (ct-eval ctx "(neg? 0)")) "neg? 0") + (assert (= false (ct-eval ctx "(neg? 1)")) "neg? 1") + (assert (= true (ct-eval ctx "(even? 0)")) "even? 0") + (assert (= true (ct-eval ctx "(even? 2)")) "even? 2") + (assert (= false (ct-eval ctx "(even? 1)")) "even? 1") + (assert (= true (ct-eval ctx "(odd? 1)")) "odd? 1") + (assert (= true (ct-eval ctx "(odd? 3)")) "odd? 3") + (assert (= false (ct-eval ctx "(odd? 2)")) "odd? 2")) +(print " ok") + +# --- Math --- +(print "3: math operations...") +(let [ctx (init)] + (assert (= 0 (ct-eval ctx "(+)")) "+ 0 args") + (assert (= 5 (ct-eval ctx "(+ 2 3)")) "+ 2 args") + (assert (= 10 (ct-eval ctx "(+ 1 2 3 4)")) "+ varargs") + (assert (= -5 (ct-eval ctx "(- 5)")) "- unary") + (assert (= 2 (ct-eval ctx "(- 5 3)")) "- binary") + (assert (= 1 (ct-eval ctx "(*)")) "* 0 args") + (assert (= 6 (ct-eval ctx "(* 2 3)")) "*") + (assert (= 0.5 (ct-eval ctx "(/ 2)")) "/ unary reciprocal") + (assert (= 2 (ct-eval ctx "(/ 4 2)")) "/ binary") + (assert (= 2 (ct-eval ctx "(quot 5 2)")) "quot") + (assert (= 1 (ct-eval ctx "(rem 5 2)")) "rem") + (assert (= 1 (ct-eval ctx "(mod 5 2)")) "mod") + (assert (= 43 (ct-eval ctx "(inc 42)")) "inc") + (assert (= 41 (ct-eval ctx "(dec 42)")) "dec") + (assert (= 3 (ct-eval ctx "(max 1 2 3)")) "max") + (assert (= 1 (ct-eval ctx "(min 1 2 3)")) "min") + (assert (= 5 (ct-eval ctx "(abs -5)")) "abs negative") + (assert (= 5 (ct-eval ctx "(abs 5)")) "abs positive") + (assert (= 0 (ct-eval ctx "(abs 0)")) "abs 0") + (assert (= 3.14 (ct-eval ctx "(abs -3.14)")) "abs float") + (assert (= true (ct-eval ctx "(< (rand) 1)")) "rand < 1") + (assert (= true (ct-eval ctx "(number? (rand-int 10))")) "rand-int type")) +(print " ok") + +# --- Comparison --- +(print "4: comparison...") +(let [ctx (init)] + (assert (= true (ct-eval ctx "(= 1 1)")) "= same") + (assert (= true (ct-eval ctx "(= 1 1 1)")) "= multi same") + (assert (= false (ct-eval ctx "(= 1 2)")) "= different") + (assert (= true (ct-eval ctx "(not= 1 2)")) "not= different") + (assert (= false (ct-eval ctx "(not= 1 1)")) "not= same") + (assert (= true (ct-eval ctx "(< 1 2)")) "<") + (assert (= false (ct-eval ctx "(< 2 1)")) "< false") + (assert (= true (ct-eval ctx "(> 2 1)")) ">") + (assert (= true (ct-eval ctx "(<= 1 1)")) "<=") + (assert (= true (ct-eval ctx "(>= 2 2)")) ">=")) +(print " ok") + +# --- Boolean logic --- +(print "5: boolean logic...") +(let [ctx (init)] + (assert (= true (ct-eval ctx "(and)")) "and empty") + (assert (= true (ct-eval ctx "(and true)")) "and true") + (assert (= nil (ct-eval ctx "(and nil)")) "and nil") + (assert (= false (ct-eval ctx "(and false)")) "and false") + (assert (= 3 (ct-eval ctx "(and 1 2 3)")) "and last") + (assert (= nil (ct-eval ctx "(and 1 nil 3)")) "and short-circuit") + (assert (= nil (ct-eval ctx "(or)")) "or empty") + (assert (= true (ct-eval ctx "(or true)")) "or true") + (assert (= nil (ct-eval ctx "(or nil)")) "or nil") + (assert (= false (ct-eval ctx "(or false)")) "or false") + (assert (= 1 (ct-eval ctx "(or nil false 1 2)")) "or first truthy") + (assert (= false (ct-eval ctx "(or nil false)")) "or all falsy") + (assert (= false (ct-eval ctx "(not true)")) "not true") + (assert (= true (ct-eval ctx "(not nil)")) "not nil") + (assert (= true (ct-eval ctx "(not false)")) "not false")) +(print " ok") + +# --- Collections --- +(print "6: collections...") +(let [ctx (init)] + (assert (= true (ct-eval ctx "(empty? nil)")) "empty? nil") + (assert (= true (ct-eval ctx "(empty? [])")) "empty? []") + (assert (= true (ct-eval ctx "(empty? ())")) "empty? ()") + (assert (= true (ct-eval ctx "(empty? {})")) "empty? {}") + (assert (= true (ct-eval ctx "(empty? #{})")) "empty? #{}") + (assert (= true (ct-eval ctx "(empty? \"\")")) "empty? empty string") + (assert (= false (ct-eval ctx "(empty? [1])")) "empty? non-empty") + (assert (= true (ct-eval ctx "(every? even? [2 4 6])")) "every? true") + (assert (= false (ct-eval ctx "(every? even? [2 3 4])")) "every? false") + (assert (= 3 (ct-eval ctx "(count [1 2 3])")) "count vector") + (assert (= 2 (ct-eval ctx "(count {:a 1 :b 2})")) "count map") + (assert (= 3 (ct-eval ctx "(count #{1 2 3})")) "count set") + (assert (= 3 (ct-eval ctx "(count \"abc\")")) "count string")) +(print " ok") + +# --- Sequence Operations --- +(print "7: sequence operations...") +(let [ctx (init)] + (assert (= 1 (ct-eval ctx "(first [1 2 3])")) "first") + (assert (= nil (ct-eval ctx "(first [])")) "first empty") + (assert (= nil (ct-eval ctx "(first nil)")) "first nil") + (assert (= :a (ct-eval ctx "(nth [:a :b :c] 0)")) "nth 0") + (assert (= :c (ct-eval ctx "(nth [:a :b :c] 2)")) "nth 2") + (assert (= :d (ct-eval ctx "(nth [:a :b :c] 3 :d)")) "nth default") + (assert (= nil (ct-eval ctx "(seq [])")) "seq empty -> nil") + (assert (= nil (ct-eval ctx "(seq nil)")) "seq nil -> nil") + (assert (= true (ct-eval ctx "(= [2 3 4] (map inc [1 2 3]))")) "map") + (assert (= true (ct-eval ctx "(= [2 4] (filter even? [1 2 3 4]))")) "filter") + (assert (= 6 (ct-eval ctx "(reduce + [1 2 3])")) "reduce") + (assert (= 10 (ct-eval ctx "(reduce + 4 [1 2 3])")) "reduce init") + (assert (= true (ct-eval ctx "(= [1 2] (take 2 [1 2 3 4]))")) "take") + (assert (= true (ct-eval ctx "(= [3 4] (drop 2 [1 2 3 4]))")) "drop") + (assert (= true (ct-eval ctx "(= [3 2 1] (reverse [1 2 3]))")) "reverse") + (assert (= true (ct-eval ctx "(= 3 (count (distinct [1 1 2 2 3 3])))")) "distinct")) +(print " ok") + +# --- Collections: conj, assoc, dissoc, get --- +(print "8: collection mutation...") +(let [ctx (init)] + (assert (= true (ct-eval ctx "(= [1 2 3] (conj [1 2] 3))")) "conj vector") + (assert (= true (ct-eval ctx "(= (quote (0 1 2)) (conj (quote (1 2)) 0))")) "conj list prepend") + (assert (= true (ct-eval ctx "(= {:a 1 :b 2} (assoc {:a 1} :b 2))")) "assoc") + (assert (= true (ct-eval ctx "(= {:a 1} (dissoc {:a 1 :b 2} :b))")) "dissoc") + (assert (= 1 (ct-eval ctx "(get {:a 1} :a)")) "get") + (assert (= nil (ct-eval ctx "(get {:a 1} :z)")) "get missing") + (assert (= :d (ct-eval ctx "(get {:a 1} :z :d)")) "get default") + (assert (= true (ct-eval ctx "(contains? {:a 1} :a)")) "contains? map") + (assert (= false (ct-eval ctx "(contains? {:a 1} :z)")) "contains? missing") + (assert (= true (ct-eval ctx "(contains? [5 6 7] 1)")) "contains? vector") + (assert (= false (ct-eval ctx "(contains? [5 6 7] 3)")) "contains? vector oob")) +(print " ok") + +# --- Higher-order functions --- +(print "9: higher-order functions...") +(let [ctx (init)] + (assert (= 3 (ct-eval ctx "((comp inc inc) 1)")) "comp") + (assert (= 42 (ct-eval ctx "(identity 42)")) "identity") + (assert (= 99 (ct-eval ctx "((constantly 99) :anything)")) "constantly") + (assert (= true (ct-eval ctx "(= 10 ((partial + 3 7)))")) "partial") + (assert (= true (ct-eval ctx "((every-pred even? pos?) 4)")) "every-pred true") + (assert (= false (ct-eval ctx "((every-pred even? pos?) -4)")) "every-pred false") + (assert (= false (ct-eval ctx "((complement even?) 2)")) "complement")) +(print " ok") + +# --- Destructuring --- +(print "10: destructuring...") +(let [ctx (init)] + (assert (= 1 (ct-eval ctx "(let [[x] [1 2 3]] x)")) "seq destructure first") + (assert (= 2 (ct-eval ctx "(let [[_ y] [1 2 3]] y)")) "seq destructure second")) +(print " ok") + +(print "\nAll Systematic Coverage tests passed!")