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

@ -287,6 +287,46 @@
(defn tagged-literal? [x] (= (get x :jolt/type) :jolt/tagged-literal))
(defn record? [x] (some? (get x :jolt/deftype)))
(defn uuid? [x] (= (get x :jolt/type) :jolt/uuid))
;; Clojure 1.11 map transformers. PHM base so transformed keys canonicalize
;; (collisions: last entry in seq order wins, matching the reference).
(defn update-keys [m f]
(reduce-kv (fn [acc k v] (assoc acc (f k) v)) (hash-map) m))
(defn update-vals [m f]
(reduce-kv (fn [acc k v] (assoc acc k (f v))) (hash-map) m))
;; Vector-returning partition variants (1.11): lazy seqs OF vectors.
(defn partitionv
([n coll] (map vec (partition n coll)))
([n step coll] (map vec (partition n step coll)))
([n step pad coll] (map vec (partition n step pad coll))))
(defn partitionv-all
([n coll] (map vec (partition-all n coll)))
([n step coll] (map vec (partition-all n step coll))))
;; First part a vector, rest a seq — matching the reference implementation.
(defn splitv-at [n coll]
[(vec (take n coll)) (drop n coll)])
;; with-redefs-fn: temporarily set each var's root to the mapped value, run
;; the thunk, restore the saved roots even on throw. The with-redefs macro
;; (30-macros) builds the {var val} map from names.
(defn with-redefs-fn [binding-map func]
(let [vars (vec (keys binding-map))
saved (mapv var-get vars)]
(doseq [v vars] (var-set v (get binding-map v)))
(try
(func)
(finally
;; loop/recur, not dotimes: dotimes is a 30-macros macro and this tier
;; compiles before it exists (a forward ref would resolve to the macro
;; fn at runtime and mis-apply it).
(loop [i 0]
(when (< i (count vars))
(var-set (nth vars i) (nth saved i))
(recur (inc i))))))))
;; Jolt has no chunked seqs (Phase 5 territory), so this is always false.
(defn chunked-seq? [x] false)

View file

@ -57,6 +57,22 @@
;; defonce: define name only if it isn't already bound to a non-nil root;
;; returns the existing var untouched otherwise (matching the prior arm).
;; time: evaluate expr, print the elapsed wall-clock, return the value.
;; current-time-ms is the host's monotonic clock.
(defmacro time [expr]
`(let [start# (current-time-ms)
ret# ~expr]
(println (str "Elapsed time: " (- (current-time-ms) start#) " msecs"))
ret#))
;; with-redefs: temporary root rebinding, restored on exit (incl. throw).
;; Builds (hash-map (var n1) v1 ...) — a call form, since map-literal forms
;; can't carry call forms as keys.
(defmacro with-redefs [bindings & body]
(let [pairs (reduce (fn [acc p] (conj (conj acc `(var ~(first p))) (second p)))
[] (partition 2 bindings))]
`(with-redefs-fn (hash-map ~@pairs) (fn [] ~@body))))
(defmacro defonce [name expr]
`(let [v# (resolve (quote ~name))]
(if (and v# (some? (var-get v#)))