I am attempting to answer the following exercise.
Write a macro function OUR-IF that translates the following macro calls.
(our-if a then b) translates to (cond (a b)) (our-if a then b else c) translates to (cond (a b) (t c))
My solution is the following:
(defmacro our-if (test then then-clause &optional else else-clause)
(if (equal 'else else)
`(cond (,test ,then-clause) (t ,else-clause))
`(cond (,test ,then-clause))))
That works fine, but I'm not totally satisfied by it. There's no syntax checking on the "then" and "else" arguments. Then could be anything. And if I gave the wrong symbol for "else", I'd get the wrong behaviour. The symbols aren't even checked to be symbols.
I could model them as keyword arguments and get close, but that's not exactly right either. It reminds me of the LOOP macro, which is a much more complicated example of the same thing. That made me wonder: how does LOOP do it? Maybe there's some pattern for "mini languages in macro arguments". But I couldn't find anything.
I found the hyperspec page for LOOP which confirms that the grammar is complicated. But I don't know if there's a nice way to implement it.
The LOOP macro accepts symbols that are compared by name and not by identity to the keywords defined in the grammar (see 6.1.1.2 Loop Keywords ). The section doesn't specify how the comparison by name is done, but in ECL and SBCL (I tried only two implementations), the expected case is upper-case (using
|for|
doesn't work). All three invocations below are equivalent:For your simple macro it can be sufficient to add checks as follows:
Note the use of a
elsep
andecp
(if you prefer longer names:else-clause-p
) in the lambda list to know if the optional arguments are given or not (depending on their default value would not be enough to disambiguate the different cases).