feat: ~40 more core fns + fix map iteration to yield entries

While digging into test-load-sci, add genuinely-missing clojure.core fns:
doall/dorun/run!/tree-seq, key/val/map-entry?, rand-nth/replicate/
bounded-count/counted?/reversible?/seqable?, nat-int?/pos-int?/neg-int?/
double?/float?/ratio?/decimal?/rational?/numerator/denominator, list*,
special-symbol?/record?, promise/deliver, comparator/completing/halt-when/
ensure-reduced/unreduced, keyword-identical?/object?/tagged-literal/re-groups.

Fix a real pre-existing bug: iterating a map (first/seq/map/filter/reduce/vec/
realize-for-iteration) returned keys or values instead of [k v] entries.
Map literals are Janet structs (not phm), which several paths mishandled.
Now first/seq/realize-for-iteration yield entries for both structs and phms,
matching Clojure — e.g. (vec {:a 1}) => [[:a 1]], (map key m), (reduce f init m).

Tests: conformance 218/218 (+12), features 78/78, jank 120. Advances the SCI
bootstrap further (its clojure.core aggregation map resolves more symbols).
This commit is contained in:
Yogthos 2026-06-04 19:38:25 -04:00
parent a0c9696900
commit 39edb88074
4 changed files with 164 additions and 2 deletions

View file

@ -18,3 +18,4 @@
{"id":"int-700a80a8","kind":"field_change","created_at":"2026-06-04T19:56:36.343102Z","actor":"Yogthos","issue_id":"jolt-7dy","extra":{"field":"status","new_value":"closed","old_value":"deferred"}}
{"id":"int-0e80bc4f","kind":"field_change","created_at":"2026-06-04T20:23:37.457452Z","actor":"Yogthos","issue_id":"jolt-cn4","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Closed"}}
{"id":"int-8cd2e476","kind":"field_change","created_at":"2026-06-04T21:28:16.906665Z","actor":"Yogthos","issue_id":"jolt-y29","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Closed"}}
{"id":"int-6042189d","kind":"field_change","created_at":"2026-06-04T23:20:46.565771Z","actor":"Yogthos","issue_id":"jolt-ns4","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Done across 3 rounds (pushed a0c9696): build-time mutable/immutable toggle, structural-sharing persistent vectors (32-way trie), and persistent O(1)-prepend linked lists. Plus bit-clear/get-method and a batch of missing core fns. conformance 206/206, features 78/78, jank 120."}}

View file

@ -5,7 +5,7 @@
{"_type":"issue","id":"jolt-x8s","title":"CRITICAL: collections not callable as IFn (vector/map/set)","status":"closed","priority":1,"issue_type":"bug","owner":"yogthos@gmail.com","created_at":"2026-06-04T17:46:14Z","created_by":"Yogthos","updated_at":"2026-06-04T17:50:54Z","closed_at":"2026-06-04T17:50:54Z","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"jolt-alz","title":"CRITICAL: self-referential lazy-seq/lazy-cat yields only literal prefix","status":"closed","priority":1,"issue_type":"bug","owner":"yogthos@gmail.com","created_at":"2026-06-04T17:46:13Z","created_by":"Yogthos","updated_at":"2026-06-04T18:09:15Z","closed_at":"2026-06-04T18:09:15Z","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"jolt-wec","title":"CRITICAL: multi-collection map returns unrealized thunk","status":"closed","priority":1,"issue_type":"bug","owner":"yogthos@gmail.com","created_at":"2026-06-04T17:46:12Z","created_by":"Yogthos","updated_at":"2026-06-04T18:06:02Z","closed_at":"2026-06-04T18:06:02Z","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"jolt-ns4","title":"Persistent collections + build-time mutable/immutable toggle","description":"Add JOLT_MUTABLE build flag (config.janet). Fix list-mutation bug (conj mutated original). Round 2: structural-sharing persistent vector (32-way trie) hooked into all core ops. Round 3: persistent lists / HAMT maps.","notes":"R1 (build flag + list mutation fix) and R2 (structural-sharing persistent vectors + mutable toggle) done \u0026 pushed (1eb2843). Immutable: conformance 206/206, features 71/71, jank 119. R3 (persistent linked lists) pending user decision: lists are immutable Janet arrays — O(1) sharing cons, O(n) conj.","status":"open","priority":2,"issue_type":"feature","owner":"yogthos@gmail.com","created_at":"2026-06-04T22:15:39Z","created_by":"Yogthos","updated_at":"2026-06-04T22:57:59Z","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"jolt-ns4","title":"Persistent collections + build-time mutable/immutable toggle","description":"Add JOLT_MUTABLE build flag (config.janet). Fix list-mutation bug (conj mutated original). Round 2: structural-sharing persistent vector (32-way trie) hooked into all core ops. Round 3: persistent lists / HAMT maps.","notes":"R1 (build flag + list mutation fix) and R2 (structural-sharing persistent vectors + mutable toggle) done \u0026 pushed (1eb2843). Immutable: conformance 206/206, features 71/71, jank 119. R3 (persistent linked lists) pending user decision: lists are immutable Janet arrays — O(1) sharing cons, O(n) conj.","status":"closed","priority":2,"issue_type":"feature","owner":"yogthos@gmail.com","created_at":"2026-06-04T22:15:39Z","created_by":"Yogthos","updated_at":"2026-06-04T23:20:46Z","closed_at":"2026-06-04T23:20:46Z","close_reason":"Done across 3 rounds (pushed a0c9696): build-time mutable/immutable toggle, structural-sharing persistent vectors (32-way trie), and persistent O(1)-prepend linked lists. Plus bit-clear/get-method and a batch of missing core fns. conformance 206/206, features 78/78, jank 120.","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"jolt-gxr","title":"HIGH: aliased namespace calls (require :as) don't resolve (s/join)","status":"closed","priority":2,"issue_type":"bug","owner":"yogthos@gmail.com","created_at":"2026-06-04T18:09:58Z","created_by":"Yogthos","updated_at":"2026-06-04T18:45:52Z","closed_at":"2026-06-04T18:45:52Z","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"jolt-4z9","title":"HIGH: dispatch — multimethod :default, protocol-on-record, extend-protocol","status":"closed","priority":2,"issue_type":"bug","owner":"yogthos@gmail.com","created_at":"2026-06-04T18:09:57Z","created_by":"Yogthos","updated_at":"2026-06-04T18:40:43Z","closed_at":"2026-06-04T18:40:43Z","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"jolt-40n","title":"HIGH: update/update-in/assoc-in fail on map literals (struct)","status":"closed","priority":2,"issue_type":"bug","owner":"yogthos@gmail.com","created_at":"2026-06-04T18:09:56Z","created_by":"Yogthos","updated_at":"2026-06-04T18:40:42Z","closed_at":"2026-06-04T18:40:42Z","dependency_count":0,"dependent_count":0,"comment_count":0}

View file

@ -48,6 +48,9 @@
(pvec? c) (pv->array c)
(plist? c) (pl->array c)
(set? c) (phs-seq c)
(phm? c) (phm-entries c)
# struct map literal (no :jolt/type marker — not a symbol/char) -> entries
(and (struct? c) (nil? (get c :jolt/type))) (map (fn [k] (tuple k (get c k))) (keys c))
(lazy-seq? c)
(do
(var items @[])
@ -400,6 +403,10 @@
(lazy-seq? coll) (ls-first coll)
(pvec? coll) (if (= 0 (pv-count coll)) nil (pv-nth coll 0))
(plist? coll) (if (pl-empty? coll) nil (pl-first coll))
# maps and sets: first of their seq (an entry / element)
(phm? coll) (let [e (phm-entries coll)] (if (= 0 (length e)) nil (in e 0)))
(set? coll) (let [s (phs-seq coll)] (if (= 0 (length s)) nil (in s 0)))
(struct? coll) (let [ks (keys coll)] (if (= 0 (length ks)) nil (tuple (in ks 0) (get coll (in ks 0)))))
(or (nil? coll) (= 0 (length coll))) nil
(string? coll) (make-char (in coll 0))
(in coll 0)))
@ -438,7 +445,7 @@
(phm? coll) (tuple ;(phm-entries coll))
(tuple? coll) (tuple/slice coll)
(string? coll) (if (= 0 (length coll)) nil (tuple ;(map make-char (string/bytes coll))))
(struct? coll) (tuple ;(keys coll))
(struct? coll) (tuple ;(map (fn [k] (tuple k (get coll k))) (keys coll)))
coll))
(defn core-vec [coll]
@ -2656,6 +2663,109 @@
(var parts @[]) (each x xs (array/push parts (str-render-one x)))
(string (string/join parts " ") "\n"))
# ============================================================
# Additional clojure.core functions
# ============================================================
(defn- intval? [x] (and (number? x) (= x (math/floor x))))
# Forcing lazy seqs
(defn core-doall [a & rest]
(let [coll (if (= 0 (length rest)) a (in rest 0))]
(realize-for-iteration coll) coll))
(defn core-dorun [a & rest]
(let [coll (if (= 0 (length rest)) a (in rest 0))]
(realize-for-iteration coll) nil))
(defn core-run! [f coll]
(each x (realize-for-iteration coll) (f x)) nil)
(defn core-tree-seq [branch? children root]
(def out @[])
(defn walk [node]
(array/push out node)
(when (truthy? (branch? node))
(each c (realize-for-iteration (children node)) (walk c))))
(walk root)
(tuple ;out))
# Map entries (represented as 2-element vectors)
(defn core-key [e] (core-nth e 0))
(defn core-val [e] (core-nth e 1))
(defn core-map-entry? [x] (and (or (pvec? x) (tuple? x)) (= 2 (core-count x))))
(defn core-rand-nth [coll]
(let [c (realize-for-iteration coll)]
(in c (math/floor (* (math/random) (length c))))))
(defn core-replicate [n x] (tuple ;(map (fn [_] x) (range n))))
(defn core-bounded-count [n coll]
(let [c (realize-for-iteration coll)] (min n (length c))))
(defn core-counted? [x]
(or (pvec? x) (plist? x) (phm? x) (set? x) (tuple? x) (array? x) (string? x)))
(defn core-reversible? [x] (or (pvec? x) (tuple? x) (array? x)))
(defn core-seqable? [x]
(or (nil? x) (tuple? x) (array? x) (pvec? x) (plist? x) (phm? x) (set? x)
(struct? x) (lazy-seq? x) (string? x)
(and (table? x) (or (get x :jolt/type) (get x :jolt/deftype)))))
# Numeric predicates (Jolt has no ratios/bigdec, so those are always false)
(defn core-nat-int? [x] (and (intval? x) (>= x 0)))
(defn core-pos-int? [x] (and (intval? x) (> x 0)))
(defn core-neg-int? [x] (and (intval? x) (< x 0)))
(defn core-double? [x] (and (number? x) (not (intval? x))))
(defn core-float? [x] (and (number? x) (not (intval? x))))
(defn core-ratio? [x] false)
(defn core-decimal? [x] false)
(defn core-rational? [x] (intval? x))
(defn core-numerator [x] x)
(defn core-denominator [x] 1)
(defn core-list* [& args]
(let [n (length args)]
(if (= 0 n) nil
(let [head (array/slice args 0 (- n 1))
tail (realize-for-iteration (in args (- n 1)))]
(var r (if (array? tail) tail (array ;tail)))
(var i (- (length head) 1))
(while (>= i 0) (set r (pl-cons (in head i) r)) (-- i))
r))))
(def- special-syms
{"if" true "do" true "let*" true "fn*" true "quote" true "var" true "def" true
"loop*" true "recur" true "throw" true "try" true "catch" true "finally" true
"new" true "set!" true "." true "monitor-enter" true "monitor-exit" true})
(defn core-special-symbol? [x]
(and (core-symbol? x) (= true (get special-syms (x :name)))))
(defn core-record? [x] (and (table? x) (not (nil? (get x :jolt/deftype)))))
# Promise: single-threaded box backed by an atom (deref returns nil until set).
(defn core-promise [] (core-atom nil))
(defn core-deliver [p v] (core-reset! p v) p)
(defn core-comparator [pred]
(fn [a b] (cond (truthy? (pred a b)) -1 (truthy? (pred b a)) 1 true 0)))
(defn core-completing [rf & cf]
(let [c (if (> (length cf) 0) (in cf 0) (fn [x] x))]
(fn [& a] (case (length a) 0 (rf) 1 (c (in a 0)) (rf (in a 0) (in a 1))))))
(defn core-keyword-identical? [a b] (= a b))
(defn core-object? [x] false)
(defn core-tagged-literal [tag form] @{:jolt/type :jolt/tagged-literal :tag tag :form form})
(defn core-ensure-reduced [x] (if (core-reduced? x) x (core-reduced x)))
(defn core-halt-when [pred & rest]
(let [retf (if (> (length rest) 0) (in rest 0) nil)]
(fn [rf]
(fn [& a]
(case (length a)
0 (rf)
1 (rf (in a 0))
(if (truthy? (pred (in a 1)))
(core-reduced (if retf (retf (rf (in a 0)) (in a 1)) (in a 1)))
(rf (in a 0) (in a 1))))))))
(defn core-re-groups [m] (error "re-groups: stateful matchers are not supported in Jolt"))
(def- core-bindings
"Map of symbol name → function for all core functions."
@{"nil?" core-nil?
@ -2744,6 +2854,43 @@
"reduce" core-reduce
"apply" core-apply
"second" core-second
"doall" core-doall
"dorun" core-dorun
"run!" core-run!
"tree-seq" core-tree-seq
"key" core-key
"val" core-val
"map-entry?" core-map-entry?
"rand-nth" core-rand-nth
"replicate" core-replicate
"bounded-count" core-bounded-count
"counted?" core-counted?
"reversible?" core-reversible?
"seqable?" core-seqable?
"nat-int?" core-nat-int?
"pos-int?" core-pos-int?
"neg-int?" core-neg-int?
"double?" core-double?
"float?" core-float?
"ratio?" core-ratio?
"decimal?" core-decimal?
"rational?" core-rational?
"numerator" core-numerator
"denominator" core-denominator
"list*" core-list*
"special-symbol?" core-special-symbol?
"record?" core-record?
"promise" core-promise
"deliver" core-deliver
"comparator" core-comparator
"completing" core-completing
"keyword-identical?" core-keyword-identical?
"object?" core-object?
"tagged-literal" core-tagged-literal
"ensure-reduced" core-ensure-reduced
"unreduced" core-unreduced
"halt-when" core-halt-when
"re-groups" core-re-groups
"ffirst" core-ffirst
"nfirst" core-nfirst
"fnext" core-fnext

View file

@ -106,6 +106,20 @@
["subvec" "[2 3]" "(subvec [1 2 3 4 5] 1 3)"]
["subvec to-end" "[3 4 5]" "(subvec [1 2 3 4 5] 2)"]
["reduce-kv" "{:a 2 :b 3}" "(reduce-kv (fn [m k v] (assoc m k (inc v))) {} {:a 1 :b 2})"]
### ---- iterating maps yields entries ----
["map over map" "true" "(= #{1 2} (set (map val {:a 1 :b 2})))"]
["map keys over map" "true" "(= #{:a :b} (set (map key {:a 1 :b 2})))"]
["first of map" "true" "(let [e (first {:a 1})] (and (= (key e) :a) (= (val e) 1)))"]
["vec of map" "[[:a 1]]" "(vec {:a 1})"]
["reduce over map" "6" "(reduce (fn [a [k v]] (+ a v)) 0 {:a 1 :b 2 :c 3})"]
["into transform map" "{:a 2 :b 3}" "(into {} (map (fn [[k v]] [k (inc v)]) {:a 1 :b 2}))"]
["filter over map" "true" "(= [[:b 2]] (filterv (fn [[k v]] (> v 1)) {:a 1 :b 2}))"]
["doall realizes" "(quote (2 3 4))" "(doall (map inc [1 2 3]))"]
["tree-seq" "(quote (1 2 3))" "(map (fn [x] x) (filter (complement coll?) (tree-seq coll? seq [1 [2 [3]]])))"]
["key/val" "true" "(let [e [:k 9]] (and (= :k (key e)) (= 9 (val e))))"]
["nat-int?" "true" "(and (nat-int? 0) (nat-int? 5) (not (nat-int? -1)))"]
["list* prepend" "(quote (1 2 3 4))" "(list* 1 2 [3 4])"]
["cycle" "(quote (1 2 3 1 2 3 1))" "(take 7 (cycle [1 2 3]))"]
["partition-all" "(quote ((1 2) (3 4) (5)))" "(partition-all 2 [1 2 3 4 5])"]
["reductions" "(quote (1 3 6 10))" "(reductions + [1 2 3 4])"]