C++ 解析歧义:构造函数与带括号的声明符
C++ parsing ambiguity: Constructor vs. parenthesized declarator
我正在尝试编写一个 yacc (menhir) 语法来解析一个非常精简的 C++ 子集(没有模板,headers 只有不允许函数体...)并且我已经 运行 含糊不清。
typedef int B;
class A {
A(); // (*)
B(c)(); // (**)
};
Case * 是构造函数,Case ** 是带括号的声明符。解析器如何分辨差异?我可以想象一些我 可以 讲述的方式,但我想知道兼容的 c++ 解析器是如何做到的。我也明白我可能无法使用 yacc 解析 C++ 的一个实际子集,但我只是想更好地理解发生了什么。最后也许我会切换到解析器组合器或其他东西。另外,我应该注意到我对链接 clang 的兴趣为零,因为我打算添加一些自定义语法。
通常,识别 C++ 中的构造函数(和析构函数)需要特别识别包含 class 的名称,区别于任何其他标识符。
这可以通过词法分析器中的上下文查找来完成 returning 不同的标记。在组成 class A 主体的 {
...}
内部,词法分析器可以识别 A
标记和 return 它的不同标记类型(不同从 TYPENAME 令牌中,它将 return for B
)
解析 C++ 是一项令人沮丧的练习,因为 C++ 本质上不是 context-free。您需要知道标识符是指模板、类型还是其他东西,C++ 中的名称解析也不是一项简单的任务。例如,您可能必须实例化一个模板才能知道 templated class 的成员是变量还是类型名。 (程序只需使用 typename
作为依赖类型的名称。)
因此,您当然不能期望在不考虑包含的头文件的情况下解析翻译单元。 (无论如何,你不能那样做,因为头文件可以定义一个宏,其扩展以意想不到的方式改变语法。所以你需要一个预处理器。)
当我写这个答案时,我的眼睛跳过了括号,其中 OP 指出“yacc”是指“menhir”。对不起。不幸的是,据我所知,menhir 不会生成 GLR 解析器。但我认为该方法仍然有效,并且足够有用,因此我将其保留。也许有一个 OCaml GLR(或 GLL)解析器生成器,或者也许 C/C++ 生成的代码仍然有用。
话虽如此,但并没有失去一切。您可以使用 bison 生成 GLR 解析器,然后尝试编写自定义消歧函数。如果消歧函数需要知道一个名称是否是 typedef,它可以尝试在符号 table 中查找它。与 classic“lexer hack”方法不同,GLR 消歧发生在解析器中,因此不需要构建笨重的反向通道。
这可能是一个迭代过程,因为您不需要弄清楚如何解决您尝试编译的特定代码中不存在的歧义。最终,您可能想要处理所有这些问题,但根据需要进行处理可能会证明是一种更充实的体验。
我正在尝试编写一个 yacc (menhir) 语法来解析一个非常精简的 C++ 子集(没有模板,headers 只有不允许函数体...)并且我已经 运行 含糊不清。
typedef int B;
class A {
A(); // (*)
B(c)(); // (**)
};
Case * 是构造函数,Case ** 是带括号的声明符。解析器如何分辨差异?我可以想象一些我 可以 讲述的方式,但我想知道兼容的 c++ 解析器是如何做到的。我也明白我可能无法使用 yacc 解析 C++ 的一个实际子集,但我只是想更好地理解发生了什么。最后也许我会切换到解析器组合器或其他东西。另外,我应该注意到我对链接 clang 的兴趣为零,因为我打算添加一些自定义语法。
通常,识别 C++ 中的构造函数(和析构函数)需要特别识别包含 class 的名称,区别于任何其他标识符。
这可以通过词法分析器中的上下文查找来完成 returning 不同的标记。在组成 class A 主体的 {
...}
内部,词法分析器可以识别 A
标记和 return 它的不同标记类型(不同从 TYPENAME 令牌中,它将 return for B
)
解析 C++ 是一项令人沮丧的练习,因为 C++ 本质上不是 context-free。您需要知道标识符是指模板、类型还是其他东西,C++ 中的名称解析也不是一项简单的任务。例如,您可能必须实例化一个模板才能知道 templated class 的成员是变量还是类型名。 (程序只需使用 typename
作为依赖类型的名称。)
因此,您当然不能期望在不考虑包含的头文件的情况下解析翻译单元。 (无论如何,你不能那样做,因为头文件可以定义一个宏,其扩展以意想不到的方式改变语法。所以你需要一个预处理器。)
当我写这个答案时,我的眼睛跳过了括号,其中 OP 指出“yacc”是指“menhir”。对不起。不幸的是,据我所知,menhir 不会生成 GLR 解析器。但我认为该方法仍然有效,并且足够有用,因此我将其保留。也许有一个 OCaml GLR(或 GLL)解析器生成器,或者也许 C/C++ 生成的代码仍然有用。
话虽如此,但并没有失去一切。您可以使用 bison 生成 GLR 解析器,然后尝试编写自定义消歧函数。如果消歧函数需要知道一个名称是否是 typedef,它可以尝试在符号 table 中查找它。与 classic“lexer hack”方法不同,GLR 消歧发生在解析器中,因此不需要构建笨重的反向通道。
这可能是一个迭代过程,因为您不需要弄清楚如何解决您尝试编译的特定代码中不存在的歧义。最终,您可能想要处理所有这些问题,但根据需要进行处理可能会证明是一种更充实的体验。