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 24 25class Parser(object): 26 """ 27 Base class for a lexer/parser that has the rules defined as methods 28 """ 29 tokens = () 30 precedence = () 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])[ 37 1] + "_" + self.__class__.__name__ 38 except: 39 modname = "parser" + "_" + self.__class__.__name__ 40 self.debugfile = modname + ".dbg" 41 self.tabmodule = modname + "_" + "parsetab" 42 # print self.debugfile, self.tabmodule 43 44 # Build the lexer and parser 45 lex.lex(module=self, debug=self.debug) 46 yacc.yacc(module=self, 47 debug=self.debug, 48 debugfile=self.debugfile, 49 tabmodule=self.tabmodule) 50 51 def run(self): 52 while 1: 53 try: 54 s = raw_input('calc > ') 55 except EOFError: 56 break 57 if not s: 58 continue 59 yacc.parse(s) 60 61 62class Calc(Parser): 63 64 tokens = ( 65 'NAME', 'NUMBER', 66 'PLUS', 'MINUS', 'EXP', 'TIMES', 'DIVIDE', 'EQUALS', 67 'LPAREN', 'RPAREN', 68 ) 69 70 # Tokens 71 72 t_PLUS = r'\+' 73 t_MINUS = r'-' 74 t_EXP = r'\*\*' 75 t_TIMES = r'\*' 76 t_DIVIDE = r'/' 77 t_EQUALS = r'=' 78 t_LPAREN = r'\(' 79 t_RPAREN = r'\)' 80 t_NAME = r'[a-zA-Z_][a-zA-Z0-9_]*' 81 82 def t_NUMBER(self, t): 83 r'\d+' 84 try: 85 t.value = int(t.value) 86 except ValueError: 87 print("Integer value too large %s" % t.value) 88 t.value = 0 89 # print "parsed number %s" % repr(t.value) 90 return t 91 92 t_ignore = " \t" 93 94 def t_newline(self, t): 95 r'\n+' 96 t.lexer.lineno += t.value.count("\n") 97 98 def t_error(self, t): 99 print("Illegal character '%s'" % t.value[0]) 100 t.lexer.skip(1) 101 102 # Parsing rules 103 104 precedence = ( 105 ('left', 'PLUS', 'MINUS'), 106 ('left', 'TIMES', 'DIVIDE'), 107 ('left', 'EXP'), 108 ('right', 'UMINUS'), 109 ) 110 111 def p_statement_assign(self, p): 112 'statement : NAME EQUALS expression' 113 self.names[p[1]] = p[3] 114 115 def p_statement_expr(self, p): 116 'statement : expression' 117 print(p[1]) 118 119 def p_expression_binop(self, p): 120 """ 121 expression : expression PLUS expression 122 | expression MINUS expression 123 | expression TIMES expression 124 | expression DIVIDE expression 125 | expression EXP expression 126 """ 127 # print [repr(p[i]) for i in range(0,4)] 128 if 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 elif p[2] == '**': 137 p[0] = p[1] ** p[3] 138 139 def p_expression_uminus(self, p): 140 'expression : MINUS expression %prec UMINUS' 141 p[0] = -p[2] 142 143 def p_expression_group(self, p): 144 'expression : LPAREN expression RPAREN' 145 p[0] = p[2] 146 147 def p_expression_number(self, p): 148 'expression : NUMBER' 149 p[0] = p[1] 150 151 def p_expression_name(self, p): 152 'expression : NAME' 153 try: 154 p[0] = self.names[p[1]] 155 except LookupError: 156 print("Undefined name '%s'" % p[1]) 157 p[0] = 0 158 159 def p_error(self, p): 160 if p: 161 print("Syntax error at '%s'" % p.value) 162 else: 163 print("Syntax error at EOF") 164 165if __name__ == '__main__': 166 calc = Calc() 167 calc.run() 168