The Macrological Fascicle

Chapter 5

The syntax-rules system

The syntax-rules system can be used to write the simplest macros somewhat more concisely than is possible in the syntax-case system, with which it is closely related. A macro implemented in the syntax-rules system cannot perform arbitrary Scheme evaluation during its expansion. It also has no means of identifier capture, though this can sometimes be simulated using syntax parameters (see section 2.3).

(syntax-rules (pattern literal ...) syntax rule ...)
syntax
(syntax-rules custom ellipsis (pattern literal ...) syntax rule ...)
syntax
_
auxiliary syntax
...
auxiliary syntax

Syntax: Each pattern literal must be an identifier. If a custom ellipsis is provided, it must be an identifier. Each syntax rule has the form

(rule pattern template)

A template has the same form as in the definition of syntax. A rule pattern must have one of the following four forms:

(identifier pattern ...)

(identifier pattern ... . pattern)

(identifier pattern ... pattern ellipsis pattern ...)

(identifier pattern ... pattern ellipsis pattern ... . pattern)

Pattern has the same form as in the definition of syntax-case.

Custom ellipsis, if provided, must be an identifier. Within a rule pattern, pattern, and template, ellipsis refers to an identifier which is bound-identifier=? to this custom ellipsis identifier, if it is provided, or to the auxiliary syntax keyword ... otherwise.

Semantics: An instance of syntax-rules evaluates to a transformer procedure which operates according to a sequence of hygienic rewrite rules. A use of a macro whose keyword is associated with a transformer specified by syntax-rules is matched against the patterns contained in the syntax rules, beginning with the leftmost syntax rule. When a match is found, the macro use is transcribed hygienically according to the template. It is a syntax violation when no match is found.

The identifier at the beginning of a rule pattern is not involved in the matching and is considered neither a pattern variable nor a literal identifier. Thus, rule patterns are like syntax-case patterns, but are restricted to matching uses of macros which are not identifier macros.

Excluding the initial identifier, which is treated as if it were the auxiliary syntax keyword _, a macro use defined using syntax-rules is transcribed according to the template of the matching syntax rule as if the pattern had been matched using syntax-case with the same given list of pattern literals, and a syntax expression containing the template were the only content of that syntax-case clause’s output expression.

Examples:

The following macro destructively swaps the values associated with the identifiers it is given.

(define-syntax swap!
  (syntax-rules ()
    ((_ a b)
     (let ((temp a))
       (set! a b)
       (set! b temp)))))

Because hygiene is automatically maintained, the use of temp as an identifier internal to the expansion does not conflict with any existing binding called temp which exists in the place where the macro is used:

(define temp 37)
(define fever-temp 38)
(swap! fever-temp temp)
(values temp fever-temp)
38 37

Further, local redefinitions or re-bindings of let or set! at the place the macro is used do not affect the meaning of the macro expansion:

(let-syntax
    ((let (erroneous-syntax "let is not allowed here")))
  (swap! x y))
swaps the values of x and y without raising an error

Because the identifiers introduced by each macro transcription step receive a unique time-stamp, a recursively-expanding syntax-rules macro can generate an arbitrary number of distinct identifiers. The following example uses this together with the guaranteed evaluation order of let* to define a macro which expands into a normal Scheme procedure call, but guarantees that the procedure and its operands will be evaluated in left-to-right order.

(define-syntax call*
  (syntax-rules ()
    ((_ args ...)
     (call*-aux (args ...) ()))))

(define-syntax call*-aux
  (syntax-rules ()
    ((_ (expr . more-exprs) (exprs-w/gen-ids ...))
     (call*-aux more-exprs (exprs-w/gen-ids ... (gen-id expr))))
    ((_ () ((gen-id expr) ...))
     (let* ((gen-id expr) ...)
       (gen-id ...)))))

If the body of the let expression in the following example were simply (cons (read source) (read source)), an implementation of Scheme would be allowed to return ((then this) . this-first). Using this call* macro guarantees the intended result.

(let ((source (open-input-string "this-first (then this)")))
  (call* cons (read source)
              (read source)))
(this-first then this)

Implementation:

(define-syntax syntax-rules
  (lambda (x)
    (syntax-case x ()
      ((_ ell (lit ...) ((k . p) t) ...)
       (and (identifier? #'ell)
            (every identifier? #'(lit ... k ...)))
       #'(lambda (x)
           (syntax-case (custom-ellipsis ell) x (lit ...)
             ((_ . p) (syntax (custom-ellipsis ell) t)) ...)))
      ((_ (lit ...) ((k . p) t) ...)
       (every identifier? #'(lit ... k ...))
       #'(lambda (x)
           (syntax-case x (lit ...)
             ((_ . p) #'t) ...))))))

Note: The syntax-rules of this report is a compatible extension to that of the small language, and the (scheme base) library must export the same binding [Editorial note: as whichever large language library this ends up in ]. Compared to the small language version, the ability to match macro uses of the form (keyword datum ... . datum), to use multiple ellipses after a subtemplate, and to use ellipsized subtemplates which include pattern variables with mismatching levels of ellipsis nesting in the pattern have been added.

Compared to the R6RS version of syntax-rules, the ability to rename the ellipsis and to use the ellipsis and underscore as literals have been added.

Compared to the R5RS version of syntax-rules, the pattern language has been extended to allow further patterns after an ellipsis, and the special identifier _ to match any input form without creating a pattern variable and the ability to rename the ellipsis have been added. As in the small language version, the R5RS version also did not allow macro uses of the form (keyword datum ... . datum), nor multiple ellipses after a subtemplate, nor ellipsized subtemplates with pattern variables at different levels of nesting in the pattern.

(identifier-syntax template)
syntax
(identifier-syntax (identifier1 template1) ((set! identifier2 pattern) template2))
syntax
set!
auxiliary syntax

Identifier-syntax is a purely template-based form for creating transformers for identifier macros, in the same way that syntax-rules is a purely template-based form for creating transformers for macros that are not identifier macros.

Syntax: The pattern must be as for syntax-case, and the templates must be as for syntax and syntax-rules.

Note: The set! referred to here as auxiliary syntax has the same binding as the set! keyword used as non-auxiliary syntax.

Semantics: Identifier-syntax evaluates to a macro transformer.

In the first form of identifier-syntax, every instance of the syntax keyword bound to the returned transformer is replaced by the template within the scope of the keyword. It is a syntax violation to use set! on syntax keywords associated with transformers created with this form of identifier-syntax.

The second, more general, form of identifier-syntax permits the transformer to determine what happens when set! is used. In this case, uses of the syntax keyword itself are replaced by template1, and uses of set! with the syntax keyword are replaced by template2.

Pattern variables within the templates are substituted as in syntax-rules; for this purpose, the identifiers and pattern are treated as patterns matched against the relevant parts of the macro uses.

Note: Use of patterns more complex than a single pattern variable in the set! clause of the second form of identifier-syntax is inadvisable because the resulting macros will likely surprise users when they cannot be used to set! the identifier to the value of another, existing variable.

Todo: Should the pattern be restricted to one identifier? [Editorial note: Issue 142. ]

Todo: Should identifier-syntax support custom-ellipsis or otherwise ellipsis renaming?

Example: Identifier-syntax could be used to define constants which cannot be mutated anywhere.

(define-syntax define-constant
  (syntax-rules ()
    ((_ name value)
     (begin
       (define constant-value value)
       (define-syntax name (identifier-syntax constant-value))))))

Using define-constant in the manner of define creates an identifier binding which cannot be set! in any context.

(define-constant π 3.1415927)
(set! π #e3.2)
syntax violation

Conversely, another possible use of identifier-syntax is to create the illusion that a library exports a variable which can be mutated.

(library (magic-library)
  (export magic-variable)
  (import #;(todo))

  (define magic-variable-contents (cons 'initial-value '()))
  (define-syntax magic-variable
    (identifier-syntax
      (_ (car magic-variable-contents))
      ((set! _ val) (set-car! magic-variable-contents val)))))

Programs and libraries which import (magic-library) can seemingly see and set the value of an identifier called magic-variable. Its value is always the same in all contexts in which it is imported. In reality, the car of a pair is holding the value, rather than any binding directly.

Implementation:

(define-syntax identifier-syntax
  (lambda (x)
    (syntax-case x (set!)
      ((_ e)
       #'(lambda (x)
           (syntax-case x ()
             (id (identifier? #'id) #'e)
             ((_ x (... ...)) #'(e x (... ...))))))
      ((_ (id exp1) ((set! var val) exp2))
       (and (identifier? #'id) (identifier? #'var))
       #'(make-variable-transformer
          (lambda (x)
            (syntax-case x (set!)
              ((set! var val) #'exp2]
              ((id x (... ...)) #'(exp1 x (... ...)))
              (id (identifier? #'id) #'exp1))))))))

Indicating erroneous macro uses

(syntax-error message irritant ...)
syntax

Syntax: Message must be a string literal. The irritants may be any Scheme datums.

Semantics: Any attempt to expand a syntax-error form results in a syntax violation being signalled. The condition value raised is a compound condition with at least three components: a &syntax-violation condition whose form and subform fields are unspecified, but which should both be set to #f if no useful value can be provided; a &message condition whose field is set to the given message; and an &irritants condition whose field is set to a list of the irritants as syntax objects. An implementation may also include additional components within the compound condition.

This can be used as a syntax-rules template for a rule pattern that is an invalid use of the macro, which can provide more descriptive error messages. An implementation of syntax-rules can recognize when an instance of syntax-error is the only content of a template and use this to provide more helpful information for the fields of the &syntax-violation condition component.

Example:

(define-syntax simple-let
  (syntax-rules ()
    ((_ ((x . y) val) body1 body2 ...)
     (syntax-error "expected an identifier" (x . y)))
    ((_ (name val) body1 body2 ...)
     ((lambda (name) body1 body2 ...) val))))

Implementation:

(define-syntax syntax-error
  (lambda (stx)
    (syntax-case stx ()
      ((_ message irritant ...)
       (string? (syntax->datum #'message))
       (raise
        (condition
         (make-syntax-violation #f #f)
         (make-message-condition (syntax->datum #'message))
         (make-irritants-condition #'(irritant ...))))))))
(erroneous-syntax)
syntax
(erroneous-syntax message)
syntax

Syntax: Message, if given, must be a string literal.

Semantics: An instance of erroneous-syntax evaluates to a macro transformer which always signals a syntax violation when invoked. If a message is provided, it will become the value of the message field of the syntax violation; if no message is provided, a default message (which may vary depending on the exact form of the macro use) should indicate that the keyword cannot be used in this context.

This can be used to define auxiliary syntax keywords which can never be correct macro uses on their own, as well as syntax parameters with no meaningful default transformers, and keys for identifier properties.

Examples: An implementation of Scheme might contain this somewhere for use by the cond and case forms:

(define-syntax => (erroneous-syntax))
(define-syntax else (erroneous-syntax))

A keyword used only as a key for identifier properties might be defined thus:

(define-syntax documentation
  (erroneous-syntax "The documentation keyword is used only as an \
identifier property key"))