【问题标题】:Why Antlr4 could not resolve the declarator of FunctionDefinitionContext in C grammar为什么 Antlr4 无法解析 C 语法中 FunctionDefinitionContext 的声明符
【发布时间】:2019-03-27 03:00:36
【问题描述】:

为什么当我们有一个“declarationList”只包含一个指针参数时,C 语法中的 Antlr4 无法解析“FunctionDefinitionContext”的“声明符”。

所以如果我解析以下函数:

int print_queue(int idx,void * data) 解析器检索 print_queue 作为声明符

解析时: int destroy_queue (void * data)

解析器检索 (void * data) 作为声明符(我认为它认为它是一个函数指针)

如何解决这个问题?

【问题讨论】:

  • 你指的是哪个语法?来自 Grammars-v4 存储库的 C 语法?
  • 是的,来自 Grammars-v4 存储库的 C 语法
  • 我认为语法可能只是被破坏了。看起来作者可能试图通过尽可能频繁地将标识符视为类型名来使类型转换工作,并在此过程中破坏了其他一些情况。

标签: antlr4


【解决方案1】:

这是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)和您的示例。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-02-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-03-02
    • 2013-04-23
    • 2021-03-17
    相关资源
    最近更新 更多