(defn my-interleave [a b] "Like interleave, but uses all elements from both lists." (loop [acc [] a a b b] (if (and (nil? a) (nil? b)) acc (let [acc2 (if (nil? a) acc (conj acc (first a))) acc3 (if (nil? b) acc2 (conj acc2 (first b)))] (recur acc3 (rest a) (rest b)))))) ;; (my-interleave [1 2 3 4 5 6 7 8] [:a :b :c :d]) ;; => [1 :a 2 :b 3 :c 4 :d 5 6 7 8] (defn read-from-string [s] (read (java.io.PushbackReader. (java.io.StringReader. s)))) (defn tokenize [s] (let [positions (let [mm (re-matcher #"\\$(?!\\\\)\\{.*?\\}" s)] (loop [acc []] (if (.find mm) (recur (conj acc [(.start mm) (.end mm)])) acc))) intermed (conj (apply vector 0 (apply concat positions)) (.length s)) textposns (partition 2 intermed)] (my-interleave (map (fn [[a b]] [:text (. (.substring s a b) (replaceAll "\\$\\\\\\{" "\\${"))]) textposns) (map (fn [[a b]] [:pat (.substring s (+ a 2) (- b 1))]) positions)))) ;; (prn (tokenize "${greeting}, my name is ${name} and my age is ${age} $\\{a}.")) ;; (. "$\\{a}" (replaceAll "\\$\\\\\\{" "\\${")) (defmacro i [s] (apply list 'str (map (fn [[type value]] (if (= type :text) value (read-from-string value))) (tokenize s)))) ;; test ;; (let [greeting "Hello" name "Fred" age 33] ;; (prn (i "${greeting}, my name is ${name} and my age is ${age}."))) ;; (macroexpand '(i "${greeting}, my name is ${name} and my age is ${age}.")) ;; you can get a literal ${ by using $\\{ ;; (macroexpand '(i "${greeting}, my name is ${name} and my age is ${age} $\\{a}."))