From 6a93d0c531c75aba685ad31644317e4f8d819cfd Mon Sep 17 00:00:00 2001 From: Matthew Flatt Date: Thu, 22 May 2008 16:12:51 +0000 Subject: [PATCH] macros chapter in guide svn: r9927 --- collects/scribble/eval.ss | 1 + collects/scribble/manual.ss | 2 +- collects/scribble/scheme.ss | 8 +- collects/scribblings/guide/certificates.scrbl | 4 +- collects/scribblings/guide/macros.scrbl | 16 +- .../scribblings/guide/pattern-macros.scrbl | 481 ++++++++++++++++++ collects/scribblings/guide/proc-macros.scrbl | 462 +++++++++++++++++ 7 files changed, 968 insertions(+), 6 deletions(-) create mode 100644 collects/scribblings/guide/pattern-macros.scrbl create mode 100644 collects/scribblings/guide/proc-macros.scrbl diff --git a/collects/scribble/eval.ss b/collects/scribble/eval.ss index dbde375ce8..765793bb08 100644 --- a/collects/scribble/eval.ss +++ b/collects/scribble/eval.ss @@ -173,6 +173,7 @@ (cond [(and v (hash-ref ht v #f)) => (lambda (v) v)] + [(syntax? v) (make-literal-syntax v)] [(string? v) (install ht v (string-copy v))] [(bytes? v) (install ht v (bytes-copy v))] [(pair? v) diff --git a/collects/scribble/manual.ss b/collects/scribble/manual.ss index 4c00bc3f1e..4a1d7e2386 100644 --- a/collects/scribble/manual.ss +++ b/collects/scribble/manual.ss @@ -66,7 +66,7 @@ (list (datum->syntax #'filename - `(code:comment (unsyntax (t "In \"" ,#'filename "\":"))) + `(code:comment (unsyntax (t "In \"" ,(syntax-e #'filename) "\":"))) #'filename)) null)]) (syntax/loc stx (schemeblock file ... modtag rest ...)))] diff --git a/collects/scribble/scheme.ss b/collects/scribble/scheme.ss index df493dbc0d..4adf2cf551 100644 --- a/collects/scribble/scheme.ss +++ b/collects/scribble/scheme.ss @@ -21,7 +21,8 @@ current-meta-list (struct-out shaped-parens) - (struct-out just-context)) + (struct-out just-context) + (struct-out literal-syntax)) (define no-color "schemeplain") (define reader-color "schemereader") @@ -121,7 +122,9 @@ (memq (syntax-e c) (current-variable-list)))] [(s it? sub?) (let ([sc (syntax-e c)]) - (let ([s (format "~s" sc)]) + (let ([s (format "~s" (if (literal-syntax? sc) + (literal-syntax-stx sc) + sc))]) (if (and (symbol? sc) ((string-length s) . > . 1) (char=? (string-ref s 0) #\_) @@ -654,6 +657,7 @@ (define-struct shaped-parens (val shape)) (define-struct just-context (val ctx)) + (define-struct literal-syntax (stx)) (define-struct graph-reference (bx)) (define-struct graph-defn (r bx)) diff --git a/collects/scribblings/guide/certificates.scrbl b/collects/scribblings/guide/certificates.scrbl index 3428bfd031..763ce46e43 100644 --- a/collects/scribblings/guide/certificates.scrbl +++ b/collects/scribblings/guide/certificates.scrbl @@ -3,7 +3,7 @@ scribble/eval "guide-utils.ss") -@title[#:style 'quiet]{Syntax Certificates} +@title[#:tag "stx-certs" #:style 'quiet]{Syntax Certificates} A use of a macro can expand into a use of an identifier that is not exported from the module that binds the macro. In general, such an @@ -18,7 +18,7 @@ expands to a use of @scheme[unchecked-go]: (module m mzscheme (provide go) (define (unchecked-go n x) - ;; to avoid disaster, @scheme[n] must be a number + (code:comment #, @t{to avoid disaster, @scheme[n] must be a number}) (+ n 17)) (define-syntax (go stx) (syntax-case stx () diff --git a/collects/scribblings/guide/macros.scrbl b/collects/scribblings/guide/macros.scrbl index d900fd0a2d..44e7d6029d 100644 --- a/collects/scribblings/guide/macros.scrbl +++ b/collects/scribblings/guide/macros.scrbl @@ -5,7 +5,21 @@ @title[#:tag "macros" #:style 'toc]{Macros} +A @deftech{macro} is a syntactic form with an associated +@deftech{transformer} that @deftech{expands} the original form +into existing forms. To put it another way, a macro is an +extension to the Scheme compiler. Most of the syntactic forms of +@schememodname[scheme/base] and @schememodname[scheme] are +actually macros that expand into a small set of core constructs. + +Like many languages, Scheme provides pattern-based macros that +make simple transformations easy to implement and reliable to +use. Scheme also supports arbitrary macro transformers that are +implemented in Scheme---or in a macro-extended variant of Scheme. + @local-table-of-contents[] @;------------------------------------------------------------------------ -@include-section["certificates.scrbl"] +@include-section["pattern-macros.scrbl"] +@include-section["proc-macros.scrbl"] + diff --git a/collects/scribblings/guide/pattern-macros.scrbl b/collects/scribblings/guide/pattern-macros.scrbl new file mode 100644 index 0000000000..e20d775421 --- /dev/null +++ b/collects/scribblings/guide/pattern-macros.scrbl @@ -0,0 +1,481 @@ +#lang scribble/doc +@(require scribble/manual + scribble/eval + "guide-utils.ss") + +@title[#:tag "pattern-macros"]{Pattern-Based Macros} + +A @deftech{pattern-based macro} replaces any code that matches a +pattern to an expansion that uses parts of the original syntax that +match parts of the pattern. + +@; ---------------------------------------- + +@section{@scheme[define-syntax-rule]} + +The simplest way to create a macro is to use +@scheme[define-syntax-rule]: + +@specform[(define-syntax-rule pattern template)] + +As a running example, consider the @scheme[swap] macro, which swaps +the values stored in two variables. It can be implemented using +@scheme[define-syntax-rule] as follows: + +@margin-note{The macro is ``un-Schemely'' in the sense that it +involves side effects on variables---but the point of macros is to let +you add syntactic forms that some other language designer might not +approve.} + +@schemeblock[ +(define-syntax-rule (swap x y) + (let ([tmp x]) + (set! x y) + (set! y tmp))) +] + +The @scheme[define-syntax-rule] form binds a macro that matches a +single pattern. The pattern must always start with an open parenthesis +followed by an identifier, which is @scheme[swap] in this case. After +the initial identifier, other identifiers are @deftech{macro pattern +variables} that can match anything in a use of the macro. Thus, this +macro matches the for @scheme[(swap _form_1 _form_2)] for any +@scheme[_form_1] and @scheme[_form_2]. + +@margin-note{Macro pattern variables similar to pattern variables for + @scheme[match]. See @secref["match"].} + +After the pattern in @scheme[define-syntax-rule] is the +@deftech{template}. The template is used in place of a form that +matches the pattern, except that each instance of a pattern variable +in the template is replaced with the part of the macro use the pattern +variable matched. For example, in + +@schemeblock[(swap first last)] + +the pattern variable @scheme[x] matches @scheme[first] and @scheme[y] +matches @scheme[last], so that the expansion is + +@schemeblock[ + (let ([tmp first]) + (set! first last) + (set! last tmp)) +] + +@; ---------------------------------------- + +@section{Lexical Scope} + +Suppose that we use the @scheme[swap] macro to swap variables named +@scheme[tmp] and @scheme[other]: + +@schemeblock[ +(let ([tmp 5] + [other 6]) + (swap tmp other) + (list tmp other)) +] + +The result of the above expression should be @schemeresult[(6 5)]. The +naive expansion of this use of @scheme[swap], however, is + +@schemeblock[ +(let ([tmp 5] + [other 6]) + (let ([tmp tmp]) + (set! tmp other) + (set! other tmp)) + (list tmp other)) +] + +whose result is @schemeresult[(5 6)]. The problem is that the naive +expansion confuses the @scheme[tmp] in the context where @scheme[swap] +is used with the @scheme[tmp] that is in the macro template. + +Scheme doesn't produce the naive expansion for the above use of +@scheme[swap]. Instead, it produces + +@schemeblock[ +(let ([tmp 5] + [other 6]) + (let ([tmp_1 tmp]) + (set! tmp other) + (set! other tmp_1)) + (list tmp other)) +] + +with the correct result in @schemeresult[(6 5)]. Similarly, in the +example + +@schemeblock[ +(let ([set! 5] + [other 6]) + (swap set! other) + (list set! other)) +] + +the expansion is + +@schemeblock[ +(let ([set!_1 5] + [other 6]) + (let ([tmp_1 tmp]) + (set! set!_1 other) + (set! other tmp_1)) + (list set!_1 other)) +] + +so that the local @scheme[set!] binding doesn't interfere with the +assignments introduced by the macro template. + +In other words, Scheme's pattern-based macros automatically maintain +lexical scope, so macro implementors can reason about variable +reference in macros and macro uses in the same way as for functions +and function calls. + +@; ---------------------------------------- + +@section{@scheme[define-syntax] and @scheme[syntax-rules]} + +The @scheme[define-syntax-rule] form binds a macro that matches a +single pattern, but Scheme's macro system supports transformers that +match multiple patterns starting with the same identifier. To write +such macros, the programmer much use the more general +@scheme[define-syntax] form along with the @scheme[syntax-rules] +transformer form: + +@specform[(define-syntax id + (syntax-rules (literal-id ...) + [pattern template] + ...))] + +@margin-note{The @scheme[define-syntax-rule] form is itself a macro + that expands into @scheme[define-syntax] with a @scheme[syntax-rules] + form that contains only one pattern and template.} + +For example, suppose we would like a @scheme[rotate] macro that +generalizes @scheme[swap] to work on either two or three identifiers, +so that + +@schemeblock[ +(let ([red 1] [green 2] [blue 3]) + (rotate red green) (code:comment #, @t{swaps}) + (rotate red green blue) (code:comment #, @t{rotates left}) + (list red green blue)) +] + +produces @schemeresult[(1 3 2)]. We can implement @scheme[rotate] +using @scheme[syntax-rules]: + +@schemeblock[ +(define-syntax rotate + (syntax-rules () + [(rotate a b) (swap a b)] + [(rotate a b c) (begin + (swap a b) + (swap b c))])) +] + +The expression @scheme[(rotate red green)] matches the first pattern +in the @scheme[syntax-rules] form, so it expands to @scheme[(swap red +green)]. The expression @scheme[(rotate a b c)] matches the second +pattern, so it expands to @scheme[(begin (swap red green) (swap green +blue))]. + +@; ---------------------------------------- + +@section{Matching Sequences} + +A better @scheme[rotate] macro would allow any number of identifiers, +instead of just two or three. To match a use of @scheme[rotate] with +any number of identifiers, we need a pattern form that has something +like a Kleene star. In a Scheme macro pattern, a star is written as +@scheme[...]. + +To implement @scheme[rotate] with @scheme[...], we need a base case to +handle a single identifier, and an inductive case to handle more than +one identifier: + +@schemeblock[ +(define-syntax rotate + (syntax-rules () + [(rotate a) (void)] + [(rotate a b c ...) (begin + (swap a b) + (rotate b c ...))])) +] + +When a pattern variable like @scheme[c] is followed by @scheme[...] in +a pattern, then it must be followed by @scheme[...] in a template, +too. The pattern variable effectively matches a sequence of zero or +more forms, and it is replaced in the template by the same sequence. + +Both versions of @scheme[rotate] so far are a bit inefficient, since +pairwise swapping keeps moving the value from the first variable into +every variable in the sequence until it arrives at the last one. A +more efficient @scheme[rotate] would move the first value directly to +the last variable. We can use @scheme[...] patterns to implement the +more efficient variant using a helper macro: + +@schemeblock[ +(define-syntax rotate + (syntax-rules () + [(rotate a c ...) + (shift-to (c ... a) (a c ...))])) + +(define-syntax shift-to + (syntax-rules () + [(shift-to (from0 from ...) (to0 to ...)) + (let ([tmp from0]) + (set! to from) ... + (set! to0 tmp))])) +] + +In the @scheme[shift-to] macro, @scheme[...] in the template follows +@scheme[(set! to from)], which causes the @scheme[(set! to from)] +expression to be duplicated as many times as necessary to use each +identifier matched in the @scheme[to] and @scheme[from] +sequences. (The number of @scheme[to] and @scheme[from] matches must +be the same, otherwise the macro expansion fails with an error.) + +@; ---------------------------------------- + +@section{Identifier Macros} + +Given our macro definitions, the @scheme[swap] or @scheme[rotate] +identifiers must be used after an open parenthesis, otherwise a syntax +error is reported: + +@interaction-eval[(define-syntax swap (syntax-rules ()))] + +@interaction[(+ swap 3)] + +An @deftech{identifier macro} works in any expression. For example, we +can define @scheme[clock] as an identifier macro that expands to +@scheme[(get-clock)], so @scheme[(+ clock 3)] would expand to +@scheme[(+ (get-clock) 3)]. An identifier macro also cooperates with +@scheme[set!], and we can define @scheme[clock] so that @scheme[(set! +clock 3)] expands to @scheme[(put-clock! 3)]. + +The @scheme[syntax-id-rules] form is like @scheme[syntax-rules], but +it creates a transformer that acts as an identifier macro: + +@specform[(define-syntax id + (syntax-id-rules (literal-id ...) + [pattern template] + ...))] + +Unlike a @scheme[syntax-rules] form, the @scheme[_pattern]s are not +required to start with an open parenthesis. Also, @scheme[set!] is +typically used as a literal to match a use of @scheme[set!] in the +pattern (as opposed to being a pattern variable. + +@schemeblock[ +(define-syntax clock + (syntax-id-rules (set!) + [(set! cock e) (put-clock! e)] + [(clock a ...) ((get-clock) a ...)] + [clock (get-clock)])) +] + +The @scheme[(clock a ...)] pattern is needed because, when an +identifier macro is used after an open parenthesis, the macro +transformer is given the whole form, like with a non-identifier macro. +Put another way, the @scheme[syntax-rules] form is essentially a +special case of the @scheme[syntax-id-rules] form with errors in the +@scheme[set!] and lone-identifier cases. + +@; ---------------------------------------- + +@section{Macro-Generating Macros} + +Suppose that we have many identifier like @scheme[clock] that we'd +like to redirect to accessor and mutator functions like +@scheme[get-clock] and @scheme[put-clock!]. We'd like to be able to +just write + +@schemeblock[ +(define-get/put-id clock get-clock put-clock!) +] + +Naturally, we can implement @scheme[define-get/put-id] as a macro: + +@schemeblock[ +(define-syntax-rule (define-get/put-id id get put!) + (define-syntax clock + (syntax-id-rules (set!) + [(set! cock e) (put-clock! e)] + [(clock a (... ...)) ((get-clock) a (... ...))] + [clock (get-clock)]))) +] + +The @scheme[define-get/put-id] macro is a @deftech{macro-generating +macro}. The only non-obvious part of its definition is the +@scheme[(... ...)], which ``quotes'' @scheme[...] so that it takes its +usual role in the generated macro, instead of the generating macro. + +@; ---------------------------------------- + +@section[#:tag "pattern-macro-example"]{Extended Example: Call-by-Reference Functions} + +We can use pattern-matching macros to implement add a form to Scheme +for defining first-order @deftech{call-by-reference} functions. When a +call-by-reference function body mutates its formal argument, the +mutation applies to variables that are supplied as actual arguments in +a call to the function. + +For example, if @scheme[define-cbr] is like @scheme[define] except +that it defines a call-by-reference function, then + +@schemeblock[ +(define-cbr (f a b) + (swap a b)) + +(let ([x 1] [y 2]) + (f x y) + (list x y)) +] + +produces @schemeresult[(2 1)]. + +We will implement call-by-reference functions by having function calls +supply accessor and mutators for the arguments, instead of supplying +argument values directly. In particular, for the function @scheme[f] +above, we'll generate + +@schemeblock[ +(define (do-f get-a get-b put-a! put-b!) + (define-get/put-id a get-a put-a!) + (define-get/put-id b get-b put-b!) + (swap a b)) +] + +and redirect a function call @scheme[(f x y)] to + +@schemeblock[ +(do-f (lambda () x) + (lambda () y) + (lambda (v) (set! x v)) + (lambda (v) (set! y v))) +] + +Clearly, then @scheme[define-cbr] is a macro-generating macro, which +binds @scheme[f] to a macro that expands to a call of @scheme[do-f]. +That is, @scheme[(define-cbr (f a b) (swap ab))] needs to generate the +definition + +@schemeblock[ +(define-syntax f + (syntax-rules () + [(id actual ...) + (do-f (lambda () actual) + ... + (lambda (v) + (set! actual v)) + ...)])) +] + +At the same time, @scheme[define-cbr] needs to define @scheme[do-f] +using the body of @scheme[f], this second part is slightly more +complex, so we defer most it to a @scheme[define-for-cbr] helper +module, which lets us write @scheme[define-cbr] easily enough: + + +@schemeblock[ +(define-syntax-rule (define-cbr (id arg ...) body) + (begin + (define-syntax id + (syntax-rules () + [(id actual (... ...)) + (do-f (lambda () actual) + (... ...) + (lambda (v) + (set! actual v)) + (... ...))])) + (define-for-cbr do-f (arg ...) + () (code:comment #, @t{explained below...}) + body))) +] + +Our remaining task is to define @scheme[define-for-cbr] so that it +converts + +@schemeblock[ +(define-for-cbr do-f (a b) () (swap a b)) +] + +to the function definition @scheme[do-f] above. Most of the work is +generating a @scheme[define-get/put-id] declaration for each argument, +@scheme[a] ad @scheme[b], and putting them before the body. Normally, +that's an easy task for @scheme[...] in a pattern and template, but +this time there's a catch: we need to generate the names +@scheme[get-a] and @scheme[put-a!] as well as @scheme[get-b] and +@scheme[put-b!], and the pattern language provides no way to +synthesize identifiers based on existing identifiers. + +As it turns out, lexical scope gives us a way around this problem. The +trick is to iterate expansions of @scheme[define-for-cbr] once for +each argument in the function, and that's why @scheme[define-cbr] +starts with an apparently useless @scheme[()] after the argument +list. We need to keep track of all the arguments seen so far and the +@scheme[get] and @scheme[put] names generated for each, in addition to +the arguments left to process. After we've processed all the +identifiers, then we have all the names we need. + +Here is the definition of @scheme[define-for-cbr]: + +@schemeblock[ +(define-syntax define-for-cbr + (syntax-rules () + [(define-for-cbr do-f (id0 id ...) + (gens ...) body) + (define-for-cbr do-f (id ...) + (gens ... (id0 get put)) body)] + [(define-for-cbr do-f () + ((id get put) ...) body) + (define (do-f get ... put ...) + (define-get/put-id id get put) ... + body)])) +] + +Step-by-step, expansion proceeds as follows: + +@schemeblock[ +(define-for-cbr do-f (a b) + () (swap a b)) +=> (define-for-cbr do-f (b) + ([a get_1 put_1]) (swap a b)) +=> (define-for-cbr do-f () + ([a get_1 put_1] [b get_2 put_2]) (swap a b)) +=> (define (do-f get_1 get_2 put_1 put_2) + (define-get/put-id a get_1 put_1) + (define-get/put-id b get_2 put_2) + (swap a b)) +] + +The ``subscripts'' on @scheme[get_1], @scheme[get_2], +@scheme[put_1], and @scheme[put_2] are inserted by the macro +expander to preserve lexical scope, since the @scheme[get] +generated by each iteration of @scheme[define-for-cbr] should not +bind the @scheme[get] generated by a different iteration. In +other words, we are essentially tricking the macro expander into +generating fresh names for us, but the technique illustrates some +of the surprising power of pattern-based macros with automatic +lexical scope. + +The last expression eventually expands to just + +@schemeblock[ +(define (do-f get_1 get_2 put_1 put_2) + (let ([tmp (get_1)]) + (put_1 (get_2)) + (put_2 tmp))) +] + +which implements the call-by-name function @scheme[f]. + +To summarize, then, we can add call-by-reference functions to +Scheme with just three small pattern-based macros: +@scheme[define-cbr], @scheme[define-for-cbr], and +@scheme[define-get/put-id]. + diff --git a/collects/scribblings/guide/proc-macros.scrbl b/collects/scribblings/guide/proc-macros.scrbl new file mode 100644 index 0000000000..8e47bf0ace --- /dev/null +++ b/collects/scribblings/guide/proc-macros.scrbl @@ -0,0 +1,462 @@ +#lang scribble/doc +@(require scribble/manual + scribble/eval + "guide-utils.ss") + +@(define check-eval (make-base-eval)) +@(interaction-eval #:eval check-eval (require (for-syntax scheme/base))) + +@(define-syntax-rule (schemeblock/eval #:eval e body ...) + (begin + (interaction-eval #:eval e body) ... + (schemeblock body ...))) + +@title[#:tag "proc-macros" #:style 'toc]{General Macro Transformers} + +The @scheme[define-syntax] form creates a @deftech{transformer +binding} for an identifier, which is a binding that can be used at +compile time while expanding expressions to be evaluated at run time. +The compile-time value associated with a transformer binding can be +anything; if it is a procedure of one argument, then the binding is +used as a macro, and the procedure is the @deftech{macro transformer}. + +The @scheme[syntax-rules] and @scheme[syntax-id-rules] forms are +macros that expand to procedure forms. For example, if you evaluate a +@scheme[syntax-rules] form directly (instead of placing on the +right-hand of a @scheme[define-syntax] form), the result is a +procedure: + +@interaction[ +(syntax-rules () [(nothing) something]) +] + +Instead of using @scheme[syntax-rules], you can write your own macro +transformer procedure directly using @scheme[lambda]. The argument to +the procedure is a values that represents the source form, and the +result of the procedure must be a value that represents the +replacement form. + +@local-table-of-contents[] + +@; ---------------------------------------- + +@section[#:tag "stx-obj"]{Syntax Objects} + +The input and output of a macro transformer (i.e., source and +replacement forms) are represented as @deftech{syntax objects}. A +syntax object contains symbols, lists, and constant values (such as +numbers) that essentially correspond to the @scheme[quote]d form of +the expression. For example, a representation of the expression +@scheme[(+ 1 2)] contains the symbol @scheme['+] and the numbers +@scheme[1] and @scheme[2], all in a list. In addition to this quoted +content, a syntax object associates source-location and +lexical-binding information with each part of the form. The +source-location information is used when reporting syntax errors (for +example), and the lexical-biding information allows the macro system +to maintain lexical scope. To accommodate this extra information, the +represention of the expression @scheme[(+ 1 2)] is not merely +@scheme['(+ 1 2)], but a packaging of @scheme['(+ 1 2)] into a syntax +object. + +To create a literal syntax object, use the @scheme[syntax] form: + +@interaction[ +(eval:alts (#,(scheme syntax) (+ 1 2)) (syntax (+ 1 2))) +] + +In the same way that @litchar{'} abbreviates @scheme[quote], +@litchar{#'} abbreviates @scheme[syntax]: + +@interaction[ +#'(+ 1 2) +] + +A syntax object that contains just a symbol is an @deftech{identifier +syntax object}. Scheme provides some additional operations specific to +identifier syntax objects, including the @scheme[identifier?] +operation to detect identifiers. Most notably, +@scheme[free-identifier=?] determines whether two identifiers refer +to the same binding: + +@interaction[ +(identifier? #'car) +(identifier? #'(+ 1 2)) +(free-identifier=? #'car #'cdr) +(free-identifier=? #'car #'car) +(require (only-in scheme/base [car also-car])) +(free-identifier=? #'car #'also-car) +(free-identifier=? #'car (let ([car 8]) + #'car)) +] + +The last example above, in particular, illustrates how syntax objects +preserve lexical-context information. + +To see the lists, symbols, numbers, @|etc| within a syntax object, use +@scheme[syntax->datum]: + +@interaction[ +(syntax->datum #'(+ 1 2)) +] + +The @scheme[syntax-e] function is similar to @scheme[syntax->datum], +but it unwraps a single layer of source-location and lexical-context +information, leaving sub-forms that have their own information wrapped +as syntax objects: + +@interaction[ +(syntax-e #'(+ 1 2)) +] + +The @scheme[syntax-e] function always leaves syntax-object wrappers +around sub-forms that are represented via symbols, numbers, and other +literal values. The only time it unwraps extra sub-forms is when +unwrapping a pair, in which case the @scheme[cdr] of the pair may be +recursively unwrapped, depending on how the syntax object was +constructed. + +The oppose of @scheme[syntax->datum] is, of course, +@scheme[datum->syntax]. In addition to a datum like @scheme['(+ 1 +2)], @scheme[datum->syntax] needs an existing syntax object to donate +its lexical context, and optionally another syntax object to donate +its source location: + +@interaction[ +(datum->syntax #'lex + '(+ 1 2) + #'srcloc) +] + +In the above example, the lexical context of @scheme[#'lex] is used +for the new syntax object, while the source location of +@scheme[#'srcloc] is used. + +When the second (i.e., the ``datum'') argument to +@scheme[datum->syntax] includes syntax objects, those syntax objects +are preserved intact in the result. That is, deconstructing the result +with @scheme[syntax-e] eventually produces the syntax objects that +were given to @scheme[datum->syntax]. + + +@; ---------------------------------------- + +@section[#:tag "syntax-case"]{Mixing Patterns and Expressions: @scheme[syntax-case]} + +The procedure generated by @scheme[syntax-rules] internally uses +@scheme[syntax-e] to deconstruct the given syntax object, and it uses +@scheme[datum->syntax] to construct the result. The +@scheme[syntax-rules] form doesn't provide a way to escape from +pattern-matching and template-construction mode into an arbitrary +Scheme expression. + +The @scheme[syntax-case] form lets you mix pattern matching, template +construction, and arbitrary expressions: + +@specform[(syntax-case stx-expr (literal-id ...) + [pattern expr] + ...)] + +Unlike @scheme[syntax-rules], the @scheme[syntax-case] form does not +produce a procedure. Instead, it starts with a @scheme[_stx-expr] +expression that determines the syntax object to match against the +@scheme[_pattern]s. Also, each @scheme[syntax-case] clause has a +@scheme[_pattern] and @scheme[_expr], instead of a @scheme[_pattern] +and @scheme[_template]. Within an @scheme[_expr], the @scheme[syntax] +form---usually abbreviated with @litchar{#'}---shifts into +template-construction mode; if the @scheme[_expr] of a clause starts +with @litchar{#'}, then we have something like a @scheme[syntax-rules] +form: + +@interaction[ +(syntax->datum + (syntax-case #'(+ 1 2) () + [(op n1 n2) #'(- n1 n2)])) +] + +We could write the @scheme[swap] macro using @scheme[syntax-case] +instead of @scheme[define-syntax-rule] or @scheme[syntax-rules]: + +@schemeblock[ +(define-syntax swap + (lambda (stx) + (syntax-case stx () + [(swap x y) #'(let ([tmp x]) + (set! x y) + (set! y tmp))]))) +] + +One advantage of using @scheme[syntax-case] is that we can provide +better error reporting for @scheme[swap]. For example, with the +@scheme[define-syntax-rule] definition of @scheme[swap], then +@scheme[(swap x 2)] produces a syntax error in terms of @scheme[set!], +because @scheme[2] is not an identifier. We can refine our +@scheme[syntax-case] implementation of @scheme[swap] to explicitly +check the sub-forms: + +@schemeblock[ +(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, @scheme[(swap x 2)] provides a syntax error +originating from @scheme[swap] instead of @scheme[set!]. + +In the above definition of @scheme[swap], @scheme[#'x] and +@scheme[#'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 +@scheme[#'x] or @scheme[#'y] is used in the call to +@scheme[raise-syntax-error], so that the syntax-error message can +point directly to the source location of the non-identifier. + +@; ---------------------------------------- + +@section[#:tag "with-syntax"]{@scheme[with-syntax] and @scheme[generate-temporaries]} + +Since @scheme[syntax-case] lets us compute with arbitrary Scheme +expression, we can more simply solve a problem that we had in +writing @scheme[define-for-cbr] (see +@secref["pattern-macro-example"]), where we needed to generate a +set of names based on a sequence @scheme[id ...]: + +@schemeblock[ +(define-syntax (define-for-cbr stx) + (syntax-case stx () + [(_ do-f (id ...) body) + .... + #'(define (do-f get ... put ...) + (define-get/put-id id get put) ... + body) ....])) +] + +@margin-note{This example uses @scheme[(define-syntax (_id _arg) _body ...+)], + which is equivalent to @scheme[(define-syntax _id (lambda (_arg) _body ...+))].} + +In place of the @scheme[....]s above, we need to bind @scheme[get +...] and @scheme[put ...] to lists of generated identifiers. We +cannot use @scheme[let] to bind @scheme[get] and @scheme[put], +because we need bindings that count as pattern variables, instead +of normal local variables. The @scheme[with-syntax] form lets us +bind pattern variables: + +@schemeblock[ +(define-syntax (define-for-cbr stx) + (syntax-case stx () + [(_ do-f (id ...) body) + (with-syntax ([(get ...) ....] + [(put ...) ....]) + #'(define (do-f get ... put ...) + (define-get/put-id id get put) ... + body))])) +] + +Now we need an expression in place of @scheme[....] that +generates as many identifiers as there are @scheme[id] matches in +the original pattern. Since this is a common task, Scheme +provides a helper function, @scheme[generate-temporaries], that +takes a sequece of identifiers and returns a sequence of +generated identifiers: + +@schemeblock[ +(define-syntax (define-for-cbr stx) + (syntax-case stx () + [(_ do-f (id ...) body) + (with-syntax ([(get ...) (generate-temporaries #'(id ...))] + [(put ...) (generate-temporaries #'(id ...))]) + #'(define (do-f get ... put ...) + (define-get/put-id id get put) ... + body))])) +] + +This way of generating identifiers is normally easier to think +about than tricking the macro expander into generating names with +purely pattern-based macros. + +In general, the right-hand side of a @scheme[with-handlers] +binding is a pattern, just like in @scheme[syntax-case]. In fact, +a @scheme[with-handlers] form is just a @scheme[syntax-case] form +turned partially inside-out. + +@; ---------------------------------------- + +@section[#:tag "stx-phases"]{Compile and Run-Time Phases} + +As sets of macros get more complicated, you might want to write +your own helper functions, like +@scheme[generate-temporaries]. For example, to provide good +syntax-error messsage, @scheme[swap], @scheme[rotate], and +@scheme[define-cbr] all should check that certain sub-forms in +the source form are identifiers. We could use a +@scheme[check-ids] to perform this checking everywhere: + +@schemeblock/eval[ +#:eval check-eval +(define-syntax (swap stx) + (syntax-case stx () + [(swap x y) (begin + (check-ids stx #'(x y)) + #'(let ([tmp x]) + (set! x y) + (set! y tmp)))])) + +(define-syntax (rotate stx) + (syntax-case stx () + [(rotate a c ...) + (begin + (check-ids stx #'(a c ...)) + #'(shift-to (c ... a) (a c ...)))])) +] + +The @scheme[check-ids] function can use the @scheme[syntax->list] +function to convert a synatx-object wrapping a list into a list +of syntax objects: + +@schemeblock[ +(define (check-ids stx forms) + (for-each + (lambda (form) + (unless (identifier? form) + (raise-syntax-error #f + "not an identifier" + stx + form))) + (syntax->list forms))) +] + +If you define @scheme[swap] and @scheme[check-ids] in this way, +however, it doesn't work: + +@interaction[ +#:eval check-eval +(let ([a 1] [b 2]) (swap a b)) +] + +The problem is that @scheme[check-ids] is defined as a run-time +expression, but @scheme[swap] is trying to use it at compile time. In +interactive mode, compile time and run time are interleaved, but they +are not interleaved within the body of a module, and they are not +interleaved or across modules that are compiled ahead-of-time. To help +make all of these modes treat code consistently, Scheme separates the +binding spaces for different phases. + +To define a @scheme[check-ids] function that can be referenced at +compile time, use @scheme[define-for-syntax]: + +@schemeblock/eval[ +#:eval check-eval +(define-for-syntax (check-ids stx forms) + (for-each + (lambda (form) + (unless (identifier? form) + (raise-syntax-error #f + "not an identifier" + stx + form))) + (syntax->list forms))) +] + +With this for-syntax definition, then @scheme[swap] works: + +@interaction[ +#:eval check-eval +(let ([a 1] [b 2]) (swap a b) (list a b)) +(swap a 1) +] + +When organizing a program into modules, you may want to put helper +functions in one module to be used by macros that reside on other +modules. In that case, you can write the helper function using +@scheme[define]: + +@schememod[#:file +"utils.ss" +scheme + +(provide check-ids) + +(define (check-ids stx forms) + (for-each + (lambda (form) + (unless (identifier? form) + (raise-syntax-error #f + "not an identifier" + stx + form))) + (syntax->list forms))) +] + +Then, in the module that implements macros, import the helper function +using @scheme[(require (for-syntax "utils.ss"))] instead of +@scheme[(require "utils.ss")]: + +@schememod[ +scheme + +(require (for-syntax "utils.ss")) + +(define-syntax (swap stx) + (syntax-case stx () + [(swap x y) (begin + (check-ids stx #'(x y)) + #'(let ([tmp x]) + (set! x y) + (set! y tmp)))])) +] + +Since modules are separately compiled and cannot have circular +dependencies, the @filepath["utils.ss"] module's run-time body can be +compiled before the compiling the module that implements +@scheme[swap]. Thus, the run-time definitions in +@filepath["utils.ss"] can be used to implement @scheme[swap], as long +as they are explicitly shifted into compile time by @scheme[(require +(for-syntax ....))]. + +The @schememodname[scheme] module provides @scheme[syntax-case], +@scheme[generate-temporaries], @scheme[lambda], @scheme[if], and more +for use in both the run-time and compile-time phases. That is why we +can use @scheme[syntax-case] in the @scheme[mzscheme] @tech{REPL} both +directly and in the right-hand side of a @scheme[define-syntax] +form. + +The @schememodname[scheme/base] module, in contrast, exports those +bindings only in the run-time phase. If you change the module above +that defines @scheme[swap] so that it uses the +@schememodname[scheme/base] language instead of +@schememodname[scheme], then it no longer works. Adding +@scheme[(require (for-syntax scheme/base))] imports +@scheme[syntax-case] and more into the compile-time phase, so that the +module works again. + +Suppose that @scheme[define-syntax] is used to define a local macro in +the right-hand side of a @scheme[define-syntax] form. In that case, +the right-hand side of the inner @scheme[define-syntax] is in the +@deftech{meta-compile phase level}, also known as @deftech{phase level +2}. To import @scheme[syntax-case] into that phase level, you would +have to use @scheme[(require (for-syntax (for-syntax scheme/base)))] +or, equivalently, @scheme[(require (for-meta 2 scheme/base))]. + +Negative phase levels also exist. If a macro uses a helper function +that is imported @scheme[for-syntax], and if the helper function +returns syntax-object constants generated by @scheme[syntax], then +identifiers in the syntax will need bindings at @deftech{phase level +-1}, also known as the @deftech{template phase level}, to have any +binding at the run-time phase level relative to the module that +defines the macro. + +@; ---------------------------------------- + +@include-section["certificates.scrbl"]