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

Hello Monks,

I have tried to find an answer on this issue for the past day or so. I saw a similar thread: Value of a scalar specified by a second scalar ,but still came out empty handed. My problem child is here:

chomp($exp = <STDIN>); $out = 'exp'; print $$out, "\n";

I'm not sure if referencing is the proper thing to do here since I am getting an "unitialized variable" here. The <STDIN> is an expression along the lines of:

(7 * 3) + (2 + 1)

I am trying to get this block to return the answer, which in this case is "24". When I just do:

print "$exp\n"

I get the expression back and not the answer. I was hoping I could get a few pointers on this.


thank you in advance.

Es gibt mehr im Leben als Bücher, weißt du. Aber nicht viel mehr. - (Die Smiths)

Replies are listed 'Best First'.
Re: Chomp a numerical expression, return the answer.
by blazar (Canon) on May 18, 2006 at 15:19 UTC

    You want eval. But... you don't want it!

    $ perl -lne "print eval" (7 * 3) + (2 + 1) 24

    Now, to convince you that you don't want it, think about

    "system 'rm -rf /'"

    You may be interested in Safe.pm, but that's been reported not to be bullet-proof either.

      or at the very least limit the input...

      Something like

      if($exp =~ /^[-+/*%()\s\d]+$/) { print eval $exp; }
      or something along those lines it would make it much harder to actually do anything underhanded.

      Exactly what taint mode encourages people to do.

                      - Ant
                      - Some of my best work - (1 2 3)

      If eval is as big of a No-No as "system 'rm -rf /' ", I think I better seek an alternative.
      I'll do the suggested and put a strict limit on the input via a regex.

      Es gibt mehr im Leben als Bücher, weißt du. Aber nicht viel mehr. - (Die Smiths)"
Re: Chomp a numerical expression, return the answer.
by Trix606 (Monk) on May 18, 2006 at 15:23 UTC
    Try this:
    chomp($exp = <STDIN>); $out = eval $exp; print $out, "\n";
    UPDATE: As blazar points out, while this code does what you want, know that it will blissfully run any valid Perl command. You might add some editing of your input before handing it to eval.

    Good Luck,

    Trix

Re: Chomp a numerical expression, return the answer.
by calin (Deacon) on May 18, 2006 at 16:14 UTC

    Check out the modules in Bundle::Math::Expression. They may be safer than a bare eval.

    DISCLAIMER: I have never used any of these modules and can't vouch on their safety.

Re: Chomp a numerical expression, return the answer.
by philcrow (Priest) on May 18, 2006 at 15:16 UTC
    You need the string form of eval to turn arithmetic expressions into numbers.

    Phil

      Or else just roll his own parser and evaluator. Oh, well, maybe there are ready-made ones around...

        In the case of eval, the "ready-made one" is perl.

            -Bryan

Re: Chomp a numerical expression, return the answer.
by samtregar (Abbot) on May 18, 2006 at 16:08 UTC
    If the above posters have convinced you that you don't want to use eval, you might take a look at the guts of HTML::Template::Expr. It implements a little expression language using Parse::RecDescent. You could take the parser and execution engine and cut it down to whatever sub-set makes sense for your problem.

    -sam

Re: Chomp a numerical expression, return the answer.
by TedPride (Priest) on May 18, 2006 at 19:17 UTC
    EDIT: I'm an idiot--. I don't know how I messed up the code that badly. Yes, order from left to right does matter (at least for multiplication / division). Here's the fixed version:
    use strict; use warnings; while (<DATA>) { chomp; print process($_), "\n"; } sub process { my $exp = $_[0]; 1 while $exp =~ s/\(([^(]*)\)/reduce($1)/eg; $exp = reduce($exp); $exp =~ s/\s+//g; return reduce($exp); } sub reduce { my $exp = $_[0]; 1 while $exp =~ s{(\d+)\s+([*/])\s+(\d+)} {$2 eq '*' ? $1*$3 : $1/$3}e; 1 while $exp =~ s{(\d+)\s+([+-])\s+(\d+)} {$2 eq '+' ? $1+$3 : $1-$3}e; return $exp; } __DATA__ (7 * 3) + (2 + 1) 4 * (5 / (3 + 2)) - 6 2 * 2 * 2 8 / 2 * 3

      Your code doesn't support 2 * 2 * 2 and returns the wrong answer for 8 / 2 * 3. Fix:

      sub reduce { my $exp = $_[0]; 1 while $exp =~ s{(\d+)\s+([*/])\s+(\d+)} { $2 eq '*' ? $1*$3 : $1/$3 }e; 1 while $exp =~ s{(\d+)\s+([+-])\s+(\d+)} { $2 eq '+' ? $1+$3 : $1-$3 }e; return $exp; }