diff --git a/.beads/interactions.jsonl b/.beads/interactions.jsonl index 81c8ea3..cc5a56a 100644 --- a/.beads/interactions.jsonl +++ b/.beads/interactions.jsonl @@ -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-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"}} diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl index 58b749b..c8fa589 100644 --- a/.beads/issues.jsonl +++ b/.beads/issues.jsonl @@ -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-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} diff --git a/src/jolt/api.janet b/src/jolt/api.janet index b0b067f..3c6f5f9 100644 --- a/src/jolt/api.janet +++ b/src/jolt/api.janet @@ -11,6 +11,7 @@ (defn- load-persistent-structures "Load immutable persistent data structures and swap clojure.core bindings." [ctx] + (def saved-ns (ctx-current-ns ctx)) (def source (slurp "src/jolt/clojure/lang/persistent_vector.clj")) (var cur source) (while (> (length (string/trim cur)) 0) @@ -18,11 +19,17 @@ (set cur rest) (when (not (nil? form)) (eval-form ctx @{} form))) - (let [core-ns (ctx-find-ns ctx "clojure.core") - pv-ns (ctx-find-ns ctx "jolt.lang.persistent-vector")] - (ns-intern core-ns "vec" (var-get (ns-find pv-ns "vector"))) - (ns-intern core-ns "vector" (var-get (ns-find pv-ns "vector"))) - (ns-intern core-ns "vector?" (var-get (ns-find pv-ns "vector?"))))) + # Vectors are represented as Janet tuples throughout core; bind vec/vector/ + # vector? to the tuple-based implementations so literals (`[...]`) and the + # constructors share one representation. The PersistentVector namespace stays + # loaded for code that wants it explicitly via jolt.lang.persistent-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 "Create a new Jolt evaluation context. diff --git a/src/jolt/core.janet b/src/jolt/core.janet index e06e0a8..b38ea63 100644 --- a/src/jolt/core.janet +++ b/src/jolt/core.janet @@ -86,13 +86,19 @@ # 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] (if (< (length args) 2) true (do (var ok true) (var i 0) (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 (if (and (tuple? a) (array? b)) (deep= a (tuple/slice (tuple ;b))) @@ -232,24 +238,54 @@ (if (struct? coll) (tuple ;(keys 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] - (if (tuple? coll) coll - (if (array? coll) (tuple ;coll) - (if (struct? coll) (tuple ;(map |(in (kvs coll) (+ (* $ 2) 1)) (range (/ (length (kvs coll)) 2)))) - (tuple))))) + (let [coll (realize-for-iteration coll)] + (if (tuple? coll) coll + (if (array? coll) (tuple ;coll) + (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] - (if (tuple? to) - (tuple/slice (tuple ;(array/concat (array/slice to) (if (tuple? from) from (array/slice from))))) - (if (array? to) - (array/concat to from) - (if (struct? to) + (let [items (realize-for-iteration from)] + (cond + # map target: each item is a [k v] pair (or map entry) to assoc + (or (phm? to) (struct? to) (and (table? to) (get to :jolt/deftype))) (do (var result to) - (each [k v] (pairs from) - (set result (merge result {k v}))) + (each item items + (set result (core-assoc result (in item 0) (in item 1)))) 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] (if (phm? (first maps)) @@ -289,18 +325,6 @@ # 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] (if (= 1 (length colls)) diff --git a/src/jolt/evaluator.janet b/src/jolt/evaluator.janet index 8db631c..2947f65 100644 --- a/src/jolt/evaluator.janet +++ b/src/jolt/evaluator.janet @@ -49,7 +49,7 @@ and deftype/record values implementing IFn. `args` is an array." [ctx f args] (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)) (and (struct? f) (= :symbol (f :jolt/type))) (coll-lookup (get args 0) f (get args 1)) @@ -66,11 +66,11 @@ (and (table? f) (get f :jolt/deftype)) (let [ifn-fn (find-protocol-method ctx (get f :jolt/deftype) "IFn" "-invoke")] (if ifn-fn (apply ifn-fn f args) - (if (get f :jolt/protocol-methods) - (let [invoke-fn (get (f :jolt/protocol-methods) :-invoke)] - (if invoke-fn (apply invoke-fn f args) - (error (string "Cannot call " (type f) " as a function")))) - (error (string "Cannot call " (type f) " as a function"))))) + (if (and (get f :jolt/protocol-methods) (get (f :jolt/protocol-methods) :-invoke)) + (apply (get (f :jolt/protocol-methods) :-invoke) f args) + # No IFn impl: fall back to map-like field access, e.g. (point :x) + (let [v (get f (get args 0) :jolt/not-found)] + (if (= v :jolt/not-found) (get args 1) v))))) (and (table? f) (get f :jolt/protocol-methods)) (let [invoke-fn (get (f :jolt/protocol-methods) :-invoke)] (if invoke-fn (apply invoke-fn f args)