我们可以把它分成两部分。括号表达式有一个规则,乘法有另一个规则。
而不是为了解释目的而故意简化的维基百科文章,我将遵循更详细的示例,例如通过递归下降解析表达式,它处理括号表达式。
这是我用于解析器的代码,可以使用隐式乘法。我有多个字母的变量名称,并使用空格来分隔不同的变量,这样您就可以拥有“2 pi r”。
protected void expression() throws ParseException {
prefixSuffix();
Token t = it.peekNext();
while(t!=null) {
if(t.isBinary()) {
pushOp(t);
it.consume();
prefixSuffix();
}
else if(t.isImplicitMulRhs()) {
pushOp(implicitMul);
prefixSuffix();
}
else
break;
t=it.peekNext();
}
while(!sentinel.equals(ops.peek())) {
popOp();
}
}
这需要一些其他功能。
我使用了一个单独的标记化步骤,它将输入分解为离散的标记。该类Tokens
有许多方法,特别是Token.isBinary()
测试运算符是否是二元运算符,如 +、=、*、/。另一种方法Token.isImplicitMulRhs()
测试标记是否可以出现在隐式乘法的右侧,这对于数字、变量名和左括号都是正确的。
AnIterator<Token>
用于输入流。it.peekNext()
查看下一个标记并it.consume()
移动到输入中的下一个标记。
pushOp(Token)
将一个令牌压入运算符堆栈并popOp
删除一个 和 。pushOp 具有处理不同运算符优先级的逻辑。如果优先级较低,则弹出运算符
protected void pushOp(Token op)
{
while(compareOps(ops.peek(),op))
popOp();
ops.push(op);
}
特别值得注意的是implicitMul
一个与乘法具有相同优先级的人工令牌,它被推入运算符堆栈。
prefixSuffix()
处理可以是带有可选前缀的后缀运算符的数字和变量的表达式。这将识别“2”、“x”、“-2”、“x++”从输入中删除标记,并将它们适当地添加到输出/运算符堆栈中。
我们可以将 BNF 中的这个例程视为
<expression> ::=
<prefixSuffix> ( <binaryOp> <prefixSuffix> )* // normal binary ops x+y
| <prefixSuffix> ( <prefixSuffix> )* // implicit multiplication x y
处理括号是在prefixSuffix()
. 如果这检测到左括号,它将递归调用expression()
. 为了检测匹配的右括号,一个特殊的哨兵令牌被推送到操作员堆栈上。当在输入中遇到右括号时,主循环中断,并且操作符堆栈上的所有操作符都弹出,直到遇到标记并将控制返回给prefixSuffix()
. 代码可能类似于
void prefixSuffix() {
Token t = it.peekNext();
if(t.equals('(')) {
it.consume(); // advance the input
operatorStack.push(sentinel);
expression(); // parse until ')' encountered
t = it.peekNext();
if(t.equals(')')) {
it.consume(); // advance the input
return;
} else throw Exception("Unmatched (");
}
// handle variable names, numbers etc
}