Pattern Matching
A match statement takes a List apart by describing the shape you expect.
Instead of a run of List_car, List_cdr, length checks, and tag tests, you
write a pattern that looks like the data, name the pieces you care about, and
use those names as ordinary local variables.
The match statement and the runtime matcher share one pattern language.
The exhaustive rules are in
the language reference.
The match statement
A match has a subject in parentheses and a sequence of arms. Each arm is
case pattern: followed by one statement, and an optional final default:
arm runs when nothing matched.
List reply = %(status ok 200);
match (reply) {
case %(status ok ?code):
printf("ok with code %d\n", Var_int(code));
case %(status ?state ?code):
printf("%s with code %d\n", String_str(Var_str(state)), Var_int(code));
default:
printf("not a status reply\n");
}
The subject is evaluated once. Arms are then tried in source order, and the
first one that matches runs. The second arm above would also have matched,
but it is never tried. If no arm matches and there is no
default, the whole statement does nothing.
An arm’s body is a single statement, so use braces when you want several.
A pattern is always a %(...) List literal.
For a single arm you may drop the outer braces, the same way you can
with if:
List value = %(one);
match (value)
case %(one):
printf("no braces needed for a single arm\n");
Arm order is also cost order, so put cheap, common shapes first.
The pattern vocabulary
Patterns are written in List-literal notation, which is described in
strings, lists, arrays, and maps.
Literals
A bare spelling in a pattern is an exact Atom that must appear in that
position. Numbers and strings compare by value, and $name or
${expression} unquotes a value to compare against. A pattern can require a
match with something computed at run time.
Wildcards
? matches exactly one element and discards it. * matches any number of
elements and discards them.
Binders
?name matches one element and binds it; *name matches a run of elements
and binds them as a List.
List command = %(move 10 20 fast quiet);
match (command) {
case %(move ?x ?y *flags):
printf("to %d,%d with %d flag(s)\n",
Var_int(x), Var_int(y), List_len(flags));
}
match (command) {
case %(move ? ? *): printf("some move\n");
}
A ?name binder is a Var; a *name binder is a List. A *name takes
the leftmost split that lets the rest of the pattern succeed, and matching a
*name against typed nil binds an empty List rather than failing.
Repeating a binder name in one pattern means “these must be equal”:
%(?x ?x) matches %(same same) and rejects %(other same).
Binder names follow the C identifier grammar, [A-Za-z_][A-Za-z0-9_]*. A
sigil followed by anything else is a mistake, and the compiler reports it with
a position. %(node ?bad-name) reports invalid match binder name.
Nested shapes
A parenthesized group inside a pattern is a sublist, so nesting states nested structure without any index arithmetic:
List tree = %(tree (node 3 4) leaf);
match (tree) {
case %(tree (node ?a ?b) ?rest):
printf("%d\n", Var_int(a) * Var_int(b));
}
Symbol wanted = <leaf>;
match (tree) {
case %(tree ? $wanted): printf("ends with the wanted tag\n");
}
Guards
Six guard operators turn a position in a pattern into a test. Each is written as a sublist headed by the operator.
(!or a b ...) succeeds when any alternative matches, (!and a b ...) when
all of them do, and (!not a b ...) when none of them do:
List reply = %(status created 201);
match (reply) {
case %(status (!or ok created) ?code):
printf("success %d\n", Var_int(code));
case %(status (!not ok) ?code):
printf("failure %d\n", Var_int(code));
}
match (%(color green)) {
case %(color (!and (!not red) (!not blue))):
printf("neither red nor blue\n");
}
(!set PATTERN ...) is membership: it succeeds when any member matches.
(!quote PATTERN) compares the input to the pattern as literal data, with no
binder interpretation inside it. (!is ...) is the type and category
predicate: (!is type string) checks a Var tag, and (!is atom),
(!is binder), (!is op), (!is var binder), and (!is list binder) check
the named category.
Every guard except !quote accepts an optional leading binder, which
captures the slice of input the guard checked:
match (%(node 7 8)) {
case %(!set ?whole (node ?a ?b)):
printf("%s holds %d and %d\n",
String_str(Var_str(whole)), Var_int(a), Var_int(b));
}
match (%(key "abc")) {
case %(key (!is type string)):
printf("a String payload\n");
}
match (%(key 42)) {
case %(key (!is ?found atom)):
printf("an atom: %s\n", String_str(Var_str(found)));
}
match (%(tag (a b))) {
case %(tag (!quote (a b))):
printf("literally (a b)\n");
}
Runtime-built patterns may put an interned operator Symbol in the head
position. List.match(input, %($op ...)) applies the same operator semantics
as its literal spelling, and the runtime operator suite checks !or, !not,
!quote, !is, and nested dynamic forms. Source match arms should spell
operators literally so the compiler can determine which named binders are
available to the arm body.
The older ?binder?, *binder?, and !op? spellings are reserved in their
historical !is operand positions. They parse, but they
are not named binders and they do not execute a predicate. Use the (!is ...)
forms above.
Where binders live
Each arm declares its binders as local variables before its body is parsed. A
?name has type Var, a *name has type List, and either works with method
syntax. A binder is visible only inside the arm that introduced it, and it
shadows any outer name for the length of that arm:
Var value = 99;
List input = %(pair 1 2);
match (input) {
case %(pair ?value ?other):
printf("inside the arm value is %d\n", Var_int(value));
}
printf("outside the match value is still %d\n", Var_int(value));
The same binder name can appear in two different arms, and a binder does not
exist after the match statement ends. There is no
“result of the match” to read afterwards; assign to a variable you declared
outside if you need to carry something out.
Method syntax follows those types:
match (%(node (a b))) {
case %(node ?child):
printf("%d\n", child.list().len());
}
Named binders must also be definitely assigned whenever their arm matches.
Binders under !not are never available, and a binder under !or or
membership-style !set must occur in every alternative. !quote is opaque
literal data. The compiler diagnoses a maybe-bound name at the pattern.
Lists, Var, and nil
The subject of a match is a List, so the shapes you can match are List
shapes. Arrays, Maps, and Strings are not match subjects.
A Var subject works because the compiler inserts the conversion to List
for you. The conversion does not check the tag, so only match a Var you
already know holds a List; test Var_is(v, <list>) at the boundary if you do
not. See values and Var for the tag rules and
symbols and atoms for what a bare pattern spelling means.
Nil is typed List data, not the absence of a List. A null List is a legal
subject, %(*rest) matches it and binds an empty List, and an explicitly
stored empty List inside a larger structure is a visible node that search can
find. What is not a node is the implicit terminal cdr of a proper List.
A successful match allocates its bindings in the current scope like any other
List, so the rules in scopes and lifetime apply.
break and continue inside an arm
break in an arm body exits the match, not any enclosing loop. continue
skips to the next iteration of the enclosing loop:
List items = %((skip) (keep) (skip));
int seen = 0, kept = 0;
foreach(Var item, items) {
seen++;
match (item) {
case %(skip): continue;
case %(keep): break;
}
kept++;
}
printf("seen %d, kept %d\n", seen, kept);
That prints seen 3, kept 1: the two skip items jump past kept++, while
the keep item leaves the match and falls through to it.
The runtime matcher
The runtime matcher exposes the same pattern language as List methods. Use
it when the pattern is data, when you need the bindings as a value, or when
you are rewriting rather than dispatching.
try_match reports success separately from the bindings, and leaves its
output pointer untouched on failure. match_replace matches once and expands
a template from the bindings:
List input = %(define x 10);
List bindings = NULL;
if (input.try_match(%(define ?name ?value), &bindings))
printf("%s = %d\n",
String_str(bindings.assoc(<?name>).str()),
Var_int(bindings.assoc(<?value>)));
List rewritten = input.match_replace(%(define ?name ?value),
%(assign ?name ?value));
printf("%s\n", rewritten.str());
Bindings are an association List keyed by the binder Atom, so you read them
with assoc and the same spelling you wrote in the pattern: <?name> for a
? binder and <*rest> for a * binder. These are standalone Symbol
literals, outside the List syntax that accepts bare Atoms.
The search family walks a whole structure instead of matching at the root.
try_search returns the first depth-first hit, search collects every hit,
and search_replace rewrites all of them:
List tree = %(root (item 1) (wrapper (item 2)));
Var node = void;
List bindings = NULL;
if (tree.try_search(%(item ?id), &node, &bindings))
printf("first id %d\n", Var_int(bindings.assoc(<?id>)));
printf("%d matches in all\n", tree.search(%(item ?id)).len());
printf("%s\n", tree.search_replace(%(item ?id),
%(entry ?id)).str());
Prefer the try_ forms. The older match, match_replace, and search
adapters cannot distinguish “matched with no bindings” from “did not match”,
because both come back as a null or empty List. search returns its results
in reverse visitation order, so do not read the list as a document order. The
standard library overview has the full API list.
Match for shape, traversal for search
Use match when the data’s structure determines what to do. Literal elements
specify what must be present; ?name and *tail name the parts you will use.
Nested patterns describe sublists, and default handles the remaining cases.
A pattern made entirely of wildcards often calls for iteration instead.
A loop can find candidate values, then match each candidate:
List program = %((set x 1) (call print x) (set y 2));
int assignments = 0;
foreach(Var node, program) {
match (node) {
case %(set ?name ?value):
assignments++;
case %(call ?fn *args):
printf("call to %s\n", String_str(Var_str(fn)));
}
}
printf("%d assignment(s)\n", assignments);
Use a plain if chain instead when there is no shape to state: one tag test,
one length check, one comparison. A match with a single arm whose pattern is
%(?x) buys nothing over the test you would have written. Use
iteration when order, accumulated state, or early exit
matters. A loop and a pattern can each do part of the work.
A binder does not check the captured value’s type. ?value captures whatever
was in that position. If the arm’s body depends on the type, say so in the
pattern with (!is type string) or check the type before the match.
Limitations to know up front
default must be the last arm. The compiler diagnoses a later arm. A final
default runs only when every earlier case failed.
A case pattern must be a %(...) List literal. The runtime matcher
accepts a standalone Symbol pattern such as <?whole>, but the statement
form does not. The compiler rejects any other case pattern with a positioned
x2c diagnostic before generating C.
Source guard operators must be literal. Runtime-built patterns may
interpolate an interned operator Symbol, but a source arm needs a literal
operator so definite binder assignment can be checked. The legacy ?binder?
family remains reserved and is not a working predicate.
The generated Match reference lists the runtime operations by API tier.