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)))))))))))