Created
February 8, 2012 01:51
-
-
Save thomcc/1764233 to your computer and use it in GitHub Desktop.
Reverse Polish Notation Calculator
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
### Reverse Polish Notation (postfix) Calculator | |
class RPNCalculator | |
attr_accessor :stack | |
def initialize | |
# the stack, which will contain all the numbers this calculator | |
# can perform operations on | |
@stack = [] | |
# the operations which this calculator supports. we store them as | |
# a hashtable which maps the symbol the user enters to a lambda of | |
# two values. | |
@ops = { | |
:+ => lambda { |a, b| a + b }, | |
:- => lambda { |a, b| a - b }, | |
:* => lambda { |a, b| a * b }, | |
:/ => lambda { |a, b| a / b } | |
} | |
end | |
def print_stack | |
puts "### Stack: " | |
@stack.reverse.each { |i| puts i } | |
end | |
def eval term | |
# term should either be a number, or a symbol | |
if term.is_a? Numeric | |
# if it's a number, then we just push it on to the stack | |
@stack.push term | |
elsif term.is_a? Symbol | |
# if it's a symbol, then we want to apply it to the first two | |
# items on the stack, which we'll do in the do_operation method | |
# but first we should make sure that it's something we know how | |
# to do so we can provide a useful error message | |
raise "Unknown operation: #{term}" unless @ops[term] | |
do_operation term | |
else | |
# if we get here it means that term isn't a number or a symbol, | |
# so chaos reigns supreme and we let the user know something | |
# went wrong. | |
raise "Unknown term type: #{term}" | |
end | |
end | |
def do_operation op | |
# make sure that the stack can handle the operation, and if not | |
# let the user know | |
raise "Stack needs more items to perform '#{op}'!" if @stack.length < 2 | |
# get our arguments from the stack | |
second_arg = @stack.pop | |
first_arg = @stack.pop | |
# get the operations from the ops (we know it will be here because | |
# we checked it in the `eval' method). | |
operation = @ops[op] | |
# apply the operation to the arguments | |
result = operation.call(first_arg, second_arg) | |
# and put it on the stack | |
@stack.push result | |
end | |
def parse str | |
# try to parse as an try to parse as a float, and on failure, | |
# assume that the user is entering an operation (and convert it to | |
# a symbol to reflect that). | |
Float(str) rescue str.to_sym | |
end | |
def calculate tokens | |
# parse and evaluate every string in tokens. | |
tokens.each { |s| eval(parse s) } | |
end | |
end | |
def repl | |
# initialize the calculator | |
calc = RPNCalculator.new | |
while true | |
# make a copy of the stack that we can revert back to should | |
# anything terrible happen | |
stack_copy = Array.new(calc.stack) | |
begin | |
# prompt for (and get) user input | |
print "> " | |
input = readline | |
# feed the input to the calculator, splitting on white-space | |
calc.calculate input.split | |
rescue EOFError | |
# the user hit ctrl-D and wants to quit. | |
break | |
rescue StandardError => e | |
# the calculator signaled an error, so we should print it out | |
# and roll back the stack. | |
puts "Error: #{e}" | |
calc.stack = stack_copy | |
end | |
# print out the stack to be friendly | |
calc.print_stack | |
end | |
end | |
# annnnnnddddd...... GO! | |
repl |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment