16.2.2 Mixing Patterns and Expressions: syntax-case

The procedure generated by syntax-rules internally uses syntax-e to deconstruct the given syntax object, and it uses datum->syntax to construct the result. The syntax-rules form doesn’t provide a way to escape from pattern-matching and template-construction mode into an arbitrary Racket expression.

The syntax-case form lets you mix pattern matching, template construction, and arbitrary expressions:

(syntax-case stx-expr (literal-id ...)
  [pattern expr]
  ...)

Unlike syntax-rules, the syntax-case form does not produce a procedure. Instead, it starts with a stx-expr expression that determines the syntax object to match against the patterns. Also, each syntax-case clause has a pattern and expr, instead of a pattern and template. Within an expr, the syntax form—usually abbreviated with #'shifts into template-construction mode; if the expr of a clause starts with #', then we have something like a syntax-rules form:

  > (syntax->datum
     (syntax-case #'(+ 1 2) ()
      [(op n1 n2) #'(- n1 n2)]))

  '(- 1 2)

We could write the swap macro using syntax-case instead of define-syntax-rule or syntax-rules:

  (define-syntax swap
    (lambda (stx)
      (syntax-case stx ()
        [(swap x y) #'(let ([tmp x])
                        (set! x y)
                        (set! y tmp))])))

One advantage of using syntax-case is that we can provide better error reporting for swap. For example, with the define-syntax-rule definition of swap, then (swap x 2) produces a syntax error in terms of set!, because 2 is not an identifier. We can refine our syntax-case implementation of swap to explicitly check the sub-forms:

  (define-syntax swap
    (lambda (stx)
      (syntax-case stx ()
        [(swap x y)
         (if (and (identifier? #'x)
                  (identifier? #'y))
             #'(let ([tmp x])
                 (set! x y)
                 (set! y tmp))
             (raise-syntax-error #f
                                 "not an identifier"
                                 stx
                                 (if (identifier? #'x)
                                     #'y
                                     #'x)))])))

With this definition, (swap x 2) provides a syntax error originating from swap instead of set!.

In the above definition of swap, #'x and #'y are templates, even though they are not used as the result of the macro transformer. This example illustrates how templates can be used to access pieces of the input syntax, in this case for checking the form of the pieces. Also, the match for #'x or #'y is used in the call to raise-syntax-error, so that the syntax-error message can point directly to the source location of the non-identifier.