reader: feature set #{:jolt :default}, clause-order matching (RFC 0002)

jolt no longer satisfies :clj in reader conditionals. The shortcut was a
measured net liability: :clj branches carry JVM interop and JVM-specific
test expectations jolt fails, and they shadowed :default branches jolt
passes. A/B over the suite: clj,default = 4967 assertions / 4324 pass / 119
errors; jolt,default = 5069 / 4470 / 81 (+146 pass, -38 errors, +8 clean
files). Baselines raised to 4470/86.

Matching is now by CLAUSE order like Clojure — the first clause whose key is
in the feature set wins (#?(:default 5 :clj 6) is 5 everywhere); the old code
scanned for :clj first, then :default, regardless of position.

Foreign clj-targeted libraries are a property of the LOADING CONTEXT, not the
platform: reader-features-set! opts a load into a compatibility set, and the
SCI bootstrap/runtime tests load SCI under ["jolt" "clj" "default"] (its
.cljc selects implementations via :clj with no :jolt branches).
JOLT_FEATURES remains the process-wide override.

RFC 0002 records the decision with the measured data; spec 02-reader S18 is
now normative (clause order, documented feature set, per-context override).
Reader tests updated to the portable set + an opt-in round-trip.
This commit is contained in:
Yogthos 2026-06-10 11:40:06 -04:00
parent 2224e40afc
commit fdfd086df6
8 changed files with 164 additions and 33 deletions

View file

@ -387,6 +387,34 @@
(array/push arg-names rest-sym))
[@[(sym "fn*") (tuple ;arg-names) replaced] new-pos]))
# The reader-conditional feature set (spec 02-reader S18): jolt is its own
# dialect, so the portable convention applies — the dialect key plus :default.
# Matching is by CLAUSE order (the first clause whose key is in the feature
# set wins), exactly like Clojure — NOT key-priority. JOLT_FEATURES overrides
# (comma-separated, e.g. "jolt,clj,default") for compat experiments and A/B
# measurement; :default is always honored.
# Mutable so a loading context can opt a clj-targeted foreign library into
# :clj compatibility (e.g. SCI) — see reader-features-set!. jolt itself and
# the conformance surface read under the portable set.
(var reader-features nil)
(defn reader-features-set!
"Replace the active reader-conditional feature set (a list of keyword-name
strings or keywords). :default is always honored. Returns the previous set
so callers can restore."
[names]
(def prev reader-features)
(def t @{})
(each n names (put t (if (keyword? n) n (keyword n)) true))
(put t :default true)
(set reader-features t)
prev)
(reader-features-set!
(let [env (os/getenv "JOLT_FEATURES")]
(if env (filter |(> (length $) 0) (string/split "," env))
["jolt" "default"])))
(defn read-reader-conditional [s pos]
# pos is at #, next char is ? or ?@
(def splice? (and (< (+ pos 2) (length s)) (= (s (+ pos 2)) 64))) # @ = 64
@ -394,23 +422,16 @@
(let [[form new-pos] (read-form s form-start)]
(if (array? form)
(do
# First clause (in clause order) whose feature key is in the set.
# `matched` is tracked separately: a matched clause may be nil.
(var result nil)
(var matched false)
(var i 0)
(while (< i (length form))
(if (= (in form i) :clj)
(do
(set result (in form (+ i 1)))
(set i (length form)))
(++ i)))
# Fallback to :default if :clj not matched
(when (nil? result)
(set i 0)
(while (< i (length form))
(if (= (in form i) :default)
(do
(set result (in form (+ i 1)))
(set i (length form)))
(++ i))))
(while (and (not matched) (< i (length form)))
(when (get reader-features (in form i))
(set result (in form (+ i 1)))
(set matched true))
(+= i 2))
(if splice?
# #?@ splicing: resolve :clj branch, wrap for splice
(let [items (if (nil? result)