diff --git a/.beads/interactions.jsonl b/.beads/interactions.jsonl index 80f1952..584eea4 100644 --- a/.beads/interactions.jsonl +++ b/.beads/interactions.jsonl @@ -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."}} diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl index e5856b1..11b5345 100644 --- a/.beads/issues.jsonl +++ b/.beads/issues.jsonl @@ -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} diff --git a/src/jolt/core.janet b/src/jolt/core.janet index d5f18a1..26711e7 100644 --- a/src/jolt/core.janet +++ b/src/jolt/core.janet @@ -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 diff --git a/test/conformance.janet b/test/conformance.janet index 9f2d4a5..dfbf85f 100644 --- a/test/conformance.janet +++ b/test/conformance.janet @@ -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])"]