The library is built out of a few stacked ideas: matcher combinators, matchers, consequences and rules.
[binding-frame data success-continuation]
and either returns nil
or false
, or calls its success-continuation
with a potentially-updated binding map.
A matcher
is a function of a data
input that can succeed or fail. If it succeeds, it will return the binding map generated by the matching process.
On failure, a matcher returns a special failure
singleton object. You can check for this object using [[fail?]].
A consequence is a function from the binding map returned by a matcher to a final form. A consequence can fail by returning false
, nil
or the special failure
singleton.
A rule is a function of data
built from a pair of:
If the matcher fails on the rule's input, the whole rule returns failure
. If the match succeeds, the rule calls the consequence function with the matcher's binding map. If THIS function succeeds, the rule returns that value.
If the consequence function returns nil
or false
(or failure
), the whole rule
fails with failure
.
emmy.pattern.match
contains many matcher combinators. These are either functions with the contract described above, or functions that take one or more combinators and return a new combinator with the same contract. Examples are [[or]], [[not]], [[and]], [[match-when]] and more below.
emmy.pattern.rule
contains many rule combinators, which are either primitive rules or functions from zero or more rules to a new rule.
Finally, emmy.pattern.syntax
defines a small pattern language. Any matcher combinator that take another matcher can take a pattern instead.
This section introduces functions that are able to build new matcher combinators out of the primitive matcher combinators defined above.
Each of the following functions can take EITHER a matcher combinator or a "pattern". The emmy.pattern.syntax is described in emmy.pattern.syntax
.
As an example, you might provide the symbol '?x
instead of an explicit (bind '?x)
:
Segment variables introduce some additional trouble. Unlike other matchers, a segment variable is not tested against a fixed input, but against a sequence such that it may match any prefix. This means that in general, segment variables must search, trying one match and possibly backtracking.
There are two circumstances when the search can be avoided:
if the variable is already bound, the bound value needs to be checked against the input data, and will either fail or succeed.
If the segment variable is the last matcher in its enclosing list (which actually happens quite often!) then the segment matcher can match the entire remaining segment, no search required.
This requires a different interface for the continutation. Segment matchers pass TWO arguments into their success continuation - the binding frame, and the remaining unmatched segment.
The following two functions let us mark matcher combinators with this interface using their metadata.
The next function transforms a pattern (as defined by emmy.pattern.syntax
) into a matcher combinator. Any function you pass to [[pattern->combinators]] is returned, so it's appropriate to pass other matcher combinators as pattern elements.
This concludes the matcher combinator section of our program. On to the next act: the "matcher"!
Once you've built up a combinator out of smaller matcher combinators, you can turn your combinator into a "matcher". This is a function from a data object to either:
failure
singleton object.This interface will become important in emmy.rule
, for building up groups of rules that can, say, search for the first successful matcher of many, or accumulate binding maps from matchers run in sequence until one fails.
The next few functions define this explicit failure
singleton.