Today I wrote a proof-of-concept implementation of a macro system for Clojurescript. The code is here.
Clojurescript already had a sort of macro system in the form of Clojure macros, but this is different – the macros are written in Clojurescript, get compiled to JavaScript, and are evaluated by a JavaScript interpreter. They can be mixed in with Clojurescript code and call Clojurescript functions. In theory, they should work with any Clojurescript backend that implements the REPL-related protocols (but who knows if that's true).
So that's the big announcement. What follows are some implementation details and other notes.
Implementation details
The macros are defined by passing strings back and forth between the
Clojurescript compiler and the Rhino JavaScript interpreter. The
strings from Rhino are read by Clojure using read-string
, so
macros are limited to things that can be printed by Clojurescript in
a form that Clojure can read.
Macros are not yet put into the correct namespaces. I don't think it will be too hard to do that correctly though.
Rhino is a slow beast. It adds multiple seconds to the startup time
of the compiler. It might be smart to scan the file for calls to
defmacro
before including the macro-interpreting code. However,
since macro expansion requires that all functions are defined in the
interpreter, once a defmacro
is hit, all the functions in the file
preceding it (and in all require
'd files) must be re-parsed.
Existing Clojure macros should still work. If two macros have the same name, the Clojurescript one will take precedence, but of course getting namespaces working should eliminate most conflicts.
It should go without saying that this is completely experimental at this point. Things seem like they work to me, but they might yet blow up in unexpected ways.
An example
Here is a simple Clojurescript file which implements the unless
macro (also known as when-not
in Clojure, but I think giving it a
different name shows better that it is really not using the Clojure
macros).
(defmacro unless [pred & body] `(if (not ~pred) (do ~@body) nil)) (let [a (unless true (/ 1 0) (+ 1 1))] a)
And here is its output:
var a__5799 = (cljs.core.truth_(cljs.core.not.call(null,true))?(function (){(1 / 0); return (1 + 1); })():null); a__5799;
It's a bit ugly, but it should be obvious what's going on.
Conclusion
I'm pretty happy with the progress so far. It really shows how flexible the Clojurescript compiler is that a macro system could be added in under 75 lines of code, with nearly half of that being very lightly modified from the compiler itself.