[racket-dev] generalized `begin-for-syntax'

From: Matthew Flatt (mflatt at cs.utah.edu)
Date: Thu Sep 8 16:56:18 EDT 2011

The `begin-for-syntax' form in v5.1.3.7 supports variable and macro
definitions within a module for all phases N >= 0 (finally!).

As a simple example, you can use `struct' for-syntax without having to
add `#:omit-define-syntaxes':

 #lang racket

  (require racket/match)

  (struct posn (x y)) ; binds `posn' as syntax

  (match (posn 1 2)
    [(posn x y) ; uses `posn' as syntax
     (displayln (list x y))]))

You can also define macros at the top level of a module to
be used by macro implementations within the module:

 #lang racket

  ;; like `syntax-rules', but quasiquotes the template:
  (define-syntax-rule (syntax-rules~ () [pat tmpl] ...)
    (lambda (stx)
      (syntax-case stx ()
        [pat #`tmpl]
  ;; plain old compile-time helper:
  (define (check-id stx)
    (unless (identifier? stx)
      (raise-syntax-error #f "not an identifier" stx))

 (define-syntax define-five
   (syntax-rules~ ()
     [(_ id) (define #,(check-id #'id) 5)]
     [(_ id 5) (define-five id)]))

 (define-five V)
 (define-five wu 5)
 (+ V wu)

Perhaps most usefully, you can define a syntax class in the same module
as a macro that uses `syntax-parse':

 #lang racket
 (require (for-syntax syntax/parse))

  (define-syntax-class two
    (pattern [x y])))

 (define-syntax (ark stx)
   (syntax-parse stx
     [(_ a:two ...)
      #'(list '(a ...))]))

 (ark [snake snake] [tiger tiger] [ant ant])

A `begin-for-syntax' form can contain `begin-for-syntax' forms with
phase-2 definitions, and so on.


As part of this change, the grammar of fully expanded expressions has
changed. It no longer includes `define-values-for-syntax', but
`begin-with-syntax' is a core form, and `begin-with-syntax' forms can
be nested.

Code that uses `kernel-syntax-case' with a `define-values-for-syntax'
case needs to be changed to one with a `begin-with-syntax' case. (I've
changed the ones in the main code repo.) Fortunately, most code that
uses `kernel-syntax-case' only handles expressions, in which case there
is no `define-values-for-syntax' case.

The `syntax-local-module-defined-identifiers' function now returns a
hash table mapping phase levels to lists, instead of two lists.

Some structs from `compiler/zo-structs' have changed: `def-for-syntax'
is gone, `seq-for-syntax' is added, some `mod' fields have changed
(such as a `syntax-bodies' instead of `syntax-body'), and
`def-syntaxes' has a new field.

The order of expansion for a module body has changed slightly. A
`begin-for-syntax' form is expanded with the same two-pass algorithm as
a module body overall, which means that partial expansion is used to
expose definitions and meta-forms before expanding expressions. All
`provide' (or, more precisely, `#%provide') forms across all phases are
delayed until the module is otherwise expanded, and then the
`#%provide' forms are expanded in the order that they appear within the
module (independent of phase).

Posted on the dev mailing list.