diff --git a/jolt-core/clojure/core/00-syntax.clj b/jolt-core/clojure/core/00-syntax.clj index ed85a2d..1cd83e8 100644 --- a/jolt-core/clojure/core/00-syntax.clj +++ b/jolt-core/clojure/core/00-syntax.clj @@ -14,6 +14,35 @@ ;; (backend/recompile-defns!) once the analyzer is alive — same lifecycle as ;; the defmacro expanders. +;; zero?/pos?/every? live HERE (not 20-coll): empty? below calls zero?, and +;; the self-hosted analyzer — compiled right after the kernel tier — uses all +;; three. Raw def+fn* per the file constraint. zero? checks number? itself +;; (= doesn't throw); pos? inherits throwing from >. +(def zero? + (fn* zero? [x] + (if (number? x) + (= x 0) + (throw (str "zero? requires a number, got: " x))))) + +;; pos? checks number? explicitly: this tier is recompiled by the staged pass, +;; where a bare (> x 0) emits the native janet op that happily orders strings +;; (the documented native-ops relaxation) — the guard keeps Clojure's throw. +(def pos? + (fn* pos? [x] + (if (number? x) + (> x 0) + (throw (str "pos? requires a number, got: " x))))) + +;; Canonical every?: short-circuits on the first falsey result, so infinite +;; seqs with an early counterexample terminate. +(def every? + (fn* every? [pred coll] + (if (nil? (seq coll)) + true + (if (pred (first coll)) + (recur pred (next coll)) + false)))) + ;; empty?/keys/vals live HERE (not 20-coll) because the expanders below call ;; them at expansion time, which first happens during the kernel-tier compile. ;; empty? keeps O(1) dispatch for counted things; only the lazy/list fallback diff --git a/jolt-core/clojure/core/20-coll.clj b/jolt-core/clojure/core/20-coll.clj index a0717ce..2ce0089 100644 --- a/jolt-core/clojure/core/20-coll.clj +++ b/jolt-core/clojure/core/20-coll.clj @@ -14,14 +14,8 @@ ;; neg? throws on non-numbers via <, as Clojure's Numbers.isNeg does. (defn neg? [x] (< x 0)) -;; even?/odd? accept any integral number (jolt has one number type, so 2.0 -;; counts) and throw otherwise — Clojure's IllegalArgumentException wording. -(defn even? [n] - (if (integer? n) - (zero? (rem n 2)) - (throw (str "Argument must be an integer: " n)))) - -(defn odd? [n] (not (even? n))) +;; even?/odd? stay in the seed: (filter even? ...) is idiomatic-hot and the +;; overlay versions cost an extra call layer per element (seq-pipe bench 4x). ;; Base is (hash-map), not the {} literal: a literal map is a struct that doesn't ;; canonicalize collection keys across representations (a {:a 1} literal vs @@ -569,6 +563,7 @@ (defn record? [x] (some? (get x :jolt/deftype))) (defn uuid? [x] (= (get x :jolt/type) :jolt/uuid)) (defn inst? [x] (= (get x :jolt/type) :jolt/inst)) +(defn char? [x] (= (get x :jolt/type) :jolt/char)) ;; inst-ms: epoch milliseconds of an instant; throws on a non-inst (Clojure ;; protocol behavior). diff --git a/src/jolt/core.janet b/src/jolt/core.janet index f4cb37f..a85ef58 100644 --- a/src/jolt/core.janet +++ b/src/jolt/core.janet @@ -229,9 +229,12 @@ (if (and (number? x) (= x x) (< (if (< x 0) (- x) x) math/inf) (= x (math/floor x))) x (error (string op " requires an integer")))) -(defn core-zero? [x] (= (need-num x "zero?") 0)) -(defn core-pos? [x] (> (need-num x "pos?") 0)) -# neg? / even? / odd? live in the Clojure collection tier (core/20-coll.clj). +# zero? / pos? live in the syntax tier (core/00-syntax.clj) — empty? and the +# analyzer use them; neg? lives in the collection tier (20-coll.clj). +# even?/odd? are PERF-WALL residents: (filter even? ...) is idiomatic and the +# overlay versions cost an extra call layer per element (seq-pipe bench 4x). +(defn core-even? [n] (= 0 (% (need-int n "even?") 2))) +(defn core-odd? [n] (not= 0 (% (need-int n "odd?") 2))) # Finite integral number: NaN and the infinities are NOT integers (floor of # inf is inf, so the naive floor check wrongly accepted them). @@ -244,27 +247,8 @@ # empty? now lives in the syntax tier (core/00-syntax.clj): the expanders # call it, so it must exist before the kernel tier compiles. -(defn core-every? [pred coll] - # Short-circuit on the first false — and pull lazily so an infinite seq with an - # early false (e.g. (every? pos? (range))) returns rather than hanging. Walks - # cells via realize-ls directly (core-first/lazy-from are defined later). - (if (lazy-seq? coll) - (do - (var cur coll) (var result true) (var go true) - (while (and result go) - (let [cell (realize-ls cur)] - (if (or (nil? cell) (= :jolt/pending cell) (= 0 (length cell))) - (set go false) - (if (pred (in cell 0)) - (let [rt (in cell 1)] - (if (nil? rt) (set go false) (set cur (ls-rest-cached cur rt)))) - (set result false))))) - result) - (do - (var result true) - (each x (realize-for-iteration coll) - (if (not (pred x)) (do (set result false) (break)))) - result))) +# every? lives in the syntax tier (core/00-syntax.clj) — the analyzer uses it; +# the canonical seq/first/next walk short-circuits lazy seqs the same way. # ============================================================ # Math — Clojure semantics (variadic, / with one arg = reciprocal) @@ -711,6 +695,8 @@ # Indexed collections: an O(1) lazy view from index 1 (Clojure: rest of a # vector is a seq, not a vector). Slicing per step made first/rest loops # over concrete collections O(n^2) — a 20k rest-loop took two seconds. + # These stay ABOVE the set/map branches: rest-of-vector is every seq loop's + # hot path and must not pay the wrapper-tag checks. (pvec? coll) (let [a (pv->array coll)] (if (<= (length a) 1) @[] (make-lazy-seq (fn [] (indexed-cells a 1))))) @@ -718,6 +704,16 @@ (string? coll) (tuple ;(map make-char (string/bytes (string/slice coll 1)))) (tuple? coll) (if (<= (length coll) 1) @[] (make-lazy-seq (fn [] (indexed-cells coll 1)))) + # Sets, maps and sorted colls rest via their seq. Without these branches + # they fell into the indexed fall-through, which walked the wrapper table's + # INTERNAL fields — (next #{1 2}) was (nil nil) until the canonical every? + # started seq-walking sets (seed-shrink round 4). + (set? coll) (if (= 0 (coll :cnt)) @[] (core-rest (phs-seq coll))) + (phm? coll) (if (= 0 (coll :cnt)) @[] (core-rest (tuple ;(phm-entries coll)))) + (core-sorted? coll) (core-rest ((sorted-op coll :seq) coll)) + # plain struct maps (untagged literals) rest via entries too + (and (struct? coll) (nil? (get coll :jolt/type))) + (core-rest (tuple ;(map-entries-of coll))) (if (<= (length coll) 1) @[] (make-lazy-seq (fn [] (indexed-cells coll 1)))))) @@ -2631,17 +2627,16 @@ "seq?" core-seq? "coll?" core-coll? "identical?" core-identical? - "zero?" core-zero? - "pos?" core-pos? "integer?" core-integer? "list?" core-list? - "every?" core-every? "+" core-+ "-" core-sub "*" core-* "/" core-/ "inc" core-inc "dec" core-dec + "even?" core-even? + "odd?" core-odd? "mod" core-mod "rem" core-rem "quot" core-quot @@ -2847,7 +2842,6 @@ "double" core-double "float" core-float "char" core-char - "char?" core-char? # Hash "hash" core-hash "atom" core-atom diff --git a/test/integration/clojure-test-suite-test.janet b/test/integration/clojure-test-suite-test.janet index 9577396..7bce4d5 100644 --- a/test/integration/clojure-test-suite-test.janet +++ b/test/integration/clojure-test-suite-test.janet @@ -43,7 +43,10 @@ # Raised 4004 -> 4034 / clean 66 -> 67 porting partition-all + repeatedly to the # overlay, which required fixing two leniencies (a char is not callable; take # validates its count) — correct beyond those fns, so the suite rose broadly. -(def baseline-pass 4660) +# Raised 4660 -> 4695 (observed 4703) after the seed-shrink rounds: rest/next +# over sets/maps/sorted colls fixed (they walked the wrapper table's internal +# fields), canonical halt-when/==/memfn. Margin covers rand-based suite jitter. +(def baseline-pass 4695) # A file is "clean" when it ran with zero failures AND zero errors. (def baseline-clean-files 88) # Per-file wall-clock budget (seconds). Normal files finish in well under 1s, so diff --git a/test/spec/predicates-spec.janet b/test/spec/predicates-spec.janet index a75370c..fd77d90 100644 --- a/test/spec/predicates-spec.janet +++ b/test/spec/predicates-spec.janet @@ -205,3 +205,25 @@ ["string NOT" "false" "(ifn? \"s\")"] ["number NOT" "false" "(ifn? 5)"] ["nil NOT" "false" "(ifn? nil)"]) + +# zero?/pos? throw on non-numbers (Numbers.isZero/isPos), as in Clojure; +# every? short-circuits on the first falsey pred result, so an infinite seq +# with an early counterexample terminates. char? is the tagged-value check. +(defspec "predicates / numeric guards & every? (overlay moves)" + ["zero? zero" "true" "(zero? 0)"] + ["zero? nonzero" "false" "(zero? 3)"] + ["zero? throws" :throws "(zero? :a)"] + ["zero? throws on nil" :throws "(zero? nil)"] + ["pos? positive" "true" "(pos? 2)"] + ["pos? zero" "false" "(pos? 0)"] + ["pos? throws" :throws "(pos? \"x\")"] + ["neg? throws" :throws "(neg? \"x\")"] + ["every? all pass" "true" "(every? odd? [1 3 5])"] + ["every? one fails" "false" "(every? odd? [1 2 5])"] + ["every? vacuous" "true" "(every? odd? [])"] + ["every? nil coll" "true" "(every? odd? nil)"] + ["every? infinite short-circuit" "false" "(every? pos? (range))"] + ["char? char" "true" "(char? \\x)"] + ["char? string" "false" "(char? \"x\")"] + ["char? number" "false" "(char? 97)"] + ["char? nil" "false" "(char? nil)"]) diff --git a/test/spec/sequences-spec.janet b/test/spec/sequences-spec.janet index f851a75..2c9f3a8 100644 --- a/test/spec/sequences-spec.janet +++ b/test/spec/sequences-spec.janet @@ -290,3 +290,20 @@ "(loop [xs (seq (vec (range 20000))) n 0] (if xs (recur (next xs) (inc n)) n))"] ["mapv scales (50k linear)" "50000" "(count (mapv inc (vec (range 50000))))"] ["nested walk" "[2 3]" "(vec (rest (mapv inc [0 1 2])))"]) + +# rest/next over sets, maps, and sorted colls used to fall into the indexed +# fall-through and walk the wrapper table's internal fields ((next #{1 2}) +# was (nil nil)) — exposed when the canonical every? started seq-walking. +(defspec "sequences / rest & next over set-like colls" + ["next of set" "true" "(let [n (next #{1 2})] (and (= 1 (count n)) (contains? #{1 2} (first n))))"] + ["rest of set count" "1" "(count (rest #{1 2}))"] + ["next of singleton set" "nil" "(next #{1})"] + ["rest of empty set" "0" "(count (rest #{}))"] + ["next of map" "true" "(let [n (next {:a 1 :b 2})] (and (= 1 (count n)) (map-entry? (first n))))"] + ["next of singleton map" "nil" "(next {:a 1})"] + ["rest of sorted-set" "(quote (2 3))" "(rest (sorted-set 3 1 2))"] + ["next of sorted-map" "(quote ([2 :b]))" "(next (sorted-map 1 :a 2 :b))"] + ["every? over set" "true" "(every? pos? #{1 2 3})"] + ["every? over set false" "false" "(every? odd? #{1 2})"] + ["every? over sorted-set" "true" "(every? pos? (sorted-set 1 2 3))"] + ["every? over map entries" "true" "(every? map-entry? (seq {:a 1 :b 2}))"])