Skip to content

joddie/macrostep

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

17 Commits
 
 
 
 
 
 

Repository files navigation

macrostep: interactive macro expansion for Emacs Lisp

Overview

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.

Key-bindings and usage

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.

Expanding sub-forms

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.

Bugs and known limitations

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.

Acknowledgements

Thanks to:

  • John Wiegley for fixing a bug with the face definitions under Emacs 24 & for plugging macrostep in his EmacsConf presentation!
  • George Kettleborough for bug reports and patch to highlight the expanded region

Changelog

  • v0.5, 2013-04-16: highlight region, maintain cleaner buffer state
  • v0.4, 2013-04-07: only enter macrostep-mode on successful macro-expansion
  • v0.3, 2012-10-30: print dotted lists correctly. autoload definitions.