[SPOILER] Re: Perl Quiz of the Week #25 (RPN calculator)

"Peter Haworth" <[email protected]> Tue, 5 Oct 2004 17:42:17 +0100
Newsgroups gmane.comp.lang.perl.qotw.discuss
Message-ID <[email protected]>
This is a pretty simple solution, which holds operator definitions
in a hash. It implements all of the extensions from the original
quiz specification.

I'm thinking about allowing user-defined functions, but not being a
Forth programmer, I'm not sure how the syntax ought to look. Some of
the other submitted solutions which implement this don't look
"reverse" enough to me to fit with the rest of the language.


#!/usr/bin/perl

use strict;
$|=1;

my @stack;

# Helper routines for building the operator hash
sub unary{
  my($op)=@_;
  $op => [1,eval "sub{ \$stack[-1]= $op \$stack[-1]; }"];
}
sub binary{
  my($op)=@_;
  $op => [2,eval "sub{ my \$op2=pop \@stack; \$stack[-1] $op= \$op2; }"];
}

my $ofmt=my $dec_ofmt='%.6g';
my %ops=(
  # Standard ops
  map(binary($_),qw(+ - * / % **)),
  dup   => [1, sub{ push @stack,$stack[-1]; }],
  swap  => [2, sub{ @stack[-1,-2]=@stack[-2,-1]; }],
  drop  => [1, sub{ pop @stack; }],
  clear => [0, sub{ @stack=(); }],

  # additional perl ops
  map(binary($_),qw(& | atan2)),
  map(unary($_),qw(~ abs int cos sin exp log sqrt)),

  # display modes
  dec => [0,sub{ $ofmt=$dec_ofmt; }],
  bin => [0,sub{ $ofmt='0b%b'; }],
  oct => [0,sub{ $ofmt='0%o'; }],
  hex => [0,sub{ $ofmt='0x%x'; }],

  # N roll
  roll  => [1, sub{
    my $n=pop @stack; 
    $n>=0
      or die "roll operator requires a non-negative roll size\n";
    @stack>$n
      or die "roll operator requires a stack depth greater than the roll size\n";
    my $val=splice @stack,-$n-1,1;
    push @stack,$val;
  }],
  # N rolld
  rolld => [1, sub{
    my $n=pop @stack;
    $n>=0
      or die "rolld operator requires a non-negative roll size\n";
    @stack>$n
      or die "rolld operator requires a stack depth graeater than the roll size\n";
    my $val=pop @stack;
    splice @stack,-$n-1,0,$val;
  }],
);

print '> ';
while(<>){
  for my $tok(split){
    if($tok=~/\A0o?(.+)\z/){
      my $oct=$1;
      $oct=~/\A[0-7]+\z/
        or die "Invalid octal number: $tok\n";
      push @stack,oct $oct;
    }elsif($tok=~/\A0x(.+)\z/){
      my $hex=$1;
      $hex=~/\A[0-9a-f]+\z/i
        or die "Invalid hex number: $tok\n";
      push @stack,hex $hex;
    }elsif($tok=~/\A0b(.+)\z/){
      my $bin=$1;
      $bin=~/\A[01]+\z/
        or die "Invalid binary number: $tok\n";
      push @stack,oct $tok;
    }elsif($tok=~/\A-?\d+\z/){ # Only integers ATM
      push @stack,$tok;
    }elsif(my $op=$ops{$tok}){
      my($depth,$sub)=@$op;
      my @args;
      @stack>=$depth
        or die "$tok operator requires a stack depth of at least $depth\n";
      
      $sub->();
    }else{
      die "Unrecognised token: $tok\n";
    }
  }
  for my $i(0..$#stack){
    printf "%d: $ofmt\n",$#stack-$i,$stack[$i];
  }
  print '> ';
}
print "\n";


-- 
	Peter Haworth	[email protected]
"Fortunately, I know nothing about C++."
	-- Sian Leitch