calc.py revision 6498
1#!/usr/bin/env python 2 3# ----------------------------------------------------------------------------- 4# calc.py 5# 6# A simple calculator with variables. This is from O'Reilly's 7# "Lex and Yacc", p. 63. 8# 9# Class-based example contributed to PLY by David McNab. 10# 11# Modified to use new-style classes. Test case. 12# ----------------------------------------------------------------------------- 13 14import sys 15sys.path.insert(0,"../..") 16 17if sys.version_info[0] >= 3: 18 raw_input = input 19 20import ply.lex as lex 21import ply.yacc as yacc 22import os 23 24class Parser(object): 25 """ 26 Base class for a lexer/parser that has the rules defined as methods 27 """ 28 tokens = () 29 precedence = () 30 31 32 def __init__(self, **kw): 33 self.debug = kw.get('debug', 0) 34 self.names = { } 35 try: 36 modname = os.path.split(os.path.splitext(__file__)[0])[1] + "_" + self.__class__.__name__ 37 except: 38 modname = "parser"+"_"+self.__class__.__name__ 39 self.debugfile = modname + ".dbg" 40 self.tabmodule = modname + "_" + "parsetab" 41 #print self.debugfile, self.tabmodule 42 43 # Build the lexer and parser 44 lex.lex(module=self, debug=self.debug) 45 yacc.yacc(module=self, 46 debug=self.debug, 47 debugfile=self.debugfile, 48 tabmodule=self.tabmodule) 49 50 def run(self): 51 while 1: 52 try: 53 s = raw_input('calc > ') 54 except EOFError: 55 break 56 if not s: continue 57 yacc.parse(s) 58 59 60class Calc(Parser): 61 62 tokens = ( 63 'NAME','NUMBER', 64 'PLUS','MINUS','EXP', 'TIMES','DIVIDE','EQUALS', 65 'LPAREN','RPAREN', 66 ) 67 68 # Tokens 69 70 t_PLUS = r'\+' 71 t_MINUS = r'-' 72 t_EXP = r'\*\*' 73 t_TIMES = r'\*' 74 t_DIVIDE = r'/' 75 t_EQUALS = r'=' 76 t_LPAREN = r'\(' 77 t_RPAREN = r'\)' 78 t_NAME = r'[a-zA-Z_][a-zA-Z0-9_]*' 79 80 def t_NUMBER(self, t): 81 r'\d+' 82 try: 83 t.value = int(t.value) 84 except ValueError: 85 print("Integer value too large %s" % t.value) 86 t.value = 0 87 #print "parsed number %s" % repr(t.value) 88 return t 89 90 t_ignore = " \t" 91 92 def t_newline(self, t): 93 r'\n+' 94 t.lexer.lineno += t.value.count("\n") 95 96 def t_error(self, t): 97 print("Illegal character '%s'" % t.value[0]) 98 t.lexer.skip(1) 99 100 # Parsing rules 101 102 precedence = ( 103 ('left','PLUS','MINUS'), 104 ('left','TIMES','DIVIDE'), 105 ('left', 'EXP'), 106 ('right','UMINUS'), 107 ) 108 109 def p_statement_assign(self, p): 110 'statement : NAME EQUALS expression' 111 self.names[p[1]] = p[3] 112 113 def p_statement_expr(self, p): 114 'statement : expression' 115 print(p[1]) 116 117 def p_expression_binop(self, p): 118 """ 119 expression : expression PLUS expression 120 | expression MINUS expression 121 | expression TIMES expression 122 | expression DIVIDE expression 123 | expression EXP expression 124 """ 125 #print [repr(p[i]) for i in range(0,4)] 126 if p[2] == '+' : p[0] = p[1] + p[3] 127 elif p[2] == '-': p[0] = p[1] - p[3] 128 elif p[2] == '*': p[0] = p[1] * p[3] 129 elif p[2] == '/': p[0] = p[1] / p[3] 130 elif p[2] == '**': p[0] = p[1] ** p[3] 131 132 def p_expression_uminus(self, p): 133 'expression : MINUS expression %prec UMINUS' 134 p[0] = -p[2] 135 136 def p_expression_group(self, p): 137 'expression : LPAREN expression RPAREN' 138 p[0] = p[2] 139 140 def p_expression_number(self, p): 141 'expression : NUMBER' 142 p[0] = p[1] 143 144 def p_expression_name(self, p): 145 'expression : NAME' 146 try: 147 p[0] = self.names[p[1]] 148 except LookupError: 149 print("Undefined name '%s'" % p[1]) 150 p[0] = 0 151 152 def p_error(self, p): 153 if p: 154 print("Syntax error at '%s'" % p.value) 155 else: 156 print("Syntax error at EOF") 157 158if __name__ == '__main__': 159 calc = Calc() 160 calc.run() 161