[Spoiler] RPN Calculator, Take 2

Rod Adams <[email protected]> Fri, 01 Oct 2004 15:10:13 -0500
Newsgroups gmane.comp.lang.perl.qotw.discuss
Message-ID <[email protected]>
Well, putting more than 20 minutes into it, I came up with the solution 
below.

It fixes the atan2 issue Pr. West made me aware of. Also added the 
Math::Trig functions I mentioned the first time.

Biggest change: added support for complex numbers via Math::Complex.

-- Rod


#!/usr/bin/perl
use Math::Complex;
use Math::Trig;

 
sub spop {
  return pop @stack if @stack;
  print "Stack Underflow\n";
  return undef;
}

sub spushe {
  my $code = $_[0];
  my @x = eval $code;
  if ($@) {
    print "Error : $@\n";
  } else {
    push @stack, @x;
  }
}
 
@stack = ();

%cmds =
  (atan2   =>('defined($y = spop()) or last; defined($x = spop()) or 
last; ' .
          'spushe "atan2 \$x, \$y";'),

   drop    => 'defined(spop()) or last;',

   swap    =>('defined($y = spop()) or last; defined($x = spop()) or 
last; ' .
              'push @stack, $y, $x;'),

   clear   => '@stack = ();',

   dup     =>('defined($x = spop()) or last; ' .
              'push @stack, $x, $x;'),

   rolld   =>('defined($n = spop()) or last; ' .
          'if ($n > @stack) { print "Stack Underflow\n"; last; } ' .
          'defined($x = spop()) or last; ' .
          'splice(@stack, -$n, 0, $x); '),

   roll    =>('defined($n = spop()) or last; ' .
          'if ($n > @stack) { print "Stack Underflow\n"; last; } ' .
          'push @stack, splice(@stack, -$n-1, 1);'),
   );

@cmds{qw{ + - / * % ** << >> & ^ | }} = ('binary') x 11;

@cmds{qw{ ++ -- sin cos abs int exp log sqrt},
      qw{ tan csc sec cot asin acos atan acsc asec acot sinh cosh tanh },
      qw{ csch sech coth asinh acosh atanh acsch asech acoth }
    } = ('unary')  x (9+13+9);

@cmds{qw{ exit rand pi }} = ('func') x 3;

print "> ";
while (<>) {
  chomp;
  my @cmds = split;
  while (@cmds) {
    my $cmd = lc shift(@cmds);
   
    # push a number
    if (($cmd =~ m{^    ( [+-]?\d+ (?:\.\d*)? (?:e[+-]?\d+)? )
               (( [+-] \d* (?:\.\d*)? (?:e[+-]?\d+)? )i )? $ }ix ||
     $cmd =~ m{^() (( [+-]?\d* (?:\.\d*)? (?:e[+-]?\d+)? )i )  $ }ix )
    && length($cmd) > 0 ) {
      my $real = 0+$1;
      my $imag = $3;
      $imag = 1 if $2 =~ /^[+-]?i$/i;
      push @stack, Math::Complex->make($real, $imag)+0;
      next;
    }


    if (!$cmds{$cmd}) {
      print "Bad Command: $cmd\n";
      last;
    }

    if ($cmds{$cmd} eq 'binary') {
      defined($y = spop()) or last;
      defined($x = spop()) or last;
      spushe "\$x $cmd \$y";
      next;
    }
   
    if($cmds{$cmd} eq 'unary') {
      defined($x = spop()) or last;
      spushe "$cmd \$x";
      next;
    }

    if($cmds{$cmd} eq 'func') {
      spushe $cmd;
      next;
    }
       
    eval $cmds{$cmd};
  }

  # print stack
  for ($i = 0 ; $i <= $#stack  ; ++$i) {
    print $#stack-$i, ': ', $stack[$i], "\n";
  }
  print "> ";
}

 
__END__