Merge pull request #75 from jolt-lang/seed-shrink-r4-syntax-tier

core: zero?/pos?/every? to 00-syntax, char? to the overlay; fix rest/next over sets and maps
This commit is contained in:
Dmitri Sotnikov 2026-06-11 17:33:31 +00:00 committed by GitHub
commit b9c7a623bb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 97 additions and 37 deletions

View file

@ -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

View file

@ -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).

View file

@ -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

View file

@ -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

View file

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

View file

@ -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}))"])