ANTLR4 向侦听器发送空上下文属性
ANTLR4 sending a null context attribute to listener
我正在创建一个简单的语言编译器,但遇到了意外行为。我将语法简化如下:
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 页的示例),但不知何故这不是这里发生的事情(这是一个错误吗? ?)。当我使用以下侦听器时,我得到 "Oh no!":
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" 导致空语句块。我还是不明白为什么会这样。
这个问题可能与 ANTLR4 错误恢复有关。我不知道它是如何工作的,但从以前的调试会话中我知道 Parser:
- 插入预期的标记
- 删除标记,直到出现预期的标记
从您的错误消息看来,恢复可能会按如下方式重写令牌流:
int b() {
}
/*deleted: i nt a() {*/
int x /*deleted = b();*/(){
}
然而(){
的插入并没有产生一个语句块而是一个错误节点。所以函数声明将是可访问的(虽然它以int x
开头而不是int a
)但是语句块不存在(=是一个错误节点)。
恢复策略可能记录在ANTLR4书中,否则你将不得不调试DefaultErrorStrategy。如果您对此不满意,可以更改错误策略。
And why does this happen for listener but not for the rule action?
funcDecl
的动作没有被执行,因为它从来没有被解析过,而是被解析器错误恢复合成的。错误恢复无法考虑语义谓词或操作。
现在为什么解析的结果是一个funcDecl
节点,虽然它没有被解析?答案是:如果一个错误破坏了父节点的构建,那么树的最顶层节点总是错误节点。在错误上破坏完整的树并不是对错误恢复的普遍理解。
I was wondering how should I handle this in my listener code. Checking nulls everywhere?
监听器是处理错误的错误位置。
如果要修复错误:
使用另一个错误策略(你可以继承默认策略并添加你的代码,我用ANTLR3做过一次):
- 您可以通过分离损坏的
funcDecl
的方式来实现它
- 你也可以尝试修复它
- 在这种情况下,您可以简单地报告错误或抛出异常
如要报错:
查看错误策略是否报错。如果是这样,则不要向访问者应用向用户报告错误(可能重写文本以使用户友好)。如果解析树包含错误,请不要应用访问者。
我正在创建一个简单的语言编译器,但遇到了意外行为。我将语法简化如下:
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 页的示例),但不知何故这不是这里发生的事情(这是一个错误吗? ?)。当我使用以下侦听器时,我得到 "Oh no!":
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" 导致空语句块。我还是不明白为什么会这样。
这个问题可能与 ANTLR4 错误恢复有关。我不知道它是如何工作的,但从以前的调试会话中我知道 Parser:
- 插入预期的标记
- 删除标记,直到出现预期的标记
从您的错误消息看来,恢复可能会按如下方式重写令牌流:
int b() {
}
/*deleted: i nt a() {*/
int x /*deleted = b();*/(){
}
然而(){
的插入并没有产生一个语句块而是一个错误节点。所以函数声明将是可访问的(虽然它以int x
开头而不是int a
)但是语句块不存在(=是一个错误节点)。
恢复策略可能记录在ANTLR4书中,否则你将不得不调试DefaultErrorStrategy。如果您对此不满意,可以更改错误策略。
And why does this happen for listener but not for the rule action?
funcDecl
的动作没有被执行,因为它从来没有被解析过,而是被解析器错误恢复合成的。错误恢复无法考虑语义谓词或操作。
现在为什么解析的结果是一个funcDecl
节点,虽然它没有被解析?答案是:如果一个错误破坏了父节点的构建,那么树的最顶层节点总是错误节点。在错误上破坏完整的树并不是对错误恢复的普遍理解。
I was wondering how should I handle this in my listener code. Checking nulls everywhere?
监听器是处理错误的错误位置。
如果要修复错误:
使用另一个错误策略(你可以继承默认策略并添加你的代码,我用ANTLR3做过一次):
- 您可以通过分离损坏的
funcDecl
的方式来实现它 - 你也可以尝试修复它
- 在这种情况下,您可以简单地报告错误或抛出异常
如要报错:
查看错误策略是否报错。如果是这样,则不要向访问者应用向用户报告错误(可能重写文本以使用户友好)。如果解析树包含错误,请不要应用访问者。