fix: unify vector repr on tuples; realize lazy-seqs in =/vec/into; fix into for maps/lists; restore ns after PV load; deftype field-access fallback in IFn dispatch

This commit is contained in:
Yogthos 2026-06-04 13:59:36 -04:00
parent accb7a2c2f
commit 2ea5b2bb5f
5 changed files with 69 additions and 37 deletions

View file

@ -1 +1,2 @@
{"id":"int-621ea914","kind":"field_change","created_at":"2026-06-04T17:50:54.012588Z","actor":"Yogthos","issue_id":"jolt-x8s","extra":{"field":"status","new_value":"closed","old_value":"open"}} {"id":"int-621ea914","kind":"field_change","created_at":"2026-06-04T17:50:54.012588Z","actor":"Yogthos","issue_id":"jolt-x8s","extra":{"field":"status","new_value":"closed","old_value":"open"}}
{"id":"int-978620fa","kind":"field_change","created_at":"2026-06-04T17:59:35.53184Z","actor":"Yogthos","issue_id":"jolt-lp5","extra":{"field":"status","new_value":"closed","old_value":"open"}}

View file

@ -1,4 +1,4 @@
{"_type":"issue","id":"jolt-lp5","title":"CRITICAL: vec/into over lazy-seq leaks PV struct; into {} builds int keys","status":"open","priority":1,"issue_type":"bug","owner":"yogthos@gmail.com","created_at":"2026-06-04T17:46:15Z","created_by":"Yogthos","updated_at":"2026-06-04T17:46:15Z","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"jolt-lp5","title":"CRITICAL: vec/into over lazy-seq leaks PV struct; into {} builds int keys","status":"closed","priority":1,"issue_type":"bug","owner":"yogthos@gmail.com","created_at":"2026-06-04T17:46:15Z","created_by":"Yogthos","updated_at":"2026-06-04T17:59:35Z","closed_at":"2026-06-04T17:59:35Z","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"jolt-nte","title":"Build phased Clojure conformance harness (extracted assertions)","status":"open","priority":1,"issue_type":"task","owner":"yogthos@gmail.com","created_at":"2026-06-04T17:46:15Z","created_by":"Yogthos","updated_at":"2026-06-04T17:46:15Z","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"jolt-nte","title":"Build phased Clojure conformance harness (extracted assertions)","status":"open","priority":1,"issue_type":"task","owner":"yogthos@gmail.com","created_at":"2026-06-04T17:46:15Z","created_by":"Yogthos","updated_at":"2026-06-04T17:46:15Z","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"jolt-tws","title":"CRITICAL: iterate arity bug","status":"open","priority":1,"issue_type":"bug","owner":"yogthos@gmail.com","created_at":"2026-06-04T17:46:14Z","created_by":"Yogthos","updated_at":"2026-06-04T17:46:14Z","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"jolt-tws","title":"CRITICAL: iterate arity bug","status":"open","priority":1,"issue_type":"bug","owner":"yogthos@gmail.com","created_at":"2026-06-04T17:46:14Z","created_by":"Yogthos","updated_at":"2026-06-04T17:46:14Z","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_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-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}

View file

@ -11,6 +11,7 @@
(defn- load-persistent-structures (defn- load-persistent-structures
"Load immutable persistent data structures and swap clojure.core bindings." "Load immutable persistent data structures and swap clojure.core bindings."
[ctx] [ctx]
(def saved-ns (ctx-current-ns ctx))
(def source (slurp "src/jolt/clojure/lang/persistent_vector.clj")) (def source (slurp "src/jolt/clojure/lang/persistent_vector.clj"))
(var cur source) (var cur source)
(while (> (length (string/trim cur)) 0) (while (> (length (string/trim cur)) 0)
@ -18,11 +19,17 @@
(set cur rest) (set cur rest)
(when (not (nil? form)) (when (not (nil? form))
(eval-form ctx @{} form))) (eval-form ctx @{} form)))
(let [core-ns (ctx-find-ns ctx "clojure.core") # Vectors are represented as Janet tuples throughout core; bind vec/vector/
pv-ns (ctx-find-ns ctx "jolt.lang.persistent-vector")] # vector? to the tuple-based implementations so literals (`[...]`) and the
(ns-intern core-ns "vec" (var-get (ns-find pv-ns "vector"))) # constructors share one representation. The PersistentVector namespace stays
(ns-intern core-ns "vector" (var-get (ns-find pv-ns "vector"))) # loaded for code that wants it explicitly via jolt.lang.persistent-vector.
(ns-intern core-ns "vector?" (var-get (ns-find pv-ns "vector?"))))) (let [core-ns (ctx-find-ns ctx "clojure.core")]
(ns-intern core-ns "vec" core-vec)
(ns-intern core-ns "vector" core-vector)
(ns-intern core-ns "vector?" core-vector?))
# Restore the namespace: loading the PV file above left current-ns set to
# jolt.lang.persistent-vector, which would shadow clojure.core bindings.
(ctx-set-current-ns ctx saved-ns))
(defn init (defn init
"Create a new Jolt evaluation context. "Create a new Jolt evaluation context.

View file

@ -86,13 +86,19 @@
# Comparison # Comparison
# ============================================================ # ============================================================
(defn- norm-for-= [x]
"Realize a lazy-seq to a tuple so it compares as a Clojure sequence."
(if (lazy-seq? x)
(let [s (ls-seq x)] (if (nil? s) [] (tuple/slice (tuple ;s))))
x))
(defn core-= [& args] (defn core-= [& args]
(if (< (length args) 2) true (if (< (length args) 2) true
(do (do
(var ok true) (var ok true)
(var i 0) (var i 0)
(while (and ok (< i (dec (length args)))) (while (and ok (< i (dec (length args))))
(let [a (args i) b (args (+ i 1))] (let [a (norm-for-= (args i)) b (norm-for-= (args (+ i 1)))]
(set ok (set ok
(if (and (tuple? a) (array? b)) (if (and (tuple? a) (array? b))
(deep= a (tuple/slice (tuple ;b))) (deep= a (tuple/slice (tuple ;b)))
@ -232,24 +238,54 @@
(if (struct? coll) (tuple ;(keys coll)) (if (struct? coll) (tuple ;(keys coll))
coll)))))))) coll))))))))
(defn realize-for-iteration [c]
"If c is a lazy-seq, traverse and return all its elements as an array.
Otherwise return c as-is. Warning: will loop on infinite lazy-seqs.
Correctly handles nil elements (terminates on the empty cell, not on nil)."
(if (lazy-seq? c)
(do
(var items @[])
(var cur c)
(var go true)
(while go
(let [cell (realize-ls cur)]
(if (or (nil? cell) (= :jolt/pending cell) (= 0 (length cell)))
(set go false)
(do
(array/push items (in cell 0))
(let [rt (in cell 1)]
(if (nil? rt) (set go false) (set cur (make-lazy-seq rt))))))))
items)
c))
(defn core-vec [coll] (defn core-vec [coll]
(if (tuple? coll) coll (let [coll (realize-for-iteration coll)]
(if (array? coll) (tuple ;coll) (if (tuple? coll) coll
(if (struct? coll) (tuple ;(map |(in (kvs coll) (+ (* $ 2) 1)) (range (/ (length (kvs coll)) 2)))) (if (array? coll) (tuple ;coll)
(tuple))))) (if (struct? coll) (tuple ;(map |(in (kvs coll) (+ (* $ 2) 1)) (range (/ (length (kvs coll)) 2))))
(if (string? coll) (tuple ;(map |(string/from-bytes $) (string/bytes coll)))
(tuple)))))))
(defn core-into [to from] (defn core-into [to from]
(if (tuple? to) (let [items (realize-for-iteration from)]
(tuple/slice (tuple ;(array/concat (array/slice to) (if (tuple? from) from (array/slice from))))) (cond
(if (array? to) # map target: each item is a [k v] pair (or map entry) to assoc
(array/concat to from) (or (phm? to) (struct? to) (and (table? to) (get to :jolt/deftype)))
(if (struct? to)
(do (do
(var result to) (var result to)
(each [k v] (pairs from) (each item items
(set result (merge result {k v}))) (set result (core-assoc result (in item 0) (in item 1))))
result) result)
to)))) # list target (jolt lists are arrays): conj prepends -> reversed order
(array? to)
(do
(var result (array/slice to))
(each x items (array/insert result 0 x))
result)
# vector target (jolt vectors are tuples): conj appends
(tuple? to)
(tuple/slice (tuple ;(array/concat (array/slice to) (array/slice items))))
to)))
(defn core-merge [& maps] (defn core-merge [& maps]
(if (phm? (first maps)) (if (phm? (first maps))
@ -289,18 +325,6 @@
# Sequence operations # Sequence operations
# ============================================================ # ============================================================
(defn- realize-for-iteration [c]
"If c is a lazy-seq, traverse and return all elements as an array.
Otherwise return c as-is. Warning: will loop on infinite lazy-seqs."
(if (lazy-seq? c)
(do
(var items @[])
(var cur c)
(while (not (nil? (ls-first cur)))
(array/push items (ls-first cur))
(set cur (ls-rest cur)))
items)
c))
(defn core-map [f & colls] (defn core-map [f & colls]
(if (= 1 (length colls)) (if (= 1 (length colls))

View file

@ -49,7 +49,7 @@
and deftype/record values implementing IFn. `args` is an array." and deftype/record values implementing IFn. `args` is an array."
[ctx f args] [ctx f args]
(cond (cond
(function? f) (apply f args) (or (function? f) (cfunction? f)) (apply f args)
(keyword? f) (coll-lookup (get args 0) f (get args 1)) (keyword? f) (coll-lookup (get args 0) f (get args 1))
(and (struct? f) (= :symbol (f :jolt/type))) (and (struct? f) (= :symbol (f :jolt/type)))
(coll-lookup (get args 0) f (get args 1)) (coll-lookup (get args 0) f (get args 1))
@ -66,11 +66,11 @@
(and (table? f) (get f :jolt/deftype)) (and (table? f) (get f :jolt/deftype))
(let [ifn-fn (find-protocol-method ctx (get f :jolt/deftype) "IFn" "-invoke")] (let [ifn-fn (find-protocol-method ctx (get f :jolt/deftype) "IFn" "-invoke")]
(if ifn-fn (apply ifn-fn f args) (if ifn-fn (apply ifn-fn f args)
(if (get f :jolt/protocol-methods) (if (and (get f :jolt/protocol-methods) (get (f :jolt/protocol-methods) :-invoke))
(let [invoke-fn (get (f :jolt/protocol-methods) :-invoke)] (apply (get (f :jolt/protocol-methods) :-invoke) f args)
(if invoke-fn (apply invoke-fn f args) # No IFn impl: fall back to map-like field access, e.g. (point :x)
(error (string "Cannot call " (type f) " as a function")))) (let [v (get f (get args 0) :jolt/not-found)]
(error (string "Cannot call " (type f) " as a function"))))) (if (= v :jolt/not-found) (get args 1) v)))))
(and (table? f) (get f :jolt/protocol-methods)) (and (table? f) (get f :jolt/protocol-methods))
(let [invoke-fn (get (f :jolt/protocol-methods) :-invoke)] (let [invoke-fn (get (f :jolt/protocol-methods) :-invoke)]
(if invoke-fn (apply invoke-fn f args) (if invoke-fn (apply invoke-fn f args)