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. The ply documentation contains an multiple file example.

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

Parses a simple lisp with a '+' function and integers."""

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