Skip to content

Instantly share code, notes, and snippets.

@thomcc
Created February 8, 2012 01:51
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 thomcc/1764233 to your computer and use it in GitHub Desktop.
Save thomcc/1764233 to your computer and use it in GitHub Desktop.
Reverse Polish Notation Calculator
### 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