1
0
Fork
You've already forked nitrate
0
Scheme procedure-based pattern matcher https://florida.moe/nitrate/
  • Scheme 99.7%
  • Makefile 0.3%
Find a file
2026年05月06日 07:24:01 -04:00
examples example 2026年01月06日 18:53:27 -05:00
lib remove parameters, call/cc, disjoint matcher type 2026年03月29日 15:02:01 -04:00
tests reorganize tests for snow 2026年01月09日 21:03:00 -05:00
.gitignore CHICKEN 5 2026年01月05日 22:46:56 -05:00
Makefile bump to 2.0 2026年05月06日 06:54:26 -04:00
nitrate.egg remove parameters, call/cc, disjoint matcher type 2026年03月29日 15:02:01 -04:00
nitrate.release-info bump to 2.0 2026年05月06日 06:54:26 -04:00
README.tsm README.tsm: fix markup error 2026年05月06日 07:24:01 -04:00

head| meta| @| charset| utf-8
 link| @| href| https://florida.moe/assets/treeside-simple.css
 rel| stylesheet
 type| text/css
nh1| nitrate — Combinator Based Pattern Matcher
{href `https://codeberg.org/phm/nitrate` Git repository (latest)}
Releases:
dl|
 dt| bleeding-edge
 dd| {href `https://codeberg.org/phm/nitrate` Git repository}
 dt| 2.0
 dd| {href `https://codeberg.org/phm/nitrate/src/tag/2.0` Git repository}
 dd| {href `https://florida.moe/nitrate/nitrate-2.0.html` manual}
 dd| {href `https://florida.moe/nitrate/nitrate-2.0.tgz` Release tarball}
 dt| 1.0
 dd| {href `https://codeberg.org/phm/nitrate/src/tag/1.0` Git repository}
 dd| {href `https://florida.moe/nitrate/nitrate-1.0.html` manual}
 dd| {href `https://florida.moe/nitrate/nitrate-1.0.tgz` Release tarball}
Nitrate is a combinator (procedure) based backtracking pattern
matcher. Being procedure based, as opposed to syntax based, the matcher
is extensible with any predicate or record type and its basic components
are composable and can represent recursive structure. Designed for
{R7RS}-small systems without fancy macro systems or optimizers.
This could also be called monadic
pattern matching or a parser-combinator library for Scheme values.
Nitrate has been tested to work on CHICKEN 5 and Chibi Scheme.
the-table-of-contents|
nh2| Why a Procedure-Based Pattern Matcher?
Scheme has no standard pattern matcher. A commonly used one is the
"Wright" pattern matcher, with a notable `syntax-rules` implementation
using CPS macros by Alex Shinn. Such patterns matchers are not extensible.
Some proposals for high-end, extensible pattern matchers include {SRFI
257} and {SRFI 262}.
These matchers are all at the syntax level and are very difficult to
implement. A static-typed language like Standard
ML or {href `https://www.haskell.org/` Haskell} requires a matcher as
a syntactic construct because it has no way to discriminate against sum types.
But Scheme is a latently typed language, so it does not have this issue.
Higher-order procedures should be able to do the same thing that pattern
matching syntax can, with the added benefit of easy extensibility.
This procedure-based pattern matcher is very composable. For instance,
the pattern {idref or~} could be defined as:
code| pre|
 (define {idref or~}
 (case-lambda
 (() {idref _~})
 ((x~) x~)
 ((x~ . spats) ({idref if~} x~ {idref _~} (apply {idref or~} spats)))))
This code almost looks like how one would write `or` in a call-by-name
version of Scheme. Since procedures are composed before matching, the
code that makes up the patterns passed to {idref or~} are not run until
then.
The composition is done over a finite list (the argument list), hence the
recursion is done at a meta-level to the matching. But with this system,
one can also write recursive patterns at the matching level. Consider the
following possible implementation of {idref member~}:
 code| pre|
 ({idref define-recpat} (member~ pat~)
 ({idref or~} ({idref cons~} pat~ {idref _~}) ({idref cons~} {idref _~} (member~ pat~))))
The macro {idref define-recpat} uses an eta-expansion trick to allow for
a composition of matching procedures to be recursive.
A procedure-based pattern matcher would have a uniform syntax for matching
any structured data. This may make the basic case of lists and vectors less
ergonomic than a specalized solution, but this could encourage people to
use abstract patterns instead of concrete ones.
One thing that a procedure-based pattern matcher can't do that a syntactic
pattern matcher can is to bind values to identifiers, because identifiers
are a syntactic construct. This has its upsides, though: a binding can
appear zero or more times, and the client code can extract values as
desired.
nh2| Design of the Matcher
The matcher is a depth-first, left-to-right traversal. Many {R7RS}-small
systems are not going to take much opportunity to optimize anyways by
re-arranging the evaulation order, so by fixing an evaluation order the
matcher can express things such as cuts.
The matcher threads an immutable map through the match. Its immutability
makes it easy to backtrack. The map is returned at the end of the match.
The matcher procedures either return the map, or `#f`.
The matcher also threads a dynamic state through the match, roughly
equivalent to `parameterize` but will always tail-call its pattern.
The default matcher is `call/cc` safe. That means that a matcher using
only the procedures here can be entered and exited multiple times. This
could be used, for instance, to match one branch, do some processing, and
cut back to try another match if something goes wrong in the client code.
nh3| Future Directions
One idea is to use identifiers ({R6RS}) instead of symbols. This would
make the patterns easier to read and help with possible scope and
hygiene issues. A more radical change would be to use {SRFI 247} style
syntactic monads.
Another possibility is to do a version of {SRFI 262} lowered to the
procedure level. SRFI 262 is designed to be compilable efficiently,
which suggests a route for optimizing compilers: given a pattern (i.e.
in a procedure), lift it such that it only needs to be evaluated once,
evaluate derived matching procedures into matching primitives (maybe
record types), optimize the primitives, and then output a matching
procedure with compiler hints to aggresively inline.
nh2| Examples
An example of a toy expander for the R4RS special forms is bundled in
the repository in the `examples` folder.
Match a list with specified atoms:
code| pre|
 (define (one-two-three-four lst)
 ({idref match} lst dict
 (({idref list~} 1 2 3 4) #t)
 ({idref _~} #f)))
 (one-two-three-four '(1 2 3 4)) ; => #t
 (one-two-three-four '#(1 2 3 4)) ; => #f
Match a list, mapping values to symbols:
code| pre|
 (define (simple lst)
 ({idref match} lst dict
 (({idref list~} 1 ({idref b~} 'second) 3) (car (dict-ref (dto) dict 'second)))
 ({idref _~} #f)))
 (simple '(1 100 3)) ; => 100
 (simple '(1 100 5~~ ; => #f
Match as many numbers at the head of a list as possible:
code| pre|
 (define (leading lst)
 ({idref match} lst dict
 (({idref not~} ({idref ?~} pair?)) (values '() '()))
 (({idref list*~} ({idref and~} ({idref ?~} number?) ({idref b~} 'n)) ({idref b~} 'rest))
 (values (dict-ref/default (dto) dict 'n '())
 (car (dict-ref (dto) dict 'rest))))))
 (leading '(1 2 3 4)) ; => (values '(1 2 3 4) '())
 (leading '(1 2 3 x y 5)) ;=> (values '(1 2 3) '(x y 5))
 (leading '(x 1 2)) ; => (values '() '(x 1 2))
 (leading 'abcd) ; => (values '() '())
Partition a list using a predicate:
code| pre|
 (define (partition predicate? lst)
 ({idref match} lst dict
 (({idref list*~} ({idref or~} ({idref and~} ({idref ?~} predicate?) ({idref b~} 'hit))
 ({idref b~} 'miss))
 '())
 (values (dict-ref/default (dto) dict 'hit '())
 (dict-ref/default (dto) dict 'miss '())))))
 (partition positive? '(1 2 3 -5 -6 7 8 -9))
 ; => (values '(1 2 3 7 8) '(-5 -6 -9))
nh2| API
nh3| High-Level Matching
feature|
 syntax| {name match} {non-terminal expression} {non-terminal identifier} {gstar ({non-terminal pattern} {non-terminal body})}
 Each {non-terminal pattern} must evaluate to something that can be
 passed to {idref mt}. This is generally a matching procedure.
 Matches {non-terminal expression} to each of the {non-terminal pattern}
 in turn. If a match results in a success, bind the resulting dictionary
 to {non-terminal identifier}, with all entries in left-to-right matching
 order, and tail-evaluate {non-terminal body} with {non-terminal identifier}
 in scope.
feature|
 syntax| {name match-pr} {non-terminal expression} ({non-terminal identifier{sub 1}} {non-terminal identifier{sub 2}}) {gstar ({non-terminal pattern} {non-terminal body})}
 Like {idref match}, except instead of binding the dictionary to an identifier:
 li| the identifier {non-terminal identifer{sub 1}} is bound to a procedure
 `(r x [d])`. It looks up the symbol `x` in the dictionary, returning
 the resulting list, or `d` (defaults to the empty list) if not found.
 li| the identifier {non-terminal identifier{sub 2}} is bound to a procedure
 `(r1 x [d])`. It looks up the symbol `x` in the dictionary, returning
 the first matched result, or `d` (defaults to `#f`) if not found.
feature|
 syntax| {name match-rev} {non-terminal expression} {non-terminal identifier} {gstar ({non-terminal pattern} {non-terminal body})}
 Like {idref match}, except that the dictionary is returned without
 transforming the order of the bound values.
nh3| Dictionaries
{index {l {e `dict`}} {ref `dict` argument parameter definition}}
All procedure arguments that start with {label `dict` `dict`} or are described as dictionaries must
be objects that are {code (dictionary? ({ref dto}) dict)} when
passed to the procedure.
nh4| dto
This object is the global, immutable {SRFI 225} DTO.
nh4| empty-dict
This object is the global, immutable dict corresponding to the {SRFI 225} DTO.
nh3| Shorthands
feature|
 syntax| {name define-recpat} {non-terminal define-head} {non-terminal body}
 Define an eta-expanded composition of matching procedures. This is useful
 to express recursive pattern matching that would normally cause a call-by-value
 evaulation order to loop forever.
 For example, consider the following matcher:
 code| pre|
 ({idref define-recpat} (member~ pat~)
 ({idref or~} ({idref cons~} pat~ {idref _~}) ({idref cons~} {idref _~} (member~ pat~))))
 If this was defined using `define`, then `(member~ pat~)` would loop
 forever as it will call `(member~ pat~)` again. The `define-recpat` version
 is equivalent to
 code| pre|
 (define (member~ pat~)
 (lambda (dict input)
 ({idref or~} ({idref cons~} pat~ {idref _~}) ({idref cons~} {idref _~} (member~ pat~)))))
 Hence the evaluation of `(member~ pat~)` stops at the `lambda`.
feature|
 syntax| {name matcher-wrapper} ({non-terminal predicate} {gplus {non-terminal accessor}})
 All the arguments to the macro must evaluate to procedures.
 Create a procedure that in turn creates matching procedures.
 The procedure takes the same number of arguments as {non-terminal
 accessor}s, which must be matching procedures.
 The procedure returns a matching procedure that fails if the passed
 value does not satisfy {non-terminal predicate}. Otherwise, it
 matches the first {non-terminal accessor} with the input dictionary,
 the second {non-terminal accessor} with the returned dictionary, and
 so on, returning the result of the final {non-terminal accessor}. The
 final {non-terminal accessor} is called in the tail-position.
nh3| Matching
{index {l {e `~`}} {ref `tilde` naming convention}}
{label `tilde` Identifers ending with `~` are either values that can be
passed to {idref `mt`}, or procedures that create values that can be
passed to {idref `mt`}.}
feature|
 procedure| name| mt
 args| pred| `tilde` mat~
 pred| `dict` dict
 pred| `dict` dyn
 name| input
 returns| name| {ref `dict` dict}/#f
 Matches {var mat~} against {var input}.
 li| If {var mat~} is a procedure, then tail-call
 the underlying procedure with {var dict} and {var input} as arguments.
 li| If the {var mat~} is `equal?` to {var input}, return the input dict.
 li| Otherwise, return false.
nh3| Unconditional Matchers
feature|
 procedure| name| b~
 args| name| symbol
 opt| name| procedure
 returns| name| procedure?
 Creates a matcher that unconditionally matches the passed value.
 
 The returned dictionary maps {var symbol} to a list where the head is
 `(procedure value)`,
 and {var value} is the passed value. If {var procedure} is not given,
 the default is the identity function.
 If the only variable bindings in the matche are made with this procedure,
 then the resulting dictionary will have the bindings of the match in
 reverse order.
feature|
 procedure| name| _~
 args| pred| `dict` dict
 name| input
 returns| pred| `dict` dict
 A matching procedure that always suceeds and returns the input
 dictionary.
feature|
 procedure| name| fail~
 args| pred| `dict` dict
 name| input
 A matching procedure that always fails.
nh3| Conditional Matchers
feature|
 procedure| name| if~
 args| name| test~
 name| consequent~
 name| alternative~
 returns| name| `procedure?`
 Try to match {var test~}. 
 
 If the match of {var test~} succeeds, try to match
 {var consequent~} in the tail-position with the returned dictionary.
 Otherwise try to match {var alternative~} in the tail-position with
 the input dictionary.
feature|
 procedure| name| if*~
 args| rep| name| test~
 name| consequent~
 An analogue of `cond`. Equivalent to:
 code| pre|
 ({idref if~} test{sub 1}~ consequent{sub 1}~
 ({idref if~} test{sub 2}~ consequent{sub 2}~
 ({idref if~} ... {idref fail~})))
feature|
 procedure| name| and~
 args| rep| pred| `tilde` pat~
 returns| name| `procedure?`
 Create a matching procedure that will try to match the first {var pat~},
 and then match the second {var pat~} with the same input and the
 dictionary of the first {var pat~}, and so on. If one fails, the entire
 match fails. The final {var pat~} is tail-called.
feature|
 procedure| name| or~
 args| rep| pred| `tilde` pat~
 returns| name| `procedure?`
 Create a matching procedure that tries each {var pat~} in sequence
 against the same input and dictionary. If a match fails, the next match
 is tested. If a match succeeds, return the dictionary from it. If no
 match succeeds, the match fails. The final {var pat~} is tail-called.
feature|
 procedure| name| not~
 args| pred| `tilde` pat~
 returns| name| `procedure?`
 Create a matching procedure that will fail if {var pat~} succeeds, and
 suceed with the input dictionary if {var pat~} fails.
feature|
 procedure| name| opt~
 args| pred| `tilde` pat~
 returns| name| `procedure?`
 Creating a matching procedure that matches {var pat~} against the input.
 If the match succeeds, return the value. Otherwise return the input
 dictionary.
 The maching procedure always succeeds.
feature|
 procedure| name| as-many~
 args| rep| pred| `tilde` pat~
 returns| name| `procedure?`
 Creates a matching procedure that attempts to match each {var pat~}
 against the input. The successful matches are merged from left to right.
 The maching procedure always succeeds.
nh3| Calling Scheme Procedures
feature|
 procedure| name| ?~
 args| name| procedure
 returns| name| `procedure?`
 Creates a matching procedure that will pass its input to {var procedure}.
 If {var procedure} returns false, the match fails. Otherwise return the
 input {ref `dict` dictionary} unchanged.
feature|
 procedure| name| inspect~
 args| name| predicate
 returns| name| `procedure?`
 Creates a matching procedure that calls {var predicate} with one argument,
 the input dictionary. If {var predicate} returns false, the match fails.
 Otherwise return the input {ref `dict` dictionary} unchanged.
feature|
 procedure| name| xfrm~
 args| name| procedure
 name| pat~
 returns| name| `procedure?`
 Creates a matching procedure that tail-calls {var pat~} with the output
 of calling {var procedure} on the input value.
nh3| Numbers
feature|
 procedure| name| =~
 args| name| x
 returns| name| `procedure?`
 Creates a matching procedure that will succeed only if the input is
 a number that is numerically equal to {var x}. The procedure will
 return an empty {ref `dict` dictionary} for a succesful match.
feature|
 procedure| name| =Reps~
 args| name| eps
 name| x
 returns| name| `procedure?`
 Creates a matching procedure that will succeed only if the input is
 a number that is less than {var eps} in distance from {var x}, measured
 by absolute value. The returned dictionary is the input dictionary.
feature|
 procedure| name| signmag~
 args| name| sign~
 name| mag~
 returns| name| `procedure?`
 Creates a matching procedure that will succeed if the input is a real
 number, and the sign (as `-1`, or `1`) matches {var sign~}, and
 the magnitude (as a non-negative real number) matches {var mag~}.
 Exact zero and inexact positive zero have a positive sign. Inexact
 negative zero, if it exists, has a negative sign.
feature|
 procedure| name| =eps~
 args| name| eps
 name| x
 returns| name| `procedure?`
 Like {idref =Reps~}, except that non-real values are supported and
 compared using magnitude.
feature|
 library| (nitrate complex)
 procedure| name| rect~
 args| name| real~
 name| imag~
 returns| name| `procedure?`
 Matches a complex number with real part {var real~} and imaginary part
 {var imag~}.
feature|
 library| (nitrate complex)
 procedure| name| polar~
 args| name| magnitude~
 name| angle~
 returns| name| `procedure?`
 Matches a complex number with magnitude {var magnitude~} and angle
 {var angle~}.
feature|
 library| (nitrate rational)
 procedure| name| rat~
 args| name| numerator~
 name| denominator~
 returns| name| `procedure?`
 Matches a rational number with numerator {var numerator~} and
 denominator {var denominator~}.
nh3| Lists and Pairs
feature|
 procedure| name| cons~
 args| pred| `tilde` car~
 pred| `tilde` cdr~
 returns| name| `procedure?`
 Creates a matching procedure that will succeed only if the input is a
 pair whose car matches {var car~} and whose cdr matches {var cdr~}.
 The matcher for {var cdr~} is tail-called.
feature|
 procedure| name| cons~*
 args| pred| `tilde` pat~{sub 1}
 rep| pred| `tilde` pat~{sub n}
 opt| pred| `tilde` tail~
 returns| name| `procedure?`
 Equivalent to
 code| pre|
 ({idref cons~} pat~{sub 1} ({idref cons~} pat~{sub 1} (... ({idref cons~} pat~{sub 1} tail~))))
feature|
 procedure| name| list~
 args| rep| pred| `tilde` pat~
 returns| name| `procedure?`
 Like {idref `cons~*`}, except the tail must be the empty list.
feature|
 procedure| name| list-tail~
 args| name| n
 pred| `tilde` pat~
 returns| name| `procedure?`
 Creates a matching procedure that matches the input if the input is
 a possibly improper list of at least {var n} elements, whose {var n}th cdr
 matches {var pat~}. The matching of {var pat~} occurs in tail-position.
feature|
 procedure| name| list-ref~
 args| name| n
 pred| `tilde` pat~
 returns| name| `procedure?`
 Equivalent to:
 code| pre|
 ({idref list-tail~} n (cons pat~ {idref _~}))
feature|
 procedure| name| member-tail~
 args| pred| `tilde` pat~
 returns| name| `procedure?`
 Creates a matching procedure that will match the first cdr of the
 possibly improper input list where some cdr of the list matches
 {var pat~}. If there is no such cdr, the match fails. Otherwise the
 dictionary returned is the one returned from {var pat~}.
feature|
 procedure| name| member~
 args| pred| `tilde` pat~
 returns| name| `procedure?`
 Equivalent to
 code| pre|
 ({idref member-tail~} ({idref cons~} pat~ {idref _~}))
feature|
 procedure| name| assoc~
 args| pred| `tilde` key~
 opt| pred| `tilde` val~
 returns| name| `procedure?`
 If {var val~} is absent, defaults to {idref _~}. Equivalent to
 code| pre|
 ({idref member~} ({idref cons~} key~ val~))
feature|
 procedure| name| list*~
 args| pred| `tilde` pat~
 pred| `tilde` tail~
 returns| name| `procedure?`
 Matchs {var pat~} greedily over the elements of the input list. Once
 {var pat~} fails, then try to match {var tail~} on the remaining list.
nh3| Vectors
feature|
 procedure| name| vector~
 args| rep| pred| `tilde` pat~
 returns| name| `procedure?`
 Return a matcher procedure that will succeed when the input is a vector
 that has the same number of elements as the arguments to this procedure.
 Each {var pat~} is matched to the corresponding element in the vector
 from left to right, passing the returned dictionaries alogn the way.
 The final {var pat~} is matched in tail-position. The match fails if any
 match fails.
feature|
 procedure| name| vector-ref~
 args| name| n
 pred| `tilde` pat~
 returns| name| `procedure?`
 Returns a matcher procedure that will succeed when the input is a vector
 with a length greater than {var n} and whose {var n}th element satisfies
 {var pat~}. The {var pat~} is matched in tail-position.
nh2| Version Changes
nh3| 1.0.0 to 2.0.0
The design of the library was simplified. The disjoint record
`matcher-procedure` was removed, along with all parameter objects. The
DTO is now global and hence much easier for a program to optimize without
having to do whole program analysis.
Matchers now return `#f` when they fail. There is no backtracking with
`call/cc` at all.
nh2| Copyright
© Peter McGoron 2026
Permission to use, copy, modify, and/or distribute this software for
any purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE
FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY
DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
nh2| Index
the-index|