Minimal example of a Python Ply parser in one file

This is a minimal example of a python ply parser run from a single file which does not produce any files on disk when run. It should use most of the main features of the ply language so might be suitable for a paste-and-adapt approach.

Download
"""A minimal example of using ply all in one file, illustrating most
of the features you are likely to need.

Parses an simple lisp with a + function and numbers."""

from ply.lex import lex
from ply.yacc import yacc

def listify(gen):
    "Convert a generator into a function which returns a list"
    def patched(*args, **kwargs):
    	return list(gen(*args, **kwargs))
    return patched

class Lexer(object):
    t_PLUS = r"\+"
    t_OPEN = r"\("
    t_CLOSE = r"\)"
    t_ignore = " "

    def t_NUMBER(self, t):
    	r"\d+"
    	t.value = int(t.value)
    	return t

    tokens = "PLUS OPEN CLOSE NUMBER".split()

    def t_error(self, t):
    	raise Exception(t)
lexer = lex(module=Lexer())

class Parser(object):
    "e.g (+ (+ 1 2) (+ 3 4))"
    starting = "statement" # default is first method
    def p_statement(self, p):
    	"""statement :  OPEN PLUS numbers CLOSE
    	           |    OPEN PLUS statements CLOSE
    			   """
    	p[0] = sum(p[3])

    def p_statements(self, p):
    	"statements : statement statements"
    	p[0] = (p[1],) + p[2]

    def p_statements2(self, p):
    	"statements : statement"
    	p[0] = (p[1],)

    def p_numbers(self, p):
    	"numbers : NUMBER"
    	p[0] = (p[1],)

    def p_numbers2(self, p):
    	"numbers : numbers NUMBER"
    	p[0] = p[1] + (p[2],)

    def p_error(self, p):
    	raise Exception(p)

    tokens = Lexer.tokens

if __name__ == '__main__':
    lexer = lex(module=Lexer())
    parser = yacc(module=Parser(), write_tables=0)
    while True:
    	print parser.parse(raw_input("lisp>"))

# TESTS - run with nosetests modulename

def test_parser():
    lexer = lex(module=Lexer())
    parser = yacc(module=Parser())
    assert parser.parse("(+(+1 2) (+3 4))   ", lexer=lexer) == 10

def test_lexer():
    lexer = lex(module=Lexer())
    lexer.input("( )    1 2 + (")
    tokens = []
    @listify
    def collect_until_None(f):
    	while True:
    		thing = f()
    		if thing is None:
    			break
    		yield thing
    tokens = collect_until_None(lexer.token)
    types = [t.type for t in tokens]
    assert types == "OPEN CLOSE NUMBER NUMBER PLUS OPEN".split()
    assert (tokens[2].value, tokens[3].value) == (1, 2)
2010-01-24