我最近一直在编写基于C语言的解析器。我正在使用CUP(Yacc for Java)。
我想实现“lexer hack”(http://eli.thegreenplace.net/2011/05/02/the-context-sensitivity-of-c%E2%80%99s-grammar-revisited/或https://en.wikipedia.org/wiki/The_lexer_hack),以区分typedef名称和变量/函数名称等。启用声明与前面声明的类型相同名称的变量(例子来自第一个链接):
typedef int AA;
void foo() {
AA aa; /* OK - define variable aa of type AA */
float AA; /* OK - define variable AA of type float */
}
我们必须介绍一些新的产品,其中变量/函数名称可以是IDENTIFIER
或TYPENAME
。这就是困难发生的时刻 - 语法冲突。
我试图不将这个凌乱的Yacc语法用于gcc 3.4(http://yaxx.googlecode.com/svn-history/r2/trunk/gcc-3.4.0/gcc/c-parse.y),但这次我不知道如何自己解决冲突。我看了一下Yacc语法:
declarator:
after_type_declarator
| notype_declarator
;
after_type_declarator:
...
| TYPENAME
;
notype_declarator:
...
| IDENTIFIER
;
fndef:
declspecs_ts setspecs declarator
// some action code
// the rest of production
...
setspecs: /* empty */
// some action code
declspecs_ts
表示declaration_specifiers在哪里
“是否已经看到类型说明符;在类型说明符之后,typedef名称是重新声明的标识符(_ts或_nots)。”
从declspecs_ts我们可以到达
typespec_nonreserved_nonattr:
TYPENAME
...
;
乍一看,我无法相信如何不出现转换/减少冲突!
setspecs
为空,因此我们declspecs_ts
后跟declarator
,因此我们可以预期解析器应该混淆TYPENAME
是来自declspecs_ts
还是来自declarator
{1}}。
任何人都可以简单地(甚至是精确地)解释这个。提前谢谢!
编辑: 有用的链接:http://www.gnu.org/software/bison/manual/bison.html#Semantic-Tokens
答案 0 :(得分:1)
我不能代表具体的代码。
但基本的诀窍是C lexer检查每个IDENTIFIER,并决定是否可能是typedef的名称。如果是这样,那么它将lexeme类型更改为TYPEDEF并将其交给解析器。
词法分析器如何知道typedef是什么标识符?解析器必须通过在运行时捕获typedef信息来告诉它。在与声明相关的语法中,必须有一个动作来提供这些信息。我原以为它会附加到typedef声明的语法规则。
你没有表明“setspec”的作用;也许就是那个地方。与LR解析器生成器一起使用的常见技巧是引入一个带有空右手的语法规则E(您的示例“setspec”?),以便在其他语法规则(您的示例“fndef”)中调用,以便启用在处理该规则的过程中访问语义动作。
如果你不能从其他标识符告诉typedef,那么整个技巧就是解决歧义问题。如果你的解析器容忍歧义,你根本不需要这个hack;只需解析,并使用(子)解析构建AST。获取AST后,树行走可以找到类型信息并消除不一致的子算法。我们使用GLR对C和C ++执行此操作,并且将解析与名称解析完美地分开。