Macro Expansion Hook for Gauche
Inspired by Common
Lisp’s
*macroexpand-hook*,
here’s a pretty useful snippet I recently hacked together to assist me in
debugging
Scheme
macros for
Gauche.
(define-macro (define-macroexpand-hook hook)
`((with-module gauche define-macro) (define-macro formals . body)
(cond
((list? formals)
`(define-macro ,(car formals)
(lambda (,@(cdr formals)) ,@body)))
((symbol? formals)
`((with-module gauche define-macro) ,formals
(lambda args (apply ,',hook ,@body ',formals args))))
(else
(error "symbol required, but got" formals)))))
define-macroexpand-hook is a straightforward (well, not too hairy, anyway)
macro-generating macro that overwrites the binding of the define-macro special
form in the current module, with the result that any macros subsequently defined
will include a call to a user-defined procedure (passed as the argument to
define-macroexpand-hook), which will be invoked whenever the macro is applied.
(The immense power inherent in this brief code snippet will not, sadly, be something most programmers unfamiliar with Lisp can appreciate. What this is doing is essentially dynamically injecting additional code into the Scheme compiler itself — it’s not every language that lets you patch the compiler on the fly, while running.)
The hook procedure is expected to perform the actual macro expansion by applying
the supplied transformer. This allows the hook to actually modify the macro
code, or even suppress the macro’s expansion altogether. An example will make
this clearer, so here follows a snippet that shows how to use
define-macroexpand-hook to print out a debug message whenever a macro is
applied. The output will contain a pretty-printed version of the expression that
caused the macro application, as well as the macro’s actual expansion. This
makes debugging most macros a fair bit easier while working in the REPL:
(use slib)
(require 'pretty-print)
(define-macroexpand-hook
(lambda (transformer name . args)
(let1 expanded (apply transformer args)
(format #t "DEBUG: macro expansion ~a => ~a"
(pretty-print->string (cons name args))
(pretty-print->string expanded))
(newline)
expanded)))
Here’s a brief example session in Gauche’s REPL:
gosh> (define-macro while-not-perfect
(lambda (universe . body)
`(let loop ()
(unless (= 42 (answer-to-life-and-everything ,universe))
(begin ,@body))
(increment-version! ,universe)
(loop))))
gosh> (while-not-perfect *universe*
(randomize *universe*)
(reboot *universe*))
DEBUG: macro expansion (while-not-perfect
*universe*
(randomize *universe*)
(reboot *universe*))
=> (let loop ()
(unless
(= 42 (answer-to-life-and-everything *universe*))
(begin
(randomize *universe*)
(reboot *universe*)))
(increment-version! *universe*)
(loop))
^C*** UNHANDLED-SIGNAL-ERROR: unhandled signal 2 (SIGINT)
Note that the definition of answer-to-life-and-everything has been omitted
due to space constraints.
define-macroexpand-hook is related to some exploration I’ve doing lately on
hooking into and overriding Gauche’s syntax primitives, as reported on the
Gauche mailing
list.
Updated in April 2006 to utilize the extended with-module form now available
in Gauche
0.8.7,
making the code less hairy.