sorted-map/sorted-map-by/sorted-set/sorted-set-by/sorted?/sorted-map?/
sorted-set?/subseq/rsubseq now live in their own overlay tier (25-sorted.clj).
A sorted coll is a tagged host table with a comparator-ordered :entries
vector, a 3-way :cmp, and the tier's op implementations ATTACHED to the value
(:ops map): the seed's conj/assoc/get/seq/count/... branches are each a
one-line call through (coll :ops), so the ops travel with the value — correct
across contexts, forks, and AOT images, no module-level hooks to re-wire.
The host surface grows by three minimal value primitives: jolt.host/
tagged-table, ref-put! (already there), and ref-get — a raw field read,
because plain get on a sorted coll IS the comparator lookup and reading
:entries with it recurses.
This fixes a pile of Clojure-correctness gaps the Janet kernel had:
- lookup/membership now go through the COMPARATOR: (contains? (sorted-set 1)
1.0) was a deep= scan, (conj (sorted-set 1) 1.0) and assoc of a
comparator-equal key now no-op/replace as in Clojure
- equality is representation-agnostic: (= (sorted-map :a 1) {:a 1}) and
(= (sorted-set 1 2) #{1 2}) were false
- iteration was broken: (map inc (sorted-set 3 1 2)) errored
(realize-for-iteration and coll->cells had no sorted branches)
- empty?/empty saw the host wrapper, not the collection: (empty? (sorted-map))
was false, (empty sc) returned a bare table; it now keeps the comparator
- sorted colls canonicalize as map keys; comparator fns may be boolean
predicates or 3-way (Clojure's fn->comparator)
- sorted-map throws on odd kv count; conj nil is a no-op
Also fixes jolt-h86 en passant: into-conj had no branch for sets (or sorted
colls) and silently returned the target unchanged — (into #{} [:a :b]) was
#{}. The fallback now folds conj. Regression rows in sets-spec.
sorted-spec grows to 77 rows (comparator-based membership, equality,
empty/rseq/printing, seq-fn interop, subseq/rsubseq on maps). Gate green:
conformance 326x3, suite 4577 (vs 4566 prior — the battery gained rows),
sorted+sets specs, full jpm test, bench at parity with main back-to-back
(4521ms vs 4619ms TOTAL under identical load).
45 lines
2.8 KiB
Text
45 lines
2.8 KiB
Text
# Specification: sets, including clojure.set.
|
|
(use ../support/harness)
|
|
|
|
(defspec "set / construct & predicate"
|
|
["literal" "#{1 2 3}" "#{1 2 3}"]
|
|
["hash-set" "#{1 2 3}" "(hash-set 1 2 3)"]
|
|
["set from vector" "#{1 2 3}" "(set [1 2 3 1])"]
|
|
["empty" "#{}" "#{}"]
|
|
["set? true" "true" "(set? #{1})"]
|
|
["set? false on vector" "false" "(set? [1])"]
|
|
["count dedups" "3" "(count (set [1 1 2 3]))"]
|
|
["equality order-indep" "true" "(= #{1 2 3} #{3 2 1})"]
|
|
# jolt-h86: into-conj had no set branch and returned the set unchanged
|
|
["into set" "#{:a :b}" "(into #{} [:a :b])"]
|
|
["into non-empty set" "#{1 2 3}" "(into #{1} [2 3 2])"])
|
|
|
|
(defspec "set / operations"
|
|
["conj adds" "#{1 2 3}" "(conj #{1 2} 3)"]
|
|
["conj dup no-op" "#{1 2}" "(conj #{1 2} 1)"]
|
|
["disj removes" "#{1 2}" "(disj #{1 2 3} 3)"]
|
|
["disj missing no-op" "#{1 2}" "(disj #{1 2} 9)"]
|
|
["contains?" "true" "(contains? #{1 2} 1)"]
|
|
["contains? missing" "false" "(contains? #{1 2} 9)"]
|
|
["get present" "1" "(get #{1 2} 1)"]
|
|
["get missing nil" "nil" "(get #{1 2} 9)"]
|
|
["set as fn present" "2" "(#{1 2 3} 2)"]
|
|
["set as fn missing" "nil" "(#{1 2 3} 9)"])
|
|
|
|
(defspec "set / literals & value elements"
|
|
["literal evaluates elements" "#{2 4}" "#{(inc 1) (* 2 2)}"]
|
|
["map elements by value" "true" "(= #{{:a 1}} #{(hash-map :a 1)})"]
|
|
["contains? map by value" "true" "(contains? #{(hash-map :x 1)} {:x 1})"]
|
|
["dedup equal maps" "1" "(count (set [{:a 1} (hash-map :a 1)]))"]
|
|
["vector elements" "true" "(contains? #{[1 2]} (vec [1 2]))"])
|
|
|
|
(defspec "clojure.set"
|
|
["union" "#{1 2 3 4}" "(do (require (quote [clojure.set :as s])) (s/union #{1 2} #{3 4}))"]
|
|
["intersection" "#{2}" "(do (require (quote [clojure.set :as s])) (s/intersection #{1 2} #{2 3}))"]
|
|
["difference" "#{1}" "(do (require (quote [clojure.set :as s])) (s/difference #{1 2} #{2 3}))"]
|
|
["subset? true" "true" "(do (require (quote [clojure.set :as s])) (s/subset? #{1} #{1 2}))"]
|
|
["superset? true" "true" "(do (require (quote [clojure.set :as s])) (s/superset? #{1 2} #{1}))"]
|
|
["select" "#{2 4}" "(do (require (quote [clojure.set :as s])) (s/select even? #{1 2 3 4}))"]
|
|
["join" "#{{:a 1, :b 2, :c 3}}" "(do (require (quote [clojure.set :as s])) (s/join #{{:a 1 :b 2}} #{{:b 2 :c 3}}))"]
|
|
["map-invert" "{1 :a}" "(do (require (quote [clojure.set :as s])) (s/map-invert {:a 1}))"]
|
|
["rename-keys" "{:b 1}" "(do (require (quote [clojure.set :as s])) (s/rename-keys {:a 1} {:a :b}))"])
|