我完全不懂ANTLR4,所以请原谅我的无知。我遇到了这个演示文稿,其中定义了一个非常简单的算术表达式语法。它看起来像是:
grammar Expressions;
start : expr ;
expr : left=expr op=('*'|'/') right=expr #opExpr
| left=expr op=('+'|'-') right=expr #opExpr
| atom=INT #atomExpr
;
INT : ('0'..'9')+ ;
WS : [ \t\r\n]+ -> skip ;
这很棒,因为它将生成一个非常简单的二叉树,可以使用访问者模式遍历该树,如幻灯片中所解释的那样,例如,这是访问expr
的函数:
public Integer visitOpExpr(OpExprContext ctx) {
int left = visit(ctx.left);
int right = visit(ctx.right);
String op = ctx.op.getText();
switch (op.charAt(0)) {
case '*': return left * right;
case '/': return left / right;
case '+': return left + right;
case '-': return left - right;
default: throw new IllegalArgumentException("Unkown opeator " + op);
}
}
接下来我想要添加的是对括号的支持。因此,我修改了expr
如下:
expr : '(' expr ')' #opExpr
| left=expr op=('*'|'/') right=expr #opExpr
| left=expr op=('+'|'-') right=expr #opExpr
| atom=INT #atomExpr
;
很不幸,上面的代码在遇到括号时会失败,因为三个属性
op
、left
和right
都为空(NPE失败)。我认为可以通过定义一个新属性来解决这个问题,例如
parenthesized='(' expr ')'
,然后在访问者代码中处理它。但是,对我来说,使用一个完整的额外节点类型来表示带括号的表达式似乎过于复杂。一个更简单但更丑陋的解决方案是在visitOpExpr
方法的开头添加以下代码行:if (ctx.op == null) return visit(ctx.getChild(1)); // 0 and 2 are the parentheses!
我完全不喜欢上面的内容,因为它非常脆弱,且高度依赖语法结构。
我在想是否有一种方法可以告诉ANTLR只是“吃掉”括号,并将表达式视为子级。有吗?有更好的方法吗?
注意:我的最终目标是扩展示例以包括布尔表达式,这些表达式本身可以包含算术表达式,例如:(2+4*3)/10 >= 11
,即,算术表达式之间的关系(<,>,==,~=等)可以定义一个原子布尔表达式。这很简单,我已经勾勒出了语法,但我遇到了同样的括号问题,即,我需要能够编写以下内容(我还将添加对变量的支持):
((2+4*x)/10 >= 11) | ( x>1 & x<3 )
编辑:修正了括号表达式的优先级,括号始终具有更高的优先级。
((((((((((2+3))))))))))
,而不是2+3
,语法显然是有效的,但由于所有括号节点的存在,树所占用的空间要大得多。我只是惊讶地发现没有一种方法来定义一个短路,以便将前面的表达式转换为后者。 - Giovanni Botta