ANTLR4向侦听器发送一个空上下文属性



我正在创建一个简单的语言编译器,但我面临着一个意外的行为。我把语法简化如下:

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     : [ tu000Cnr]+ -> skip;

由于statementBlockfuncDecl规则中的强制规则,我希望在侦听器中,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); };

并修改了监听器的exitUncDecl以打印标识符:

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似乎正在调用exitUncDecl,但没有调用规则操作。我认为这里的规则操作行为是正确的,因为"x"导致了null语句块。我仍然不明白为什么会发生这种事。

此问题可能与ANTLR4错误恢复有关。我不知道它是如何工作的,但从以前的调试会话中我知道Parser:

  • 插入预期的令牌
  • 删除令牌,直到出现预期的令牌

从您的错误消息来看,恢复很可能会重写令牌流,如下所示:

int b() {
}
/*deleted: i nt a() {*/
int x /*deleted = b();*/(){
}

然而,(){的插入并不产生语句块,而是产生错误节点。因此,函数声明将是可访问的(尽管它以int x而不是int a开头),但语句块不存在(=是一个错误节点)。

恢复策略可能记录在ANTLR4书中,否则您将不得不调试DefaultErrorStrategy。如果您不满足于此策略,您可以更改错误策略。

为什么侦听器会发生这种情况,而规则操作却不会发生这种情况?

funcDecl的操作没有执行,因为它从未被解析过,而是由解析器错误恢复合成的。错误恢复不能考虑语义谓词或操作。

现在,为什么解析的结果是funcDecl节点,尽管它没有被解析?答案是:如果一个错误破坏了父节点的构建,那么树中最顶端的节点总是错误节点。破坏错误的完整树并不是错误恢复的常见理解。

我想知道我应该如何在侦听器代码中处理这个问题。到处检查null?

侦听器的位置错误,无法处理错误。

如果您想修复错误:

使用另一种错误策略(你可以继承默认策略并添加你的代码,我用ANTLR3做过一次):

  • 您可以通过分离损坏的funcDecl的方式来实现它
  • 你也可以试着修理它
  • 在这种情况下,您可以简单地报告错误或抛出异常

如果您想报告错误:

检查错误策略是否已报告错误。如果是这样,则不要向访问者应用错误报告给用户(可能会重写文本以便于用户使用)。如果解析树包含错误,请不要应用访问者。

相关内容

  • 没有找到相关文章

最新更新