diff --git a/src/jolt/clojure/java/io.clj b/src/jolt/clojure/java/io.clj index 7f87a4c..1848a54 100644 --- a/src/jolt/clojure/java/io.clj +++ b/src/jolt/clojure/java/io.clj @@ -26,7 +26,13 @@ ;; java.io.Reader surface (.read/.mark/.reset) plus line-seq's ;; :read-line-fn, which a raw janet file handle has neither of :else (java.io.StringReader. (janet/slurp (str x))))) -(defn writer [x] (janet.file/open (str x) :w)) +(defn writer [x] + (cond + ;; already a Writer/StringWriter shim — pass through, like the JVM's + ;; io/writer on a Writer (markdown-clj's md-to-html writes into a StringWriter) + (= :jolt/writer (get x :jolt/type)) x + (= :core/file (janet/type x)) x ; already a janet file handle + :else (janet.file/open (str x) :w))) ; a path (defn input-stream [x] (reader x)) (defn output-stream [x] (writer x)) diff --git a/src/jolt/core.janet b/src/jolt/core.janet index fa78e65..6bd1f6c 100644 --- a/src/jolt/core.janet +++ b/src/jolt/core.janet @@ -1237,7 +1237,11 @@ (set cur (ls-rest cur))) (if (seq-done? cur) nil (realize-ls cur)))) (make-lazy-seq (dwstep coll))) - (let [c (realize-for-iteration coll)] + # A string iterates as a seq of chars in Clojure; realize-for-iteration + # passes strings through, so char-seq it here (as take-while/remove do) — + # otherwise pred sees raw bytes and array/slice rejects the string. + (let [c0 (realize-for-iteration coll) + c (if (string? c0) (map make-char (string/bytes c0)) c0)] (var start 0) (while (and (< start (length c)) (pred (c start))) (++ start)) diff --git a/src/jolt/javatime.janet b/src/jolt/javatime.janet index 5986d4d..2dcf199 100644 --- a/src/jolt/javatime.janet +++ b/src/jolt/javatime.janet @@ -176,7 +176,9 @@ :buf (cond (nil? init) @"" (number? init) (buffer/new init) (buffer init))}) (defn make-string-writer [] - @{:jolt/type :jolt/writer :buf @"" :sink nil}) + # :close lets with-open close the writer (core-close-resource calls :close); + # it's a no-op so .toString after with-open still sees the buffer. + @{:jolt/type :jolt/writer :buf @"" :sink nil :close (fn [] nil)}) (defn make-out-writer [] @{:jolt/type :jolt/writer :buf nil :sink prin}) @@ -186,6 +188,12 @@ (and (struct? x) (= :jolt/char (get x :jolt/type))) (string/from-bytes (x :ch)) (string x))) +# Writer.write(int) writes the CHAR for that code (unlike StringBuilder.append(int), +# which appends the int's digits). jolt chars are bytes, so this round-trips UTF-8 +# byte-for-byte with readLine. +(defn- writer-piece [x] + (if (number? x) (string/from-bytes (math/trunc x)) (render-piece x))) + # Read one unit from any reader-ish value: our shims dispatch through their # tagged "read"; a janet core/file reads one byte. -1 at EOF. (defn- read-unit [r] @@ -221,6 +229,7 @@ (let [b ((self :s) (self :pos))] (put self :pos (+ 1 (self :pos))) b))) + "readLine" (fn [self] ((self :read-line-fn))) "mark" (fn [self &opt limit] (put self :marked (self :pos)) nil) "reset" (fn [self] (put self :pos (or (self :marked) 0)) nil) "skip" (fn [self n] (put self :pos (min (length (self :s)) (+ (self :pos) n))) n) @@ -232,8 +241,8 @@ (register-tagged-methods! :jolt/writer @{"write" (fn [self x] (if (self :sink) - ((self :sink) (render-piece x)) - (buffer/push-string (self :buf) (render-piece x))) + ((self :sink) (writer-piece x)) + (buffer/push-string (self :buf) (writer-piece x))) nil) "append" (fn [self x] (if (self :sink) diff --git a/src/jolt/reader.janet b/src/jolt/reader.janet index 9673b6d..af38533 100644 --- a/src/jolt/reader.janet +++ b/src/jolt/reader.janet @@ -456,6 +456,10 @@ (cond (= i :rest) (set has-rest true) (and i (> i max-n)) (set max-n i))) (or (array? f) (tuple? f)) (each x f (scan-pct x)) + # set literal form — scan its elements (#(... #{%} ...)) + (and (struct? f) (= :jolt/set (f :jolt/type))) (each x (f :value) (scan-pct x)) + # map literal form — scan its keys AND values (#(... {:k %} ...)) + (not (nil? (form-kv-order f))) (each x (form-kv-order f) (scan-pct x)) nil)) (scan-pct form) # One canonical gensym per slot 1..max-n (placeholders for unused), plus rest. @@ -473,6 +477,10 @@ f)) (array? f) (array ;(map replace-pct f)) (tuple? f) (tuple ;(map replace-pct f)) + (and (struct? f) (= :jolt/set (f :jolt/type))) + {:jolt/type :jolt/set :value (tuple ;(map replace-pct (f :value)))} + (not (nil? (form-kv-order f))) + (reader-map (array ;(map replace-pct (form-kv-order f)))) f)) (def replaced (replace-pct form)) (def arg-names @[]) diff --git a/src/jolt/regex.janet b/src/jolt/regex.janet index f7e4862..4b239d0 100644 --- a/src/jolt/regex.janet +++ b/src/jolt/regex.janet @@ -397,7 +397,9 @@ (array/push out (groups->result g (re :ngroups))) (set pos (+ pos (max 1 (length whole))))) (++ pos))) - out) + # Clojure's re-seq is nil (not an empty seq) when there are no matches, so + # `(if-let [m (re-seq ...)] ...)` works — an empty seq would be truthy. + (if (= 0 (length out)) nil out)) (defn re-split [re s] (def re (re-pattern re))