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 12 import sys 13 sys.path.insert(0, "../..") 14 15 if sys.version_info[0] >= 3: 16 raw_input = input 17 18 import ply.lex as lex 19 import ply.yacc as yacc 20 import os 21 22 23 class Parser: 24 """ 25 Base class for a lexer/parser that has the rules defined as methods 26 """ 27 tokens = () 28 precedence = () 29 30 def __init__(self, **kw): 31 self.debug = kw.get('debug', 0) 32 self.names = {} 33 try: 34 modname = os.path.split(os.path.splitext(__file__)[0])[ 35 1] + "_" + self.__class__.__name__ 36 except: 37 modname = "parser" + "_" + self.__class__.__name__ 38 self.debugfile = modname + ".dbg" 39 self.tabmodule = modname + "_" + "parsetab" 40 # print self.debugfile, self.tabmodule 41 42 # Build the lexer and parser 43 lex.lex(module=self, debug=self.debug) 44 yacc.yacc(module=self, 45 debug=self.debug, 46 debugfile=self.debugfile, 47 tabmodule=self.tabmodule) 48 49 def run(self): 50 while 1: 51 try: 52 s = raw_input('calc > ') 53 except EOFError: 54 break 55 if not s: 56 continue 57 yacc.parse(s) 58 59 60 class 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] == '+': 127 p[0] = p[1] + p[3] 128 elif p[2] == '-': 129 p[0] = p[1] - p[3] 130 elif p[2] == '*': 131 p[0] = p[1] * p[3] 132 elif p[2] == '/': 133 p[0] = p[1] / p[3] 134 elif p[2] == '**': 135 p[0] = p[1] ** p[3] 136 137 def p_expression_uminus(self, p): 138 'expression : MINUS expression %prec UMINUS' 139 p[0] = -p[2] 140 141 def p_expression_group(self, p): 142 'expression : LPAREN expression RPAREN' 143 p[0] = p[2] 144 145 def p_expression_number(self, p): 146 'expression : NUMBER' 147 p[0] = p[1] 148 149 def p_expression_name(self, p): 150 'expression : NAME' 151 try: 152 p[0] = self.names[p[1]] 153 except LookupError: 154 print("Undefined name '%s'" % p[1]) 155 p[0] = 0 156 157 def p_error(self, p): 158 if p: 159 print("Syntax error at '%s'" % p.value) 160 else: 161 print("Syntax error at EOF") 162 163 if __name__ == '__main__': 164 calc = Calc() 165 calc.run() 166