James Gray
9/30/2004 11:43:00 PM
On Sep 30, 2004, at 5:55 PM, Andreas Schwarz wrote:
> Hello,
>
> it's really nice, however there is one problem: if a define contains a
> syntax error, the calculator quits instead of just showing the error
> message.
>
> Andreas
I noticed that about the same time you posted this. It's fixed in the
version below.
Did I mention it has display modes? Type "bin", or "hex". :)
I know, it's a toy. A fun one though...
James Edward Gray II
#!/usr/bin/env ruby
# class for generating RPN Calculator objects
class RPNCalc
attr_accessor :mode # defines methods mode() and mode = ...
# handles setup after constructor
def initialize( mode = "dec", stack = [ ] )
@mode = mode
@stack = stack
@ops = { }
end
### Stack manipulation methods ###
# primary input method, adds something to stack if it's numerical
def push( number )
if number.kind_of? Numeric # don't touch non-numbers
# the following if converts floats to ints, when it doesn't matter
if number.kind_of?(Float) and number == number.to_i
@stack.unshift( number.to_i )
else
@stack.unshift( number )
end
return top # return top value, what we just added
else
return nil
end
end
# pop from stack
def drop( ) return unary { nil } end
# swap top two elements from stack
def swap( )
return binary do |l, r|
push( r )
l
end
end
# empty stack
def clear( ) return @stack = [ ] end
def dup( )
return unary do |num|
push( num )
num
end
end
# arbitrary stack position swap (up)
def roll( )
if @stack.size < top
raise "Insufficient elements on stack for operation."
end
return unary { |num| @stack.delete_at(num) }
end
# arbitrary stack position swap (down)
def rolld( )
if @stack.size < top
raise "Insufficient elements on stack for operation."
end
return binary do |num, n|
@stack.insert(n, num)
nil
end
end
### Basis math methods ###
def add( ) return binary { |l, r| l + r } end
def sub( ) return binary { |l, r| l - r } end
def mul( ) return binary { |l, r| l * r } end
def mod( ) return binary { |l, r| l % r } end
def pow( ) return binary { |l, r| l ** r } end
def div( )
return binary do |l, r|
# defeat Ruby's integer division
if l.integer? and r.integer? and l % r != 0
l / r.to_f
else
l / r
end
end
end
### To int methods ###
def floor( ) return unary { |num| num.floor } end
def ceil( ) return unary { |num| num.ceil } end
def round( ) return unary { |num| num.round } end
### Higher math methods ###
def abs( ) return unary { |num| num.abs } end
def sqrt( ) return unary { |num| Math.sqrt( num ) } end
def exp( ) return unary { |num| Math.exp( num ) } end
def log( ) return unary { |num| Math.log( num ) } end
def log10( ) return unary { |num| Math.log10( num ) } end
def sin( ) return unary { |num| Math.sin( num ) } end
def cos( ) return unary { |num| Math.cos( num ) } end
def tan( ) return unary { |num| Math.tan( num ) } end
def sinh( ) return unary { |num| Math.sinh( num ) } end
def cosh( ) return unary { |num| Math.cosh( num ) } end
def tanh( ) return unary { |num| Math.tanh( num ) } end
def asin( ) return unary { |num| Math.asin( num ) } end
def acos( ) return unary { |num| Math.acos( num ) } end
def atan( ) return unary { |num| Math.atan( num ) } end
def asinh( ) return unary { |num| Math.asinh( num ) } end
def acosh( ) return unary { |num| Math.acosh( num ) } end
def atanh( ) return unary { |num| Math.atanh( num ) } end
def atan2( ) return binary { |l, r| Math.atan2( l, r ) } end
### Bitewise manipulation methods ###
# Warning: These methods convert their operands to ints before
operation
def bit_and( ) return binary { |l, r| l.to_i & r.to_i } end
def bit_or( ) return binary { |l, r| l.to_i | r.to_i } end
def bit_xor( ) return binary { |l, r| l.to_i ^ r.to_i } end
def bit_neg( ) return unary { |num| ~num.to_i } end
### Operator definition methods ###
# adds Ruby code as new operator
def define_op( op, &code )
@ops[op] = code
end
### Input/Output methods ###
# parses and executes expressions in postfix notation
# also understands "def OP RUBY_PROC_CODE" on it's own line
def calc( postfix_exp )
if postfix_exp =~ /^\s*def\s+(\S+)\s+(\{.+\})\s*$/ # define new op
define_op( $1.downcase, &eval( "proc #{$2}" ) )
else # ... or process terms
terms = postfix_exp.downcase.split(" ")
terms.each do |t|
if @ops.include? t # use custom op definition
if @ops[t].arity == 2 # choose handler by proc args
binary &@ops[t]
else
unary &@ops[t]
end
next
end
case t # ... or hardcoded definition
when "+" then add
when "-" then sub
when "*" then mul
when "%" then mod
when "**" then pow
when "/" then div
when "&" then bit_and
when "|" then bit_or
when "^" then bit_xor
when "~" then bit_neg
when /^-?(?:\d[,_\d]*|0[,_0-7]+|0x[,_0-9a-f]+|0b[,_01]+)$/,
/^-?\d[,_\d]*\.\d[,_\d]*(?:e-?[,_\d]+)?$/
push( eval( t.tr(",", "") ) )
when "drop", "swap", "clear", "dup", "roll", "rolld",
"floor", "ceil", "round",
"abs", "sqrt",
"exp", "log", "log10",
"sin", "cos", "tan", "sinh", "cosh", "tanh",
"asin", "acos", "atan", "asinh", "acosh", "atanh",
"atan2"
send( t.to_sym )
when "bin", "dec", "hex", "oct"
@mode = t
else
raise "Invalid term: #{t}."
end
end
end
return top
end
# getter for top of stack
def top( ) return @stack[0] end
# primary output method, display stack with optional limit
def to_s( limit = @stack.size )
if @stack.size == 0
return "Empty Stack\n"
else
case @mode # support four types of numerical display
when "bin"
return (0...limit).to_a.reverse.inject("") do |str, i|
str + "#{i}: #{'%b' % @stack[i]}\n"
end
when "dec"
return (0...limit).to_a.reverse.inject("") do |str, i|
str + "#{i}: #{@stack[i]}\n"
end
when "hex"
return (0...limit).to_a.reverse.inject("") do |str, i|
str + "#{i}: #{'%x' % @stack[i]}\n"
end
when "oct"
return (0...limit).to_a.reverse.inject("") do |str, i|
str + "#{i}: #{'%o' % @stack[i]}\n"
end
end
end
end
private
### Operator processing methods ###
def unary( &op )
if @stack.size < 1
raise "Insufficient elements on stack for operation."
end
return push( op.call( @stack.shift ) )
end
def binary( &op )
if @stack.size < 2
raise "Insufficient elements on stack for operation."
end
right, left = @stack.slice!(0, 2)
return push( op.call( left, right ) )
end
end
# basic stand-alone interface, just process lines and show the stack...
if __FILE__ == $0
rpn = RPNCalc.new
print "\n" + rpn.to_s + "\n"
print "> " if STDIN.tty?
while line = ARGF.gets
line.chomp!
if STDIN.tty? and line =~ /^q(?:uit)?|exit$/i
break
else
begin
rpn.calc line
rescue SyntaxError
puts "Syntax Error: " + $!
rescue
puts "Error: " + $!
end
print "\n" + rpn.to_s + "\n"
print "> " if STDIN.tty?
end
end
end
__END__