clj-kondo 2025-10-13

Hi all, I'm trying to write a :macroexpand hook for a string interpolation macro:

(def foo "bar")
(<< "foo's value is: #{ bar }")
I tried duplicating some of the macro's implementation in the hook, but I've found that some of the required API is unavailable - java.io.PushbackReader and slurp specifically. Any ideas for how I could make this work? (you might recognize this as a version of Chas Emerick's string interpolation macro; many thanks to him. https://cemerick.com/blog/2009/12/04/string-interpolation-in-clojure.html)

what do you need slurp for?

This is the function that reads the interpolated values:

(defn- silent-read
  "Attempts to clojure.core/read a single form from the provided String, returning
  a vector containing the read form and a String containing the unread remainder
  of the provided String. Returns nil if no valid form can be read from the
  head of the String."
  [s]
  (try
    (let [r (-> s java.io.StringReader. java.io.PushbackReader.)]
      [(read r) (string/triml (slurp r))])
    (catch Exception e)))

this one is available:

'java.io.StringReader java.io.StringReader
                       'clojure.lang.LineNumberingPushbackReader clojure.lang.LineNumberingPushbackReader

you can probably implement slurp in user space... let me think

I bet clojure.lang.LineNumberingPushbackReader will work for the pushback reader part, thanks.

yeah maybe not, perhaps it requires a pushback reader... not sure

clojure.lang.LineNumberingPushbackReader works in the actual macro impl so presumably works fine in the hook too. Now I think I just need to figure out the slurp replacement to get the remainder of the string

(defn reader->string [^java.io.Reader r]
  (let [sb (StringBuilder.)
        buf (char-array 4096)]
    (loop []
      (let [n (.read r buf)]
        (when (pos? n)
          (.append sb buf 0 n)
          (recur))))
    (str sb)))
Not sure if that works in hooks

Ah this one may work since StringWriter is included by default in SCI:

(defn reader->string [^java.io.Reader r]
  (let [sw (java.io.StringWriter.)
        buf (char-array 4096)]
    (loop []
      (let [n (.read r buf)]
        (when (pos? n)
          (.write sw buf 0 n)
          (recur))))
    (str sw)))

Got a warning about the type hint but with that removed it appears to work. The hook still doesn't appear to be doing what I want but this gets me a lot closer. Thanks @borkdude!

so reader->string worked?

yep, it works in the macro impl, and I'm not seeing any complaints about it when linting w/ the hook. I'll run a repl for the hook impl in a bit to be sure, got some other stuff to deal with right now.

Hook works correctly now. Thanks again!