Re: [SPOILER] Solution for QotW #25 (RPN calculator) 2 edition
Alex Smolianinov <[email protected]> Thu, 07 Oct 2004 18:28:11 +0400
| Newsgroups | gmane.comp.lang.perl.qotw.discuss |
|---|---|
| Message-ID | <[email protected]> |
There are some bug fixes and some new features. And no, there are no user functions in my calculator. Sory :)
Implemented:
- * + - / % ** ^ | & ~
- sin(), cos(), abs(), sqrt(), ...
- swap drop clear dup
- roll rold
New:
- 0x/0/0b prefixes
- hex/oct/bin/dec modes
- n float - set precision mode
- n sum - sumarize n top elements in stack
- N - number of elements in stack
- ... and others
And I realy think it's simple to add new operations in this implementation of rpn calculator.
#!/usr/local/bin/perl
# RPN calculator (QOTW #25) by Filin
$VERSION=0.03;
use strict;
use warnings;
# What is a number?
my $number_re = qr/^ (?!0\d) [+-]? (?: \d+(?:\.\d*)? | \.\d+ ) (?:[eE][+-]?\d+)? $/x;
my $hex_int_re = qr/^ 0x [\da-z]+ $/ix;
my $oct_int_re = qr/^ 0 [\d]+ $/ix;
my $bin_int_re = qr/^ 0b [\d]+ $/ix;
my $nondec_re = qr/^$hex_int_re|$oct_int_re|$bin_int_re$/;
# For error handling:
my $not_enough_msg = "Not enough elements in stack\n";
my @stack; # the stack. ;)
my $pmode = "%s"; # printf mode
my @warns;
sub pi() {4*atan2 1,1} # the pi constant
##################################### Definitions of operations:
sub perl_infix($);
sub function($$); # unfortunately '&$' is ill-bred
my %ops = (
# just perl binary operators:
'*' => perl_infix '*',
'+' => perl_infix '+',
'-' => perl_infix '-',
'/' => perl_infix '/',
'%' => perl_infix '%',
'**' => perl_infix '**',
'|' => perl_infix '|',
'&' => perl_infix '&',
'^' => perl_infix '^',
# 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),
int => (function sub{int shift}, 1),
abs => (function sub{abs shift}, 1),
exp => (function sub{exp shift}, 1),
log => (function sub{log shift}, 1),
sin => (function sub{sin shift}, 1),
cos => (function sub{cos shift}, 1),
# some trigonometry. Math::Trig may be better, but what about comlex numbers?
atan2 => (function sub{atan2 shift, shift}, 2),
pi => sub{push @stack, pi},
deg2rad => (function sub{pi*shift()/180}, 1),
rad2deg => (function sub{180*shift()/pi}, 1),
# 4 standart stack manipulation commands:
drop => (function sub{return }, 1),
dup => (function sub{(shift) x 2 }, 1),
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{no warnings; 1 + pop @stack}),
roll => (function sub{push (@_, shift @_); @_}, sub{no warnings; 1 + pop @stack}),
ndrop => (function sub{return}, sub{pop @stack}), # drops last (top) n elements
sum => (function sub{ # sumarize last n elements
my $sum; for (@_) {$sum+=$_};
return $sum
}, sub{pop @stack}),
N => sub{push @stack, scalar @stack}, # number of elements in stack
# printf modes:
hex => sub{$pmode="%#x"},
oct => sub{$pmode="%#o"},
bin => sub{$pmode="%#b"},
dec => sub{$pmode="%s"},
float => sub{
my $prec=int pop @stack;
die $not_enough_msg if not defined $prec;
die "Wrong precision\n" if $prec<0;
$pmode="%.${prec}f";
},
);
# 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 !defined($arity) or @stack<$arity;
die "Wrong arity\n" if $arity<=0;
my @args; for (1..$arity) {unshift @args, pop @stack}
my @res = &$fun(@args);
push @stack, @res;
return @args;
}
}
# 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 "> ";
}
print "\n";
sub calculate {
my (@toks) = @_;
my @oldstack=@stack;
local $@; # there are no error yet
for my $tok (@toks) {
if ($tok=~m/$nondec_re/) {
no warnings qw(portable overflow);
local $SIG{__WARN__}=sub{$@=shift};
push @stack, oct $tok;
}
elsif ($tok=~m/$number_re/) { push @stack, $tok+0; }
elsif ($ops{$tok}) { eval { $ops{$tok}->() }; }
else { $@ = "Undefined operation $tok\n"; }
if ($@) {
$@=~s/at .* line \s+ \d+.*//x;
push @warns, $@;
die "Error: $@" if $@ eq $not_enough_msg;
}
}
@stack=@oldstack if $@; # rollback if error
}
sub print_stack {
for (my $i=0; $i<@stack; $i++) {
printf(' ' . ($#stack-$i) . ": $pmode\n", $stack[$i]);
}
while (my $w=pop @warns) {warn $w}
}