Beefy Boxes and Bandwidth Generously Provided by pair Networks
Clear questions and runnable code
get the best and fastest answer
 
PerlMonks  

eval question

by Anonymous Monk
on Apr 16, 2007 at 12:59 UTC ( [id://610339]=perlquestion: print w/replies, xml ) Need Help??

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

Hi Monks,
I'd like to have an text entry box on a web page to allow users to enter math expresions and calculate them. Naturally, I first thought of using eval perl, something like:
$result = eval " $1 $2 $3 ";
where $1 and $3 are the numbers, and $2 is the operator.
However, the potential for disaster is fairly large, so I'm wondering is there a safer way of doing this not using eval?

Replies are listed 'Best First'.
Re: eval question
by liverpole (Monsignor) on Apr 16, 2007 at 13:06 UTC
    Sure!  Just use a dispatch table ...
    #!/usr/bin/perl -w use strict; use warnings; my $p_operations = { '+' => sub { $_[0] + $_[1] }, '-' => sub { $_[0] - $_[1] }, '*' => sub { $_[0] * $_[1] }, '/' => sub { $_[0] / $_[1] }, }; # Get operation $op, and values $x and $x in whatever fashion # ... # For testing my $x = 12; my $y = 7; my $op = "*"; my $result = $p_operations->{$op}->($x, $y); print "The result of $x $op $y is: $result\n";

    s''(q.S:$/9=(T1';s;(..)(..);$..=substr+crypt($1,$2),2,3;eg;print$..$/
      I second your suggestion. Parse::RecDescent would work for this (recently figured that one out too) but in this case, the above example will work well for simple calculations.

      However - what if you need to do something that involves precedence? i.e. -
      1 + 9 * ( 60 / 5 )

      What is the recommended path there?
        I'll confess that I haven't used Parse::RecDescent ... a need for it hasn't yet arisen.

        If you were doing something complicated (eg. your precedence problem), my first inclination would be to use eval, but only after validating that the input was sane.  For example:

        #!/usr/bin/perl -w use strict; use warnings; # Test data my $string = "1 + 9 * ( 60 / 5 )"; # Validate if ($string !~ m:^[-+*/()0-9.\s]+$:) { die "Bad input '$string'\n"; } my $result = eval $string; print "Result of '$string' = $result\n";

        But I don't know offhand if that's the best way to do it, or what other caveats you might run into.

        Update:  Fixed the regexp.


        s''(q.S:$/9=(T1';s;(..)(..);$..=substr+crypt($1,$2),2,3;eg;print$..$/
      Dispatch table is so cool. And it's a good substitute of 'switch'. But when perl 6 come out (built-in switch), it will be useless.May be I'm wrong.
        I've used dispatch tables in C, even though it has a switch statement. The advantage of dispatch tables is that you don't have to repeat the code the lists the args and you don't have to repeat the code that stores the return value.
        my @callbacks = ( &some_handler, &another_handler, &foo_handler, &bar_handler, ); my $rv = $callbacks[$event]->($arg0, $arg1);

        vs

        my $rv; given ($event) { when SOME_EVENT { $rv = some_handler ($arg0, $arg1); } when ANOTHER_EVENT { $rv = another_handler($arg0, $arg1); } when FOO_EVENT { $rv = foo_handler ($arg0, $arg1); } when BAR_EVENT { $rv = bar_handler ($arg0, $arg1); } };

        Arrays of function procs are also useful when the entire list needs to be executed.

        foreach (@procs) { $_->(); }
Re: eval question
by jettero (Monsignor) on Apr 16, 2007 at 13:03 UTC

    There are probably already packages that do this — particularly if you're really only interested in math expressions. However, if you wish to write your own reasonably safe math parser/grammar, a good place to start might be: Parse::RecDescent.

    Another direction you could go is the Safe module. I doubt I'd execute code from some random third party on my computers, but if I did, I'd use Safe and a chroot and probably various other things — and still lose sleep over it.

    -Paul

      This is probably massively over-engineered for what you want, but for what it's worth...
      use strict; use warnings; my $expression = shift; ###################### Parse expression ########################## use Parse::RecDescent; my $grammar = <<'__GRAMMAR__'; expression: <leftop: term m{(\+|\-)} term> { my $n = [ shift @{$item[1]} ]; while ( my ( $op, $arg ) = splice @{$item[1]}, 0, 2 ) { if ( $op eq '+' ) { $n = [ $n, $arg, [ 'add' ] ] } elsif ( $op eq '-' ) { $n = [ $n, $arg, [ 'subtract' ] ] } } $n; } term: <leftop: factor m{(\*|\/|\%)} factor> { my $n = shift @{$item[1]}; while ( my ( $op, $arg ) = splice @{$item[1]}, 0, 2 ) { if ( $op eq '*' ) { $n = [ $n, $arg, [ 'multiply' ] ] } elsif ( $op eq '/' ) { $n = [ $n, $arg, [ 'divide' ] ] } elsif ( $op eq '%' ) { $n = [ $n, $arg, [ 'modulus' ] ] } } $n; } factor: <leftop: primary m{(\*\*|\^)} primary> { my $n = [ shift @{$item[1]} ]; while ( my ( $op, $arg ) = splice @{$item[1]}, 0, 2 ) { $n = [ $n, $arg, [ 'power' ] ]; } $n; } primary: sign(?) '(' expression ')' { if ( $item{'sign(?)'}[0] eq 'minus' ) { [ $item{'expression'}, [ 'negate' ] ]; } else { $item{'expression'}; } } | sign(?) number { if ( $item{'sign(?)'}[0] eq 'minus' ) { [ $item{'number'}, [ 'negate' ] ]; } else { $item{'number'}; } } sign: '+' { 'plus' } | '-' { 'minus' } number: m{([+-]?((\.\d+)|(\d+\.?\d*))([Ee][+-]?\d+)?)} { [ 'push', $1 ] } __GRAMMAR__ my $parser = Parse::RecDescent->new( $grammar ); my $parse_tree = $parser->expression( $expression ); unless ( defined $parse_tree ) { die "Expression is invalid\n"; } ###################### Compile parse tree ######################## my $assembly = compile( $parse_tree ); sub compile { my ( $node ) = @_; my @opcodes; for ( my $i = 0; $i < @{ $node }; $i++ ) { if ( UNIVERSAL::isa( $node->[ $i ], 'ARRAY' ) ) { push @opcodes, @{ compile( $node->[ $i ] ) }; } else { push @opcodes, $node; last; } } return \@opcodes; } ####################### Execute opcodes ########################## my @stack; my %dispatch = ( 'add' => sub { my ( $x, $y ) = splice @stack, -2; push @stack, $x + $y; }, 'subtract' => sub { my ( $x, $y ) = splice @stack, -2; push @stack, $x - $y; }, 'negate' => sub { my ( $x ) = pop @stack; push @stack, -$x; }, 'multiply' => sub { my ( $x, $y ) = splice @stack, -2; push @stack, $x * $y; }, 'divide' => sub { my ( $x, $y ) = splice @stack, -2; push @stack, $x / $y; }, 'modulus' => sub { my ( $x, $y ) = splice @stack, -2; push @stack, $x % $y; }, 'power' => sub { my ( $x, $y ) = splice @stack, -2; push @stack, $x ** $y; }, 'push' => sub { my ( $x ) = @_; push @stack, $x; }, ); my $i; for my $opcode ( @{ $assembly } ) { my ( $function, @args ) = @{ $opcode }; $dispatch{ $function }->( @args ); } my $result = pop @stack; print $result; exit 0;
      You can just compute the calculation on the fly by getting e.g. the 'term' action to return a product or quotient, and this will be much quicker if you only intend on performing a given calculation once. The code above has the advantage of producing an intermediate form ($assembly) that you can store, so it's quicker if the expression you are parsing is very large and will be executed repeatedly.
Re: eval question
by Rhandom (Curate) on Apr 16, 2007 at 15:55 UTC
    You could use CGI::Ex::Template which has exposed the expression parsing methods from the Template::Toolkit mini-language. This gives you all of the access to numeric extensions. You can also get support for variables (though you have to use the Template::Toolkit expression syntax for setting a variable (encasing the set expression in parens). And you can use the TT virtual methods (plus filters exposed by CGI::Ex::Template).

    use strict; use CGI::Ex::Template; my $t = CGI::Ex::Template->new; ### hmm - semi documented $t->_vars({ PI => 3.14159, r => 1.5, }); foreach ("2 + 3", "1 + (2 + 4) / 3", "2 ** 3 ** 4", '12345.fmt("%.3e")', '0xFF', "PI", "PI * r ** 2", "1 + (", "foo", "(foo = 123456 + 1)", "foo", ) { my $expr = $_; print "-------------\n"; print "Expression: $expr\n"; my $optree = eval { $t->parse_expr(\$expr) }; #use CGI::Ex::Dump qw(debug); #debug $optree; my $result; if (my $err = $@) { print "Error: ".(ref $err ? $err->info : "$err")."\n"; } else { $result = $t->play_expr($optree); print "Result: $result\n"; } }
    That chunk of code would print out the following:

    ------------- Expression: 2 + 3 Result: 5 ------------- Expression: 1 + (2 + 4) / 3 Result: 3 ------------- Expression: 2 ** 3 ** 4 Result: 2.41785163922926e+24 ------------- Expression: 12345.fmt("%.3e") Result: 1.234e+04 ------------- Expression: 0xFF Result: 255 ------------- Expression: PI Result: 3.14159 ------------- Expression: PI * r ** 2 Result: 7.0685775 ------------- Expression: 1 + ( Error: Missing close ) ------------- Expression: foo Result: ------------- Expression: (foo = 123456 + 1) Result: 123457 ------------- Expression: foo Result: 123457


    I also noticed an interesting thing, the parsing of CGI:Ex::Template would turn 1 + * 3 into 1 + undef * 3 which would evaluate to 1 when you may have wanted it to report an error. I'll see if we can get a fix in for CGI::Ex::Template.

    my @a=qw(random brilliant braindead); print $a[rand(@a)];
Re: eval question
by shmem (Chancellor) on Apr 16, 2007 at 16:14 UTC

    A safe approach would be to require users to use Reverse Polish (or Postfix) Notation. Then you could just split the input on whitespace and pass the list to rpn() of Math::RPN:

    #!/usr/bin/perl -T use strict; use Math::RPN; my $string = '5 2 2 3 add pow 2 sub mul 10 div'; # (5*(2**(2+3)-2)) / + 10 == 15 my $result = rpn (split /\s+/, $string); print "result: $result\n"; __END__ result: 15

    Place a link to the wikipedia RPN entry on your web page, a list of Math::RPN's operators, and done :-)

    --shmem

    _($_=" "x(1<<5)."?\n".q·/)Oo.  G°\        /
                                  /\_¯/(q    /
    ----------------------------  \__(m.====·.(_("always off the crowd"))."·
    ");sub _{s./.($e="'Itrs `mnsgdq Gdbj O`qkdq")=~y/"-y/#-z/;$e.e && print}
      Unless your entire audience is composed of mathemeticians and programmers, this is an awful idea.
        Why?

        I'm not a mathematician and a programmer only somehow, but I enjoy inciting people to think different. Stack processing isn't that hard to grasp, even cooking follows that rule: first place your onion on the board, then chop; have your ingredients ready, then blend etc.

        --shmem

        _($_=" "x(1<<5)."?\n".q·/)Oo.  G°\        /
                                      /\_¯/(q    /
        ----------------------------  \__(m.====·.(_("always off the crowd"))."·
        ");sub _{s./.($e="'Itrs `mnsgdq Gdbj O`qkdq")=~y/"-y/#-z/;$e.e && print}
Re: eval question
by tsee (Curate) on Apr 17, 2007 at 15:46 UTC

    Others have suggested using Parse::RecDescent to create a parser for arithmetic expressions. That's all good advice, but you may want something less involved that writing your own parser. Maybe you should have a look at the Math::Symbolic module on CPAN. (Caveat: I'm the author.)

    You could do this: (untested)

    use Math::Symbolic qw/parse_from_string/; my $ms; eval { $ms = parse_from_string($expression) }; if ($@) {...} # catch parse errors my $value; eval { $value = $ms->value() }; if ($@) {...} # catch execution errors

    The above code might break because Math::Symbolic supports variables - which have no value by default. So you can check that the user didn't use any variables in his expression before calling value():

    # parse... unless ( $ms->is_constant() ) { # complain about variables in the tree } my $value; eval { $value = $ms->value() }; if ($@) {...} # catch execution errors

    If you have to evaluate the expressions very often, you will find that calling ->value() is slow. (It walks the expression tree.) In that case, you can compile the tree to Perl code. You'd do that with ->to_sub():

    # parse... # note the parens for list context my ($coderef) = $ms->to_sub(); my $value = $coderef->();

    The module can do much more than that, of course. For something fancy, you can check out an experiment of mine, which is unfortunately rather undocumented: Math::SymbolicX::Calculator::Interface::Web. It's a feature-starved AJAX-enabled symbolic calculator with a worksheet-ish appearance. That's just a reference for your entertainment, though. Don't ever think of using that.

    Admittedly, if I was sticking to my promise of this being less complex, I should have stopped after the second code snipped. :)

    Cheers,
    Steffen

Re: eval question (or, use the Google)
by Anonymous Monk on Apr 17, 2007 at 06:29 UTC
    I'd like to have an text entry box on a web page to allow users to enter math expresions and calculate them.

    Check out this smooth Perl/Ajax/Google Calculator achieved by adding 3 measly lines to a script that Perlmonk varian posted here 2 days ago:

    #!/usr/bin/perl -w use strict; use CGI::Ajax; use CGI qw(:all); use WWW::Google::Calculator; my $cgi = CGI->new; my $pjx = CGI::Ajax->new('submitted_formdata' => \&process_formdata); print $pjx->build_html($cgi,\&show_some_html); sub show_some_html { return <<'END_HTML'; <html> <head><title>Google Calculator</title></head> <body><form method="POST"> <b><a href=http://www.google.com/help/calculator.html title="How to use the Google calculator">Google</a></b> <input type="text" name="surname" id="surname"> <input type="button" onClick= "submitted_formdata( ['surname'],['response1'],'POST');" value="Calculate"> </form> <div id="response1" name="response1"></div> </body> </html> END_HTML } sub process_formdata { my $surname = shift; my $calc = WWW::Google::Calculator->new; return $calc->calc($surname) || 'ERROR ' . $calc->error; }
Re: eval question
by Moron (Curate) on Apr 18, 2007 at 16:26 UTC
    ^I *nix has a maths expression calculator called bc.

    ^P Piping the input to bc is completely safe.

    ^B But bc doesn't use real-numbered division and will reject brackets and most maths function calls.

    ^C You could write a very simple parser that looks only for identifiers (make a hash of valid maths. function names to check against) and brackets and replaces them with 1 just to submit to bc to check the rest of the expression syntax, using also bc to check the syntax inside each bracketed expression (need to recusively parse the brackets). Such a parser should only be a few lines of code, so ask for further advice if you are generating more than say half a page for that. To submit to bc, use e.g. IPC::Open3 and just check if anything comes back on the error channel.

    The point of the algorithm is: If there is nothing left after eliminating expected function calls, bracketed expressions, their 1-modified contents being validated by bc and finally the outer 1-modified expression is also validated by bc, then it's okay to go ahead and eval the (unmodified!) expression.

    The wrapper for checkbc would look something like:

    sub CheckBC { my $pid = open3 my $wh, my $rh, my $eh, "bc" or die; print $wh shift() . "\n"; close $wh or die; my $throwResultAway = <$rh>; close $rh or die; my $error = <$eh>; close $eh or die; waitpid $pid, 0; # else forkbomb, zombies, etc. !$error; }
    __________________________________________________________________________________

    ^M Free your mind!

    Key to hats: ^I=white ^B=black ^P=yellow ^E=red ^C=green ^M=blue - see Moron's scratchpad for fuller explanation.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://610339]
Approved by Corion
Front-paged by Corion
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others having a coffee break in the Monastery: (4)
As of 2024-04-25 07:54 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found