Re: [SPOILER] Re: Perl Quiz of the Week #25 (RPN calculator)
"Peter Haworth" <[email protected]> Wed, 6 Oct 2004 13:52:49 +0100
| Newsgroups | gmane.comp.lang.perl.qotw.discuss |
|---|---|
| Message-ID | <[email protected]> |
On Tue, 5 Oct 2004 13:01:28 -0400, Mark Jason Dominus wrote:
> On Oct 5, 2004, at 12:42 PM, Peter Haworth wrote:
> >
> > I'm thinking about allowing user-defined functions, but not being
> > a Forth programmer, I'm not sure how the syntax ought to look.
>
> In PostScript you define a procedure by doing something like:
>
> { 2 mul } /double def
>
> After processing the '}' operator, the stack contains an anonymous
> function. '/double' pushes the "name object" representing the name
> "double", and then "def" is a binary operator which expects a name
> and a value and associates the two in the current dictionary.
OK, that's nice and clean. Here's a solution which implements that,
plus a "cond" operator, which evaluates one of two anonymous functions
depending on the value of its third argument.
#!/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,@tokens);
%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;
}],
# Function definition
def => [2, sub{
my($sub,$name)=splice @stack,-2;
ref($sub) && $name=~/\A[a-zA-Z_]/
or die "def operator expects an anonymous function and a name as arguments\n";
$ops{$name}=[0,sub{
unshift @tokens,@$sub;
}];
}],
# Conditional
cond => [3,sub{
my($true,$false,$cond)=splice @stack,-3;
!$true || ref($true) and !$false || ref($false)
or die "cond operator expects two anonyous functions and a true/false value\n";
my $func=$cond ? $true : $false;
unshift @tokens,@$func if $func;
}],
);
print '> ';
my @compile;
while(<>){
@tokens=split;
while(@tokens){
my $tok=shift @tokens;
if(ref $tok){
push @stack,$tok;
}elsif($tok eq '{'){
push @compile,scalar @stack;
}elsif($tok eq '}'){
@compile
or die "No '{' seen before '}'\n";
my $mark=pop @compile;
my @code=splice @stack,$mark;
push @stack,\@code;
}elsif(@compile){
push @stack,$tok;
}elsif($tok=~/\A\/([a-zA-Z_]\w*)\z/){
push @stack,$1;
}elsif($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){
my $val=$stack[$i];
my $pos=$#stack-$i;
print "$pos: ";
if(ref $val){
print "func\n";
}elsif($val=~/\A[\d-]/){
printf "$ofmt\n",$val;
}else{
print "word: $val\n";
}
}
print '> ';
}
print "\n";
--
Peter Haworth [email protected]
"I even make off-by-one errors counting on my fingers.
No wonder I can't debug my programs :-)"
-- Nathan Torkington