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.

Reply

Please solve the math problem above and type in the result. e.g. for 1+1, type 2
The content of this field is kept private and will not be shown publicly.
  • Allowed HTML tags: <a> <em> <strong> <cite> <pre> <code> <ul> <ol> <li> <dl> <dt> <dd> <sub> <sup> <tt>
  • Lines and paragraphs break automatically.
More information about formatting options