我正在创建一个简单的语言编译器,并且遇到了意外的行为。我将语法简化如下:

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;


由于statementBlockfuncDecl规则中的强制性规则,因此我希望在侦听器中FuncDeclContext始终包含非null的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”导致空的statementBlock。我仍然不明白为什么会这样。

最佳答案

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


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


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

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


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

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


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


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

现在,为什么解析的结果是funcDecl节点,尽管它没有被解析?答案是:如果单个错误破坏了父节点的构建,则树的最顶层节点始终是错误节点。在错误上打破完整的树并不是对错误恢复的普遍理解。


  我想知道如何在我的侦听器代码中处理此问题。在各处检查空值?


侦听器是处理错误的错误位置。

如果要修复错误:

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


您可以通过断开损坏的funcDecl的方式来实现它
你也可以尝试修复它
在这种情况下,您可以简单地报告错误或引发异常


如果要报告错误:

检查错误策略是否报告了错误。如果是这样,则不要将访问者的错误报告给用户(可能为了用户友好而重写文本)。如果解析树包含错误,请不要应用访问者。

09-27 11:40