A toy string template macro

32 views
Skip to first unread message

Victor Rodriguez

unread,
Mar 21, 2009, 12:30:44 AM3/21/09
to clo...@googlegroups.com
Here is something I wrote as a Clojure learning exercise.

I have a suspicion that a serious implementation of this may exist,
please let me know if that is so!

Cheers,

Victor Rodriguez.

http://gist.github.com/82727

;;; A toy to experiment with Clojure macros.
;;;
;;; (f "1 + 2 = ~(+ 1 2)") results in "1 + 2 = 3"
;;;
;;; Know of a real implementation of this? Please Let me know at
;;; vic...@gmail.com.


(defn tokenize
"Split a string into a sequence of plain strings and
''unquoted'' form-strings. For example, it turns

\"~a = ~(- b c)\" into (\"~a\" \" = \"~(- b c)\")"
[s]
(let [unquote-re
(re-pattern
(str "~\\([^\\)]*\\)" ; a tilde followed by a paren
"|"
"~\\w+" ; a tilde not followed by an open paren...
"|"
"[^~]+" ; anything that is not a tilde
"|"
"~$"))] ; a tilde at the end
(re-seq unquote-re s)))

(defn to-form
"Convert a strings to forms. For example, it turns
''~foo'' and ''~(inc a)'' into 'foo' and '(inc a)',
respectively."
[s]
(if (= \~ (first s))
(read-string (.substring s 1))
s))

(defn f*
"Convert a string with ''unquoted'' forms into a
list of strings and forms."
[s]
(loop [tokens (tokenize s)
form '(str )]
(if-let [token (first tokens)]
(recur (rest tokens)
(conj form (to-form token)))
(reverse form))))

(defmacro f
"Compile time string templates!"
[s]
(f* s))

(comment

(let [s "a + b ="
a 1
b 2
m {:title 'chihuahua }]
(f "let ~s ~(+ a b) and the title be ~(:title m)"))
)

Laurent PETIT

unread,
Mar 21, 2009, 8:48:19 AM3/21/09
to clo...@googlegroups.com
Hello,


2009/3/21 Victor Rodriguez <vic...@gmail.com>


Here is something I wrote as a Clojure learning exercise.

I have a suspicion that a serious implementation of this may exist,
please let me know if that is so!

In common lisp, there is cl-interpol (for String INTERPOLation) : http://www.weitz.de/cl-interpol/
 
Your f* function, in terms of data, splits a list into chunks into a seq, and then converts each element of the list in either code, either String part, so you can do this an order of magnitude more concise by using map :


(defn f*
 "Convert a string with ''unquoted'' forms into a
list of strings and forms."
 [s]
 (loop [tokens (tokenize s)
        form '(str )]
   (if-let [token (first tokens)]
     (recur (rest tokens)
            (conj form (to-form token)))
     (reverse form))))

becomes


(defn f*
 "Convert a string with ''unquoted'' forms into a
list of strings and forms."
 [s]
 (cons 'str
          (map to-form (tokenize s))))

Ah, and currently it is not possible to use nested levels of parens in expressions, e.g. (f "3 * 4 + 1=~(+ (* 3 4) 1)") does not work.

HTH,

--
Laurent

Reply all
Reply to author
Forward
0 new messages