我正在创建一个简单的语言编译器,我面临一个意想不到的行为。我将语法简化如下:
grammar Language;
program : (varDecl)* (funcDecl)* EOF;
varDecl : type IDENTIFIER ('=' expression)? ';';
funcDecl : type IDENTIFIER '(' ')' statementBlock;
type : 'int' # IntType
;
statementBlock : '{' (statement)* '}';
statement : varDecl ;
expression : IDENTIFIER '(' (expression (',' expression)*)? ')' # FuncCallExpression
;
IDENTIFIER : ('a'..'z')+;
WHITE_SPACE : [ \t\u000C\n\r]+ -> skip;
由于statementBlock
是funcDecl
规则中的强制规则,我希望在监听器内,FuncDeclContext
始终包含非空funcDecl
。问题是我得到以下输入的空statementBlock
:
int b() {
}
i nt a() {
int x = b();
}
据我了解,当面对无效输入时,ANTLR会插入表示预期匹配的特殊节点(如本书第163页的示例),但不知何故这不是这里发生的事情(这是一个错误吗?) 。当我使用以下听众时,我得到“哦不!”:
public class DummyListener extends LanguageBaseListener {
@Override
public void exitFuncDecl(LanguageParser.FuncDeclContext ctx) {
super.exitFuncDecl(ctx);
if (ctx.statementBlock() == null) {
System.out.println("Oh, no :(");
}
}
}
这种行为的原因是什么?
进一步调查
我发现了一个有趣的行为。
我更改了funcDecl
规则以包含操作:
funcDecl : type IDENTIFIER '(' ')' statementBlock { System.out.println("ID: " + $IDENTIFIER.text + ", text is: " + $statementBlock.text); };
并从侦听器修改exitFuncDecl以打印标识符:
System.out.println("Listener: id " + ctx.IDENTIFIER().getText());
if (ctx.statementBlock() == null) {
System.out.println("Oh, no :(");
} else {
System.out.println("content is " + ctx.statementBlock().getText());
}
输出结果为:
line 3:0 extraneous input 'i' expecting {<EOF>, 'int'}
ID: b, text is: {}
line 4:7 mismatched input '=' expecting '('
Listener: id b
content is {}
Listener: id x
Oh, no :(
似乎ANTLR正在调用exitFuncDecl而不是规则操作。我认为规则操作行为在这里是正确的,因为“x”导致null statementBlock。我仍然不明白为什么会这样。
答案 0 :(得分:3)
此问题可能与ANTLR4错误恢复有关。我不确切知道它是如何工作的,但是从以前的调试会话中我知道Parser:
从错误消息中可能看出,恢复会重写令牌流,如下所示:
int b() {
}
/*deleted: i nt a() {*/
int x /*deleted = b();*/(){
}
然而,插入(){
不会产生语句块,而是产生错误节点。所以函数声明是可访问的(尽管它以int x
而不是int a
开头)但语句块不存在(=是错误节点)。
恢复策略可能记录在ANTLR4书中,否则你将不得不调试DefaultErrorStrategy。如果您对此错误策略不满意,可以更改错误策略。
为什么这会发生在听众身上而不是规则行动呢?
funcDecl
的操作未执行,因为它从未被解析过,但是由解析器错误恢复合成。错误恢复不能将语义谓词或操作考虑在内。
现在为什么解析funcDecl
节点的结果虽然没有被解析?答案是:如果单个错误破坏了父节点的构建,那么树的最顶层节点始终是错误节点。在错误中打破整个树并不是对错误恢复的常见理解。
我想知道如何在我的侦听器代码中处理这个问题。到处检查空值?
侦听器是处理错误的错误位置。
如果您想修复错误:
使用其他错误策略(您可以继承默认策略并添加代码,我已经使用ANTLR3完成了一次):
funcDecl
来实现它如果您想举报错误:
检查错误策略是否报告了错误。如果是这样,那么不要向访问者报告错误报告给用户(可能重写文本是用户友好的)。如果解析树包含错误,请不要应用访问者。