Friday, December 25, 2009

How to write a Clojure reader macro, part 2

This is a follow up to my earlier post about writing a reader macro. Here's the disclaimer again.


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.

Okay, now that that's been said, let's get on with it. We're going to write a multi-character delimited reader macro this time. Since I'm a point free junkie, we're going to use partial for our example.

Here's what the final use case is going to be

user=>#[+ 1]
#< core$partial ...>

Let's start modifying again. The first thing we're going to do is insert a static symbol

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

Now, Let's take a look around line 84. You'll see the following entry in the array

macros['#'] = new DispatchReader();

The # character is bound to a DispatchReader. This is the object closure uses to implement multiple character reader macros (ever notice that they all start with #?). You'll also notice that there is a dispatchMacros array with several entires in it. Add the following entry

dispatchMacros['['] = new PartialReader();

We also need to define the PartialReader class. It is based on the VectorReader class, which can be found around line 994.

The heavy lifting is done by the readDelimitedList method. Note that the closing delimiter needs to be provided, and the recursive flag should be set to true. It returns an IPersistentList object. The only thing that needs to be done is to prepend a partial to the list. That is why the cons method is used to add a partial symbol (you still need classic macro-fu).

We've added everything we need to add to All that's left to do is recompile clojure.jar and test the results

user=>(map #[+ 1] [1 2 3])
(2 3 4)

Of course, now that I think about it, comment might be a better symbol to use than partial...

That's how you add a delimited reader macro. Next time we'll look at creating a new dispatch character, and properly escaping everything.

No comments:

Post a Comment