Parsing and rendering templates in Clojure & Haskell

I rewrote somewhat difficult to understand templating code that was originally in Clojure into much simpler Haskell and yielded a large performance benefit for my trouble.

yogthos’ code for a parser I was to replicate in Haskell:

(ns parser)

(defn read-char [rdr]
  (let [ch (.read rdr)]
    (if-not (== -1 ch) (char ch))))

(def expr-tags
  {:if #(str "if tag args: " (clojure.string/join ", " %1))})

(defn expr-tag [{:keys [tag-name args] :as tag} rdr]
  (if-let [handler (get expr-tags tag-name)]
    (handler args)
    (throw (Exception. (str "unrecognized tag: " tag-name)))))

(defn filter-tag [{:keys [tag-value]}]
  (str "filter tag value: " tag-value))

(defn read-tag-info [rdr]
  (let [buf (StringBuilder.)
        tag-type (if (= \{ (read-char rdr)) :filter :expr)]
    (loop [ch1 (read-char rdr)
           ch2 (read-char rdr)]
      (when-not (and (or (= \} ch1) (= \% ch1))
                     (= \} ch2))
        (.append buf ch1)
        (recur ch2 (read-char rdr))))
    (let [content (->>  (.split (.toString buf ) " ")
                        (remove empty?) (map (memfn trim)))]
      (merge {:tag-type tag-type}
             (if (= :filter tag-type)
               {:tag-value (first content)}
               {:tag-name (keyword (first content))
                :args (rest content)})))))

(defn parse-tag [{:keys [tag-type] :as tag} rdr]
  (if (= :filter tag-type)
    (filter-tag tag)
    (expr-tag tag rdr)))

(defn handle-tag [rdr]
  (let [tag (read-tag-info rdr)]
    (parse-tag tag rdr)))

(defn parse [file]
  (with-open [rdr ( file)]
      (let [template (transient [])
            buf      (StringBuilder.)]
        (loop [ch (read-char rdr)]
          (when ch
            (if (= \{ ch)
                (conj! template (.toString buf))
                (.setLength buf 0)
                (conj! template (handle-tag rdr))
                (recur (read-char rdr)))
                (.append buf ch)
                (recur (read-char rdr))))))
        (conj! template (.toString buf))
        (persistent! template))))

(defn render [template args]
  (let [buf (StringBuilder.)]
    (doseq [element template]      
      (when element
        (.append buf (if (string? element) element (element args)))))
    (.toString buf)))

(defn render-file [filename args]
  (render (parse filename) args))

Note this Clojure code doesn’t actually work:

The Haskell code below works. I included an alternate version of one of the parsers.


If I get a working version of the Clojure parser, I’ll do a performance comparison.

I know this site is a bit of a disaster zone, but if you like my writing or think you could learn something useful from me, please take a look at the Haskell book I've been writing. There's a free sample available too!