macrostep

Homepage: https://github.com/emacsorphanage/macrostep

Author: Jon Oddie

Updated:

Summary

Interactive macro expander

Commentary

`macrostep' is an Emacs 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
temporarily read-only while macro expansions are visible.  You can
expand and collapse macro forms one step at a time, and evaluate or
instrument the expansions for debugging with Edebug as normal (but see
"Bugs and known limitations", below).  Single-stepping through the
expansion is particularly useful for debugging macros that expand into
another macro form.  These can be difficult to debug with Emacs'
built-in `macroexpand', which continues expansion until the top-level
form is no longer a macro call.

Both globally-visible macros as defined by `defmacro' and local macros
bound by `(cl-)macrolet' or another macro-defining form can be expanded.
Within macro expansions, calls to macros and compiler macros are
fontified specially: macro forms using `macrostep-macro-face', and
functions with compiler macros using `macrostep-compiler-macro-face'.
Uninterned symbols (gensyms) are fontified based on which step in the
expansion created them, to distinguish them both from normal symbols and
from other gensyms with the same print name.

As of version 0.9, it is also possible to extend `macrostep' to work
with other languages with macro systems in addition to Emacs Lisp.  An
extension for Common Lisp (via SLIME) is in the works; contributions for
other languages are welcome.  See "Extending macrostep" below for
details.


1 Key-bindings and usage
========================

  The standard keybindings in `macrostep-mode' 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:

  ,----
  | (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 by typing `q' or `C-c C-c', or by successively
  typing `c' to collapse all surrounding expansions.


2 Customization options
=======================

  Type `M-x customize-group RET macrostep RET' to customize options and
  faces.

  To display macro expansions in a separate window, instead of inline in
  the source buffer, customize `macrostep-expand-in-separate-buffer' to
  `t'.  The default is `nil'.  Whichever default behavior is selected,
  the alternative behavior can be obtained temporarily by giving a
  prefix argument to `macrostep-expand'.

  To have `macrostep' ignore compiler macros, customize
  `macrostep-expand-compiler-macros' to `nil'.  The default is `t'.

  Customize the faces `macrostep-macro-face',
  `macrostep-compiler-macro-face', and `macrostep-gensym-1' through
  `macrostep-gensym-5' to alter the appearance of macro expansions.


3 Locally-bound macros
======================

  As of version 0.9, `macrostep' can expand calls to a locally-bound
  macro, whether defined by a surrounding `(cl-)macrolet' form, or by
  another macro-defining macro.  In other words, it is possible to
  expand the inner `local-macro' forms in both the following examples,
  whether `local-macro' is defined by an enclosing `cl-macrolet' --

  ,----
  | (cl-macrolet ((local-macro (&rest args)
  |                 `(expansion of ,args)))
  |   (local-macro (do-something)))
  `----

  -- or by a macro which expands into `cl-macrolet', provided that its
  definition of macro is evaluated prior to calling `macrostep-expand':

  ,----
  | (defmacro with-local-macro (&rest body)
  |   `(cl-macrolet ((local-macro (&rest args)
  |                    `(expansion of ,args)))
  |      ,@body))
  |
  | (with-local-macro
  |     (local-macro (do something (else)))
  `----

  See the `with-js' macro in Emacs's `js.el' for a real example of the
  latter kind of macro.

  Expansion of locally-bound macros is implemented by instrumenting
  Emacs Lisp's macro-expander to capture the environment at point.  A
  similar trick is used to detect macro- and compiler-macro calls within
  expanded text so that they can be fontified accurately.


4 Expanding sub-forms
=====================

  By moving point around in the macro expansion using
  `macrostep-next-macro' and `macrostep-prev-macro' (bound to the `n'
  and `p' keys), it is possible to expand other macro calls within the
  expansion before expanding the outermost form.  This can sometimes be
  useful, although it does not correspond to the real order of macro
  expansion in Emacs Lisp, which proceeds by fully expanding the outer
  form to a non-macro form before expanding sub-forms.

  The main reason to expand sub-forms out of order is to help with
  debugging macros which programmatically expand their arguments in
  order to rewrite them.  Expanding the arguments of such a macro lets
  you visualise what the macro definition would compute via
  `macroexpand-all'.


5 Extending macrostep for other languages
=========================================

  Since version 0.9, it is possible to extend macrostep to work with
  other languages besides Emacs Lisp.  In typical Emacs fashion, this is
  implemented by setting buffer-local variables to different function
  values.  Six buffer-local variables define the language-specific part
  of the implementation:

  - `macrostep-sexp-bounds-function'
  - `macrostep-sexp-at-point-function'
  - `macrostep-environment-at-point-function'
  - `macrostep-expand-1-function'
  - `macrostep-print-function'
  - `macrostep-macro-form-p-function'

  Typically, an implementation for another language would set these
  variables in a major-mode hook.  See the docstrings of each variable
  for details on how each one is called and what it should return.  At a
  minimum, another language implementation needs to provide
  `macrostep-sexp-at-point-function', `macrostep-expand-1-function', and
  `macrostep-print-function'.  Lisp-like languages may be able to reuse
  the default `macrostep-sexp-bounds-function' if they provide another
  implementation of `macrostep-macro-form-p-function'.  Languages which
  do not implement locally-defined macros can set
  `macrostep-environment-at-point-function' to `ignore'.

  Note that the core `macrostep' machinery only interprets the return
  value of `macrostep-sexp-bounds-function', so implementations for
  other languages can use any internal representations of code and
  environments which is convenient.  Although the terminology is
  Lisp-specific, there is no reason that implementations could not be
  provided for non-Lisp languages with macro systems, provided there is
  some way of identifying macro calls and calling the compiler /
  preprocessor to obtain their expansions.


6 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.

  Please send other bug reports and feature requests to the author.


7 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 patches to highlight the
    expanded region and properly handle backquotes.
  - Nic Ferrier for suggesting support for local definitions within
    macrolet forms
  - Luís Oliveira for suggesting and implementing SLIME support

  `macrostep' was originally inspired by J. V. Toups's 'Deep Emacs Lisp'
  articles ([part 1], [part 2], [screencast]).

  [EmacsConf presentation] http://youtu.be/RvPFZL6NJNQ

  [part 1]
  http://dorophone.blogspot.co.uk/2011/04/deep-emacs-part-1.html

  [part 2]
  http://dorophone.blogspot.co.uk/2011/04/deep-emacs-lisp-part-2.html

  [screencast]
  http://dorophone.blogspot.co.uk/2011/05/monadic-parser-combinators-in-elisp.html

Dependencies

Reverse dependencies