For Development HEAD DRAFTSearch (procedure/syntax/module):

5.7 Debugging macros

Macro expansion happens at the compile time, which makes it difficult to debug. The best way to avoid headache of macro debugging is not to write macros unless they’re absolutely necessary, and keep them as simple as possible if you need to write ones.

However, if you find yourself in an unfortunate situation that you have to untangle hairy macros, Gauche has some tools to help.


5.7.1 Tracing macro expansion

Macro tracing shows the input to the macro expander and the result of its expansion on selected macros. Suppose you have the following macro definition. It’s essentially the same as shown in the definition of letrec in R7RS section 7.3:

(define-syntax my-letrec
  (syntax-rules ()
    [(_ ((var init) ...) body ...)
     (my-letrec "tmps" (var ...) () ((var init) ...) body ...)]
    [(_ "tmps" () (tmp ...) ((var init) ...) body ...)
     (let ((var 'undefined) ...)
       (let ((tmp init) ...)
         (set! var tmp) ...
         body ...))]
    [(_ "tmps" (x y ...) (tmp ...) binds body ...)
     (my-letrec "tmps" (y ...) (newtmp tmp ...) binds body ...)]))

The my-letrec macro uses an idiom to generate temporary variables by looping with "tmps" tag. You can see how the macro is expanded step by step, by tracing my-letrec:

gosh> (trace-macro 'my-letrec)
(my-letrec)
gosh> (my-letrec [(ev? (^n (if (= n 0) #t (od? (- n 1)))))
                  (od? (^n (if (= n 0) #f (ev? (- n 1)))))]
        (ev? 3))
Macro input>>>
(my-letrec
 ((ev? (^n (if (= n 0) #t (od? (- n 1)))))
  (od? (^n (if (= n 0) #f (ev? (- n 1))))))
 (ev? 3))

Macro output<<<
(my-letrec
 "tmps"
 (ev? od?)
 ()
 ((ev? (^n (if (= n 0) #t (od? (- n 1)))))
  (od? (^n (if (= n 0) #f (ev? (- n 1))))))
 (ev? 3))

Macro input>>>
(my-letrec
 "tmps"
 (ev? od?)
 ()
 ((ev? (^n (if (= n 0) #t (od? (- n 1)))))
  (od? (^n (if (= n 0) #f (ev? (- n 1))))))
 (ev? 3))

Macro output<<<
(my-letrec
 "tmps"
 (od?)
 (newtmp.0)
 ((ev? (^n (if (= n 0) #t (od? (- n 1)))))
  (od? (^n (if (= n 0) #f (ev? (- n 1))))))
 (ev? 3))

Macro input>>>
(my-letrec
 "tmps"
 (od?)
 (newtmp.0)
 ((ev? (^n (if (= n 0) #t (od? (- n 1)))))
  (od? (^n (if (= n 0) #f (ev? (- n 1))))))
 (ev? 3))

Macro output<<<
(my-letrec
 "tmps"
 ()
 (newtmp.1 newtmp.0)
 ((ev? (^n (if (= n 0) #t (od? (- n 1)))))
  (od? (^n (if (= n 0) #f (ev? (- n 1))))))
 (ev? 3))

Macro input>>>
(my-letrec
 "tmps"
 ()
 (newtmp.0 newtmp.1)
 ((ev? (^n (if (= n 0) #t (od? (- n 1)))))
  (od? (^n (if (= n 0) #f (ev? (- n 1))))))
 (ev? 3))

Macro output<<<
(let
 ((ev? (quote undefined)) (od? (quote undefined)))
 (let
  ((newtmp.0 (^n (if (= n 0) #t (od? (- n 1)))))
   (newtmp.1 (^n (if (= n 0) #f (ev? (- n 1))))))
  (set! ev? newtmp.0)
  (set! od? newtmp.1)
  (ev? 3)))

#f

In the above example, the S-expressions after gosh> prompt is what you type; all other things are Gauche’s answer, including Macro input and Macro output S-expressions.

The S-expression shown with Macro input is the input of the macro expander, and the one with Macro output is the expanded result. Actual macro output has syntactic information attached, but the tracer strips them off for the legibility.

Note that the loop introduces new temporary variables with the same name (newtemp), but they are treated as different identifiers in the macro expansion.

Once you’re done debugging, don’t forget to call untrace-macro with no arguments to remove macro traces. If there’s a macro trace set, all macro expansions get some overhead, so don’t leave macro traces.

gosh> (untrace-macro)
#f
Function: trace-macro
Function: trace-macro boolean
Function: trace-macro name-or-pattern …

Get/set current macro trace setting. Macro trace setting can be one of the following values:

#f

Macro tracing is off. This is the default setting.

#t

All macro expansions are traced.

(name-or-pattern …)

Trace macros that match any one of name-or-pattern, which is either a symbol or a regexp. If it’s a symbol, a macro whose name is the same as the symbol is traced. If it’s a regexp, macros whose name match the regexp are traced.

When called without arguments, trace-macro doesn’t change the setting; it returns the current setting.

When called with single boolean value, it sets the current setting to that value. Returns the updated setting.

When called with one or more name-or-pattern, it adds them to the current setting. Note that if the current setting is #t, it remains #t, for all macros are already traced. Returns the updated setting.

If macro trace settings is not #f, it incurs overhead for every macro expansion. Be careful not to leave macro trace set.

The trace information is output to the current trace port. (see Common port operations).

Function: untrace-macro
Function: untrace-macro name-or-pattern …

When called without arguments, it turns macro trace off.

When called with one or more name-or-pattern, which is either a symbol or a regexp, untrace-macro removes them from the currently traced macros. Note that if the current macro trace setting is #t (trace all macros), you can’t remove traced macro individually.

It returns the updated macro trace setting.


5.7.2 Expanding macros manually

Function: macroexpand form :optional env
Function: macroexpand-1 form :optional env

If form is a list and its first element is a variable globally bound to a macro, macroexpand-1 invokes its macro transformer and returns the expanded form. Otherwise, returns form as is.

macroexpand repeats macroexpand-1 until the outermost expression of form can’t be expanded. (It doesn’t expand macros other than outermost one. If you want to expand all the macros within form, use macroexpand-all).

These procedures can be used to expand globally defined macros.

Internally, hygienic macro expansion wraps symbols in form with syntactic information to keep hygiene. However, such information is hard to read, and not suitable when you just want to expand a macro in REPL to check its result. So, by default, these procedures strips syntactic information. For the identifiers introduced in the macro, it renames them to avoid name conflicts.

The following example expands my-letrec macro (see Tracing macro expansion, for the definition) and results shows temporary variable introduced by the macro (newtemp) to be renamed.

(macroexpand
  '(my-letrec [(ev? (^n (if (= n 0) #t (od? (- n 1)))))
               (od? (^n (if (= n 0) #f (ev? (- n 1)))))]
     (ev? 3)))

⇒

(let
 ((ev? (quote undefined)) (od? (quote undefined)))
 (let
  ((newtmp.0 (^n (if (= n 0) #t (od? (- n 1)))))
   (newtmp.1 (^n (if (= n 0) #f (ev? (- n 1))))))
  (set! ev? newtmp.0)
  (set! od? newtmp.1)
  (ev? 3)))

If you pass a module to the env argument, it is used as the macro use environment. You can also pass #t to let it use the current runtime environment as the macro use environment. In those cases, syntactic information in the output won’t be stripped.

If you want to use the output of macroexpand as a program, e.g. embed it into another macro expansion, you need syntactic information preserved.

Function: macroexpand-all form :optional env

Fully expand macros inside form. The result only contains function calls and Gauche’s built-in syntax.

By default, or #t is passed to env, the form is assumed to be a toplevel form within the current runtime module. You can also pass a module to env to specify the alternative toplevel environment.

Any local variables introduced in form is renamed to avoid collision. Since each local variable has unique name, all let forms become letrec forms (we can safely replace let with letrec if no bindings introduced by let shadows outer bindings.)

NB: If a macro in form inserts a reference to a global variable which belongs to other module, the information is lost in the current implementation. There are a few ways to address this issue; we may leave such reference as an identifier object, convert it to with-module form, or introduce a special syntax to represent such case. It’s undecided currently, so do not rely too much on the current behavior. For the time being, it’s best to use this feature only for interactive macro testing.

(macroexpand-all
 '(letrec-syntax
      [(when-not (syntax-rules ()
                   [(_ test . body) (if test #f (begin . body))]))]
    (let ([if list])
      (define x (expt foo))
      (let1 x 3
        (when-not (bar) (if x))))))
 ⇒ (letrec ((if.0 list))
     (letrec ((x.1 (expt foo)))
       (letrec ((x.2 '3))
        (if (bar) '#f (if.0 x.2)))))
Special Form: %macroexpand form
Special Form: %macroexpand-1 form


For Development HEAD DRAFTSearch (procedure/syntax/module):
DRAFT