Find file
Fetching contributors…
Cannot retrieve contributors at this time
168 lines (124 sloc) 4.29 KB
"""A destructing pattern matcher for Python, inspired by all manners
of functional programming languages!
>>> from util.match import M, A, A_
M - destructuring match-expression, ~M pure match expression
A - argument for destructuring -- specify # by `/'-operator
A_ - any-argument
With `M', you build a match-expression, eg.
>>> ~M((1, (A_, 3), A/1, A/0))
This represents the pattern itself, a match-expression is useless
until it is _bound_. You can bind them with the `==' operator. Eg.:
>>> ~M((1, (A_, 3), A/1, A/0)) == (1, (2, 3), 1, 2)
The `==' operator _destructures_ the match if the `~' is dropped.
>>> M((1, (A_, 3), A/1, A/0)) == (1, (2, 3), 1, 2)
(2, 1)
Now, to make it more interesting, match-expressions can be or-ed
together, resulting in the first match. This is the typical use of the
pattern-matching aspect of destructuring matching:
>>> M([A/0]) | M(A/0) == [5]
>>> M([A/0]) | M(A/0) == 5
Note that, as in all destructuring systems, order is very
important. For example, imagine this:
>>> M([A/0]) | M(A/0) | M([1, A/0]) == [1, -10]
[1, -10]
Here, the second match-expression catches it (since A/0 will match
anything), and our intent was most likely to have this match the third
>>> M([A/0]) | M([1, A/0]) | M(A/0) == [1, -10]
TODO: add support for dicts, sets and pystructs.
TODO: add support for head/rest for dicts and structs
TODO: actually distinguish between binding vs. non-binding args, and
allow them to repeat, eg.:
M([A/0, A/0, A_])
would require both A/0s to be equivalent.
Match-expressions also support a *default* argument that can be used
to return a different value if the expression matches. This can be
handy for dealing with polymorphic return values, eg. for array
>>> M([A/0]) | M([], d=False) == arr[:1]
here, if arr contains one element, we pick it; if it's empty, we
return `False'."""
from itertools import chain
import types
__all__ = ['M', 'A', 'A_']
class _Arg(object):
def __init__(self, argn=None, value=None):
self.value = value
self.argn = argn
def __div__(self, other):
return _Arg(argn=other)
def bind(self, value):
return _Arg(argn=self.argn, value=value)
A_ = _Arg()
A = _Arg()
def match(p, v):
if isinstance(p, _Arg):
return True, [p.bind(v)]
t = type(p)
if not isinstance(v, t):
return False, []
if p == v:
return True, []
elif t == types.ListType or t == types.TupleType:
if len(p) != len(v):
return False, []
def combine((x, y), (a, b)):
if not x:
return x, y
matched, matches = match(a, b)
if matched:
return True, (y + matches)
return False, y
return reduce(combine, zip(p, v), (True, []))
return False, []
def match_and_bind(p, v):
matched, matches = match(p, v)
if not matched:
return None
return tuple(map(lambda x: x.value,
sorted(filter(lambda x: x.argn is not None, matches),
key=lambda x: x.argn)))
class M(object):
def __init__(self, pattern, **kwargs):
self.pattern = pattern
self.matches = []
self.has_d = 'd' in kwargs
if self.has_d:
self.d = kwargs.pop('d')
self.destruct = True
def __invert__(self):
self.destruct = False
return self
def __eq__(self, other):
return self.match(other)
def match(self, value):
for m in chain(reversed(self.matches), [self]):
res = match_and_bind(m.pattern, value)
if res is None:
elif not m.destruct:
return True
elif m.has_d:
return m.d
return res
if self.destruct:
raise ValueError, 'No match for %s' % value
return False
def _append(self, others):
def __or__(self, other):
# left-to-right, so prefer self.
other._append([self] + self.matches)
return other