This has been discussed in the comp.lang.lisp file since 2013 . Jathd pointed out:
The behavior that you observe is related to how the general macroextension works: when processing a form for evaluation (or compilation, or macroexpansion or ...), if it is a macro call, replace it with the corresponding extension and start processing from the very beginning with a new one form.
So, you will have to actively do something special for character macros, unlike regular macros, so that they are not "recursive".
Pascal Constanta provided this advice:
A good solution is to turn a character macro into a regular macro.
(macrolet ((regular-macro (...) ...)) (symbol-macrolet ((sym (regular-macro))) ...))
although Informatimago points out that it still exhibits the same behavior as the original:
This will still fail if regular macro expansion is included in macroexpandable which places the character denoting the character macro.
The answer to the question "Is there a way to expand a character once without executing the full code?" it seems no, unfortunately. However, it is not so difficult to get around this; The "solution" in the linked chain ends up using gensyms to avoid the problem. For instance:.
(let ((x 32)) ; just to provide a value for x (let ((#1=#:genx x)) ; new variable with x value (symbol-macrolet ((x (values #1#))) ; expansion contains the new variable (* xx)))) ; === (* (values #1#) (values #1#)) ;=> 1024
Writing #1# or similar things in macro exposure is not fun. This is not so bad if you automatically generate extensions, but if you do it manually, it may be useful to use the fact that let can be a shadow symbol-macrolet . This means that you can wrap the extension in let , which restores the binding you want:
(let ((x 32)) (let ((
If you find that you are doing this a lot, you can complete it in the "inexpressible" version of the macro-symbol:
(defmacro unshadowing-symbol-macrolet (((var expansion)) &body body) "This is like symbol-macrolet, except that var, which should have a binding in the enclosing environment, has that same binding within the expansion of the symbol macro. This implementation only handles one var and expansion; extending to n-ary case is left as an exercise for the reader." (let ((hidden-var (gensym (symbol-name var)))) `(let ((,hidden-var ,var)) (symbol-macrolet ((,var (let ((,var ,hidden-var)) ,expansion))) ,@body)))) (let ((x 32)) (unshadowing-symbol-macrolet ((x (values x))) (* xx))) ;=> 1024
This will only work for a variable that already has a lexical binding, of course. Generic Lisp does not provide much for accessing objects in the environment, except for their distribution in macro extensions. If your implementation provides access to the environment, you can specify that the unshadowing-symbol-macrolet macro will be attached to each var in the environment, and provide a local shadow image, if any, and not make a shadow if it is not.
Notes
It is interesting to see what the original author in this thread, Antsan, had to say about his expectations about how the macro expansion process worked:
I thought macro expansion works by repeating macro distribution to the source until a fixed point is reached. Thus, SYMBOL-MACROLET will not be automatically recursive if it was deleted by the extension macro.
Sort of:
(symbol-macrolet (a (foo a)) a) macroexpand-1> (foo a) macroexpand-1> (foo a) ; fixpoint
No special cases will be required, although I think this algorithm for macro expansion will be slower.
This is interesting because this is how Common Lisp 's' compilers work. The documentation from define-compiler-macro says:
- Unlike a regular macro, a compiler macro can refuse an extension by simply returning a form that matches the original (which can be obtained using & whole).
This would not help here, since macro characters do not get a choice about what to return; that is, there are no arguments passed to the macro symbol, so there is nothing that could be verified or used to influence the macro distribution. The only way to return the same form is with something like (symbol-macrolet ((xx)) โฆ) , which is more likely to hit the target.