Tuesday, December 15, 2009

How to write a Clojure reader macro

This is an article on how to write a basic reader macro in Clojure.


DISCLAIMER


I Completely agree with Rich's decision to NOT support reader macros. In an activity as simple as writing this post, I found many, many places to make a mistake the hard way. This is an extremely difficult activity to get right, and the end result is something that is not quite the same as normal Clojure. Use the following information at your own risk.


The first thing to identify is a behavior that you would like to have a reader macro for. For our example, I am going to use a modified form of Chas Emerick's amazing string interpolation macro. You can find his original article here. I took a modified version of his code, and placed it in core.clj (Be sure to create a new git branch). The code I used is below



Now that the desired functionality is in core, it is time to modify the reader. In this case we need to modify the file LispReader.java. I defined a static variable INTERPOLATE_S, and I am going to assign it the "|" reader macro.

//Inserted at line 40
static Symbol INTERPOLATE_S = Symbol.create("clojure.core", "interpolate-s");

Now, in order for this to be found we need to make an entry in the macros array. This can be done like so:

//Inserted at line 86
macros['|'] = new WrappingReader(INTERPOLATE_S);

The WrappingReader class takes a Symbol object, and wraps it around the next form that is read. Recall how the following form

@a-ref

is expanded to (deref a-ref). In our case

|"A string ~(+ 2 2)"

will be expanded to

(interpolate-s "A string ~(+ 2 2)")

Let's rebuild clojure.jar and try this out at a REPL.



As you can see this works just like Chas' macro. There are still a few things that need to be covered, such as:

* How to create a multiple character reader macro
* How to create a delimited reader macro

These will be topics for another day.

2 comments:

  1. You don't necessarily need to edit the Java code of the reader. You can horribly violate the Reader at runtime. I wrote about this a bit a few months ago: http://briancarper.net/blog/clojure-reader-macros

    I agree with you that it's obviously not a good idea to do this in real code anyways.

    ReplyDelete
  2. Brian,
    Thanks for the link. I was playing at the REPL today, and figured out how to do the same thing :) Cool to see it somewhere else.

    ReplyDelete