1#!/usr/bin/env python2.7
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