A simple macro to simulate ruby string interpolation

Ruby interpolation

One features that Ruby has that makes life easier is string interpolation.
With string interpolation you can make your code easier on the eyes when there
is a lot of string concatenation to be done:

# without interpolation"Hi, "+name+" "+last_name+", you last logged in at "+time+"."# with interpolation"Hi, #{name}#{last_name}, you last logged in at #{time}."

which in clojure would look something like:

(str"Hi, "name" "last-name", you last logged in at "time".")

Ruby style string interpolation

Clojure unfortunately does not have string interpolation, but thanks to macros
we can fix this.

We will implement a macro that looks like this:

(itp"Hi, #{name} #{last-name}, you last logged in at #{time}.");;=>"Hi, George Washington, you last logged in at 10:00am."

And expands to:

(str"Hi, "name" "last-name", you last logged in at "time".")

Limitations:

To keep things simple, you can only enter one symbol inside #{}, you can’t
enter complex expressions like #{(conj [1 2 3 4] 6)}.
Also we won’t be doing escaping or other fancy things, so don’t try to do
(itp "foo #{ {bar})" which will confuse our simple implementation.

Step one: parse the string

The first thing we need to do is to implement a function that given a string
with interpolations, return an array of strings and symbols.

Take input string and iterate through characters until you reach the end
of the string or you reach an #{interpolation}.

Split the string into two parts: the first part is a ‘raw’ string, the second
part will be an interpolation. Parse the interpolation and extract the
inner symbol.

(defnparse-interpolation[string];; iterate over the string, store the result in the parts
(loop[stringstringparts[]](let[[[_str-partsym-part]](re-seq#"^(.*?)#\{(.*?)\}"string)](if(andstr-partsym-part);; the 3 here accounts for the '#', '{' and '}' which were stripped
;; of by the re-seq above.
(recur(.substringstring(+(countstr-part)3(countsym-part)))(conjpartsstr-part(symbolsym-part)))(conjpartsstring)))))

Step two: build the macro

Having defined the parse-interpolation function in the previous step, it is
now trivial to define our interpolation macro as follows:

(defmacroitp[string](cons'str(parse-interpolationstring)));; Usage:
(let[name"Billy"last-name"The Kid"time"10:00pm"](itp"Hi #{name} #{last-name}, you last logged in at #{time}."));;=>"Hi Billy The Kid, you last logged in at 10:00pm."

Interpolation!

We now have an interpolation macro that will make our string concatenations
more readable.