Anonymous Monk has asked for the wisdom of the Perl Monks concerning the following question:

My buddy wrote a simple calculator utility using bash. I'm trying to duplicate his effort in perl. Unfortunately I'm stuck. I have the following values stored:

$ARGV[0] = 1 $ARGV[1] = + $ARGV[2} = 3 I want to add $ARGV[0] and $ARGV[2]. How can I get perl to see $ARGV[1] as an operator and not a string?

I haven't seen anything covering this in my copy of Learning Perl, and to be honest I'm not even sure where to look in the camel book. Any non-RTFM help is appreciated. Even RTFM is fine as long as you point me towards the appropriate section of the manual.

Replies are listed 'Best First'.
Re: extract an operator from a string
by samtregar (Abbot) on Jun 01, 2002 at 02:02 UTC
    Of course eval() would work, but it's slow, insecure and boring. I would probably do it with a table of operations stored in a hash:

    #!/usr/bin/perl -w use strict; my %ops = ( '+' => sub { $_[0] + $_[1] }, '-' => sub { $_[0] - $_[1] }, '*' => sub { $_[0] * $_[1] }, '/' => sub { $_[0] / $_[1] } ); print $ops{$ARGV[1]}->($ARGV[0], $ARGV[2]), "\n";

    Of course, if you want to support more than just a single binary operation you'll need to parse the expression. For that I'd use Parse::RecDescent. In fact - I did! You can check out HTML::Template::Expr for a rather elaborate example of this technique.

    -sam

Re: extract an operator from a string
by vladb (Vicar) on Jun 01, 2002 at 00:31 UTC
    How about this:
    my ($a, $op, $b) = @ARGV; my $res = eval("$a $op $b"); print "$a $op $b = $res\n";
    Simply using the 'eval' command to do the job? ;)

    _____________________
    $"=q;grep;;$,=q"grep";for(`find . -name ".saves*~"`){s;$/;;;/(.*-(\d+) +-.*)$/; $_=["ps -e -o pid | "," $2 | "," -v "," "];`@$_`?{print"+ $1"}:{print" +- $1"}&&`rm $1`; print$\;}
Re: extract an operator from a string
by broquaint (Abbot) on Jun 01, 2002 at 00:32 UTC
    You could use eval()
    my $res = eval "@ARGV";
    But it would be infintely safer if you just parsed the string as an optree and executed it appropriately. If you look around the Monastery you'll find many examples of this sort of situation.
    HTH

    _________
    broquaint

Re: extract an operator from a string
by monkfish (Pilgrim) on Jun 01, 2002 at 05:01 UTC
    This is not really an answer to your question, (and perhaps this would be better posted to snippets) but here is the csh alias I wrote ages ago to be my command line calculator, that I thought you might find useful.

    alias calc perl -e 'print (!*) ; '

    Usage:

    calc 7+7
    or
    calc (24+11)*(8**2)

    -monkfish (The Fishy Monk)

Re: extract an operator from a string
by mstorti (Initiate) on Jun 02, 2002 at 03:16 UTC
    I have this calculator code I found very useful. Some of it I have taken from somewhere else. I added some documentation and the $ans variable. Regards. Mario
    #!/usr/bin/perl #$Id: calcme,v 1.4 2001/10/04 18:00:57 mstorti Exp $ $input=shift; # require "$ENV{'HOME'}/perl/math.pl"; if ($input eq "-h") { print <<'EOM'; CALCME: a simple (Perl based) line calculator usage : * Compute an expression (remember to enclose <expression> in quotes or + double quotes in order to avoid shell expansion ) $ calc <expression> <result> * Interactive use: $ calc > <expression> <result> > <expression> <result> ... Can define variables as in; > $pi=atan(1,0)*2 > $pi=atan2(1,0)*2 3.14159265358979 > $e=exp(1) 2.71828182845905 > $a=$pi*$e**2 23.2134043573634 > * Get help (prints this message): $ calc -u EOM goto EXIT; } @ans=(); do {print "> ",eval $input,"\n"; goto EXIT; } if $input; print "> "; while (<STDIN>) { print $ans=eval(),"\n> "; unshift @ans,$ans; } EXIT: ;
    Some useful mathematical routines are in "math.pl". If you want to use it uncomment the line and put it somewhere.
    # # Some useful definitions for use with ePerl # $PI=2*atan2(1.,0.); $E=exp(1.); sub round { my $x = shift(); my $y = shift(); $y = 1 unless $y; return floor($x/$y + 0.5); } sub floor { my $x = shift(); my $y = shift(); $y = 1 unless $y; my $rr = $x/$y; my $r = int($rr); $r-- if $rr<0; return $r; } sub ceil { return floor(@_)+1; } sub asin { my $x=shift(); return atan2($x,sqrt(1-$x*$x)); } sub acos { my $x=shift(); return atan2(sqrt(1-$x*$x),$x); } sub sinh { my $x=shift(); return (exp($x)-exp(-$x))/2.; } sub cosh { my $x=shift(); return (exp($x)+exp(-$x))/2.; } sub tanh { my $x=shift(); return sinh($x)/cosh($x); } sub acosh { my $y=shift(); die "acosh: argument must be >1!\n" if $y<1; return log($y+sqrt($y*$y-1)); } sub asinh { my $y=shift(); return log($y+sqrt($y*$y+1.)); } sub atanh { my $y=shift(); die "atanh: argument x must be |x| < 1.\n" if abs($y)>=1; return log((1+$y)/(1-$y))/2.; } sub asin { my $y=shift(); return atan2($y,sqrt(1-$y**2)); } sub log10 { my $y=shift(); return log($y)/log(10.); } # Returns a number floored to a grid of the form 1,2,5,10,20,50, etc.. +. # The grid may be changed. # # Usage: log_floor($x) - floors to the standard grid 1,2,5 +,10,... # log_floor($x,'fine') - floors to a fine grid 1,1.5,2,3,4 +,5,7,10,... # log_floor($x,[1,1.5,2,3,4,5,6,7,8,9,10]) - floors to a use +r defined grid # the grid starts + in 1 and ends in 10. sub log_floor { my $x = shift(); return $x if $x==0; my $sign=1; if ($x<0) { $sign=-1; $x = -$x; } $log10 = log($x)/log(10.); $expo = floor($log10); $man = 10**($log10-$expo); my $grid = shift(); $grid = [1,1.5,2,3,4,5,7,10] if $grid eq 'fine'; $grid = [1,2,4,8,10] if $grid eq 'binary'; $grid = [1,2,5,10] unless $grid; # default grid for($j=1; $j<=$#{$grid}; $j++) { $r = $grid->[$j]; return $sign*$grid->[$j-1]*(10**$expo) if $man<$r; } } # Returns a number ceiled to a grid of the form 1,2,5,10,20,50, etc... # The grid may be changed. # # Usage: see log_floor # sub log_ceil { my $x = shift(); return $x if $x==0; my $sign=1; if ($x<0) { $sign=-1; $x = -$x; } $log10 = log($x)/log(10.); $expo = floor($log10); $man = 10**($log10-$expo); my $grid = shift(); $grid = [1,1.5,2,3,4,5,7,10] if $grid eq 'fine'; $grid = [1,2,4,8,10] if $grid eq 'binary'; $grid = [1,2,5,10] unless $grid; # default grid foreach $r (@{$grid}) { return $sign*$r*(10**$expo) if $man<$r; } } sub refine { $nup=shift(); my @nu=@{$nup}; my $nu1=shift(); my $nu2=shift(); if ($#nu==-1) { $nu=$nu1; # print "paso por 1\n"; } elsif ($#nu==0) { $nu=$nu2; # print "paso por 2\n"; } else { for ($k=0;$k<$#nu;$k++) { # print "k: $k\n"; $dif=$nu[$k+1]-$nu[$k]; # print "dif: $dif\n"; if ($k==0 || $dif>$difmax) { $difmax=$dif; $kmin=$k; } } # print "intervalo minimo entre ",$nu[$kmin]," y ",$nu[$kmin+1],"\n +"; $nu=($nu[$kmin]+$nu[$kmin+1])/2; } push @nu,$nu; @nu = sort @nu; @{$nup}=@nu; return $nu; } sub max { my $maxx = $_[0]; for (my $j=1; $j<$#_; $j++ ) { $maxx = $_[$j] if $_[$j]>$maxx; } return $maxx; } sub min { my $minn = $_[0]; for (my $j=1; $j<$#_; $j++ ) { print "minn: cur val: $_[$j]\n"; $minn = $_[$j] if $_[$j]<$minn; } return $minn; } 1;
Re: extract an operator from a string
by Starky (Chaplain) on Jun 02, 2002 at 05:48 UTC
    print `echo "@ARGV" | bc -l`;

    And no, I'm not being serious ;-)

Re: extract an operator from a string
by Anonymous Monk on Jun 03, 2002 at 14:58 UTC
    You could use the eval function:
    my $calc="1+2"; print eval ($calc) ."\n";
    Prints 3. (eval will execute any perl code passed to it, so you should read up on taint checking and the -T switch).