Tuesday, October 18, 2005

Records and pattern matching

##record.py
##
##"record" is an implementation of a simple record
##type constructor for records with labeled fields.
##The record types will be subclasses of "tuple".
##
##"match" is a pattern matcher for matching patterns
##of complex record structures to a record object and
##binding elements in the matched structure to parameters
##in a handler function.
##
##Example:
##from record import record, match, sym
##tree = record(sym.tree, sym.left, sym.right)
##def flatten(t):
##    return (match(t).
##    case(tree(sym.left, sym.right),
##          lambda left, right: flatten(left)+flatten(right)).
##    case(sym.leaf, lambda leaf: [leaf])
##    ())
##
##t = tree(tree(1, tree(2, 4)), 3)
##print flatten(t)
##prints:
##[1, 2, 4, 3]


class symbol(object):
    """
    Symbols for place holders for pattern matching
    """

    syms = {}
    
    def __new__(kls, name):
        if name in symbol.syms:
            return symbol.syms[name]
        else:
            o = object.__new__(kls)
            symbol.syms[name] = o
            return o
        
    def __init__(self, name):
        self.name = name

    def __str__(self):
        return self.name

    def __repr__(self):
        return self.name

class _symbol(object):
    def __getattribute__(self, name):
        return symbol(name)

sym = _symbol()

def record(name, *labels):
    """
    Construct a record type. Records are subclasses of tuple:
    <new record class> = record(name, label1, ...)
    
    Usage:
    user = record(sym.user, sym.name, sym.password)
    ram = user(name="
ram", password="*****")
    shyam = user("
shyam", "*")
    print "
ram's name = ", ram.name
    name, password = ram
    "
""
    labels = [str(label) for label in labels]
    name = str(name)
    def new(kls, *labels):
        return tuple.__new__(kls, labels)
    def __str__(self):
        return "%s(%s)" % (name,
                           ", ".join(("%s=%s"%(label, val)
                                      for label, val
                                      in zip(labels, self))))
    d = {"__new__":new,
         "__str__":__str__,
         "__repr__":__str__}
    exec ("""def __init__(self, %s):pass""" %
          ", ".join(labels)) in d
    
    for i in range(len(labels)):
        d[labels[i]] = (lambda i:
                        property(lambda self:
                                 self[i]))(i)

    return type(name, (tuple,), d)

def _combine(obj, pattern):
    if isinstance(pattern, symbol):
        return [(str(pattern), obj)]
    elif isinstance(pattern, tuple) and type(obj)==type(pattern):
        return sum((_combine(o, p) for o, p in zip(obj, pattern)), [])
    elif pattern == obj:
        return []
    else:
        return False

class MatchFailed(Exception):pass       

class match(object):
    """
    Match the given object and do some actions
    """

    def __init__(self, obj):
        self.obj = obj
        self.matchers = []
        def onDefault(obj):
            raise MatchFailed
        self._onDefault = onDefault
        
    def case(self, pattern, onMatch):
        """
        Add a match case.
        
        The match case matches a pattern to an action
        """

        self.matchers.append((pattern, onMatch))
        return self

    def default(self, onDefault):
        """
        Set a default handler if all matches fail.
        
        The handler must accept one parameter which
        will be the match object.
        """

        self._onDefault = onDefault

    def __call__(self):
        """
        Execute the match
        """

        for pattern, onMatch in self.matchers:
            c = _combine(self.obj, pattern)
            if c!=False:
                return onMatch(**dict(c))
        self._onDefault(self.obj)

0 Comments:

Post a Comment

<< Home