Skip to content

Instantly share code, notes, and snippets.

@non
Created December 2, 2012 06:28
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 10 You must be signed in to fork a gist
  • Save non/4187319 to your computer and use it in GitHub Desktop.
Save non/4187319 to your computer and use it in GitHub Desktop.
python calculator
#!/usr/bin/python
#
# by Erik Osheim
#
# This program uses a recursive descent, LL1 parser to generate a Code object
# for the given input. Code objects are pure expressions whose behavior can
# only be controlled via a dictionary of strings-to-numbers provided to run.
#
# Syntax:
#
# * decimal numbers (e.g. 3, 4.52)
# * simple names (foo, qux13).
# * built-in constants: pi, e, i
# * arithmetic operators: + - * / % // ^ !
# * parenthesis and absolute value: (2 + 3) * |4 - x|
# * built-in functions:
# abs, ceil, cos, degrees, floor, log, log10,
# log2, max, min, radians, round, sin, tan
#
# You can test this program out on the command-line. For instance:
#
# python calc.py "2 + 2"
#
# python calc.py "9!"
#
# python calc.py "2^foo - 1" foo=8
#
# python calc.py "e^(i*pi) + 1"
import math
import re
import sys
import time
# private expression cache
_cache = {}
# public methods
def prepare(s):
"Prepare the given expression 's' and return a Code object."
global _cache
if s not in _cache:
_cache[s] = Code.gen(Parser(s).parse())
return _cache[s]
def run(s, d):
"Run the given expression 's' using the variables from 'd'."
return prepare(s).run(d)
def flush():
"Clear the expression cache."
global _cache
_cache = {}
# everything below here is semi-private, except the Code class, which is
# returned from prepare().
class Lexeme(object):
def __init__(self, name, pos, data):
self.name = name
self.pos = pos
self.data = data
def __repr__(self):
return 'Lexeme(%r, %d, %r)' % (self.name, self.pos, self.data)
class Lexer(object):
num_re = re.compile(r'(?:0|[1-9][0-9]*)(?:\.[0-9]+)?')
def __init__(self, data):
self.data = data
self.lexs = []
def lexid(self, i):
j = i + 1
while j < len(self.data) and self.data[j].isalnum(): j += 1
self.lexs.append(Lexeme('id', i, self.data[i:j]))
return j
def lexnum(self, i):
j = i + 1
while j < len(self.data) and self.data[j].isdigit(): j += 1
k = j
if k < len(self.data) and self.data[k] == '.':
k += 1
while k < len(self.data) and self.data[k].isdigit(): k += 1
if k > j + 1:
self.lexs.append(Lexeme('num', i, self.data[i:k]))
return k
else:
self.lexs.append(Lexeme('num', i, self.data[i:j]))
return j
def lex(self):
i = 0
while i < len(self.data):
c = self.data[i]
if c.isspace():
i += 1
elif c.isalpha():
i = self.lexid(i)
elif c.isdigit():
i = self.lexnum(i)
elif c == '/' and self.data[i:i + 2] == '//':
self.lexs.append(Lexeme('//', i, '//'))
i += 2
else:
self.lexs.append(Lexeme(c, i, c))
i += 1
self.lexs.append(Lexeme('$', i, None))
return self.lexs
class Expr(object):
def __init__(self, op, *args):
self.op = op
self.args = args
def __repr__(self):
return 'Expr(%r, %r)' % (self.op, self.args)
def lisp(self):
if self.op == 'num':
return self.args[0]
elif self.op == 'id':
return self.args[0]
elif self.op == 'apply':
return '(%s %s)' % (self.args[0], ' '.join([a.lisp() for a in self.args[1:]]))
elif self.args:
return '(%s %s)' % (self.op, ' '.join([a.lisp() for a in self.args]))
else:
return self.op
class Parser(object):
debug = False
def error(self):
if self.debug:
raise Exception("parse error at %r" % self.cur)
else:
raise Exception("parse error at %r (byte %d)" % (self.cur.data, self.cur.pos))
def __init__(self, data):
self.lexer = Lexer(data)
def next(self):
self.k += 1
self.cur = self.lexs[self.k]
def parse(self):
self.k = 0
self.lexs = self.lexer.lex()
self.cur = self.lexs[0]
return self.parseP()
def lxin(self, names):
return self.cur.name in names
pnames = set(['num', 'id', '(', '|', '-'])
def parseP(self):
return self.parseE1()
def parseEx(self, names, f1, f2, right=False):
lhs = f1()
lst = f2()
if not lst:
return lhs
elif right:
lst = [lhs] + lst
expr = lst[-1]
i = len(lst) - 3
while i >= 0:
expr = Expr(lst[i + 1], lst[i], expr)
i -= 2
return expr
else:
expr = lhs
i = 0
while i < len(lst) - 1:
expr = Expr(lst[i], expr, lst[i + 1])
i += 2
return expr
def parseE1(self):
return self.parseEx(self.pnames, self.parseE2, self.parseE1_)
def parseE2(self):
return self.parseEx(self.pnames, self.parseE3, self.parseE2_)
dash = set(['-'])
e3names = set(['num', 'id', '(', '|'])
def parseE3(self):
if self.cur.name == '-':
self.next()
expr = self.parseE3()
return Expr('-', expr)
else:
return self.parseE4()
def parseE4(self):
return self.parseEx(self.e3names, self.parseE5, self.parseE4_, right=True)
def parseE5(self):
expr = self.parseE6()
if self.parseE5_() is None:
return expr
else:
return Expr('!', expr)
lpar = set(['('])
pipe = set(['|'])
rpar = set([')'])
def parseE6(self):
c = self.cur
if c.name == 'num':
self.next()
return Expr('num', c.data)
elif c.name == 'id':
self.next()
a = self.parseA()
if a is None:
return Expr('id', c.data)
else:
return Expr('apply', c.data, *a)
elif c.name == '(':
self.next()
e1 = self.parseE1()
if self.lxin(self.rpar):
self.next()
return e1
else:
self.error()
elif c.name == '|':
self.next()
e1 = self.parseE1()
if self.lxin(self.pipe):
self.next()
return Expr('abs', e1)
else:
self.error()
else:
self.error()
anames = set(['!', '$', '^', '*', '/', '//', '%', '+', '-', ')', '|', ','])
def parseA(self):
if self.lxin(self.lpar):
self.next()
ll = self.parseL()
if self.lxin(self.rpar):
self.next()
return ll
else:
self.error()
else:
return None
lnames = set(['num', 'id', '(', '|', '-'])
def parseL(self):
e1 = self.parseE1()
l_ = self.parseL_()
if l_ is None:
return [e1]
else:
return [e1] + l_
comma = set([','])
def parseL_(self):
if self.lxin(self.comma):
self.next()
e1 = self.parseE1()
l_ = self.parseL_()
if l_ is None:
return [e1]
else:
return [e1] + l_
else:
return None
def parseEx_(self, names, skips, f1):
if self.lxin(names):
c = self.cur
self.next()
lhs = f1()
lst = self.parseEx_(names, skips, f1)
return [c.name, lhs] + lst
else:
return []
e1_names = set(['+', '-'])
e1_skips = set([')', '|', ',', '$'])
def parseE1_(self):
return self.parseEx_(self.e1_names, self.e1_skips, self.parseE2)
e2_names = set(['*', '/', '//', '%'])
e2_skips = set(['+', '-', '$', ')', '|', ','])
def parseE2_(self):
return self.parseEx_(self.e2_names, self.e2_skips, self.parseE3)
e4_names = set(['^'])
e4_skips = set(['*', '/', '//', '%', '$', '+', '-', ')', '|', ','])
def parseE4_(self):
#return self.parseEx_(self.e4_names, self.e4_skips, self.parseE5)
if self.cur.name == '^':
self.next()
#lhs = self.parseE5()
lhs = self.parseE3()
lst = self.parseE4_()
return ['^', lhs] + lst
else:
return []
bang = set(['!'])
e5names = set(['^', '$', '*', '/', '//', '%', '+', '-', ')', '|', ','])
def parseE5_(self):
if self.lxin(self.bang):
self.next()
if self.parseE5_() is None:
return '!'
else:
return None
else:
return None
class Code(object):
# semi-private. you should probably not build these by-hand.
def __init__(self, f, names):
"Construct a Code instance from function 'f' and a list 'names'."
self.f = f
self.names = names
# public
def run(self, kw):
"Run the given Code instance using the dictionary 'kw'."
return self.f(kw)
# semi-private. requires knowledge of the AST structure.
@classmethod
def gen(cls, e):
"Generate a Code instance given a parse tree 'e'."
if e.op == 'num':
if '.' in e.args[0]:
n = float(e.args[0])
else:
n = int(e.args[0])
return cls(lambda kw: n, [])
elif e.op == 'id':
s = e.args[0]
if s == 'e':
return cls(lambda kw: math.e, [])
elif s == 'pi':
return cls(lambda kw: math.pi, [])
elif s == 'i':
return cls(lambda kw: 1j, [])
else:
return cls(lambda kw: kw[s], [s])
elif e.op == '+':
lhs = Code.gen(e.args[0])
rhs = Code.gen(e.args[1])
names = lhs.names + rhs.names
return cls(lambda kw: lhs.run(kw) + rhs.run(kw), names)
elif e.op == '-':
lhs = Code.gen(e.args[0])
if len(e.args) == 1:
return cls(lambda kw: -lhs.run(kw), lhs.names)
else:
rhs = Code.gen(e.args[1])
names = lhs.names + rhs.names
return cls(lambda kw: lhs.run(kw) - rhs.run(kw), names)
elif e.op == '*':
lhs = Code.gen(e.args[0])
rhs = Code.gen(e.args[1])
names = lhs.names + rhs.names
return cls(lambda kw: lhs.run(kw) * rhs.run(kw), names)
elif e.op == '/':
lhs = Code.gen(e.args[0])
rhs = Code.gen(e.args[1])
names = lhs.names + rhs.names
return cls(lambda kw: float(lhs.run(kw)) / rhs.run(kw), names)
elif e.op == '%':
lhs = Code.gen(e.args[0])
rhs = Code.gen(e.args[1])
names = lhs.names + rhs.names
return cls(lambda kw: lhs.run(kw) % rhs.run(kw), names)
elif e.op == '//':
lhs = Code.gen(e.args[0])
rhs = Code.gen(e.args[1])
names = lhs.names + rhs.names
return cls(lambda kw: lhs.run(kw) // rhs.run(kw), names)
elif e.op == '^':
lhs = Code.gen(e.args[0])
rhs = Code.gen(e.args[1])
names = lhs.names + rhs.names
return cls(lambda kw: lhs.run(kw) ** rhs.run(kw), names)
elif e.op == '!':
lhs = Code.gen(e.args[0])
return cls(lambda kw: math.factorial(lhs.run(kw)), lhs.names)
elif e.op == 'abs':
lhs = Code.gen(e.args[0])
return cls(lambda kw: abs(lhs.run(kw)), lhs.names)
elif e.op == 'apply':
fn = e.args[0]
if fn == 'abs':
lhs = Code.gen(e.args[1])
return cls(lambda kw: abs(lhs.run(kw)), lhs.names)
elif fn == 'ceil':
lhs = Code.gen(e.args[1])
return cls(lambda kw: math.ceil(lhs.run(kw)), lhs.names)
elif fn == 'cos':
lhs = Code.gen(e.args[1])
return cls(lambda kw: math.cos(lhs.run(kw)), lhs.names)
elif fn == 'degrees':
lhs = Code.gen(e.args[1])
return cls(lambda kw: math.degrees(lhs.run(kw)), lhs.names)
elif fn == 'floor':
lhs = Code.gen(e.args[1])
return cls(lambda kw: math.floor(lhs.run(kw)), lhs.names)
elif fn == 'log':
lhs = Code.gen(e.args[1])
return cls(lambda kw: math.log(lhs.run(kw)), lhs.names)
elif fn == 'log10':
lhs = Code.gen(e.args[1])
return cls(lambda kw: math.log10(lhs.run(kw)), lhs.names)
elif fn == 'log2':
lhs = Code.gen(e.args[1])
return cls(lambda kw: math.log(lhs.run(kw), 2), lhs.names)
elif fn == 'max':
args = [Code.gen(arg) for arg in e.args[1:]]
names = []
for arg in args: names.extend(arg.names)
return cls(lambda kw: min([a.run(kw) for a in args]), names)
elif fn == 'min':
args = [Code.gen(arg) for arg in e.args[1:]]
names = []
for arg in args: names.extend(arg.names)
return cls(lambda kw: min([a.run(kw) for a in args]), names)
elif fn == 'radians':
lhs = Code.gen(e.args[1])
return cls(lambda kw: math.radians(lhs.run(kw)), lhs.names)
elif fn == 'round':
lhs = Code.gen(e.args[1])
return cls(lambda kw: round(lhs.run(kw)), lhs.names)
elif fn == 'sin':
lhs = Code.gen(e.args[1])
return cls(lambda kw: math.sin(lhs.run(kw)), lhs.names)
elif fn == 'tan':
lhs = Code.gen(e.args[1])
return cls(lambda kw: math.tan(lhs.run(kw)), lhs.names)
else:
raise Exception("function %r is not defined" % e.args[0])
else:
raise Exception("can't handle %r" % e)
# just for playing around or building a REPL. nothing to see here...
class Main(object):
def tplize(self, s):
k, v = s.split('=')
if '.' in v:
return (k, float(v))
else:
return (k, int(v))
def argdict(self):
return dict([self.tplize(arg) for arg in sys.argv[2:]])
def gen(self, s):
return Code.gen(Parser(s).parse())
def execute(self, s, d):
return self.gen(s).run(d)
def bench(self, s, d, r):
print s, d, r
n = 10000
t0 = time.time()
x = 0
for i in xrange(0, n):
x += len(Lexer(s).lex())
t1 = time.time()
print "lexed: %s ms" % (t1 - t0)
e = Parser(s).parse()
t0 = time.time()
for i in xrange(0, n):
c = Code.gen(e)
t1 = time.time()
print "code-gen: %s ms" % (t1 - t0)
t0 = time.time()
c = self.gen(s)
for i in xrange(0, n):
assert(c.run(d) == r)
t1 = time.time()
print "execution only: %s ms" % (t1 - t0)
t0 = time.time()
for i in xrange(0, n):
assert(self.execute(s, d) == r)
t1 = time.time()
print "full run: %s ms" % (t1 - t0)
print
def cmdrun(self):
s = sys.argv[1]
print "input: %s" % s
d = self.argdict()
e = Parser(s).parse()
print "lisp: %s" % e.lisp()
c = Code.gen(e)
if c.names:
print "variables used: %s" % ', '.join(c.names)
else:
print "no variables"
try:
r = c.run(d)
print "result: %r" % r
except KeyError, k:
print "missing variable: %r" % k.message
def main(self):
mode = 'run'
if mode == 'debug':
self.cmdrun()
elif mode == 'bench':
self.bench("1", {}, 1)
self.bench("1 + 1", {}, 2)
self.bench("1 + x", {'x': 9}, 10)
self.bench("1 + x * 2 + 999", {'x': 3}, 1006)
self.bench("1 + x * 2 + 999 / y", {'x': 1, 'y': 3}, 336)
self.bench("log2(2 ^ n * x)", {'n': 8, 'x': 0.5}, 7)
self.bench("(60 * mapLevel + 3 * mapLevel^2) / 500", {'mapLevel': 1}, 63.0 / 500)
else:
print run(sys.argv[1], self.argdict())
if __name__ == "__main__":
Main().main()
# Calculator Grammar:
#
# non-terminal productions
# P -> E1
# E1 -> E2 E1_
# E1_ -> + E2 E1_ | - E2 E1_ | e
# E2 -> E3 E2_
# E2_ -> * E3 E2_ | / E3 E2_ | // E3 E2_ | % E3 E2_ | e
# E3 -> E4 | - E4
# E4 -> E5 E4_
# E4_ -> ^ E5 E4_ | e
# E5 -> E6 E5_
# E5_ -> ! E5_ | e
# E6 -> num | id A | ( E1 ) | bar E1 bar
# A -> ( L ) | e
# L -> E1 L_
# L_ -> , E1 L_ | e
#
# terminal definitions
# bar = "|"
# id = [a-zA-Z][a-zA-Z0-9]*
# num = (0|[1-9][0-9]*)(\.[0-9]+)?
# (plus other literal characters, e.g. +)
#
# first sets
# Fi(A) = ( e
# Fi(E6) = num id ( bar
# Fi(E5_) = ! e
# Fi(E5) = Fi(E6) = num id ( bar
# Fi(E4_) = ^ e
# Fi(E4) = Fi(E5) = num id ( bar
# Fi(E3) = Fi(E4) - = num id ( bar -
# Fi(E2_) = * / // % e
# Fi(E2) = Fi(E3) = num id ( bar -
# Fi(E1_) = + - e
# Fi(E1) = Fi(E2) = num id ( bar -
# Fi(P) = Fi(E1) = num id ( bar -
# Fi(L) = Fi(E1) = num id ( bar -
# Fi(L_) = , e
#
# follow sets
# Fo(E1) = ) bar Fi(L_) Fo(L) = ) bar , e
# Fo(E1_) = Fo(E1) = ) bar , e
# Fo(E2) = Fi(E1_) Fo(E1) = + - e ) bar ,
# Fo(E2_) = Fo(E2) = + - e ) bar ,
# Fo(E3) = Fi(E2_) Fo(E2) = * / // % e + - ) bar ,
# Fo(E4) = Fo(E3) = * / // % e + - ) bar ,
# Fo(E4_) = Fo(E4) = * / // % e + - ) bar ,
# Fo(E5) = Fi(E4_) Fo(E4) = ^ e * / // % + - ) bar ,
# Fo(E5_) = Fo(E5) = ^ e * / // % + - ) bar ,
# Fo(E6) = Fi(E5_) Fo(E5) = ! e ^ * / // % + - ) bar ,
# Fo(A) = Fo(E6) = ! e ^ * / // % + - ) bar ,
# Fo(L) = )
# Fo(L_) = Fo(L) = )
#
# parse table (non-terminal, list of terminals = production)
# P num id ( bar - = E1
# E1 num id ( bar - = E2 E1_
# E1_ + - = [c] E2 E1_
# E1_ ) bar , $ = e
# E2 num id ( bar - = E3 E2_
# E2_ * / // % = [c] E3 E2_
# E2_ + - $ ) bar , = e
# E3 - = - E3
# E3 num id ( bar = E4
# E4 num id ( bar = E5 E4_
# E4_ ^ = ^ E5 E4_
# E4_ * / // % $ + - ) bar , = e
# E5 num id ( bar = E6 E5_
# E5_ ! = ! E5_
# E5_ ^ $ * / // % + - ) bar , = e
# E6 num = num
# E6 id = id A
# E6 ( = ( E1 )
# E6 bar = bar E1 bar
# A ( = ( L )
# A ! e ^ * / // % + - ) bar , = e
# L num id ( bar - = E1 L_
# L_ , = , E1 L_
# L_ ) = e
@tudor1109
Copy link

Doesn't work :( Says print statement for ,s doesn't work

@wingboop1
Copy link

wingboop1 commented Sep 29, 2019

(this is not part of the code): here is a very simple calculator:

while 1<2:
print("this is a simple two digit calculator made on python, enjoy :)")
#this detects when uesr is ready:
start=int(input("press any number to start"))
#user inputs:
if start:
x=int(input("print first number"))
y=int(input("print second number"))
a=input("type +,-,*, or /")
#some simple if and elif, with some math:
if a=="+":
print(x+y)

elif a=="-":
   print(x-y)

elif a=="*":
   print(x*y)

elif a=="/":

#if the user tries to divide by zero we avoid the console from crashing and send them an error message:
try:
print("HERE")
print(x/y)
except ZeroDivisionError:
print("Oops! Error 870...You can't divide by zero!!")
else:
print("That's not an operation! Check your spelling and try again")

@kaskej
Copy link

kaskej commented Jul 23, 2021

hello, by hoping it would run I copied and pasted the codes block for calculator to Pychar, but I met the following error:
line 476
print "lexed: %s ms" % (t1 - t0)
^
SyntaxError: invalid syntax

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment