The Macrological Fascicle
Appendix A
Other macro systems
This section of the report is non-normative and aims to demonstrate how macros written with some other Scheme macro systems can be accommodated by a macro system based on syntax objects.
Unhygienic macros
Traditional Lisp macros offer no protection against accidental
identifier capture and do not ensure that free variables in the
expansion have the same references as at the point where the macro was
defined. The argument to this lisp-transformer
form is a
procedure which receives the macro use as a completely unwrapped
datum.
Implementation:
(define (lisp-transformer transformer)
(lambda (stx)
(syntax-case stx ()
((use-ctx . rest)
(datum->syntax #'use-ctx
(transformer
(syntax->datum stx)))))))
Explicit renaming macros
The explicit renaming system was introduced by Clinger (1991) as an alternative means of
implementing syntax-rules
. As a low-level macro system, its
weaknesses are its relative verbosity when used to implement a fully
hygienic macro, and its inability to control the context of identifier
capture, meaning it cannot comply with the requirements on introducing
capturing references in section 1.1. The version of the
er-macro-transformer
form defined here can avoid this
problem by allowing selective use of the datum->syntax
form to capture identifiers, instead of using symbols as in the original
definition of er-macro-transformer
.
A small number of explicit renaming macros may require adjustment to
work correctly under this version of er-macro-transformer
.
Some adjustment may be required because typical explicit renaming macros
use eq?
, eqv?
, or symbol=?
to
check whether binding one identifier would shadow another — in other
words, to provide the functionality of bound-identifier=?
.
This use was not defined in Clinger’s original paper and is a later
addition to the vernacular of explicit renaming systems.
In the syntax-case
system, two identifiers with this
property are not typically the same in the sense of eq?
or
eqv?
. If the macro implementation uses
symbol=?
to perform this check, a domain error will occur
on any such attempt to compare identifiers; such macros already do not
work on many implementations of explicit renaming, however, where
identifiers are wrapped in syntactic closures (Bawden and Rees 1988, Hanson 1991). Likewise, using
symbol?
to check whether part of a macro’s input expression
is an identifier will not work in this implementation, but such macros
were also already broken in explicit renaming systems implemented on top
of syntactic closures. In fact, there is no way to do this which already
works across different explicit renaming-based expanders.
As can be seen, this implementation’s weaknesses mainly find their
ultimate root cause in the under-specified nature of the explicit
renaming system itself. Another weakness is that if a use of a macro
defined with this implementation contains a circular literal value, the
unwrap
procedure will diverge.
Implementation:
(define (unwrap stx)
(syntax-case stx ()
((a . b) (cons (unwrap #'a) (unwrap #'b)))
(#(a ...) (vector-map unwrap #'#(a ...)))
(id (identifier? #'id) #'id)
(_ (syntax->datum stx))))
(define (rewrap ctx expr)
(let rewrap* ((e e))
(cond
((pair? e) (cons (rewrap* (car e)) (rewrap* (cdr e))))
((vector? e) (vector-map rewrap* e))
((identifier? e) e)
(else (datum->syntax ctx e)))))
(define (make-compare ctx)
(lambda (x y)
(free-identifier=? (rewrap ctx x) (rewrap ctx y))))
(define (make-rename ctx)
(lambda (x)
(datum->syntax ctx x)))
(define-syntax er-macro-transformer
(lambda (stx)
(syntax-case stx ()
((k proc-expr)
#'(let ((proc proc-expr))
(lambda (stx)
(syntax-case stx ()
((m . _)
(rewrap #'m
(proc (unwrap stx)
(make-rename #'k)
(make-compare #'m)))))))))))
Implicit renaming macros
Implicit renaming macros are a variant of explicit renaming macros
with one difference. Instead of providing a rename
procedure for referring to or inserting identifiers hygienically, and
taking bare symbols in the output as the names of identifiers to be
captured in the context of the macro keyword, the situation is inverted:
bare symbols in the output are renamed hygienically and an
inject
procedure is used to capture identifiers in the
context of the keyword at the macro use site. As above, in this
implementation datum->syntax
can be used to more safely
perform this capture in contexts other than that of the macro use
keyword.
The same caveats around the assumption that symbols are used to
represent identifiers mentioned above in the section about explicit
renaming macros apply here. The unwrap
,
rewrap
, make-compare
, and
make-rename
procedures are the same as for the explicit
renaming implementation.
Implementation:
(define-syntax ir-macro-transformer
(lambda (stx)
(syntax-case stx ()
((k proc-expr)
#'(let ((proc proc-expr))
(lambda (stx)
(syntax-case stx ()
((m . _)
(rewrap #'k
(proc (unwrap stx)
(make-rename #'m)
(make-compare #'m)))))))))))