Syntax Parse Examples
1 How to browse the examples
2 How to use the examples in another project
3 How to contribute a new example
4 A syntax-parse Crash Course
5 The Examples
5.1 first-class-or
first-class-or
5.2 Cross Macro Communication
define-for-macros
get-macroed
5.3 let-star
let-star
5.4 def
def
5.5 conditional-require
conditional-require
5.6 multi-check-true
multi-check-true
5.7 define-datum-literal-set
define-datum-literal-set
5.8 rec/  c
rec/  c
5.9 struct-list
struct-list
6 Example-Formatting Tools
tech/  guide
tech/  reference
racketfile
7.0

Syntax Parse Examples

Source code: https://github.com/bennn/syntax-parse-example

This package is a collection of useful and/or illustrative macros written using the syntax/parse library.
The Example-Formatting Tools section documents the syntax-parse-example language.

1 How to browse the examples

Two options:
  • Scroll through this document, read the macros’ source code and look at the example uses of each macro.

  • The source code for each macro is in a top-level folder at https://github.com/bennn/syntax-parse-example. For example, the source for a macro named big-mac would be in the folder https://github.com/bennn/syntax-parse-example/big-mac.

2 How to use the examples in another project

Three options, ordered from best to worst.
  • Copy/paste the example code into a new file in your project, require that new file normally.

  • Install the syntax-parse-example package, then require the macro’s defining module. For example, the defining module for the first-class-or macro is "syntax-parse-example/first-class-or/first-class-or".

  • Clone the source code, then require the module path of the file that defines the macro.

3 How to contribute a new example

To create an example named EUGENE:
  • Clone this repository (link).

  • Run raco syntax-parse-example --new EUGENE in the top-level folder of the cloned repository. This generates three new files:
    • EUGENE/EUGENE.rkt (source code)

    • EUGENE/EUGENE-test.rkt (tests)

    • EUGENE/EUGENE-doc.scrbl (Scribble documentation)

  • Fill the holes in the newly-generated files with an implementation, some unit tests, and documentation.

  • Run raco setup syntax-parse-example to generate the documentation.

4 A syntax-parse Crash Course

The syntax-parse form is a tool for un-packing data from a syntax object. It is similar to Racket’s match. Since the input to a macro is always a syntax object, syntax-parse is helpful for writing macros.

A syntax object is a Racket representation of source code. For example, #'(+ 1 2) is a syntax object that represents the sequence of characters (+ 1 2), along with the information that the + identifier is bound to a function in the racket/base library.

A macro is a compile-time function on syntax objects. In other words, a macro: (1) is a function, (2) expects a syntax object as input, (3) returns a new syntax object, and (4) runs at compile-time (see Expansion).

Here is a simple macro that expects two arguments and returns its first argument. Note that when the expander finds a macro application, it invokes the macro with a syntax object representing the whole application (see also: Macro Transformer Procedures).

The name K is historic (link) and pretentious, enjoy.

Examples:
> (require (for-syntax racket/base))
> (define-syntax (K args-stx)
    (define args (syntax-e args-stx))
    (if (= (length args) 3)
      (cadr args)
      (raise-argument-error
        'K
        "syntax object containing a list with 3 elements"
        args-stx)))
> (K 1 2)

1

> (K 1)

K: contract violation

  expected: syntax object containing a list with 3 elements

  given: #<syntax:eval:4:0 (K 1)>

> (K 1 2 3)

K: contract violation

  expected: syntax object containing a list with 3 elements

  given: #<syntax:eval:5:0 (K 1 2 3)>

Here is the same macro, defined using syntax-parse instead of the low-level syntax-e and cadr functions:

Examples:
> (require (for-syntax racket/base syntax/parse))
> (define-syntax (K args-stx)
    (syntax-parse args-stx
     [(_ ?arg0 ?arg1)
      #'?arg0]))
> (K 1 2)

1

> (K 1)

eval:4.0: K: expected more terms starting with any term

  at: ()

  within: (K 1)

  in: (K 1)

> (K 1 2 3)

eval:5.0: K: unexpected term

  at: 3

  in: (K 1 2 3)

I don’t expect that all this makes sense so far. Try running and modifying these examples. Try reading the documentation for define-syntax and syntax-e and syntax-parse and syntax (aka #').

But the last thing to point out is that (_ ?arg0 ?arg1) is a syntax pattern.
  • the parentheses say this pattern matches a (special kind of) list,

  • the underscore (_) means the first element of the list can be anything,

  • the name ?arg0 means the second element of the list can be anything and gets bound to the pattern variable ?arg0,

  • the name ?arg1 binds the third element to another pattern variable,

  • and if the list has more or fewer elements the pattern does not match.

A pattern variable is a special kind of variable; it can only be referenced inside a new syntax object. The name ?arg0 starts with a ? as a style choice — it helps me remember that it is the name of a pattern variable.

5 The Examples

5.1 first-class-or

 (require syntax-parse-example/first-class-or/first-class-or)
  package: syntax-parse-example

syntax

(first-class-or expr ...)

Racket’s or is a macro, not a function. It cannot be used like a normal value (i.e., evaluated as an identifier).

Examples:
> (or #false #true)

#t

> (apply or '(#false #true 0))

eval:2:0: or: bad syntax

  in: or

Identifier macros can be evaluated as identifiers.

So we can write a first-class-or macro to:
  • expand like Racket’s or when called like a function, and

  • expand to a function definition when used like an identifier.

In the latter case, the function that first-class-or evaluates to is similar to or, but evaluates all its arguments.

Examples:
> (first-class-or #false #true)

#t

> (apply first-class-or '(#false #true 0))

#t

> (first-class-or (+ 2 3) (let loop () (loop)))

5

> (map first-class-or '(9 #false 3) '(8 #false #false))

'(9 #f 3)

The macro:

  #lang racket/base
  (provide first-class-or)
  (require (for-syntax racket/base syntax/parse))
   
  (define-syntax (first-class-or stx)
    (syntax-parse stx
     [(_)
      #'#false]
     [(_ ?a . ?b)
      #'(let ([a-val ?a])
          (if a-val a-val (first-class-or . ?b)))]
     [_:id
      #'(lambda arg*
          (let loop ([arg* arg*])
            (cond
             [(null? arg*)
              #false]
             [(car arg*)
              (car arg*)]
             [else
              (loop (cdr arg*))])))]))
   

Some comments:
  • The first two syntax/parse clauses define what happens when or is called like a function.

  • The pattern _:id matches any identifier.

  • The dot notation in (_ ?a . ?b) could be (_ ?a ?b ...) instead. See Pairs, Lists, and Racket Syntax for intuition about what the dot means, and Syntax Patterns for what it means in a syntax pattern.

5.2 Cross Macro Communication

 (require syntax-parse-example/cross-macro-communication/cross-macro-communication)
  package: syntax-parse-example

syntax

(define-for-macros id expr)

syntax

(get-macroed id)

The define-for-macros and get-macroed demonstrate the use of syntax-local-value when communicating information across macros. Anything defined with define-for-macros can be accessed (at compile/macro expansion time) by get-macroed.

Examples:
> (define-for-macros cake 42)
> (get-macroed cake)

42

> cake

eval:3:0: cake: illegal use of syntax

  in: cake

This communication works even if the identifiers are defined and used in different files or modules:

Examples:
> (module the-definition racket
    (require syntax-parse-example/cross-macro-communication/cross-macro-communication)
    (define-for-macros shake 54)
    (provide shake))
> (module the-use racket
    (require 'the-definition
             syntax-parse-example/cross-macro-communication/cross-macro-communication)
    (get-macroed shake))
> (require 'the-use)

54

The following is the source code for define-for-macros and get-macroed:

  #lang racket/base
  (provide define-for-macros get-macroed)
  (require (for-syntax racket/base syntax/parse))
   
  (define-syntax (define-for-macros stx)
    (syntax-parse stx
      [(_ name:id expr)
       #'(define-syntax name expr)]))
   
  (define-syntax (get-macroed stx)
    (syntax-parse stx
      [(_ name:id)
       #`(#%datum . #,(syntax-local-value #'name))]))
   
   

In define-for-macros, the macro simply binds a new value at compile time using define-syntax. In this example define-for-macros is mostly synonymous with define-syntax, but it demonstrates that the name could be changed (to say add a question mark at the end), and the given expression can be changed. The get-macroed form simply takes the compile time value and puts it in the run time module. If name is used outside of a macro then a syntax error is raised.

The point of #%datum is to make it seem like a value was part of the source code. See Expansion Steps for details.

5.3 let-star

 (require syntax-parse-example/let-star/let-star)
  package: syntax-parse-example

syntax

(let-star ((id expr) ...) expr)

Racket’s let binds identifiers simultaneously; Racket’s let* binds identifiers in sequence. For example:

(let* ([a 1]
       [b (+ a 1)])
  b)

behaves the same as a nested let:

(let ([a 1])
  (let ([b (+ a 1)])
    b))

The let-star macro implements let* in terms of let.

  #lang racket/base
  (provide let-star)
  (require (for-syntax racket/base syntax/parse))
   
  (define-syntax (let-star stx)
    (syntax-parse stx
     [(_ ([x:id v:expr]) body* ...+)
      #'(let ([x v])
          body* ...)]
     [(_ ([x:id v:expr] . bind*) body* ...+)
      #'(let ([x v])
          (let-star bind* body* ...))]
     [(_ bad-binding body* ...+)
      (raise-syntax-error 'let-star
        "not a sequence of identifier--expression pairs" stx #'bad-binding)]
     [(_ (bind*))
      (raise-syntax-error 'let-star
        "missing body" stx)]))
   

Note:
  • The macro is recursive. The use of let-star in the second clause will later expand to a sequence of lets.

  • The pattern ...+ matches one or more of the previous pattern.

Examples:
> (let* 1)

eval:1:0: let*: bad syntax (missing body)

  in: (let* 1)

> (let* ([a 1]))

eval:2:0: let*: bad syntax (missing body)

  in: (let* ((a 1)))

> (let* ([a 1]
         [b (+ a 1)]
         [c (+ b 1)])
    c)

3

5.4 def

 (require syntax-parse-example/def/def)
  package: syntax-parse-example

syntax

(def (id arg-spec ...)
  doc-contract-tests ...
  expr ...)

Examples:
> (module snoc racket/base
    (require syntax-parse-example/def/def)
    (def (snoc (x* : list?) x)
      "Append the value `x` to the end of the given list"
      #:test [
        ((snoc '(1 2) 3) ==> '(1 2 3))
        ((snoc '(a b) '(c)) ==> '(a b (c)))]
      (append x* (list x)))
    (provide snoc))
> (require 'snoc)
> (snoc 1 '(2 3))

snoc: contract violation

  expected: list?

  given: 1

> (snoc '(1 2) 3)

'(1 2 3)

The def macro is similar to define but:
  • requires a docstring

  • requires test cases;

  • optionally accepts contract annotations on its arguments; and

  • optionally accepts pre- and post- conditions.

Examples:
> (module gcd racket/base
    (require syntax-parse-example/def/def)
    (def (gcd (x : integer?) (y : integer?))
      "greatest common divisor"
      #:pre [
        (>= "First argument must be greater-or-equal than second")]
      #:test [
        ((gcd 10 3) ==> 1)
        ((gcd 12 3) ==> 3)]
      (cond
       [(zero? y) x]
       [else (gcd y (- x (* y (quotient x y))))]))
     (provide gcd))
> (require 'gcd)
> (gcd 42 777)

gcd: First argument must be greater-or-equal than second

> (gcd 777 42)

21

If the docstring or test cases are missing, def throws a syntax error.

Examples:
> (def (f x)
    x)

eval:9.0: def: expected string or expected one of these

literals: #:test, #:pre, or #:post

  at: x

  in: (def (f x) x)

> (def (f x)
    "identity"
    x)

eval:10.0: def: expected string or expected one of these

literals: #:test, #:pre, or #:post

  at: x

  in: (def (f x) "identity" x)

> (def (f x)
    #:test [((f 1) ==> 1)]
    x)

eval:11.0: def: expected string or expected one of these

literals: #:test, #:pre, or #:post

  at: x

  in: (def (f x) #:test (((f 1) ==> 1)) x)

How to read the macro:
  1. The begin-for-syntax defines two syntax classes (see Syntax Classes). The first syntax class, arg-spec, captures arguments with an optional contract annotation. The second, doc-spec, captures docstrings.

  2. The large ~or pattern captures the required-and-optional stuff that def accepts: the docstring, the #:test test cases, the #:pre pre-conditions, and the #:post post-conditions.

  3. The four #:with clauses build syntax objects that run unit tests and/or checks.

  4. The syntax object made from the #:test clause creates a post-submodule (module+ test ....) and uses parameterize to capture everything that the tests print to current-output-port.

  5. The examples in the docs for the ~optional pattern help explain (1) why #'#f can be a useful #:default and (2) when it is necessary to specify the ellipses depth in a #:default, as in (check-pre* 1).

The def macro:

  #lang racket/base
  (provide def)
  (require rackunit (for-syntax racket/base syntax/parse))
   
  (begin-for-syntax
    (define-syntax-class arg-spec
      #:attributes (name type)
      #:datum-literals (:)
      (pattern
        (name:id : type:expr))
      (pattern
       name:id
       #:with type #'#f))
   
    (define-syntax-class doc-spec
      (pattern
        e:str))
  )
   
  (define-syntax (def stx)
    (syntax-parse stx #:datum-literals (==>)
     [(_ (name:id arg*:arg-spec ...)
        (~or ;; using (~or (~once a) ...) to simulate an unordered (~seq a ...)
          (~once (~describe #:role "docstring" "docstring" doc:doc-spec))
          (~once (~seq #:test ((in* ==> out*
                               (~optional (~seq #:stdout expected-output*:str)
                                          #:defaults ([expected-output* #'#f])))
                              ...)))
          (~once (~optional (~seq #:pre ([check-pre* pre-doc*:doc-spec] ...))
                            #:defaults ([(check-pre* 1) '()] [(pre-doc* 1) '()])))
          (~once (~optional (~seq #:post ([check-post* post-doc*:doc-spec] ...))
                            #:defaults ([(check-post* 1) '()] [(post-doc* 1) '()])))) ...
         body)
      #:with check-types
        #'(for ([arg-name (in-list (list arg*.name ...))]
                [arg-type (in-list (list arg*.type ...))]
                [i        (in-naturals)]
                #:when arg-type)
            (unless (arg-type arg-name)
              (raise-argument-error 'name (symbol->string (object-name arg-type)) i arg-name)))
      #:with check-pre
        #'(for ([pre-check (in-list (list check-pre* ...))]
                [pre-doc   (in-list (list pre-doc* ...))])
            (unless (pre-check arg*.name ...)
              (raise-user-error 'name pre-doc)))
      #:with check-post
        #'(lambda (result)
            (for ([post-check (in-list (list check-post* ...))]
                  [post-doc   (in-list (list post-doc* ...))])
              (unless (post-check result)
                (error 'name post-doc))))
      #:with test-cases
        #'(module+ test
            (let* ([p (open-output-string)]
                   [result-val (parameterize ([current-output-port p]) in*)]
                   [result-str (begin0 (get-output-string p)
                                       (close-output-port p))])
                (check-equal? result-val out*)
                (when expected-output*
                  (check-equal? result-str expected-output*)))
            ...)
      #'(begin
          test-cases
          (define (name arg*.name ...)
            check-types
            check-pre
            (let ([result body])
              (begin0 result
                      (check-post result)))))]))
   

Notes:
  • This macro gives poor error messages when the docstring or test cases are missing.

  • The doc-spec syntax class could be extended to accept Scribble, or another kind of docstring syntax.

  • A #:test case may optionally use the #:stdout keyword. If given, the test will fail unless running the test prints the same string to current-output-port.

5.5 conditional-require

 (require syntax-parse-example/conditional-require/conditional-require)
  package: syntax-parse-example

syntax

(conditional-require expr id id)

This macro conditionally requires one of two module paths based on a compile-time condition.

  #lang racket/base
  (provide conditional-require)
  (require (for-syntax racket/base syntax/parse))
   
  (begin-for-syntax
    (define-syntax-class mod-name
      (pattern _:id)
      (pattern _:str)))
   
  (define-syntax (conditional-require stx)
    (syntax-parse stx
     [(_ test:boolean r1:mod-name r2:mod-name)
      (if (syntax-e #'test)
        #'(require r1)
        #'(require r2))]))
   

Notes:

5.6 multi-check-true

 (require syntax-parse-example/multi-check-true/multi-check-true)
  package: syntax-parse-example

syntax

(multi-check-true expr ...)

The multi-check-true expands into a sequence of check-true unit tests. For example:

(multi-check-true
  #true
  #false
  (even? 0))

expands to code that behaves the same as:

(check-true #true)
(check-true #false)
(check-true (even? 0))

The main difference between the macro and the example is that the macro uses with-check-info* to improve test failure messages. If part of a multi-check-true fails, the error message points to the bad expression (rather than the multi-check-true macro).

  #lang racket/base
  (provide multi-check-true)
  (require rackunit (for-syntax racket/base syntax/srcloc syntax/parse))
   
  (define-syntax (multi-check-true stx)
    (syntax-parse stx
     [(_ e* ...)
      #`(begin
          #,@(for/list ([e (in-list (syntax-e #'(e* ...)))])
               (define loc (build-source-location-list e))
               #`(with-check-info* (list (make-check-location '#,loc))
                   (λ () (check-true #,e)))))]))
   

5.7 define-datum-literal-set

 (require syntax-parse-example/define-datum-literal-set/define-datum-literal-set)
  package: syntax-parse-example

syntax

(define-datum-literal-set id (id ...))

syntax-parse can match literal symbols using the #:datum-literals option or the ~datum pattern form. These work well for a small number of literals.

Given a sequence of symbols, the define-datum-literal-set macro builds a syntax class that matches these symbols.

(define-datum-literal-set C-keyword
  (auto break case char const continue default do double else))
 
(define-syntax (is-C-keyword? stx)
  (syntax-parse stx
   [(_ x:C-keyword)
    #'#true]
   [(_ x)
    #'#false]))
 
(is-C-keyword? else)
(is-C-keyword? synchronized)

The macro works by defining a literal set and then a syntax class.

  #lang racket/base
  (provide define-datum-literal-set)
  (require (for-syntax racket/base racket/syntax syntax/parse))
   
  (define-syntax (define-datum-literal-set stx)
    (syntax-parse stx
     [(_ cls-name:id (lit*:id ...))
      #:with set-name (format-id stx "~a-set" (syntax-e #'cls-name))
      #'(begin-for-syntax
        (define-literal-set set-name
          #:datum-literals (lit* ...)
          ())
        (define-syntax-class cls-name
          #:literal-sets ([set-name])
          (pattern (~or lit* ...)))) ]))
   

5.8 rec/c

 (require syntax-parse-example/rec-contract/rec-contract)
  package: syntax-parse-example

syntax

(rec/c id expr)

The rec/c macro uses Racket’s recursive-contract form to create anonymous recursive contracts.

  #lang racket/base
  (provide rec/c)
  (require racket/contract (for-syntax racket/base syntax/parse))
   
  (define-syntax-rule (rec/c t ctc)
    (letrec ([rec-ctc
              (let-syntax ([t (syntax-parser (_:id #'(recursive-contract rec-ctc)))])
                ctc)])
        rec-ctc))
   

Examples:
> (define/contract (deep n)
    (-> integer? (rec/c t (or/c integer? (list/c t))))
    (if (zero? n)
      n
      (list (deep (- n 1)))))
> (deep 4)

'((((0))))

5.9 struct-list

 (require syntax-parse-example/struct-list/struct-list)
  package: syntax-parse-example

syntax

(struct-list expr ...)

The struct-list macro has similar syntax as Typed Racket’s struct form, but creates a new datatype backed by a list instead of an actual struct. The only cosmetic difference is that type #:type-name keyword is required, and must supply a name that is different from the struct name.

Examples:
> (struct-list foo ([a : String] [b : String]) #:type-name Foo)
> (define f (foo "hello" "world"))
> (foo? f)

- : Boolean

#t

> (string-append (foo-a f) " " (foo-b f))

- : String

"hello world"

> (ann f Foo)

- : Foo

'(foo "hello" "world")

The implementation:
  1. extracts the names and type names from the syntax,

  2. creates an identifier for the predicate and a sequence of identifiers for the accessors (see the #:with clauses),

  3. and defines a constructor and predicate and accessor(s).

  #lang typed/racket/base
  (provide struct-list)
  (require (for-syntax racket/base racket/syntax syntax/parse))
   
  (define-syntax (struct-list stx)
    (syntax-parse stx #:datum-literals (:)
     [(_ name:id ([f*:id : t*] ...) #:type-name Name:id)
      #:fail-when (free-identifier=? #'name #'Name)
                  "struct name and #:type-name must be different"
      #:with name?
             (format-id stx "~a?" (syntax-e #'name))
      #:with ((name-f* i*) ...)
             (for/list ([f (in-list (syntax-e #'(f* ...)))]
                        [i (in-naturals 1)])
               (list (format-id stx "~a-~a" (syntax-e #'name) (syntax-e f)) i))
      (syntax/loc stx
        (begin
          (define-type Name (Pairof 'name (Listof Any)))
          (define (name (f* : t*) ...) : Name
            (list 'name f* ...))
          (define (name? (v : Any)) : Boolean
            (and (list? v) (not (null? v)) (eq? 'name (car v))))
          (define (name-f* (p : Name)) : t*
            (cast (list-ref p 'i*) t*))
          ...))]))
   

6 Example-Formatting Tools

The syntax-parse-example language is a small language for documenting example macros. It:
  • uses the reader from scribble/base; and

  • provides a few utility functions, documented below.

Helpers for rendering documentation.

procedure

(tech/guide pre-content ...)  element?

  pre-content : pre-content?
Similar to tech, but links to The Racket Guide.

procedure

(tech/reference pre-content ...)  element?

  pre-content : pre-content?
Similar to tech, but links to The Racket Reference.

procedure

(racketfile filename)  element?

  filename : path-string?
Typesets the contents of the given file as if its contents were wrapped in a racketblock.