Skip to content

Instantly share code, notes, and snippets.

@basicxman
Created April 20, 2012 00:27
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save basicxman/2425003 to your computer and use it in GitHub Desktop.
Save basicxman/2425003 to your computer and use it in GitHub Desktop.
import math, re
from stack import *
class RPNCalculator:
def __init__(self):
self.data = stack()
# Create lists of valid operators.
self.lang_operators = ["+", "-", "*", "/", "**", "//"]
self.binary_operators = []
self.unary_operators = []
# Iterate through the non-plumbing methods in the math module and add each
# as an operator.
for method in dir(math)[5:]:
self.add_operator(method)
print "Reverse Polish Notation Calculator"
print
print "Available Binary Operators:"
print ", ".join(self.binary_operators + self.lang_operators)
print
print "Available Unary Operators:"
print ", ".join(self.unary_operators)
print
print "Example:"
print "1 2 + 3 * 4 +"
print "1000 10 log"
# Until the user decides to exit we should print a newline between each
# calculation.
while self.execute():
print
# Looks at a method name in the math module and finds out how many parameters
# it takes. Then add it as a unary (single operand) or binary (double
# operand) operator.
#
# @param method The method name from the math module.
def add_operator(self, method):
# Python has an inspect module that would normally allow you to get the
# signature of a method, however built-in methods such as the ones in
# the math module do not have their signature available. To get around
# this I call the method without any arguments and parse the result of the
# exception.
#
# http://bugs.python.org/issue1748064
#
# For binary operators, you'll get exceptions such as:
# TypeError: pow expected 2 arguments, got 0
# TypeError: log expected at least 1 arguments, got 0
# For unary operators, you'll get an exception like:
# TypeError: exp() takes exactly one argument (0 given)
try:
# Since the dir() method returns the method's name as a string, we have
# to access the module's dictionary to get the actual method object.
math.__dict__[method]()
except Exception as e:
results = re.search("takes exactly (one) argument|expected ([0-9])|at least (1)", str(e))
if results == None: return
# Since there are multiple exception phrasings it will depend on which
# match group was found (sub phrases in parenthesis in the regex).
if results.group(1) == "one":
self.unary_operators.append(method)
elif results.group(2) == "2":
self.binary_operators.append(method)
elif results.group(3) == "1":
self.binary_operators.append(method)
# Validates an operator and pops the required operands off the stack and
# calculates the result. Will print an error if validation fails or the
# the result upon success.
#
# @param operator The operator to apply on operand(s).
# @return If the operator or operands were invalid it will return None. If
# the operation was successful it will return the result.
def calc(self, operator):
a = self.data.pop() # At minimum we'll have one operand.
try:
if operator in self.lang_operators:
b = self.data.pop()
# Built in language operators will have a syntax like 4 + 5
value = eval("%s %s %s" % (b, operator, a))
elif operator in self.binary_operators:
# Binary operators will have a syntax like math.pow(3, 3)
b = self.data.pop()
value = eval("math.%s(%s, %s)" % (operator, b, a))
elif operator in self.unary_operators:
# Unary operators will have a syntax like math.exp(5)
value = eval("math.%s(%s)" % (operator, a))
else:
print "Invalid operator %s." % operand
return
except Exception as e:
# Make sure to print a user-friendly error message instead of a stack
# trace from the exception.
print "Could not perform invalid operation."
return
print value
return value
# Collect operands and operators from one user statement in reverse polish
# notation.
def execute(self):
value = raw_input("> ").strip()
if value == "exit": return False # User is done, kill the loop condition.
for arg in value.split(" "):
# Make sure operands only contain digits and a decimal point.
if re.match("[0-9]+\.?[0-9]*", arg) != None:
self.data.push(arg)
else:
ret = self.calc(arg)
if ret != None:
self.data.push(ret)
return True
rpn = RPNCalculator()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment