这是the C grammar that you're using 中的一个错误。这个bug也已经reported on github了。
问题在于 C 的语法在类型转换方面存在歧义。例如,表达式(a)(b) 匹配函数调用规则((a) 将是评估函数指针的主表达式,(b) 将是包含单个参数的参数列表:变量b)和转换规则(将变量 b 的值转换为 a 类型(其中 a 将被识别为 typedef-name)。
C 通过说typedef-name 规则应仅应用于实际已被typedefed 的标识符来解决此歧义。也就是说,当且仅当文件中先前确实存在typedef someType a; 时,上述示例应被解析为类型转换 - 否则应将其解析为函数调用。这是无法在上下文无关语法中表达的东西。对于 ANTLR,这意味着需要语义谓词来实现此规则。
然而,这不是所讨论的语法所做的(可能是为了保持语法与语言无关或保持简单,或者可能是因为作者不知道这是正确解析所有 C 代码所必需的)。相反,原始版本解决了歧义,倾向于将标识符视为变量名 - 仅仅是由于语法中替代项的顺序。在某些时候,有人注意到这不会正确解析类型转换,并通过更改语法中替代项的顺序来“修复”这个问题。现在歧义得到了解决,有利于将标识符视为类型名称。这修复了类型转换的情况,但会破坏您的示例,因为现在您的代码中的 print_queue 被解释为类型名称。
如何解决这个问题?
您可以恢复到固定类型转换的提交之前的语法版本。然后你的代码应该可以工作,但类型转换为typedefed 类型将不起作用。如果您希望语法在所有情况下都产生正确的解析,您需要在语法中添加动作和谓词。
为此,您需要向解析器添加一组 typedefed 名称,如下所示(如果您使用不同的语言,以下代码是 Java 中的,您必须相应地调整它) :
@parser::header {
import java.util.HashSet;
import java.util.Set;
}
@parser::members {
private Set<String> typedefs = new HashSet<String>();
}
然后,您可以通过首先将Identifier 重命名为IdentifierOrTypedefName,然后添加identifier 规则并更改typedefName 规则来使整个语法区分typedefed 标识符和其他标识符,如下所示:
typedefName
: {typedefs.contains(getCurrentToken().getText())}? IdentifierOrTypedefName
;
identifier
: {!typedefs.contains(getCurrentToken().getText())}? IdentifierOrTypedefName
;
现在所有其他以前引用Identifier 的地方应该改为引用identifier。这样,标识符只有在 typedef 集合中时才会被视为类型,如果不在 typedef 集合中,则只会被视为变量或函数名。
现在剩下的就是实际填充集合。为此,我们需要向declaration 规则添加一个操作,如果声明是typedef,则将所有声明的标识符添加到集合中。我们可以这样做:
declaration
: declarationSpecifiers initDeclaratorList ';' {
if ($declarationSpecifiers.ctx.specifiers.stream().anyMatch(specifier -> specifier.getText().equals("typedef"))) {
ParseTreeWalker.DEFAULT.walk(new CBaseListener() {
@Override
public void exitIdentifier(IdentifierContext id) {
typedefs.add(id.getText());
}
}, $initDeclaratorList.ctx);
}
}
| declarationSpecifiers ';'
| staticAssertDeclaration
;
declarationSpecifiers
: specifiers+=declarationSpecifier+
;
通过这些更改,语法现在应该适用于类型转换(如果类型转换中使用的类型已正确typedefed)和您的示例。