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 The Examples
4.1 first-class-or
4.2 Cross Macro Communication
4.3 let-star
4.4 def
4.5 conditional-require
4.6 multi-check-true
4.7 define-datum-literal-set
4.8 rec/  c
rec/  c
5 Reference
tech/  guide
tech/  reference

Syntax Parse Examples

Source code:

This package is a collection of useful and/or illustrative macros written using the syntax/parse library.
Reference documents the syntax-parse-example language.

1 How to browse the examples

Two options:

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 The Examples

4.1 first-class-or

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


(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).

> (or #false #true)


> (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.

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


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


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


> (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
     [(_ ?a . ?b)
      #'(let ([a-val ?a])
          (if a-val a-val (first-class-or . ?b)))]
      #'(lambda arg*
          (let loop ([arg* arg*])
             [(null? arg*)
             [(car arg*)
              (car arg*)]
              (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.

4.2 Cross Macro Communication

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


(define-for-macros id expr)


(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.

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


> 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:

> (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
    (get-macroed shake))
> (require 'the-use)


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.

4.3 let-star

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


(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)])

behaves the same as a nested let:

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

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)]))

  • 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.

> (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)])


4.4 def

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


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

> (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.

> (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)]
       [(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)


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

> (def (f 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)

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)]

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—in particular, 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.

The def macro:

  #lang racket/base
  (provide def)
  (require rackunit (for-syntax racket/base syntax/parse))
    (define-syntax-class arg-spec
      #:attributes (name type)
      #:datum-literals (:)
        (name:id : type:expr))
       #:with type #'#f))
    (define-syntax-class doc-spec
  (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) '()])))) ...
      #: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*)))
          (define (name arg*.name ...)
            (let ([result body])
              (begin0 result
                      (check-post result)))))]))

  • 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.

4.5 conditional-require

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


(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))
    (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))]))


4.6 multi-check-true

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


(multi-check-true expr ...)

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

  (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* ...)
          #,@(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)))))]))

4.7 define-datum-literal-set

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


(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)
   [(_ x)
(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))
        (define-literal-set set-name
          #:datum-literals (lit* ...)
        (define-syntax-class cls-name
          #:literal-sets ([set-name])
          (pattern (~or lit* ...)))) ]))

4.8 rec/c

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


(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)))])

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


5 Reference

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.


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

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


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

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


(racketfile filename)  element?

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