1.2.5 More Keyword Arguments
This section shows how to express the syntax of struct’s
optional keyword arguments using syntax-parse patterns.
The part of struct’s syntax that is difficult to specify is
the sequence of struct options. Let’s get the easy part out of the way
first.
Given those auxiliary syntax classes, here is a first approximation of
the main pattern, including the struct options:
(struct name:id super:maybe-super (field:field ...) |
(~or (~seq #:mutable) |
(~seq #:super super-expr:expr) |
(~seq #:inspector inspector:expr) |
(~seq #:auto-value auto:expr) |
(~seq #:guard guard:expr) |
(~seq #:property prop:expr prop-val:expr) |
(~seq #:transparent) |
(~seq #:prefab) |
(~seq #:constructor-name constructor-name:id) |
(~seq #:extra-constructor-name extra-constructor-name:id) |
(~seq #:omit-define-syntaxes) |
(~seq #:omit-define-values)) |
...) |
The fact that
expr does not match keywords helps in the case
where the programmer omits a keyword’s argument; instead of accepting
the next keyword as the argument expression,
syntax-parse
reports that an expression was expected.
There are two main problems with the pattern above:
There’s no way to tell whether a zero-argument keyword like
#:mutable was seen.
Some options, like #:mutable, should appear at most
once.
The first problem can be remedied using
~and patterns to bind
a pattern variable to the keyword itself, as in this sub-pattern:
The second problem can be solved using repetition constraints:
The
~optional repetition constraint indicates that an
alternative can appear at most once. (There is a
~once form
that means it must appear exactly once.) In
struct’s keyword
options, only
#:property may occur any number of times.
There are still some problems, though. Without additional help,
~optional does not report particularly good errors. We must
give it the language to use, just as we had to give descriptions to
sub-patterns via syntax classes. Also, some related options are
mutually exclusive, such as #:inspector,
#:transparent, and #:prefab.
(struct name:id super:maybe-super (field:field ...) |
(~or (~optional |
(~or (~seq #:inspector inspector:expr) |
(~seq (~and #:transparent transparent-kw)) |
(~seq (~and #:prefab prefab-kw))) |
#:name "#:inspector, #:transparent, or #:prefab option") |
(~optional (~seq (~and #:mutable) mutable-kw) |
#:name "#:mutable option") |
(~optional (~seq #:super super-expr:expr) |
#:name "#:super option") |
(~optional (~seq #:auto-value auto:expr) |
#:name "#:auto-value option") |
(~optional (~seq #:guard guard:expr) |
#:name "#:guard option") |
(~seq #:property prop:expr prop-val:expr) |
(~optional (~seq #:constructor-name constructor-name:id) |
#:name "#:constructor-name option") |
(~optional |
(~seq #:extra-constructor-name extra-constructor-name:id) |
#:name "#:extra-constructor-name option") |
(~optional (~seq (~and #:omit-define-syntaxes omit-def-stxs-kw)) |
#:name "#:omit-define-syntaxes option") |
(~optional (~seq (~and #:omit-define-values omit-def-vals-kw)) |
#:name "#:omit-define-values option")) |
...) |
Here we have grouped the three incompatible options together under a
single
~optional constraint. That means that at most one of
any of those options is allowed. We have given names to the optional
clauses. See
~optional for other customization options.
Note that there are other constraints that we have not represented in
the pattern. For example, #:prefab is also incompatible with
both #:guard and #:property. Repetition constraints
cannot express arbitrary incompatibility relations. The best way to
handle such constraints is with a side condition using
#:fail-when.