Re: [SPOILER] Solution for QotW #25 (RPN calculator)
Alex Smolianinov <[email protected]> Mon, 04 Oct 2004 13:55:40 +0400
| Newsgroups | gmane.comp.lang.perl.qotw.discuss |
|---|---|
| Message-ID | <[email protected]> |
There are all extensions except hex/oct/bin, and i think it's easy to implement
more.
#!/usr/local/bin/perl
# RPN calculator (QOTW #25) by Filin
$VERSION=0.02;
use strict;
use warnings;
our $Verbose=0; # will print some messages if =1
# What is a number? (Numbers: 1, -1, 1.1, 0.1, -.1, 1.e10, -0.1E-100)
my $number_re = qr/^ [+-]? (?: \d+(?:\.\d*)? | \.\d+ ) (?:[eE][+-]?\d+)? $/x;
# For error handling:
my $not_enough_msg = "Not enough elements in stack\n";
my @stack; # the stack. ;)
##################################### Definitions of operations:
sub perl_infix($);
my %ops = (
# just perl binary operators:
'*' => perl_infix '*',
'+' => perl_infix '+',
'-' => perl_infix '-',
'/' => perl_infix '/',
'%' => perl_infix '%',
'**' => perl_infix '**',
'|' => perl_infix '|',
'&' => perl_infix '&',
'^' => perl_infix '^',
# prefix unary operators and functions, but there are no special generator
# (TODO perl_unary ?)
'~' => function(sub{~shift}, 1),
'_' => function(sub{-shift}, 1), # unary minus
sqrt => function(sub{sqrt shift }, 1),
sin => function(sub{sin shift }, 1),
cos => function(sub{cos shift }, 1),
int => function(sub{int shift }, 1),
abs => function(sub{abs shift }, 1),
exp => function(sub{exp shift }, 1),
log => function(sub{log shift }, 1),
atan2 => function(sub{atan2 shift, shift}, 2),
# 4 standart stack manipulation commands:
drop => function(sub{return }, 1),
dup => function(sub{(shift) x 2 }, 2),
swap => function(sub{pop(), pop()}, 2),
clear => sub {@stack = ()}, # doesn't use generator => no arity check
# extended stack manipulation, (and there ara extended sintax for arity checks) :
rold => function(sub{unshift(@_, pop @_); @_}, sub{1+pop @stack}),
roll => function(sub{push (@_, shift @_); @_}, sub{1+pop @stack}),
ndrop => function(sub{return}, sub{pop @stack}), # drops last (top) n elements
);
# Common wrapper generator
# function(SUB, ARITY)
# Returns a sub which checks arity and manipulates on stack
# e.g.:
# function(sub{1}, 100) - pops 100 elements from stack and then push one
# ARITY can be a sub:
# function(sub{(1) x @_}, sub{scalar @stack} - fill all elements with '1'
sub function {
my ($fun, $arity) = @_;
return sub {
my $arity = ref($arity) eq 'CODE' ? &$arity : $arity;
die $not_enough_msg if @stack<$arity;
my @args; for (1..$arity) {unshift @args, pop @stack}
my @res = &$fun(@args);
print "[ @args ==> @res ]\n" if $Verbose;
push @stack, @res;
}
}
# Generator for standart perl binary operators
sub perl_infix($) {
my ($op) = @_;
function sub { my $res = eval "$_[0] $op $_[1]";
die $@ if $@;
return $res; }, 2;
}
##################################### The main input/calculate/output cycle and its procedures
print "RPN Calculator\n> ";
while (<>) {
chomp;
calculate(split);
print_stack();
print "> ";
}
sub calculate {
my (@toks) = @_;
my @oldstack=@stack;
local $@; # there are no error yet
for my $tok (@toks) {
if ($tok=~m/$number_re/) {
push @stack, $tok;
} elsif ($ops{$tok}) {
eval { $ops{$tok}->() };
} else {
$@ = "Undefined operation $tok\n";
}
if ($@) {
$@=~s/at .* line \s+ \d+.*//x;
die "Error: $@" if $@ eq $not_enough_msg; # XXX To die or not to die?
warn "$@";
}
}
@stack=@oldstack if $@; # rollback if error
}
sub print_stack {
for (my $i=0; $i<@stack; $i++) {
print ' ', $#stack-$i, ": $stack[$i]\n";
}
}