core: spec 35-var batch A — 1.11 parsers, map/partition variants, with-redefs, ns fns

Fifteen vars from the spec coverage gap (docs/spec/coverage.md):
parse-long/parse-double/parse-boolean (strict validation; scan-number alone
accepts 0x10), newline, current-time-ms (host clock for time), update-keys/
update-vals (PHM base, collisions last-wins), partitionv/partitionv-all/
splitv-at (lazy seqs of vectors; splitv-at's tail stays a seq, matching the
reference), with-redefs/with-redefs-fn (roots restored on throw), time,
macroexpand (expand-1 to fixpoint), alias/ns-unalias (write BOTH alias stores
— require :as uses string-keyed :imports while ns-aliases reads :aliases;
split filed), ns-publics (symbol-keyed map; publics == interns, no privacy).

Three pre-existing bugs fixed along the way:
- (partition n step pad coll) misparsed pad as the coll and returned ()
- (require 'bare.symbol) rejected — only vector specs were accepted
- analyze-form leaked the interpreted analyzer's ns on a punt: a throw out of
  the analyzer left current-ns=jolt.analyzer and :compile-ns set, so the
  fallback interpretation resolved user vars against the wrong namespace
  (bit (var user-sym) under compile mode)

And one overlay-authoring landmine documented in-code: a 20-coll fn must not
use 30-macros macros (with-redefs-fn's dotimes compiled as a forward ref that
resolved to the macro fn at runtime) — loop/recur instead.

Gate: conformance 316x3 (+14 rows), suite 4324 pass / 78 clean (was 4081/72;
parse_*/update_* files now contribute), baselines raised, all specs+unit,
fixpoint, self-host, sci, staged. Coverage: missing-portable 35 -> 20.
This commit is contained in:
Yogthos 2026-06-10 11:16:54 -04:00
parent 894af34b4c
commit eb7a9f1b20
13 changed files with 277 additions and 44 deletions

View file

@ -43,9 +43,9 @@
# 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 4081)
(def baseline-pass 4324)
# A file is "clean" when it ran with zero failures AND zero errors.
(def baseline-clean-files 72)
(def baseline-clean-files 78)
# Per-file wall-clock budget (seconds). Normal files finish in well under 1s, so
# this normally only fires on genuinely-infinite-sequence hangs. It's an env var
# (JOLT_SUITE_TIMEOUT) so CI — whose runners are slower than a dev machine — can

View file

@ -177,6 +177,22 @@
["parse-uuid case =" "true" "(= (parse-uuid \"b6883c0a-0342-4007-9966-bc2dfa6b109e\") (parse-uuid \"B6883C0A-0342-4007-9966-BC2DFA6B109E\"))"]
["parse-uuid bad nil" "nil" "(parse-uuid \"df0993\")"]
["uuid as map key" ":v" "(get {(parse-uuid \"b6883c0a-0342-4007-9966-bc2dfa6b109e\") :v} (parse-uuid \"b6883c0a-0342-4007-9966-bc2dfa6b109e\"))"]
### ---- 1.11 additions + ns fns (spec 35-var batch A) ----
["parse-long" "42" "(parse-long \"42\")"]
["parse-long bad" "nil" "(parse-long \"4.2\")"]
["parse-double" "1500.0" "(parse-double \"1.5e3\")"]
["parse-boolean" "true" "(parse-boolean \"true\")"]
["update-keys" "{\"a\" 1}" "(update-keys {:a 1} name)"]
["update-vals" "{:a 2}" "(update-vals {:a 1} inc)"]
["partitionv pad" "[[1 2] [3 :p]]" "(partitionv 2 2 [:p] [1 2 3])"]
["partition pad" "[[0 1 2 3] [4 5 6 7] [8 9 :a]]" "(partition 4 4 [:a] (range 10))"]
["splitv-at" "[[1 2] [3 4]]" "(splitv-at 2 [1 2 3 4])"]
["with-redefs" "[42 1]" "(do (defn cwr [] 1) [(with-redefs [cwr (fn [] 42)] (cwr)) (cwr)])"]
["time returns value" "3" "(time (+ 1 2))"]
["macroexpand" "true" "(= (quote if) (first (macroexpand (quote (when-not false 1)))))"]
["require bare symbol" "\"a,b\"" "(do (require (quote clojure.string)) (clojure.string/join \",\" [\"a\" \"b\"]))"]
["ns-publics lookup" "true" "(do (def cnp 7) (some? (get (ns-publics (quote user)) (quote cnp))))"]
["macroexpand-1 when" "2" "(count (rest (macroexpand-1 (quote (when true 1)))))"]
### ---- HIGH: aliased namespace calls ----

View file

@ -72,3 +72,16 @@
["binding rebinds" "99" "(do (def ^:dynamic *bx* 10) (binding [*bx* 99] *bx*))"]
["binding restores" "10" "(do (def ^:dynamic *by* 10) (binding [*by* 99] *by*) *by*)"]
["binding seen by fn" "7" "(do (def ^:dynamic *bz* 0) (defn rdz [] *bz*) (binding [*bz* 7] (rdz)))"])
# time returns the body value (the timing line goes to *out*); with-redefs
# temporarily rebinds var roots and restores on exit (even on throw);
# macroexpand expands repeatedly until the head is no longer a macro.
(defspec "macros / time, with-redefs, macroexpand"
["time returns value" "3" "(time (+ 1 2))"]
["with-redefs rebinds" "42" "(do (defn wr-f [] 1) (with-redefs [wr-f (fn [] 42)] (wr-f)))"]
["with-redefs restores" "1" "(do (defn wr-g [] 1) (with-redefs [wr-g (fn [] 42)]) (wr-g))"]
["with-redefs restores on throw" "1"
"(do (defn wr-h [] 1) (try (with-redefs [wr-h (fn [] 42)] (throw (ex-info \"x\" {}))) (catch :default e nil)) (wr-h))"]
["with-redefs-fn" "42" "(do (defn wr-i [] 1) (with-redefs-fn {(var wr-i) (fn [] 42)} (fn [] (wr-i))))"]
["macroexpand full" "true" "(let [e (macroexpand (quote (when-not false 1)))] (= (quote if) (first e)))"]
["macroexpand non-macro" "[1 2]" "(macroexpand (quote [1 2]))"])

View file

@ -144,3 +144,14 @@
["dissoc keeps nil" "true" "(contains? (dissoc {:a nil :b 1} :b) :a)"]
["reduce-kv sees nil" "true" "(= #{:a :b} (reduce-kv (fn [acc k v] (conj acc k)) #{} {:a nil :b 2}))"]
["nil-free stays fast" "true" "(= {:a 1 :b 2} {:b 2 :a 1})"])
# Clojure 1.11 map transformers.
(defspec "map / update-keys & update-vals (1.11)"
["update-keys" "{\"a\" 1, \"b\" 2}" "(update-keys {:a 1 :b 2} name)"]
["update-keys empty" "{}" "(update-keys {} inc)"]
["update-keys nil" "{}" "(update-keys nil str)"]
["update-keys collide last wins" "1" "(count (update-keys {:a 1 :b 2} (fn [_] :k)))"]
["update-vals" "{:a 2, :b 3}" "(update-vals {:a 1 :b 2} inc)"]
["update-vals empty" "{}" "(update-vals {} inc)"]
["update-vals nil" "{}" "(update-vals nil inc)"]
["update-vals keeps keys" "[:a :b]" "(sort (keys (update-vals {:a 1 :b 2} inc)))"])

View file

@ -35,3 +35,10 @@
["require clojure.walk" "{:a 2}" "(do (require '[clojure.walk :as w]) (w/postwalk (fn [x] (if (number? x) (inc x) x)) {:a 1}))"]
["walk keywordize-keys" "{:a 1}" "(do (require '[clojure.walk :as w]) (w/keywordize-keys {\"a\" 1}))"]
["walk stringify-keys" "true" "(do (require '[clojure.walk :as w]) (= {\"a\" 1} (w/stringify-keys {:a 1})))"])
(defspec "namespaces / alias, ns-unalias, ns-publics"
["alias + use" "\"1,2\"" "(do (require (quote clojure.string)) (alias (quote st) (quote clojure.string)) (st/join \",\" [1 2]))"]
["ns-unalias removes" "true"
"(do (require (quote clojure.string)) (alias (quote st2) (quote clojure.string)) (ns-unalias (quote user) (quote st2)) (nil? (get (ns-aliases (quote user)) (quote st2))))"]
["ns-publics has var" "true" "(do (def npv 1) (some? (get (ns-publics (quote user)) (quote npv))))"]
["newline returns nil" "nil" "(newline)"])

View file

@ -134,3 +134,26 @@
["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])"])
# Clojure 1.11 string->scalar parsers: nil on malformed, throw on non-string.
(defspec "numbers / parse fns (1.11)"
["parse-long" "42" "(parse-long \"42\")"]
["parse-long negative" "-7" "(parse-long \"-7\")"]
["parse-long plus" "7" "(parse-long \"+7\")"]
["parse-long float nil" "nil" "(parse-long \"1.5\")"]
["parse-long hex nil" "nil" "(parse-long \"0x10\")"]
["parse-long empty nil" "nil" "(parse-long \"\")"]
["parse-long junk nil" "nil" "(parse-long \"12ab\")"]
["parse-long throws" :throws "(parse-long 42)"]
["parse-double" "1.5" "(parse-double \"1.5\")"]
["parse-double int" "4.0" "(parse-double \"4\")"]
["parse-double sci" "1500.0" "(parse-double \"1.5e3\")"]
["parse-double neg" "-0.5" "(parse-double \"-0.5\")"]
["parse-double junk" "nil" "(parse-double \"abc\")"]
["parse-double trail" "nil" "(parse-double \"1.5x\")"]
["parse-double throws" :throws "(parse-double :k)"]
["parse-boolean true" "true" "(parse-boolean \"true\")"]
["parse-boolean false" "false" "(parse-boolean \"false\")"]
["parse-boolean case" "nil" "(parse-boolean \"True\")"]
["parse-boolean junk" "nil" "(parse-boolean \"yes\")"]
["parse-boolean throws" :throws "(parse-boolean true)"])

View file

@ -261,3 +261,15 @@
["dedupe consecutive" "[1 2 3 1]" "(dedupe [1 1 2 2 3 1 1])"]
["dedupe empty" "[]" "(dedupe [])"]
["dedupe no dups" "[1 2 3]" "(dedupe [1 2 3])"])
# Clojure 1.11 vector-returning partition/split variants.
(defspec "seq / partitionv & splitv-at (1.11)"
["partitionv" "[[1 2] [3 4]]" "(partitionv 2 [1 2 3 4 5])"]
["partitionv elems are vectors" "true" "(every? vector? (partitionv 2 [1 2 3 4]))"]
["partitionv step" "[[1 2] [3 4]]" "(partitionv 2 2 [1 2 3 4 5])"]
["partitionv pad" "[[1 2] [3 :p]]" "(partitionv 2 2 [:p] [1 2 3])"]
["partitionv-all" "[[1 2] [3]]" "(partitionv-all 2 [1 2 3])"]
["partitionv-all vectors" "true" "(every? vector? (partitionv-all 2 [1 2 3]))"]
["splitv-at" "[[1 2] [3 4]]" "(splitv-at 2 [1 2 3 4])"]
["splitv-at first is vector" "true" "(vector? (first (splitv-at 2 [1 2 3])))"]
["splitv-at past end" "[[1 2] []]" "(splitv-at 5 [1 2])"])