"""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)