我想在PHP中解析布尔表达式。如:
A and B or C and (D or F or not G)
这些术语可以被视为简单标识符。它们将具有一些结构,但解析器不需要担心。它应该只识别关键字and or not ( )
。其他一切都是一个术语。
我记得我们在学校写过简单的算术表达式评估器,但我不记得它是如何完成的。我也不知道在Google / SO中要查找哪些关键字。
一个现成的库会很好,但是我记得算法非常简单,所以自己重新实现它可能很有趣也很有教育意义。
答案 0 :(得分:15)
递归下降解析器编写和易于阅读。第一步是写出你的语法。
也许这就是你想要的语法。
expr = and_expr ('or' and_expr)*
and_expr = not_expr ('and' not_expr)*
not_expr = simple_expr | 'not' not_expr
simple_expr = term | '(' expr ')'
将其转换为递归下降解析器非常简单。只需每个非终端写一个函数。
def expr():
x = and_expr()
while peek() == 'or':
consume('or')
y = and_expr()
x = OR(x, y)
return x
def and_expr():
x = not_expr()
while peek() == 'and':
consume('and')
y = not_expr()
x = AND(x, y)
return x
def not_expr():
if peek() == 'not':
consume('not')
x = not_expr()
return NOT(x)
else:
return simple_expr()
def simple_expr():
t = peek()
if t == '(':
consume('(')
result = expr()
consume(')')
return result
elif is_term(t):
consume(t)
return TERM(t)
else:
raise SyntaxError("expected term or (")
这还不完整。您必须提供更多代码:
输入功能。 consume
,peek
和is_term
是您提供的功能。使用正则表达式很容易实现它们。 consume(s)
读取输入的下一个标记,如果与s
不匹配则抛出错误。 peek()
只是在不消耗它的情况下返回下一个标记。如果is_term(s)
是一个术语,则s
会返回true。
输出函数。 OR
,AND
,NOT
和TERM
在每次表达式时都会被调用成功解析。他们可以做任何你想做的事。
包装函数。您不想直接调用expr
,而是要编写一个初始化consume
和{所用变量的包装函数。 {1}},然后调用peek
,最后检查以确保没有剩余消耗的剩余输入。
即便如此,它仍然是一小部分代码。 In Python, the complete program is 84 lines,包括一些测试。
答案 1 :(得分:4)
为什么不使用PHP解析器?
$terms=array('and','or','not','A','B','C','D'...);
$values=array('*','+','!',1,1,0,0,1....);
$expression="A and B or C and (D or F or not G)";
$expression=preg_replace($terms, $values,$expression);
$expression=preg_replace('^(+|-|!|1|0)','',$expression);
$result=eval($expression);
实际上,第二个正则表达式是错误的(只有在需要阻止任何代码注入时才需要) - 但是你明白了。
下进行。
答案 2 :(得分:2)
我会选择Pratt解析器。这几乎就像递归下降但更聪明:) Douglas Crockford(JSLint成名)here的一个不错的解释。
答案 3 :(得分:2)
Dijkstra's shunting yard algorithm是从中缀到后缀/图形的传统方法。
答案 4 :(得分:2)
我已经实现了plinth建议的分流码算法。但是,此算法只为您提供后缀表示法,即反向波兰表示法(RNP)。您仍然需要对其进行评估,但是一旦在RNP中使用表达式(例如here所述),这就很容易了。
下面的代码可能不是很好的PHP风格,我的PHP知识有点受限。尽管如此,这应该足够了。
$operators = array("and", "or", "not");
$num_operands = array("and" => 2, "or" => 2, "not" => 1);
$parenthesis = array("(", ")");
function is_operator($token) {
global $operators;
return in_array($token, $operators);
}
function is_right_parenthesis($token) {
global $parenthesis;
return $token == $parenthesis[1];
}
function is_left_parenthesis($token) {
global $parenthesis;
return $token == $parenthesis[0];
}
function is_parenthesis($token) {
return is_right_parenthesis($token) || is_left_parenthesis($token);
}
// check whether the precedence if $a is less than or equal to that of $b
function is_precedence_less_or_equal($a, $b) {
// "not" always comes first
if ($b == "not")
return true;
if ($a == "not")
return false;
if ($a == "or" and $b == "and")
return true;
if ($a == "and" and $b == "or")
return false;
// otherwise they're equal
return true;
}
function shunting_yard($input_tokens) {
$stack = array();
$output_queue = array();
foreach ($input_tokens as $token) {
if (is_operator($token)) {
while (is_operator($stack[count($stack)-1]) && is_precedence_less_or_equal($token, $stack[count($stack)-1])) {
$o2 = array_pop($stack);
array_push($output_queue, $o2);
}
array_push($stack, $token);
} else if (is_parenthesis($token)) {
if (is_left_parenthesis($token)) {
array_push($stack, $token);
} else {
while (!is_left_parenthesis($stack[count($stack)-1]) && count($stack) > 0) {
array_push($output_queue, array_pop($stack));
}
if (count($stack) == 0) {
echo ("parse error");
die();
}
$lp = array_pop($stack);
}
} else {
array_push($output_queue, $token);
}
}
while (count($stack) > 0) {
$op = array_pop($stack);
if (is_parenthesis($op))
die("mismatched parenthesis");
array_push($output_queue, $op);
}
return $output_queue;
}
function str2bool($s) {
if ($s == "true")
return true;
if ($s == "false")
return false;
die('$s doesn\'t contain valid boolean string: '.$s.'\n');
}
function apply_operator($operator, $a, $b) {
if (is_string($a))
$a = str2bool($a);
if (!is_null($b) and is_string($b))
$b = str2bool($b);
if ($operator == "and")
return $a and $b;
else if ($operator == "or")
return $a or $b;
else if ($operator == "not")
return ! $a;
else die("unknown operator `$function'");
}
function get_num_operands($operator) {
global $num_operands;
return $num_operands[$operator];
}
function is_unary($operator) {
return get_num_operands($operator) == 1;
}
function is_binary($operator) {
return get_num_operands($operator) == 2;
}
function eval_rpn($tokens) {
$stack = array();
foreach ($tokens as $t) {
if (is_operator($t)) {
if (is_unary($t)) {
$o1 = array_pop($stack);
$r = apply_operator($t, $o1, null);
array_push($stack, $r);
} else { // binary
$o1 = array_pop($stack);
$o2 = array_pop($stack);
$r = apply_operator($t, $o1, $o2);
array_push($stack, $r);
}
} else { // operand
array_push($stack, $t);
}
}
if (count($stack) != 1)
die("invalid token array");
return $stack[0];
}
// $input = array("A", "and", "B", "or", "C", "and", "(", "D", "or", "F", "or", "not", "G", ")");
$input = array("false", "and", "true", "or", "true", "and", "(", "false", "or", "false", "or", "not", "true", ")");
$tokens = shunting_yard($input);
$result = eval_rpn($tokens);
foreach($input as $t)
echo $t." ";
echo "==> ".($result ? "true" : "false")."\n";
答案 5 :(得分:0)
您可以使用LR解析器构建解析树,然后评估树以获取结果。包含示例的详细说明可在Wikipedia中找到。如果你还没有自己编码,我今晚会写一个小例子。
答案 6 :(得分:0)
最简单的方法是使用正则表达式将表达式转换为php语法中的表达式,然后使用eval,如symcbean所示。但我不确定你是否想在生产代码中使用它。
另一种方法是编写自己的简单recursive descent parser。它并不像听起来那么难。对于像你这样的简单语法(布尔表达式),你可以从头开始轻松编写代码。你也可以使用类似于ANTLR for php的解析器生成器,可能会搜索一个php解析器生成器会发现一些东西。