A clj snippet #4
Today's snippet is extracted from cider/orchard. A library that provides utilities for clojure tooling. It actually grew a lot longer than I intended it to be. We start off with a utility function that will come in handy later on.
meta-merge is useful if you don't know (and don't always want to check) if the object you are working with supports metadata. It's sort of the
vary-meta for lazy buggers like myself. Now comes the main hero of today's post.
macroexpand-all behaves just as
macroexpand-all except that it preserves metadata of the expanded forms. On top of that, it is able to save the non-expanded version of the forms in the metadata. Where might this be useful?
Imagine you are working on some tooling the keeps track of the source location. The forms might look quite different when macroexpanded so you probably want to attach the source location beforehand. For the purpose of this post we will consider just the source location in one form. We will use a vector of integers to locate a subform in the top-level form. The first value describes the subform to move to in the toplevel form, the second value describes the subsubform to move to in the subform and so on.
Examples speak a thousand words. Consider the form
then the expression
foo would have coordinate vector
 and the subform
(- x y) would have the coordinates
[3 2]. For the second example you enter the top-level form move 3 expressions forward enter that form and then move 2 expressions forward.
For those interested, this type of source location is also used in the cider debugger.
walk-indexed takes a function
f and a form and recursively applies
f on every subform with the coordinate vector described above as first argument and the original subform as second argument. We now use
walk-indexed to attach source location to Clojure forms.
Now let's put it all together. We first create a macro form, then add the source location as described above and finally macroexpand the form.
You can find the coordinate vectors attached as metadata to expressions that survived the macroexpansion in the pretty printed result.
Note that I modified
macroexpand-all slightly, so as to make it non-dependent on other internal functions of
cider/orchard. If you want to make sure that code works in all circumstances, please use the one provided by the library.