[lnkForumImage]
TotalShareware - Download Free Software

Confronta i prezzi di migliaia di prodotti.
Asp Forum
 Home | Login | Register | Search 


 

James Gray

9/30/2004 10:24:00 PM

Perl's Quiz of the Week this time around was to make an RPN calculator.
If you're one of those people who has fond memories of HP's RPN
calculators, you might want to play with this code a little. Just
thought I would share.

It understand postfix notation. You can do:

3 <enter>
5 <enter>
+ <enter>

or

3 <space> 5 <space> + <enter>

It'll break it down term by term. It understands Ruby numbers like -3
and 0b101_010 and even allows commas in them. It supports most of
Ruby's math routines and even better, lets you define your own with:

def <space> OPERATOR <space> PROC_CODE <enter>

Those must be on their own line. Example:

def avg { |left, right| (left + right) / 2.0 }

It'll know that's a binary operator because it takes two args.
Anything else is considered unary.

If anyone has any use for this, just tell me and I'll throw it on the
RAA. I was just having a little flashback fun.

Enjoy.

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
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
puts "Error: " + $!
end
print "\n" + rpn.to_s + "\n"
print "> " if STDIN.tty?
end
end
end

__END__



3 Answers

Andreas Schwarz

9/30/2004 10:51:00 PM

0

James Edward Gray II wrote:
> Perl's Quiz of the Week this time around was to make an RPN calculator.
> If you're one of those people who has fond memories of HP's RPN
> calculators, you might want to play with this code a little. Just
> thought I would share.

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

--
http://www.mikrocont... - Das Mikrocontroller-Forum

James Gray

9/30/2004 11:43:00 PM

0

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__



Mark Probert

10/1/2004 2:06:00 AM

0

James Edward Gray II <james@grayproductions.net> wrote:
>
> If you're one of those people who has fond memories of HP's RPN
> calculators, you might want to play with this code a little. Just
> thought I would share.
>

Thank you :-)

Also, if you are interested in RPN, I have ported a FORTH to Ruby (just
because ;-)). You can find it at

http://raa.ruby-lang.org/projec...

All the RPN features you could want, plus more (ie Full Ruby).

Trivial e.g.

require 'Atlast'

t = Atlast.new
t.eval(": sqr ( n -- n*n ) dup * ;")
n = t.run("4 sqr .")

puts "4*4 = #{n}"

Regards,

--
-mark. (probertm @ acm dot org)