Skip to content

Instantly share code, notes, and snippets.

@laynor
Created January 6, 2013 14:33
Show Gist options
  • Save laynor/4467556 to your computer and use it in GitHub Desktop.
Save laynor/4467556 to your computer and use it in GitHub Desktop.
### Simple RPN Calculator
## supported number types: integer, float builtin, extensible
## supported operators : +, *, -, / builtin, extensible
##
##
class Rpn
## operator symbol -> proc
@@operators = {}
## types: [[read_syntax, string_to_type_conversion_proc] ..]
@@types = []
## type1 -> type1_to_type2_simplification_proc (ex, Rational -> 4/1 -> 4)
@@simplification = {}
def initialize
@stack = []
## line to print
header_title = "Stack"
ndashes = (80 - header_title.length - 2)/2
@header = "#{'-'*ndashes} #{header_title} #{'-'*ndashes}"
end
def self.divide(x, y)
if (x.is_a? Integer) && (y.is_a? Fixnum)
Rational(x, y)
else
x/y
end
end
def self.defopalias(op, op_alias)
@@operators[op_alias] = @@operators[op]
end
def self.defstackop(op, &block)
@@operators[op] = block
end
def self.defop(op, &block)
defstackop(op) do |stack|
operands = stack.pop block.parameters.length
res = block.call(*operands)
case res
when Array
stack.concat res
else
stack.push res
end
end
end
def self.datatype(klass, read_syntax, conversion_method, simplification_method=nil)
@@types.push [read_syntax, conversion_method]
@@simplification[klass] = simplification_method
end
def self.read(str)
type_spec = @@types.find { |ts| str =~ ts[0] }
type_spec[1].call(str) if type_spec
end
defop(:+) { |x, y| x + y }
defop(:*) { |x, y| x * y }
defop(:/) { |x, y| divide(x, y) }
defop(:-) { |x, y| x - y }
def simplify(number)
sm = @@simplification[number.class]
if sm
sm.call(number)
else
number
end
end
def eval(str)
str.split(/\s+/).each do |token|
x = Rpn.read(token)
if x
@stack.push x
elsif @@operators[token.to_sym]
op = @@operators[token.to_sym]
old_stack = @stack.clone
begin
op.call(@stack)
rescue StandardError => err
@stack = old_stack
puts "ERROR: #{err}"
end
@stack.map! { |number| simplify(number) }
else
puts "Unrecognized operand or operator: #{token}"
end
end
puts @header
@stack.each_index do |i|
print "#{@stack.length - i}: "
p @stack[i]
end
end
end
IntRE = /\A\d+\Z/
FloRE = /\A(\d*\.\d+)|(\d+\.\d*)\Z/
class IncompatibleSizeError < StandardError
end
class Array1D
attr_accessor :a
def initialize(array)
@a = array
end
def +(y)
raise IncompatibleSizeError("Cannot add different sized arrays.") if y.a.length != @a.length
Array1D.new(@a.zip(y.a).map { |a, b| a + b })
end
def -@
Array1D.new(@a.map { |x| -x })
end
def -(y)
self + (-y)
end
def to_s
@a.to_s.sub!('[', '(').sub(']',')')
end
end
class Rpn
defop(:^) { |x, y| x ** y }
defop(:neg) { |x| -x }
defstackop(:drop) { |stack| stack.pop }
defopalias(:drop, :d)
defop(:swap) { |x, y| [y, x] }
defop(:dup) { |x| [x, x] }
defstackop(:clear) { |stack| stack.clear }
datatype(Integer, IntRE, lambda { |str| str.to_i })
datatype(Float, FloRE, lambda { |str| str.to_f })
datatype(Rational, /\A\d+\/\d\Z/, lambda { |str| str.to_r },
lambda { |rat| (rat.denominator == 1) ? rat.numerator : rat })
datatype(Array1D, /\(.*\)/,
lambda do |str|
out = []
tokens = str[(1..str.length - 2)].split(",")
tokens.each do |token|
x = read(token)
out << x
end
Array1D.new(out)
end)
[:acos, :asin, :tan, :atan, :sqrt, :sin, :exp].each { |fn_name|
defop(fn_name) { |x| Math.send fn_name, x }
}
end
rpn = Rpn.new
loop do
print "RPN> "
str = gets
break if str == "q" || str.nil?
rpn.eval str
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment