Skip to main content

18.2.15 Edebug and Macros

To make Edebug properly instrument expressions that call macros, some extra care is needed. This subsection explains the details.

Instrumenting Macro Calls  The basic problem.
Specification List  How to specify complex patterns of evaluation.
Backtracking  What Edebug does when matching fails.
Specification Examples  To help understand specifications.

18.2.15.1 Instrumenting Macro Calls

When Edebug instruments an expression that calls a Lisp macro, it needs additional information about the macro to do the job properly. This is because there is no a-priori way to tell which subexpressions of the macro call are forms to be evaluated. (Evaluation may occur explicitly in the macro body, or when the resulting expansion is evaluated, or any time later.)

Therefore, you must define an Edebug specification for each macro that Edebug will encounter, to explain the format of calls to that macro. To do this, add a debug declaration to the macro definition. Here is a simple example that shows the specification for the for example macro (see Argument Evaluation).

(defmacro for (var from init to final do &rest body)
"Execute a simple \"for\" loop.
For example, (for i from 1 to 10 do (print i))."
(declare (debug (symbolp "from" form "to" form "do" &rest form)))
...)

The Edebug specification says which parts of a call to the macro are forms to be evaluated. For simple macros, the specification often looks very similar to the formal argument list of the macro definition, but specifications are much more general than macro arguments. See Defining Macros, for more explanation of the declare form.

Take care to ensure that the specifications are known to Edebug when you instrument code. If you are instrumenting a function which uses a macro defined in another file, you may first need to either evaluate the require forms in the file containing your function, or explicitly load the file containing the macro. If the definition of a macro is wrapped by eval-when-compile, you may need to evaluate it.

You can also define an edebug specification for a macro separately from the macro definition with def-edebug-spec. Adding debug declarations is preferred, and more convenient, for macro definitions in Lisp, but def-edebug-spec makes it possible to define Edebug specifications for special forms implemented in C.

macro def-edebug-spec macro specification

Specify which expressions of a call to macro macro are forms to be evaluated. specification should be the edebug specification. Neither argument is evaluated.

The macro argument can actually be any symbol, not just a macro name.

Here is a table of the possibilities for specification and how each directs processing of arguments.

t

All arguments are instrumented for evaluation.

0

None of the arguments is instrumented.

a symbol

The symbol must have an Edebug specification, which is used instead. This indirection is repeated until another kind of specification is found. This allows you to inherit the specification from another macro.

a list

The elements of the list describe the types of the arguments of a calling form. The possible elements of a specification list are described in the following sections.

If a macro has no Edebug specification, neither through a debug declaration nor through a def-edebug-spec call, the variable edebug-eval-macro-args comes into play.

user option edebug-eval-macro-args

This controls the way Edebug treats macro arguments with no explicit Edebug specification. If it is nil (the default), none of the arguments is instrumented for evaluation. Otherwise, all arguments are instrumented.

18.2.15.2 Specification List

A specification list is required for an Edebug specification if some arguments of a macro call are evaluated while others are not. Some elements in a specification list match one or more arguments, but others modify the processing of all following elements. The latter, called specification keywords, are symbols beginning with ‘&’ (such as &optional).

A specification list may contain sublists, which match arguments that are themselves lists, or it may contain vectors used for grouping. Sublists and groups thus subdivide the specification list into a hierarchy of levels. Specification keywords apply only to the remainder of the sublist or group they are contained in.

When a specification list involves alternatives or repetition, matching it against an actual macro call may require backtracking. For more details, see Backtracking.

Edebug specifications provide the power of regular expression matching, plus some context-free grammar constructs: the matching of sublists with balanced parentheses, recursive processing of forms, and recursion via indirect specifications.

Here’s a table of the possible elements of a specification list, with their meanings (see Specification Examples, for the referenced examples):

sexp

A single unevaluated Lisp object, which is not instrumented.

form

A single evaluated expression, which is instrumented. If your macro wraps the expression with lambda before it is evaluated, use def-form instead. See def-form below.

place

A generalized variable. See Generalized Variables.

body

Short for &rest form. See &rest below. If your macro wraps its body of code with lambda before it is evaluated, use def-body instead. See def-body below.

function-form

A function form: either a quoted function symbol, a quoted lambda expression, or a form (that should evaluate to a function symbol or lambda expression). This is useful when an argument that’s a lambda expression might be quoted with quote rather than function, since it instruments the body of the lambda expression either way.

lambda-expr

A lambda expression with no quoting.

&optional

All following elements in the specification list are optional; as soon as one does not match, Edebug stops matching at this level.

To make just a few elements optional, followed by non-optional elements, use [&optional specs…]. To specify that several elements must all match or none, use &optional [specs…]. See the defun example.

&rest

All following elements in the specification list are repeated zero or more times. In the last repetition, however, it is not a problem if the expression runs out before matching all of the elements of the specification list.

To repeat only a few elements, use [&rest specs…]. To specify several elements that must all match on every repetition, use &rest [specs…].

&or

Each of the following elements in the specification list is an alternative. One of the alternatives must match, or the &or specification fails.

Each list element following &or is a single alternative. To group two or more list elements as a single alternative, enclose them in […].

&not

Each of the following elements is matched as alternatives as if by using &or, but if any of them match, the specification fails. If none of them match, nothing is matched, but the &not specification succeeds.

&define

Indicates that the specification is for a defining form. Edebug’s definition of a defining form is a form containing one or more code forms which are saved and executed later, after the execution of the defining form.

The defining form itself is not instrumented (that is, Edebug does not stop before and after the defining form), but forms inside it typically will be instrumented. The &define keyword should be the first element in a list specification.

nil

This is successful when there are no more arguments to match at the current argument list level; otherwise it fails. See sublist specifications and the backquote example.

gate

No argument is matched but backtracking through the gate is disabled while matching the remainder of the specifications at this level. This is primarily used to generate more specific syntax error messages. See Backtracking, for more details. Also see the let example.

other-symbol

Any other symbol in a specification list may be a predicate or an indirect specification.

If the symbol has an Edebug specification, this indirect specification should be either a list specification that is used in place of the symbol, or a function that is called to process the arguments. The specification may be defined with def-edebug-spec just as for macros. See the defun example.

Otherwise, the symbol should be a predicate. The predicate is called with the argument, and if the predicate returns nil, the specification fails and the argument is not instrumented.

Some suitable predicates include symbolp, integerp, stringp, vectorp, and atom.

[elements…]

A vector of elements groups the elements into a single group specification. Its meaning has nothing to do with vectors.

"string"

The argument should be a symbol named string. This specification is equivalent to the quoted symbol, 'symbol, where the name of symbol is the string, but the string form is preferred.

(vector elements…)

The argument should be a vector whose elements must match the elements in the specification. See the backquote example.

(elements…)

Any other list is a sublist specification and the argument must be a list whose elements match the specification elements.

A sublist specification may be a dotted list and the corresponding list argument may then be a dotted list. Alternatively, the last CDR of a dotted list specification may be another sublist specification (via a grouping or an indirect specification, e.g., (spec . [(more specs…)])) whose elements match the non-dotted list arguments. This is useful in recursive specifications such as in the backquote example. Also see the description of a nil specification above for terminating such recursion.

Note that a sublist specification written as (specs . nil) is equivalent to (specs), and (specs . (sublist-elements…)) is equivalent to (specs sublist-elements…).

Here is a list of additional specifications that may appear only after &define. See the defun example.

name

The argument, a symbol, is the name of the defining form.

A defining form is not required to have a name field; and it may have multiple name fields.

:name

This construct does not actually match an argument. The element following :name should be a symbol; it is used as an additional name component for the definition. You can use this to add a unique, static component to the name of the definition. It may be used more than once.

arg

The argument, a symbol, is the name of an argument of the defining form. However, lambda-list keywords (symbols starting with ‘&’) are not allowed.

lambda-list

This matches a lambda list—the argument list of a lambda expression.

def-body

The argument is the body of code in a definition. This is like body, described above, but a definition body must be instrumented with a different Edebug call that looks up information associated with the definition. Use def-body for the highest level list of forms within the definition.

def-form

The argument is a single, highest-level form in a definition. This is like def-body, except it is used to match a single form rather than a list of forms. As a special case, def-form also means that tracing information is not output when the form is executed. See the interactive example.

18.2.15.3 Backtracking in Specifications

If a specification fails to match at some point, this does not necessarily mean a syntax error will be signaled; instead, backtracking will take place until all alternatives have been exhausted. Eventually every element of the argument list must be matched by some element in the specification, and every required element in the specification must match some argument.

When a syntax error is detected, it might not be reported until much later, after higher-level alternatives have been exhausted, and with the point positioned further from the real error. But if backtracking is disabled when an error occurs, it can be reported immediately. Note that backtracking is also reenabled automatically in several situations; when a new alternative is established by &optional, &rest, or &or, or at the start of processing a sublist, group, or indirect specification. The effect of enabling or disabling backtracking is limited to the remainder of the level currently being processed and lower levels.

Backtracking is disabled while matching any of the form specifications (that is, form, body, def-form, and def-body). These specifications will match any form so any error must be in the form itself rather than at a higher level.

Backtracking is also disabled after successfully matching a quoted symbol or string specification, since this usually indicates a recognized construct. But if you have a set of alternative constructs that all begin with the same symbol, you can usually work around this constraint by factoring the symbol out of the alternatives, e.g., ["foo" &or [first case] [second case] ...].

Most needs are satisfied by these two ways that backtracking is automatically disabled, but occasionally it is useful to explicitly disable backtracking by using the gate specification. This is useful when you know that no higher alternatives could apply. See the example of the let specification.

18.2.15.4 Specification Examples

It may be easier to understand Edebug specifications by studying the examples provided here.

A let special form has a sequence of bindings and a body. Each of the bindings is either a symbol or a sublist with a symbol and optional expression. In the specification below, notice the gate inside of the sublist to prevent backtracking once a sublist is found.

(def-edebug-spec let
((&rest
&or symbolp (gate symbolp &optional form))
body))

Edebug uses the following specifications for defun and the associated argument list and interactive specifications. It is necessary to handle interactive forms specially since an expression argument is actually evaluated outside of the function body. (The specification for defmacro is very similar to that for defun, but allows for the declare statement.)

(def-edebug-spec defun
(&define name lambda-list
[&optional stringp] ; Match the doc string, if present.
[&optional ("interactive" interactive)]
def-body))

(def-edebug-spec lambda-list
(([&rest arg]
[&optional ["&optional" arg &rest arg]]
&optional ["&rest" arg]
)))

(def-edebug-spec interactive
(&optional &or stringp def-form)) ; Notice: def-form

The specification for backquote below illustrates how to match dotted lists and use nil to terminate recursion. It also illustrates how components of a vector may be matched. (The actual specification defined by Edebug is a little different, and does not support dotted lists because doing so causes very deep recursion that could fail.)

(def-edebug-spec \` (backquote-form))   ; Alias just for clarity.

(def-edebug-spec backquote-form
(&or ([&or "," ",@"] &or ("quote" backquote-form) form)
(backquote-form . [&or nil backquote-form])
(vector &rest backquote-form)
sexp))