core: move the pure-leaf fns to the overlay; memfn is a real macro now

Round 3 of the seed shrink. To the overlay: identity, constantly, neg?,
even?, odd? (20-coll, ahead of their first in-tier uses), not= and unreduced
(00-syntax — the kernel and seq tiers use them), ==, ensure-reduced,
halt-when, parse-boolean, parse-uuid, newline, seque, array-seq, to-array-2d,
and the masking unchecked-byte/short/char/float/double coercions. parse-uuid
validates via re-matches over a new __make-uuid host binding (overlay source
can't write :jolt/type map literals). memfn moves to 30-macros as a working
macro over the .method call sugar instead of a fn that throws.

Behavior fixes toward Clojure, each with spec rows: == now throws on
non-numbers instead of comparing them, and halt-when is the canonical
::halt-map version (the halt value replaces the whole reduction result, no
double completion). list? and map-entry? stay in the seed — both are
representation-coupled (plist/tuple checks).

clojure-test-suite goes 4701 -> 4700: update.cljc expects
(update {:k 1} :k identity 1 2 3 4) to throw an arity error, and jolt fns
don't enforce fixed arity anywhere (pre-existing, language-wide — the seed's
Janet identity threw natively). Filed as jolt-6xn; fixing it should flip
several suite rows at once.
This commit is contained in:
Yogthos 2026-06-11 12:38:12 -04:00
parent 65687c69ae
commit 7ca88ab2b5
7 changed files with 154 additions and 84 deletions

View file

@ -445,3 +445,13 @@
(defmacro lazy-cat [& colls]
`(concat ~@(map (fn [c] `(lazy-seq ~c)) colls)))
;; not= here (not 20-coll): the kernel tier uses it, and the kernel
;; bootstrap-compiles right after this file loads. Canonical Clojure arities.
(defn not=
([x] false)
([x y] (not (= x y)))
([x y & more] (not (apply = x y more))))
;; unreduced here: the seq tier's reduce machinery unwraps with it.
(defn unreduced [x] (if (reduced? x) (deref x) x))

View file

@ -6,6 +6,23 @@
;; Same migration rule as the seq tier (see 10-seq.clj): not in core-renames, no
;; internal Janet callers, not used by the self-hosted compiler.
;; Tiny leaves first — fns below in this tier (and 25-sorted) use them.
(defn identity [x] x)
(defn constantly [x] (fn [& args] x))
;; neg? throws on non-numbers via <, as Clojure's Numbers.isNeg does.
(defn neg? [x] (< x 0))
;; even?/odd? accept any integral number (jolt has one number type, so 2.0
;; counts) and throw otherwise — Clojure's IllegalArgumentException wording.
(defn even? [n]
(if (integer? n)
(zero? (rem n 2))
(throw (str "Argument must be an integer: " n))))
(defn odd? [n] (not (even? n)))
;; Base is (hash-map), not the {} literal: a literal map is a struct that doesn't
;; canonicalize collection keys across representations (a {:a 1} literal vs
;; (hash-map :a 1) key), whereas a PHM does — so counting/grouping by collection
@ -373,6 +390,17 @@
([keyfn comp coll]
(sort (fn [x y] (comp (keyfn x) (keyfn y))) coll)))
;; parse-uuid: nil unless s is a canonical 8-4-4-4-12 hex UUID string; throws
;; on a non-string (Clojure 1.11). __make-uuid is the host constructor for the
;; tagged value (overlay source can't write :jolt/type map literals — the
;; reader treats them as tagged forms).
(defn parse-uuid [s]
(if (string? s)
(when (re-matches
#"[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}" s)
(__make-uuid s))
(throw (str "parse-uuid requires a string, got: " s))))
;; Version-4 UUID (RFC 4122): zero-padded hex groups 8-4-4-4-12, version
;; nibble 4, variant 8-b — built over rand-int and validated by parse-uuid.
(defn random-uuid []
@ -882,3 +910,65 @@
;; num: Clojure coerces to java.lang.Number; jolt just checks.
(defn num [x]
(if (number? x) x (throw (str "num requires a number, got: " x))))
;; == numeric equality: 1-arity is trivially true without inspecting the value
;; (Clojure's shape); 2+ args must be numbers, as Numbers.equiv throws.
(defn ==
([x] true)
([x y]
(if (and (number? x) (number? y))
(= x y)
(throw (str "Cannot cast to number: " (if (number? x) y x)))))
([x y & more]
(if (== x y)
(apply == y more)
false)))
;; ensure-reduced / halt-when: canonical Clojure. halt-when smuggles the halt
;; value through reduce in a ::halt-keyed map and unwraps it in the completion
;; arity, so the halt REPLACES the whole reduction result.
(defn ensure-reduced [x] (if (reduced? x) x (reduced x)))
(defn halt-when
([pred] (halt-when pred nil))
([pred retf]
(fn [rf]
(fn
([] (rf))
([result]
(if (and (map? result) (contains? result ::halt))
(get result ::halt)
(rf result)))
([result input]
(if (pred input)
(reduced (hash-map ::halt (if retf (retf (rf result) input) input)))
(rf result input)))))))
;; parse-boolean: exact "true"/"false" only; nil on anything else, throw on a
;; non-string (Clojure 1.11).
(defn parse-boolean [s]
(if (string? s)
(cond (= s "true") true (= s "false") false :else nil)
(throw (str "parse-boolean requires a string, got: " s))))
(defn newline [] (print "\n") nil)
;; seque: jolt is single-threaded eager here — the queue is a no-op and the
;; coll passes through.
(defn seque
([s] s)
([n-or-q s] s))
(defn array-seq [arr & _] (seq arr))
(defn to-array-2d [coll] (to-array (map to-array coll)))
;; Masking integer coercions (not aliases): byte/short wrap to their width.
;; unchecked-char keeps jolt's historical NUMBER result (Clojure returns a
;; char) — the char wrapper is a different value type here. int handles chars,
;; so (unchecked-byte \a) works as on the JVM.
(defn unchecked-byte [x] (bit-and (int x) 0xff))
(defn unchecked-short [x] (bit-and (int x) 0xffff))
(defn unchecked-char [x] (bit-and (int x) 0xffff))
(defn unchecked-float [x] (double x))
(defn unchecked-double [x] (double x))

View file

@ -407,3 +407,10 @@
;; loads — so the macro must be registered BEFORE those tiers, else (lazy-seq …)
;; compiles as a call to the macro-as-function and leaks its expansion at runtime
;; (jolt-r81). They only need seed fns (make-lazy-seq/coll->cells/concat).
;; memfn: a fn wrapping a method call, (memfn toUpperCase) => #(.toUpperCase %).
;; The method symbol is rewritten to jolt's .method call sugar; extra arg names
;; become fn params, as in Clojure.
(defmacro memfn [method-name & args]
`(fn [target# ~@args]
(~(symbol (str "." (name method-name))) target# ~@args)))