Created
April 20, 2012 00:27
-
-
Save basicxman/2425003 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 "Available Binary Operators:" | |
print ", ".join(self.binary_operators + self.lang_operators) | |
print "Available Unary Operators:" | |
print ", ".join(self.unary_operators) | |
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(): | |
# 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