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