From: James Edward Gray II Date: 2004-10-01T08:42:37+09:00 Subject: Re: RPN Fun 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__