Next: , Previous: , Up: baselib   [Index]


4.18 Macro transformers

Syntax (for ‘expand’): syntax-rules (?literal …) ?syntax-rule
Auxiliary Syntax (for ‘expand’): _
Auxiliary Syntax (for ‘expand’): ...

Each ?literal must be an identifier. Each ?syntax-rule must have the following form:

(?srpattern ?template)

An ?srpattern is a restricted form of ?pattern, namely, a nonempty ?pattern in one of four parenthesized forms below whose first subform is an identifier or an underscore ‘_’. A ?pattern is an identifier, constant, or one of the following.

(?pattern …)
(?pattern ?pattern … . ?pattern)
(?pattern?pattern ?ellipsis ?pattern …)
(?pattern?pattern ?ellipsis ?pattern … . ?pattern)
#(?pattern …)
#(?pattern?pattern ?ellipsis ?pattern …)

An ?ellipsis is the identifier ‘...’ (three periods).

A ?template is a pattern variable, an identifier that is not a pattern variable, a pattern datum, or one of the following.

(?subtemplate …)
(?subtemplate … . ?template)
#(?subtemplate …)

A ?subtemplate is a ?template followed by zero or more ellipses.

An instance of syntax-rules evaluates, at macro–expansion time, to a new macro transformer by specifying 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.

An identifier appearing within a ?pattern may be an underscore ‘_’, an ellipsis ‘...’ or a literal identifier listed in the list of literals (?literal …). All other identifiers appearing within a ?pattern are pattern variables. It is a syntax violation if an ellipsis or underscore appears in (?literal …).

While the first subform of ?srpattern may be an identifier, the identifier is not involved in the matching and is not considered a pattern variable or literal identifier.

Pattern variables match arbitrary input subforms and are used to refer to elements of the input. It is a syntax violation if the same pattern variable appears more than once in a ?pattern.

Underscores also match arbitrary input subforms but are not pattern variables and so cannot be used to refer to those elements. Multiple underscores may appear in a ?pattern.

A ?literal identifier matches an input subform if and only if the input subform is an identifier and either both its occurrence in the input expression and its occurrence in the list of literals have the same lexical binding, or the two identifiers have the same name and both have no lexical binding.

A subpattern followed by an ellipsis can match zero or more elements of the input.

More formally, an input form F matches a pattern P if and only if one of the following holds:

When a macro use is transcribed according to the template of the matching ?syntax-rule, pattern variables that occur in the template are replaced by the subforms they match in the input.

Pattern data and identifiers that are not pattern variables or ellipses are copied into the output. A subtemplate followed by an ellipsis expands into zero or more occurrences of the subtemplate. Pattern variables that occur in subpatterns followed by one or more ellipses may occur only in subtemplates that are followed by (at least) as many ellipses. These pattern variables are replaced in the output by the input subforms to which they are bound, distributed as specified. If a pattern variable is followed by more ellipses in the subtemplate than in the associated subpattern, the input form is replicated as necessary. The subtemplate must contain at least one pattern variable from a subpattern followed by an ellipsis, and for at least one such pattern variable, the subtemplate must be followed by exactly as many ellipses as the subpattern in which the pattern variable appears. (Otherwise, the expander would not be able to determine how many times the subform should be repeated in the output.) It is a syntax violation if the constraints of this paragraph are not met.

A template of the form (?ellipsis ?template) is identical to ?template, except that ellipses within the template have no special meaning. That is, any ellipses contained within ?template are treated as ordinary identifiers. In particular, the template (... ...) produces a single ellipsis, ‘...’. This allows syntactic abstractions to expand into forms containing ellipses.

(define-syntax be-like-begin
  (syntax-rules ()
    ((be-like-begin name)
     (define-syntax name
       (syntax-rules ()
         ((name expr (... ...))
          (begin expr (... ...))))))))

(be-like-begin sequence)
(sequence 1 2 3 4)
⇒ 4

As an example for hygienic use of auxiliary identifier, if let and cond are defined as in let and cond then they are hygienic (as required) and the following is not an error.

(let ((=> #f))
  (cond (#t => 'ok)))
⇒ ok

The macro transformer for cond recognizes ‘=>’ as a local variable, and hence an expression, and not as the identifier ‘=>’, which the macro transformer treats as a syntactic keyword. Thus the example expands into:

(let ((=> #f))
  (if #t (begin => 'ok)))

instead of:

(let ((=> #f))
  (let ((temp #t))
    (if temp ('ok temp))))

which would result in an assertion violation.

Syntax (for ‘expand’): identifier-syntax ?template
Syntax (for ‘expand’): identifier-syntax (?id1 ?template1) ((set! ?id2 ?pattern) ?template2)
Auxiliary Syntax (for ‘expand’): set!

The ?ids must be identifiers. The ?templates must be as for syntax-rules.

When a keyword is bound to a transformer produced by the first form of identifier-syntax, references to the keyword within the scope of the binding are replaced by ?template.

(define p (cons 4 5))
(define-syntax p.car (identifier-syntax (car p)))
p.car           ⇒ 4
(set! p.car 15) error→ exception &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 identifier by itself are replaced by ?template1, and uses of ‘set!’ with the identifier are replaced by ?template2.

(define p (cons 4 5))

(define-syntax p.car
  (identifier-syntax
    (_
     (car p))
    ((set! _ e)
     (set-car! p e))))

(set! p.car 15)
p.car           ⇒ 15
p               ⇒ (15 . 5)

How to use literal arguments

Here we give a little explanation on the (?literal …) arguments of syntax-rules. Scheme defines the if syntax as:

(if ?test ?consequent ?alternate)

where the only “language keyword” is if itself. Many other languages use a version with then and else, which are also “reserved keywords” for those languages; Scheme has no reserved keywords.

A Scheme if with then and else, let’s call it if*, would look like:

(if* ?test (then ?consequent) (else ?alternate))

we can define it with the following simple syntax-rules:

#!r6rs
(import (rnrs))

(define-syntax if*
  (syntax-rules ()
    ((_ ?test (?then ?consequent) (?else ?alternate))
     (if ?test ?consequent ?alternate))))

(let ((a 2))
  (if* (< 1 a)
       (then (display "yeah"))
       (else (display "nay"))))
-| yeah

but notice that in this definition the pattern is:

(_ ?test (?then ?consequent) (?else ?alternate))

and the identifiers ?THEN and ?ELSE become pattern variables, with no validation of their value; so the following macro use also works with no error:

(let ((a 2))
  (if* (< 1 a)
       (123 (display "yeah"))
       (456 (display "nay"))))

that is in the template of the first arm of syntax-rules:

we can verify it with the following modified program:

#!r6rs
(import (rnrs))

(define-syntax if*
  (syntax-rules ()
    ((_ ?test (?then ?consequent) (?else ?alternate))
     (display ?then))))

(let ((a 2))
  (if* (< 1 a)
       (123 (display "yeah"))
       (456 (display "nay"))))
-| 123

This is not what we want: we would like the macro to verify that the components of the input form falling in the positions of ?then and ?else in the pattern, are identifiers having as names the symbol then and the symbol else.

Specifically we would like, in those positions, identifiers being free-identifier=? to “auxiliary keywords” then and else that we have defined. We could do such a test using syntax-case:

#!r6rs
(import (except (rnrs) else))

;;Define auxiliary keywords, they are just identifiers
;;bound to something.
(define-syntax then (syntax-rules ()))
(define-syntax else (syntax-rules ()))

(define-syntax if*
  (lambda (stx)
    (syntax-case stx ()
      ((_ ?test (?then ?consequent) (?else ?alternate))
       (begin
         (if (and (identifier? #'?then)
                  (free-identifier=? #'?then #'then))
             (display 'good)
           (display 'bad))
         #f)))))

(let ((a 2))
  (if* (< 1 a)
       (then (display "yeah"))
       (else (display "nay"))))
-| good

(let ((a 2))
  (if* (< 1 a)
       (123 (display "yeah"))
       (456 (display "nay"))))
-| bad

We do not need to use syntax-case for such a simple validation, because syntax-rules offers the ?literal arguments exactly for this purpose. So the full version of ‘IF... THEN... ELSE...’ is:

#!r6rs
(import (except (rnrs) else))

(define-syntax if*
  (syntax-rules (then else)
    ((_ ?test (then ?consequent) (else ?alternate))
     (if ?test ?consequent ?alternate))))

(define-syntax then (syntax-rules ()))
(define-syntax else (syntax-rules ()))

(let ((a 2))
  (if* (< 1 a)
       (then (display "yeah"))
       (else (display "nay"))))
-| yeah

(let ((a 2))
  (if* (< 1 a)
       (123 (display "yeah"))
       (456 (display "nay"))))
error→ syntax error

The language (rnrs) already defines an auxiliary syntax else and it is all right to use it for if* so we can just do:

#!r6rs
(import (rnrs))

(define-syntax if*
  (syntax-rules (then else)
    ((_ ?test (then ?consequent) (else ?alternate))
     (if ?test ?consequent ?alternate))))

(define-syntax then (syntax-rules ()))

Notice that with these definitions we have created actual bindings for then and else in the lexical context of the definition for if*, and if* will recognise only those. So when writing a library that exports if* we have to export then and else along with it:

#!r6rs
(library (if-star)
  (export if* then else)
  (import (rnrs))
  (define-syntax if*
    (syntax-rules (then else)
      ((_ ?test (then ?consequent) (else ?alternate))
       (if ?test ?consequent ?alternate))))
  (define-syntax then (syntax-rules ())))

and use it as:

#!r6rs
(import (rnrs)
  (if-star))

(let ((a 2))
  (if* (< 1 a)
       (then (display "yeah"))
       (else (display "nay"))))
-| yeah

(let ((a 2))
  (if* (< 1 a)
       (123 (display "yeah"))
       (456 (display "nay"))))
error→ syntax error

Next: , Previous: , Up: baselib   [Index]