Comprehensive spec (#19)

* core: fix jolt-265 — syntax-quote fully-qualifies core syms to clojure.core/

Re-attempt of the deferred gap, now unblocked. The earlier uberscript break
wasn't qualification itself but an aliased-ref resolution bug it exposed:
resolve-var looked up ns-aliases (e.g. g/foo) against the analyzer's REBOUND
ctx-current-ns (jolt.analyzer, since the analyzer runs interpreted in its own
ns) instead of the namespace being compiled. A user's aliased refs failed
mid-compile (g/hello -> nil in the bundled standalone).

- resolve-var resolves aliases against :compile-ns when set (same ns
  h-current-ns uses), falling back to ctx-current-ns — correct for both modes.
- sq-symbol qualifies a resolved clojure.core name to clojure.core/<name>
  (Clojure hygiene); special forms stay bare; unresolved syms -> current ns.

Tests updated to the qualified behavior (features-test, reader-forms-spec).

* test: comprehensive spec — regex + sorted colls + random/predicate gaps

Filling the biggest untested clojure.core areas found in a coverage audit
(168 of 506 provided fns had no spec). New + expanded suites:

- regex-spec.janet (20): #"…" literals, regex?, re-find/re-matches/re-seq
  (match/no-match/groups), re-pattern, and clojure.string split/replace with
  regex (incl $1 backrefs). Whole area was previously unspecced.
- sorted-spec.janet (14): sorted-map/sorted-set construction + ordering, sorted?,
  subseq/rsubseq. Pins the working subset — get/conj/assoc/keys/vals on sorted
  colls and the by-comparator ctors are not yet first-class (jolt-ti9).
- predicates-spec +14: seqable?, integer?, reduced?/unreduced, not-empty.
- numbers-spec +5: rand/rand-int/rand-nth invariants.

Fix: sorted? was bound to core-sorted-map? so it returned false for sorted-sets;
now true for both sorted maps and sets (core-sorted?).

Filed: jolt-ti9 (sorted collections incomplete: get/conj/assoc/keys/vals don't
operate on the sorted wrapper; sorted-*-by ignore the comparator).

Gate green incl full jpm build + jpm test.

* core: close surfaced gaps — first-class sorted colls, with-out-str, rand arity, deref reduced

jolt-ti9: sorted-map/sorted-set are now first-class across the collection fns —
get/assoc/dissoc/conj/contains?/keys/vals/disj and call-as-fn all operate on the
wrapper and preserve sort order. The by-comparator constructors (sorted-map-by/
sorted-set-by) now thread the user comparator (numeric or boolean-predicate) through
all derived colls. Sorted predicates/ctors/ops moved above core-conj so the
collection fns can branch on them; jolt-invoke (interpreter) gets inline branches.

jolt-rfw: add with-out-str (binds output to a string buffer) + the macro.
jolt-ek3: (rand n) arity and deref-of-reduced (uuid? still deferred).

Specs: new io-spec.janet; sorted-spec expanded to pin the now-working map/set ops
and by-comparator ordering; predicate/number spec restorations.

* remove old doc

* core: fix stale comment — by-comparator sorted ctors are implemented

---------

Co-authored-by: Yogthos <yogthos@gmail.com>
This commit is contained in:
Dmitri Sotnikov 2026-06-10 20:33:22 +08:00 committed by GitHub
parent afb7d17352
commit 3e9fe8d0fb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 254 additions and 462 deletions

26
test/spec/io-spec.janet Normal file
View file

@ -0,0 +1,26 @@
# Specification: printing / output (print/println/pr/prn, *-str, format, str).
# Output is captured with with-out-str (jolt-rfw); the *-str fns return strings.
(use ../support/harness)
(defspec "io / with-out-str captures"
["println" "\"hi\\n\"" "(with-out-str (println \"hi\"))"]
["print spaces" "\"a b\"" "(with-out-str (print \"a\" \"b\"))"]
["prn quotes" "\"[1 2]\\n\"" "(with-out-str (prn [1 2]))"]
["pr no newline" "\"5\"" "(with-out-str (pr 5))"]
["multiple writes" "\"12\"" "(with-out-str (print 1) (print 2))"]
["no output" "\"\"" "(with-out-str 42)"]
["println no args" "\"\\n\"" "(with-out-str (println))"])
(defspec "io / *-str builders"
["print-str" "\"a b\"" "(print-str \"a\" \"b\")"]
["println-str" "\"x\\n\"" "(println-str \"x\")"]
["prn-str" "\"[1 2]\\n\"" "(prn-str [1 2])"]
["pr-str quotes" "\"\\\"s\\\"\"" "(pr-str \"s\")"]
["pr-str keyword" "\":a\"" "(pr-str :a)"])
(defspec "io / str & format"
["str concat" "\"1:ab\"" "(str 1 :a \"b\")"]
["str nil" "\"\"" "(str nil)"]
["str of coll" "\"[1 2]\"" "(str [1 2])"]
["format d/s" "\"5-x\"" "(format \"%d-%s\" 5 \"x\")"]
["format float" "\"3.14\"" "(format \"%.2f\" 3.14159)"])

View file

@ -126,3 +126,11 @@
["bit-clear" "13" "(bit-clear 15 1)"]
["bit-test true" "true" "(bit-test 4 2)"]
["bigint 64-bit" "\"9000000000\"" "(str (bigint 9000000000))"])
(defspec "numbers / random (invariants — non-deterministic)"
["rand-int in range" "true" "(let [r (rand-int 5)] (and (integer? r) (>= r 0) (< r 5)))"]
["rand-int zero" "0" "(rand-int 1)"]
["rand in [0,1)" "true" "(let [r (rand)] (and (>= r 0) (< r 1)))"]
["rand n in [0,n)" "true" "(let [r (rand 10)] (and (>= r 0) (< r 10)))"]
["rand-nth member" "true" "(contains? #{:a :b :c} (rand-nth [:a :b :c]))"]
["rand-nth single" ":x" "(rand-nth [:x])"])

View file

@ -113,3 +113,20 @@
["not= differs" "true" "(not= [1 2] [1 3])"]
["identical? same kw" "true" "(identical? :a :a)"]
["compare strings" "-1" "(compare \"a\" \"b\")"])
(defspec "predicates / seqable, reduced & emptiness"
["seqable? vector" "true" "(seqable? [1])"]
["seqable? map" "true" "(seqable? {:a 1})"]
["seqable? string" "true" "(seqable? \"s\")"]
["seqable? nil" "true" "(seqable? nil)"]
["seqable? number" "false" "(seqable? 5)"]
["integer? int" "true" "(integer? 5)"]
["integer? fraction" "false" "(integer? 5.5)"]
["reduced? wrapped" "true" "(reduced? (reduced 1))"]
["reduced? plain" "false" "(reduced? 1)"]
["deref reduced" "9" "(deref (reduced 9))"]
["unreduced wrapped" "9" "(unreduced (reduced 9))"]
["unreduced plain" "9" "(unreduced 9)"]
["not-empty full" "[1]" "(not-empty [1])"]
["not-empty empty" "nil" "(not-empty [])"]
["not-empty string" "nil" "(not-empty \"\")"])

View file

@ -0,0 +1,33 @@
# Specification: regular expressions — #"…" literals and the re-* fns.
# (Whole area previously unspecced; some cases adapted from jank reader-macro/regex.)
(use ../support/harness)
(defspec "regex / literals & predicate"
["regex? literal" "true" "(regex? #\"\\d+\")"]
["regex? non-regex" "false" "(regex? \"\\d+\")"]
["escaped digits" "\"42\"" "(re-find #\"\\d+\" \"x42y\")"]
["escaped ws/non-ws" "\"x a\"" "(re-find #\"\\S\\s\\S\" \"x a b y\")"])
(defspec "regex / re-find"
["match" "\"123\"" "(re-find #\"\\d+\" \"abc123def\")"]
["no match nil" "nil" "(re-find #\"\\d+\" \"abc\")"]
["with groups" "[\"a1\" \"a\" \"1\"]" "(re-find #\"([a-z])(\\d)\" \"--a1--\")"]
["first match only" "\"1\"" "(re-find #\"\\d\" \"1 2 3\")"])
(defspec "regex / re-matches"
["full match" "\"123\"" "(re-matches #\"\\d+\" \"123\")"]
["partial = nil" "nil" "(re-matches #\"\\d+\" \"123abc\")"]
["groups" "[\"12\" \"1\" \"2\"]" "(re-matches #\"(\\d)(\\d)\" \"12\")"]
["no match nil" "nil" "(re-matches #\"x+\" \"yyy\")"])
(defspec "regex / re-seq"
["all matches" "(quote (\"1\" \"22\" \"333\"))" "(re-seq #\"\\d+\" \"a1b22c333\")"]
["empty when none" "nil" "(seq (re-seq #\"z\" \"abc\"))"]
["words" "(quote (\"foo\" \"bar\"))" "(re-seq #\"\\w+\" \"foo bar\")"])
(defspec "regex / re-pattern & string ops"
["re-pattern build" "\"hi\"" "(re-find (re-pattern \"\\\\w+\") \"hi!\")"]
["re-pattern is regex?" "true" "(regex? (re-pattern \"a\"))"]
["split on regex" "[\"a\" \"b\" \"c\"]" "(do (require '[clojure.string :as s]) (s/split \"a1b2c\" #\"\\d\"))"]
["replace regex" "\"X-X\"" "(do (require '[clojure.string :as s]) (s/replace \"a-b\" #\"[a-z]\" \"X\"))"]
["replace $1" "\"[a][b]\"" "(do (require '[clojure.string :as s]) (s/replace \"ab\" #\"([a-z])\" \"[$1]\"))"])

View file

@ -0,0 +1,59 @@
# Specification: sorted collections (sorted-map / sorted-set, subseq/rsubseq).
#
# sorted collections are first-class for the core ops (jolt-ti9): get/assoc/dissoc/
# conj/contains?/keys/vals/disj all work and preserve sort order, and a sorted coll
# is callable as a key-lookup fn. STILL TODO: the by-comparator constructors
# (sorted-map-by / sorted-set-by) ignore the supplied comparator (jolt-ti9). (vec
# coerces a seq to a vector so expecteds are vector literals, not quoted lists.)
(use ../support/harness)
(defspec "sorted / construction & ordering"
["sorted-set orders" "[1 2 3]" "(vec (seq (sorted-set 3 1 2)))"]
["sorted-set dedupes" "[1 2 3]" "(vec (seq (sorted-set 3 1 2 1 3)))"]
["sorted-set numeric" "[1 2 10]" "(vec (seq (sorted-set 10 1 2)))"]
["sorted-map ordered entries" "[[:a 1] [:b 2] [:c 3]]" "(vec (seq (sorted-map :c 3 :a 1 :b 2)))"]
["first is min" "1" "(first (sorted-set 5 3 9 1))"])
(defspec "sorted / sorted?"
["sorted-set" "true" "(sorted? (sorted-set 1))"]
["sorted-map" "true" "(sorted? (sorted-map :a 1))"]
["plain set" "false" "(sorted? #{1})"]
["plain map" "false" "(sorted? {:a 1})"]
["vector" "false" "(sorted? [1 2])"])
(defspec "sorted / map ops"
["get hit" "2" "(get (sorted-map :a 1 :b 2) :b)"]
["get miss default" ":none" "(get (sorted-map :a 1) :z :none)"]
["contains? yes" "true" "(contains? (sorted-map :a 1) :a)"]
["contains? no" "false" "(contains? (sorted-map :a 1) :z)"]
["assoc keeps order" "[[:a 1] [:b 2] [:c 3]]" "(vec (seq (assoc (sorted-map :c 3 :a 1) :b 2)))"]
["dissoc" "[[:a 1] [:c 3]]" "(vec (seq (dissoc (sorted-map :a 1 :b 2 :c 3) :b)))"]
["conj entry" "[[:a 1] [:z 9]]" "(vec (seq (conj (sorted-map :a 1) [:z 9])))"]
["keys sorted" "[:a :b :c]" "(vec (keys (sorted-map :c 3 :a 1 :b 2)))"]
["vals by key" "[1 2 3]" "(vec (vals (sorted-map :c 3 :a 1 :b 2)))"]
["map as fn" "2" "((sorted-map :a 1 :b 2) :b)"]
["map as fn miss" ":d" "((sorted-map :a 1) :z :d)"])
(defspec "sorted / set ops"
["get present" "2" "(get (sorted-set 1 2 3) 2)"]
["get absent" ":none" "(get (sorted-set 1 2 3) 9 :none)"]
["contains? yes" "true" "(contains? (sorted-set 1 2 3) 2)"]
["contains? no" "false" "(contains? (sorted-set 1 2 3) 9)"]
["conj keeps order" "[0 1 2 3 5]" "(vec (seq (conj (sorted-set 1 2 3) 5 0)))"]
["disj" "[1 3]" "(vec (seq (disj (sorted-set 1 2 3) 2)))"]
["set as fn" "3" "((sorted-set 1 2 3) 3)"]
["set as fn miss" "nil" "((sorted-set 1 2 3) 9)"])
(defspec "sorted / by comparator"
["sorted-set-by desc" "[10 3 2 1]" "(vec (seq (sorted-set-by > 1 3 2 10)))"]
["sorted-map-by desc" "[[3 :c] [2 :b] [1 :a]]" "(vec (seq (sorted-map-by > 1 :a 3 :c 2 :b)))"]
["conj keeps comparator" "[5 3 2 1 0]" "(vec (seq (conj (sorted-set-by > 1 3 2) 5 0)))"]
["assoc keeps comparator" "[3 2 1]" "(vec (keys (assoc (sorted-map-by > 1 :a 3 :c) 2 :b)))"]
["disj keeps comparator" "[3 1]" "(vec (seq (disj (sorted-set-by > 1 2 3) 2)))"]
["by-comparator is sorted?" "true" "(sorted? (sorted-set-by > 1 2))"])
(defspec "sorted / subseq & rsubseq"
["subseq >=" "[3 4 5]" "(vec (subseq (sorted-set 1 2 3 4 5) >= 3))"]
["subseq <" "[1 2]" "(vec (subseq (sorted-set 1 2 3 4 5) < 3))"]
["subseq range" "[2 3 4]" "(vec (subseq (sorted-set 1 2 3 4 5) > 1 < 5))"]
["rsubseq <=" "[3 2 1]" "(vec (rsubseq (sorted-set 1 2 3 4 5) <= 3))"])