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?
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$..$/
| [reply] [d/l] |
|
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?
| [reply] [d/l] |
|
| [reply] |
|
#!/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$..$/
| [reply] [d/l] [select] |
|
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.
| [reply] |
|
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) {
$_->();
}
| [reply] [d/l] [select] |
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.
| [reply] |
|
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. | [reply] [d/l] [select] |
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)];
| [reply] [d/l] [select] |
Re: eval question
by shmem (Chancellor) on Apr 16, 2007 at 16:14 UTC
|
#!/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}
| [reply] [d/l] [select] |
|
Unless your entire audience is composed of mathemeticians and programmers, this is an awful idea.
| [reply] |
|
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}
| [reply] |
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 | [reply] [d/l] [select] |
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;
}
| [reply] [d/l] |
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.
| [reply] [d/l] |
|
|