From 288b20956ca39b6e24b9c08d714e6bd1df6a637a Mon Sep 17 00:00:00 2001 From: Dmitri Sotnikov Date: Mon, 15 Jun 2026 00:28:45 +0000 Subject: [PATCH] Shim java.util.Iterator: (.iterator coll)/.hasNext/.next over jolt collections (#103) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Some Clojure libraries loop with the Java Iterator protocol — e.g. hiccup's iterate! does (let [it (.iterator coll)] (while (.hasNext it) (f (.next it)))). jolt had no Iterator, so (.iterator coll) returned nil and the loop did nothing (silently dropping content). Add an (.iterator coll) object-method that returns a :jolt/iterator over any seqable, with hasNext/next; the collection is materialized via core's realize-for-iteration (late-bound through the evaluator since core loads after it, wired in api). Found while bringing up weavejester/hiccup. Co-authored-by: Yogthos --- src/jolt/api.janet | 5 +++++ src/jolt/evaluator.janet | 20 +++++++++++++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/jolt/api.janet b/src/jolt/api.janet index a1bf9a4..62a6d54 100644 --- a/src/jolt/api.janet +++ b/src/jolt/api.janet @@ -14,6 +14,11 @@ (import ./host_iface :as host) (import ./javatime) # java.time shims register into the evaluator at load +# Wire core's collection realizer into the evaluator's (.iterator coll) shim — +# late-bound here because the evaluator loads before core. Makes Java-Iterator- +# style loops (e.g. hiccup's iterate!) work over any jolt collection. +(set-coll-realizer! realize-for-iteration) + # A defmacro expander compiles to a native fn (built as (fn args body...) and run # through the self-hosted pipeline) so macro expansion is COMPILED code, zero runtime # cost — instead of an interpreted closure, mirroring Clojure (macros are ordinary diff --git a/src/jolt/evaluator.janet b/src/jolt/evaluator.janet index 3114516..5664377 100644 --- a/src/jolt/evaluator.janet +++ b/src/jolt/evaluator.janet @@ -629,6 +629,20 @@ # ctor fns are interned as clojure.core vars at init (install-stateful-fns!). (def class-ctors @{}) (defn register-class-ctor! [nm f] (put class-ctors nm f)) + +# java.util.Iterator shim: (.iterator coll) gives a jolt iterator over any +# seqable, with (.hasNext it) / (.next it). Some Clojure libs (e.g. hiccup's +# iterate!) loop with the Java Iterator protocol; this makes that work over jolt +# collections. The realizer (core/realize-for-iteration, which handles every +# collection type) is late-bound because core loads after this file. +(var coll-realizer nil) +(defn set-coll-realizer! [f] (set coll-realizer f)) +(register-tagged-methods! :jolt/iterator + @{"hasNext" (fn [self] (< (self :pos) (length (self :items)))) + "next" (fn [self] + (def x (in (self :items) (self :pos))) + (put self :pos (+ 1 (self :pos))) + x)}) # Class names evaluate to their CANONICAL NAME STRING — the same value # core-class returns — so (defmethod m String ...) keys match a # (defmulti m (comp class :body)) dispatch (ring.util.request does this). @@ -694,7 +708,11 @@ "getCause" (fn [e] (and (table? e) (get e :cause))) "toString" (fn [x] (string x)) "equals" (fn [a b] (deep= a b)) - "hashCode" (fn [x] (hash x))}) + "hashCode" (fn [x] (hash x)) + # (.iterator coll) -> a jolt iterator (see :jolt/iterator above). Materializes + # the collection to an indexable array via the late-bound core realizer. + "iterator" (fn [coll] @{:jolt/type :jolt/iterator :pos 0 + :items (if coll-realizer (coll-realizer coll) @[])})}) (def- string-methods {"getBytes" (fn [s &opt charset] (buffer s))