feat: structural-sharing persistent vectors (immutable build) + mutable toggle

Round 2 of the persistent-collections work.

Add a real 32-way branching-trie persistent vector (src/jolt/pv.janet) with a
tail buffer: O(log32 n) conj/assoc/nth/pop, with unchanged subtrees shared by
identity. Vector literals and vec/vector/conj/assoc/subvec/etc. now produce and
maintain these in the default (immutable) build, replacing the old tuple-based
vectors. Every core seq op, the destructurer, IFn application, the printers, =,
and the evaluator's literal/splice paths were taught to handle the pvec type.

Define several Clojure seq fns that were silently leaking to Janet builtins
(some, keep, interleave, flatten, mapcat, interpose) and broke once vectors
became tables; normalize collections through realize-for-iteration everywhere.

Build-time JOLT_MUTABLE flag now selects fast Janet-native mutable collections:
in that mode vectors are arrays (conj appends in place, vector? true, print []),
sharing one representation with lists. Default build is immutable.

Tests: conformance 206/206, features 71/71, jank 119 (baseline). Test helpers
normalized so Janet-level = compares against tuple literals regardless of repr.
The 2 test-load-sci failures (bit-clear/get-method) pre-date this work.
This commit is contained in:
Yogthos 2026-06-04 18:56:55 -04:00
parent 1ca93d61c6
commit 1eb2843365
44 changed files with 525 additions and 170 deletions

View file

@ -20,7 +20,7 @@
(print "3: eval-string with core fns...")
(let [ctx (init)]
(assert (= true (eval-string ctx "(nil? nil)")) "nil?")
(assert (deep= [2 3 4] (eval-string ctx "(map inc [1 2 3])")) "map+inc")
(assert (deep= [2 3 4] (normalize-pvecs (eval-string ctx "(map inc [1 2 3])"))) "map+inc")
(assert (= 6 (eval-string ctx "(reduce + [1 2 3])")) "reduce"))
(print " passed")

View file

@ -1,5 +1,5 @@
(use ../src/jolt/api)
(defn ct-eval [ctx s] (eval-string ctx s))
(defn ct-eval [ctx s] (normalize-pvecs (eval-string ctx s)))
(print "CLJS Collections Ported Tests")
(print "1: dissoc...")

View file

@ -1,5 +1,5 @@
(use ../src/jolt/api)
(defn ct-eval [ctx s] (eval-string ctx s))
(defn ct-eval [ctx s] (normalize-pvecs (eval-string ctx s)))
(print "CLJS Core Ported Tests")
(print "1: metadata on maps...")

View file

@ -1,5 +1,5 @@
(use ../src/jolt/api)
(defn ct-eval [ctx s] (eval-string ctx s))
(defn ct-eval [ctx s] (normalize-pvecs (eval-string ctx s)))
(print "=== CLJS Ported Part 1 ===")
(print "1: core math...")
(let [ctx (init)]

View file

@ -1,5 +1,5 @@
(use ../src/jolt/api)
(defn ct-eval [ctx s] (eval-string ctx s))
(defn ct-eval [ctx s] (normalize-pvecs (eval-string ctx s)))
(print "=== CLJS Ported Part 10 ===")
(print "43: when/when-not...")

View file

@ -1,5 +1,5 @@
(use ../src/jolt/api)
(defn ct-eval [ctx s] (eval-string ctx s))
(defn ct-eval [ctx s] (normalize-pvecs (eval-string ctx s)))
(print "=== CLJS Ported Part 1 ===")
(print "1: core math...")
(let [ctx (init)]

View file

@ -1,5 +1,5 @@
(use ../src/jolt/api)
(defn ct-eval [ctx s] (eval-string ctx s))
(defn ct-eval [ctx s] (normalize-pvecs (eval-string ctx s)))
(print "=== CLJS Ported Part 1b ===")
(print "7: seq operations...")
(let [ctx (init)]

View file

@ -1,5 +1,5 @@
(use ../src/jolt/api)
(defn ct-eval [ctx s] (eval-string ctx s))
(defn ct-eval [ctx s] (normalize-pvecs (eval-string ctx s)))
(print "=== CLJS Ported Part 2 ===")
(print "12: atoms...")
(let [ctx (init)]

View file

@ -1,5 +1,5 @@
(use ../src/jolt/api)
(defn ct-eval [ctx s] (eval-string ctx s))
(defn ct-eval [ctx s] (normalize-pvecs (eval-string ctx s)))
(print "=== CLJS Ported Part 3 ===")
(print "16: destructuring...")
(let [ctx (init)]

View file

@ -1,5 +1,5 @@
(use ../src/jolt/api)
(defn ct-eval [ctx s] (eval-string ctx s))
(defn ct-eval [ctx s] (normalize-pvecs (eval-string ctx s)))
(print "=== CLJS Ported Part 3b ===")
(print "21: clojure.string...")
(let [ctx (init)]

View file

@ -1,5 +1,5 @@
(use ../src/jolt/api)
(defn ct-eval [ctx s] (eval-string ctx s))
(defn ct-eval [ctx s] (normalize-pvecs (eval-string ctx s)))
(print "=== CLJS Ported Part 4 ===")
(print "23: deftype/defrecord...")

View file

@ -1,5 +1,5 @@
(use ../src/jolt/api)
(defn ct-eval [ctx s] (eval-string ctx s))
(defn ct-eval [ctx s] (normalize-pvecs (eval-string ctx s)))
(print "=== CLJS Ported Part 5 ===")
(print "22: destructuring...")
(let [ctx (init)]

View file

@ -1,5 +1,5 @@
(use ../src/jolt/api)
(defn ct-eval [ctx s] (eval-string ctx s))
(defn ct-eval [ctx s] (normalize-pvecs (eval-string ctx s)))
(print "=== CLJS Ported Part 6 ===")
(print "28: anon fn #()...")

View file

@ -1,5 +1,5 @@
(use ../src/jolt/api)
(defn ct-eval [ctx s] (eval-string ctx s))
(defn ct-eval [ctx s] (normalize-pvecs (eval-string ctx s)))
(print "=== CLJS Ported Part 7 ===")
(print "32: seq destructuring...")

View file

@ -1,5 +1,5 @@
(use ../src/jolt/api)
(defn ct-eval [ctx s] (eval-string ctx s))
(defn ct-eval [ctx s] (normalize-pvecs (eval-string ctx s)))
(print "=== CLJS Ported Part 8 ===")
(print "35: range and repeat...")

View file

@ -1,5 +1,5 @@
(use ../src/jolt/api)
(defn ct-eval [ctx s] (eval-string ctx s))
(defn ct-eval [ctx s] (normalize-pvecs (eval-string ctx s)))
(print "=== CLJS Ported Part 9 ===")
(print "40: seq predicates...")

View file

@ -1,5 +1,5 @@
(use ../src/jolt/api)
(defn ct-eval [ctx s] (eval-string ctx s))
(defn ct-eval [ctx s] (normalize-pvecs (eval-string ctx s)))
(print "CLJS Ported Tests")
(print "1: math...")

View file

@ -1,6 +1,6 @@
# Ported from clojure/test_clojure/atoms.clj + systematic atom tests
(use ../src/jolt/api)
(defn ct-eval [ctx s] (eval-string ctx s))
(defn ct-eval [ctx s] (normalize-pvecs (eval-string ctx s)))
(print "Ported Atom Tests")

View file

@ -1,6 +1,6 @@
# Ported from clojure/test_clojure/control.clj
(use ../src/jolt/api)
(defn ct-eval [ctx s] (eval-string ctx s))
(defn ct-eval [ctx s] (normalize-pvecs (eval-string ctx s)))
(print "Ported Control Tests")

View file

@ -1,6 +1,6 @@
# Ported from clojure/test_clojure/for.clj
(use ../src/jolt/api)
(defn ct-eval [ctx s] (eval-string ctx s))
(defn ct-eval [ctx s] (normalize-pvecs (eval-string ctx s)))
(print "Ported For Tests")

View file

@ -1,6 +1,6 @@
# Ported from clojure/test_clojure/logic.clj
(use ../src/jolt/api)
(defn ct-eval [ctx s] (eval-string ctx s))
(defn ct-eval [ctx s] (normalize-pvecs (eval-string ctx s)))
(print "Ported Logic Tests (from clojure/test-clojure/logic.clj)")

View file

@ -1,6 +1,6 @@
# Ported from clojure/test_clojure/macros.clj
(use ../src/jolt/api)
(defn ct-eval [ctx s] (eval-string ctx s))
(defn ct-eval [ctx s] (normalize-pvecs (eval-string ctx s)))
(print "Ported Macros Tests")

View file

@ -1,8 +1,18 @@
(use ../src/jolt/types)
(use ../src/jolt/pv)
(use ../src/jolt/reader)
(use ../src/jolt/evaluator)
(use ../src/jolt/core)
# Normalize jolt collection results to Janet tuples so Janet-level deep=/= can
# compare against tuple literals regardless of the (pvec) representation.
(defn norm [x]
(cond
(pvec? x) (tuple ;(map norm (pv->array x)))
(tuple? x) (tuple ;(map norm x))
(array? x) (tuple ;(map norm x))
x))
# Helper: create a fresh bootstrapped context
(defn make-boot-ctx []
(let [ctx (make-ctx)]
@ -12,7 +22,7 @@
# Helper: parse + eval
(defn eval-str [ctx s]
(let [form (parse-string s)]
(eval-form ctx @{} form)))
(norm (eval-form ctx @{} form))))
(print "1: predicates...")
(let [ctx (make-boot-ctx)]

View file

@ -1,5 +1,5 @@
(use ../src/jolt/api)
(defn ct-eval [ctx s] (eval-string ctx s))
(defn ct-eval [ctx s] (normalize-pvecs (eval-string ctx s)))
(print "Eval Tests")
(print "1: eval literal...")

View file

@ -121,7 +121,8 @@
### 14. Janet interop
["interop method" "\"v=41\"" "(. {:value 41 :describe (fn [self] (str \"v=\" (:value self)))} describe)"]
["interop field" "41" "(.-value {:value 41})"]
["interop janet-type" ":tuple" "(do (require '[jolt.interop :as j]) (j/janet-type [1 2 3]))"]
# vectors are persistent vectors (Janet tables); lists are Janet arrays
["interop janet-type" ":array" "(do (require '[jolt.interop :as j]) (j/janet-type (list 1 2 3)))"]
])
(each [label expected actual] cases (check label expected actual))

View file

@ -3,7 +3,7 @@
(use ../src/jolt/api)
(defn ct-eval [ctx s] (eval-string ctx s))
(defn ct-eval [ctx s] (normalize-pvecs (eval-string ctx s)))
# Helper: compare via Clojure = which handles PHM
(defn clj= [ctx a b]

View file

@ -1,5 +1,5 @@
(use ../src/jolt/api)
(defn ct-eval [ctx s] (eval-string ctx s))
(defn ct-eval [ctx s] (normalize-pvecs (eval-string ctx s)))
(print "Janet Interop Tests")
(print "1: field access on tables...")

View file

@ -1,5 +1,5 @@
(use ../src/jolt/api)
(defn ct-eval [ctx s] (eval-string ctx s))
(defn ct-eval [ctx s] (normalize-pvecs (eval-string ctx s)))
(print "LazySeq Tests")
(print "1: lazy-seq from list...")

View file

@ -1,5 +1,5 @@
(use ../src/jolt/api)
(defn ct-eval [ctx s] (eval-string ctx s))
(defn ct-eval [ctx s] (normalize-pvecs (eval-string ctx s)))
(print "Ported Logic Tests (from clojure/test-clojure/logic.clj)")
(print "1: test-if true/false/nil...")

View file

@ -2,7 +2,7 @@
(use ../src/jolt/reader)
(use ../src/jolt/evaluator)
(defn ct-eval [ctx s] (eval-string ctx s))
(defn ct-eval [ctx s] (normalize-pvecs (eval-string ctx s)))
(defn load-clj [ctx filepath]
(def source (slurp filepath))

View file

@ -1,6 +1,6 @@
# Phase 12: Protocol System Tests
(use ../src/jolt/api)
(defn ct-eval [ctx s] (eval-string ctx s))
(defn ct-eval [ctx s] (normalize-pvecs (eval-string ctx s)))
(print "35: defprotocol...")
(let [ctx (init)]

View file

@ -1,7 +1,7 @@
(use ../src/jolt/api)
(use ../src/jolt/evaluator)
(use ../src/jolt/reader)
(defn ct-eval [ctx s] (eval-string ctx s))
(defn ct-eval [ctx s] (normalize-pvecs (eval-string ctx s)))
(defn load-clj [ctx filepath]
(var s (slurp filepath))

View file

@ -1,6 +1,6 @@
# Phase 5: Multimethods + Hierarchy Tests
(use ../src/jolt/api)
(defn ct-eval [ctx s] (eval-string ctx s))
(defn ct-eval [ctx s] (normalize-pvecs (eval-string ctx s)))
# 22. Hierarchy
(print "22: hierarchy...")

View file

@ -1,6 +1,6 @@
(use ../src/jolt/api)
(defn ct-eval [ctx s] (eval-string ctx s))
(defn ct-eval [ctx s] (normalize-pvecs (eval-string ctx s)))
(print "Phase 6: comprehensive compile-mode tests...")
(let [ctx (init {:compile? true})]

View file

@ -1,6 +1,6 @@
# Phase 6: Reader Extensions Tests
(use ../src/jolt/api)
(defn ct-eval [ctx s] (eval-string ctx s))
(defn ct-eval [ctx s] (normalize-pvecs (eval-string ctx s)))
(print "28: #inst tagged literal...")
(let [ctx (init)]

View file

@ -1,6 +1,6 @@
# Phase 7: LazySeq + PersistentHashSet completion
(use ../src/jolt/api)
(defn ct-eval [ctx s] (eval-string ctx s))
(defn ct-eval [ctx s] (normalize-pvecs (eval-string ctx s)))
(print "32: lazy-seq...")
(let [ctx (init)]

View file

@ -1,6 +1,6 @@
# Phase 8: Protocol System Tests
(use ../src/jolt/api)
(defn ct-eval [ctx s] (eval-string ctx s))
(defn ct-eval [ctx s] (normalize-pvecs (eval-string ctx s)))
(print "35: defprotocol...")
(let [ctx (init)]

View file

@ -1,6 +1,6 @@
# Systematic coverage tests for Clojure language features
(use ../src/jolt/api)
(defn ct-eval [ctx s] (eval-string ctx s))
(defn ct-eval [ctx s] (normalize-pvecs (eval-string ctx s)))
(print "Systematic Coverage Tests")