Switches execution to a block of code based on a pattern match of a source code object with a sequence of patterns.
pattern: One of a number of possible syntax patterns, as described below.
statements: a sequence of expressions to be evaluated if a pattern match is successful.
syntax-switch is a control construct whose syntax is similar to that of
switch, but with a syntax pattern at each
case statement.
It serves as a convenient form for pattern-matching and destructuring
CurlSource objects into sub-pieces. It was designed specifically to assist users in writing macros; however, it need not be used only within macros definitions.
source-object is matched against each
pattern in order. For the first
pattern that matches, the corresponding
statements are executed, with local variables defined for each named sub-pattern. If no case expression matches, the
statements following
else are executed if such a clause is present.
If
must-match? is specified as
true, and no pattern matches, then
syntax-switch throws a
SyntaxError. No
else clause is allowed if
must-match? is true.
syntax-switch uses heuristics to try to generate a good syntax error, such as seeing how far the farthest pattern match got before it failed. This is sufficient to catch easy cases like a missing
do. However, the quality of the default heuristics may not provide good enough error messages for all purposes, especially in the face of multiple alternatives.
A
pattern consists of either an identifier naming a simple pattern, or a prefix expression describing a compound pattern.
A
pattern template consists of a sequence of literal source code escaped with question marks (
?). This will be explained below.
Here are the simple
pattern choices:
expression
Matches a single curl expression. This greedily consumes the largest expression it can match.
If this pattern is named, the named source code object will be an object of type
Parsed.
The related pattern expressions matches a sequence of 0 or more expressions.
statement
Matches a single curl statement. This greedily consumes the largest statement it can parse.
Statements in curl are the same as expressions, except that this invokes special parsing rules for let and set expressions, inserting the implicit curly braces around them.
If this pattern is named, the named source code object will be an object of type
Parsed.
The related pattern statements matches a sequence of 0 or more statements.
class-member
Matches a single curl class member.
A class member is defined as anything than can appear at the top level inside a class definition (field, class variable, class procedure, method, constructor, factory, getter, setter or option).
If this pattern is named, the named source code object will be an object of type
Parsed.
The related pattern class-members matches a sequence of 0 or more class members.
identifier
Matches any curl identifier, such as hello.
If this pattern is named, the named source code object will be an object of type
Identifier.
The related pattern identifiers matches a sequence of 0 or more identifiers.
text
Matches a sequence of text characters, up to the following curly brace expression. This is how to parse arguments as text-procs do. This pattern preserves all whitespace, and processes backslash escapes.
If this pattern is named, the named source code object will be an object of type
BaseText. Its contents as a
StringInterface can be retrieved by accessing its
contents property. The text with backslash escapes unprocessed can be retrieved by calling the
get-text method on it.
The related pattern texts matches a sequence of 0 or more texts.
token
Matches a single curl token, such as 7, +, foo, or "a string literal". Note that prefix expressions, such as {something x, y} count as a single token, because the tokenization rules for the contents of the prefix expression typically depend on the head of the expression; for example, the contents of {text hello 1.2.3} are parsed as a string, and 1.2.3 is not a valid code token at all.
The related pattern tokens matches a sequence of 0 or more tokens.
verbatim
Matches the rest of the source code, preserving white space and other information that the code tokenizer would not. This pattern should be listed last.
Using it for code can be less efficient than tokens or statements, so typically it should only be used when parsing raw code.
If this pattern is named, the named source code object will be an object of type
CurlSource.
Since verbatim consumes the rest of the source code, there is no verbatims pattern.
Here are the compound patterns:
{pattern pattern-template}
This is a simple grouping operator that collects a pattern template into a single pattern.
For example:
{pattern while ?predicate:expression do ?body:statements}
literal
Matches a single curl literal that isa specified type. Matching expressions might include 7, true, null, and "a string literal".
If this pattern is named, the named source code object will be an object of type
Literal.
For example:
{pattern include ?filename:{literal String}}
{one-of pattern1 [, pattern2...]}
This will match any one of the listed patterns, preferring to match patterns earlier in the list.
one-of is the only compound pattern that takes patterns as its operands, rather than a sequence pattern template. This is to disambiguate whether commas are separating the operands to the one-of itself, or are simply part of one of the patterns.
For example:
{one-of
{pattern above},
{pattern below},
{pattern to},
{pattern downto}
}
{optional pattern-template}
This will match either pattern-template, or nothing, preferring the former.
For example:
{pattern
while ?tag-expr:{optional tag = ?:identifier,}
?predicate:expression
do
?body:statements
}
optional is equivalent to:
{one-of {pattern pattern-template},{pattern}}
{sequence pattern-template}
This will match 0 or more occurrences of pattern-template, preferring to match as few occurrences as possible (i.e., it is non-greedy).
Note that many of the simple patterns have shorthand plural forms, e.g. statements is equivalent to {sequence ?:statement}.
If a sequence pattern is named, the object produced by a match will be a container that you can loop through with for, to see all the matches. You can get the size of the container by accessing its size method.
Here's an example that matches zero or more keyword arguments, with a comma after each argument:
{pattern ?keywords:{sequence ?:identifier = ?:expression,}}
sequence is equivalent to:
{bounded-sequence 0, infinity, pattern-template}
{comma-sequence pattern-template}
This will match 0 or more occurrences of pattern-template, with each pair of occurrences separated by commas. It prefers to match as few occurrences as possible (i.e., it is non-greedy).
If a comma-sequence pattern is named, the object produced by a match will be a container that you can loop through with for, to see all the matches. The commas do not show up when you iterate, but will be present if the container is substituted into an expand-template call. You can get the size of the container by accessing its size method.
Here's an example that matches zero or more arguments, separated by commas:
{pattern ?arguments:{comma-sequence ?:expression}}
comma-sequence is equivalent to:
{bounded-comma-sequence
0, infinity, pattern-template
}
{bounded-sequence min, max,
pattern-template
}
This will match between min and max occurrences of pattern-template, inclusive, preferring to match as few occurrences as possible (i.e., it is non-greedy). Use infinity if you don't want a bound.
If a bounded-sequence pattern is named, the object produced by a match will be a container that you can loop through with for, to see all the matches. You can get the size of the container by accessing its size method.
For example, this would match any sequence of two or more identifiers:
{bounded-sequence 2, infinity, ?:identifier}
{bounded-comma-sequence min, max,
pattern-template
}
This will match between min and max occurrences of pattern-template, inclusive, with each pair of occurrences separated by commas. It prefers to match as few occurrences as possible (i.e., it is non-greedy).
If a bounded-comma-sequence pattern is named, the object produced by a match will be a container that you can loop through with for, to see all the matches. The commas do not show up when you iterate, but will be present if the container is substituted into an expand-template call. You can get the size of the container by accessing its size method.
For example, if you wanted a pattern that matched at least two comma-separated expressions, you would write this:
{bounded-comma-sequence 2, infinity, ?:expression}
A
pattern template is a sequence of normal code that must be matched exactly, interspersed with
patterns escaped with question marks (
?).
For example, the contents of this
pattern:
{pattern alpha ?id:identifier beta}will match the identifier
alpha followed by any identifier followed by the identifier
beta.
This
pattern matches the same thing, but allows any number of identifiers (including zero) between
alpha and
beta:
{pattern alpha ?ids:{sequence ?:identifier} beta}Note how the first
pattern template said
?id:identifier. In this example,
id names the pattern, so that subsequent code can access the source code that matched the pattern. Similarly, in the second example
?ids:{sequence ?:identifier} will produce an array of matches.
Named patterns are specified like this:
{syntax-switch source
case {pattern alpha ?id:identifier beta} do
{output "I matched identifier ", id.name}
else
{error "no match"}
}
In the body of the case, note how
id is bound to the matching source code.
Typically named patterns are passed to
expand-template to produce a new source code object, but advanced code might analyze them further using the methods on
CurlSource and its subclasses.
When matching any of the sequence patterns, the variable is bound to a container containing a sequence of all the matches. This container can be looped through with
for.
{syntax-switch source
case {pattern alpha ?ids:identifiers beta} do
{output "I matched ", ids.size, " identifiers"}
case {pattern whoops} do
{output "someone said whoops!"}
else
{error "no match"}
}
Note how this example uses the
identifiers shorthand for the
{sequence ?:identifier} used in the previous example; they are equivalent.
Naming a pattern is optional. If you don't need to name the matching source, just omit the name:
{pattern alpha ?:identifier beta}Named patterns may not appear inside
sequence,
optional, or other constructs where it is not guaranteed to get exactly one match.
To match a literal question mark, simply double the
? character, as in
{pattern ??}. Note that question marks that are part of an identifier, such as
some-function?, are considered part of that identifier and are not separately matchable.