This is a minor mode for interactively stepping through the
expansion of macros in Emacs Lisp source code. It lets you see
exactly what happens at each step of the expansion process by
pretty-printing the expanded forms inline in the source buffer,
which is read-only while macro expansions are visible. You can
expand and collapse macro forms one step at a time, and evaluate or
instrument them for debugging with Edebug as normal (but see “Bugs
and known limitations”, below). Single-stepping through the
expansion is useful for debugging macros that expand into another
macro form, especially one like lexical-let
that does significant
rewriting. These can be difficult to debug with Emacs’ built-in
macroexpand
, because macroexpand
continues expansion until the
top-level form is no longer a macro call.
The mode also adds some simple additional fontification to
macro-expanded code. The heads of macro sub-forms are fontified
using macrostep-macro-face
. Uninterned symbols (gensyms) are
fontified based on which step in the expansion created them, to
distinguish them from normal symbols and from other gensyms with
the same print name. Use customize-group
with the macrostep
group to customize these faces.
The standard macrostep-mode keybindings are the following:
- e, =, RET
- expand the macro form following point one step
- c, u, DEL
- collapse the form following point
- q, C-c C-c
- collapse all expanded forms and exit macrostep-mode
- n, TAB
- jump to the next macro form in the expansion
- p, M-TAB
- jump to the previous macro form in the expansion
It’s not very useful to enable and disable macrostep-mode
directly. Instead, bind macrostep-expand
to a key in
emacs-lisp-mode-map
, for example C-c e:
(add-hook
'emacs-lisp-mode-hook
(lambda ()
(define-key emacs-lisp-mode-map (kbd "C-c e") 'macrostep-expand)))
You can then enter macrostep-mode and expand a macro form
completely by typing C-c e e e ...
as many times as necessary.
Exit macrostep-mode either with q
, C-c C-c
, or by successively
typing c
to collapse all expanded forms back to their original
text.
By moving point around in the macro expansion (perhaps using the
n
and p
keys), you can macro-expand sub-forms before fully
expanding the enclosing form. This can be useful in some cases,
but you should keep in mind that it does not correspond to the way
Emacs actually expands macro calls when evaluating or compiling
your code. Macro expansion in Emacs Lisp always proceeds by fully
expanding the outer form to a non-macro form before doing anything
with the sub-forms.
For example, with cl
loaded, try expanding the following form:
(dolist (l list-of-lists)
(incf (car l)))
to produce the following:
(block nil
(let
((--cl-dolist-temp-- list-of-lists)
l)
(while --cl-dolist-temp--
(setq l
(car --cl-dolist-temp--))
(incf
(car l))
(setq --cl-dolist-temp--
(cdr --cl-dolist-temp--)))
nil))
where the forms beginning block
and incf
are both macro calls.
At this point, you can either continue expanding the block
form,
which corresponds to the real order of macro expansion in
evaluation, or type n
to move point to the unexpanded incf
and
expand it to a callf
form and finally to a let*
form. If you
then move point back to the block
and expand it, an unexpanded
incf
form appears again in the result. This might look visually
confusing, but it does at least correspond to the way real macro
expansion works.
Why allow expanding sub-forms out of order like this at all? The
main reason is for debugging macros which expand into another
macro, like lexical-let
, that programmatically expands its
contents in order to rewrite them. In this case, expanding the
sub-forms first allows you to see what lexical-let
would compute
via cl-macroexpand-all
.
You can evaluate and edebug macro-expanded forms and step through
the macro-expanded version, but the form that eval-defun
and
friends read from the buffer won’t have the uninterned symbols of
the real macro expansion. This will probably work OK with CL-style
gensyms, but may cause problems with make-symbol
symbols if they
have the same print name as another symbol in the expansion. It’s
possible that using print-circle
and print-gensym
could get
around this.
The macro stepper doesn’t bother trying to determine whether or not
a sub-form is in an evaluated position before highlighting it as a
macro. It does exclude lambda
from treatment as a macro, since
that leads to an endless series of expansions: (function (function
... ))
. It would be better to recognise function
, quote
and
other special forms using their edebug-form-spec
property.
Please send other bug reports and feature requests to the author.
Thanks to John Wiegley for fixing a bug with the face definitions under Emacs 24.
- v0.3, 2012-10-30: print dotted lists correctly. autoload definitions.