On Sunday, Jul 11, 2004 "Andrew A. Chen" said:
Hello-
I'm just starting out with PRD here, but I think I'm on the right track.
I'm attempting to write a parser that evaluates a language similar to
SQL's WHERE syntax. I'd also like to evaluate the result of this boolean
expression with some given input data. The language is a reduced version
of SQL's WHERE syntax. Here are some valid expressions (and they function
as you would imagine): nb: `=' is comparison, not assignment.
(has_access = 1 && is_disabled = 0) || has_override = 1
has_override = 1
likes_pie = 1 && likes_apples = 1
(num_oranges > 5 && num_apples > 5) || (num_grapes < 2 && num_berries < 3)
The item to the left of the operand is always the variable and the item to
the right is always some integer or floating point value. Here's the
grammar I've come up with:
--snip--
start : coupon(s /(?:&&|\|\|)/) /^\Z/
coupon : expression
'(' expression ')'
expression : statement boolean statement
statement
statement : variable comparison number
boolean : /(?:&&|\|\|)/
comparison : /(?:=|!=|>|>=|<|<=)/
number : /[-+]?\d+(\.\d+)?/
variable : /[a-z_]+/i
'.'
--snip--
It appears to properly parse things, so I'm pretty happy about that. The
question here is that I'd like to be able to evaluate the above statements
by passing in some values for num_oranges, num_apples, has_override, etc.
Idealy, I'd like to be able to give it a hashref with all of these
variables and then have PRD spit out true or false for the final result.
I was thinking about using the $arg facility and calling it as such:
my $result = $parser->start($expression, 1, $parameters);
but passing $arg around seems to be a fairly messy way to do this. In
fact, do I even want to be attempting to evaluate this from within PRD, or
should I be building a parsetree and doing it separately? Some guidance
would be appreciated. Thanks.
I think it is better to create an "executable" and then execute it. I have
done something like this and an executable was my approach. One problem you
may or may not run into is that depending on your syntax, things may or may
not have side effects, and preventing those side effects can get tricky.
Using a run-time environment avoids this.
Try the following:
use strict;
use Data::Dumper;
use Parse::RecDescent;
use Date::Parse;
$RD_HINT = 1;
$RD_TRACE = 1;
my $parser=Parse::RecDescent->new(q(
start : expression(s) /^\Z/
{
$return=bless([$item[1],{}],'QWERT');
}
expression : statement boolean statement
{
$return=sub{eval $item[1]->(@_).$item[2].$item[3]->(@_)?'1':'0'}
}
statement
{
$return=sub {$item[1]->(@_)};
}
statement : variable comparison number
{
$return=sub{eval $item[1]->(@_).$item[2].$item[3]->(@_)?'1':'0'}
}
'(' expression ')'
{
$return=sub {$item[2]->(@_)};
}
boolean : /(?:&&|\|\|)/
{
$return=$item[1];
}
comparison : /(?:=|!=|>|>=|<|<=)/
{
$return=$item[1] eq '='?'==':$item[1];
}
number : /[-+]?\d+(\.\d+)?/
{
$return=sub {$item[1]};
}
variable : /[a-z_]+/i
{
$return=sub {$_[0]->get($item[1])}
}
));
my $exe=$parser->start('(has_access = 1 && is_disabled = 0) || has_override =
1');
sub QWERT::get {
my $self=shift;
my $name=shift;
$self->[1]{$name};
}
sub QWERT::set {
my $self=shift;
my $name=shift;
$self->[1]{$name}=shift;
}
sub QWERT::execute_exp {
my $self=shift;
$self->[0][0]->($self)
}
$exe->set('is_disabled',0);
$exe->set('has_access',0);
$exe->set('has_override',1);
print "return is: '" ,$exe->execute_exp,"'\n";
$exe->set('is_disabled',1);
$exe->set('has_access',0);
$exe->set('has_override',0);
print "return is: '" ,$exe->execute_exp,"'\n";
$exe->set('is_disabled',1);
$exe->set('has_access',1);
$exe->set('has_override',0);
print "return is: '" ,$exe->execute_exp,"'\n";
$exe->set('is_disabled',0);
$exe->set('has_access',1);
$exe->set('has_override',0);
print "return is: '" ,$exe->execute_exp,"'\n";
1;
__END__
--
Intel, Corp.
5000 W. Chandler Blvd.
Chandler, AZ 85226
--
Intel, Corp.
5000 W. Chandler Blvd.
Chandler, AZ 85226