yacc如何解析C 代码片段?
我是正在写一个解析器,使用yacc,类似于yacc本身,因为我的输入语言会混入一些C 代码片段。
考察yacc的典型产生式类型:
something: some thing { $$ = makesomething($1, $2); } ;
现在,这里的语法其实是yacc语法与C 语法的混合。这个部分:
something: some thing { ... } ;
全都是yacc语法。但花括号之间的内容:
... $$ = makesomething($1, $2);
大多是C,尽管对 $$、$1、和 $2 做了一些类似预处理器的替换。
yacc本身如何解析这个?我可以想到两种可能:
- 它包含完整的C 语言语法。
- 它只是抓取
{ }之间的全部内容。
我非常怀疑是(1)。但实现(2)也很棘手,因为当然C 片段本身很可能包含它自己的花括号。因此,为了正确抓取(外层)花括号之间的C 片段,yacc的解析器和/或词法分析器必须能够匹配内部的花括号,同时还要注意忽略在任何被引号包围的字符串、字符常量和注释中的花括号。
在我自己的代码里,我正面临完全相同的问题。我将会有类似这样的产生式:
transition: TRANSITION TO ID ON '(' condition ')' ;
以及:
action: ACTION '{' actions '}' ;
其中 condition 是任意C 表达式,actions 基本上是任意的C 代码。
有没有人知道一个好方法来实现,或者yacc或 bison是怎么实现的?
附录:Johnson的关于yacc的论文的附录B 包含伪产生式
act : '{' {Copy action, translate $$, etc.} '}'
所以我猜答案基本上是我的选项“2”。(而且很可能需要对lex使用一个或两个专家模式钩子,以从输入中抓取原始文本,而不干扰lex的前瞻。)
解决方案
POSIX标准为 [Yacc] 指定了一个Yacc语法,且该语法省略了对动作内容解析的细节:
act : '{'
{
/* Copy action, translate $$, and so on. */
}
'}'
描述中有一句话:An action is an arbitrary C statement and as such can do input or output, call subprograms, and alter external variables. An action is one or more C statements enclosed in curly braces '{' and '}'. The statements shall not contain unbalanced curly brace preprocessing tokens.
该标准似乎既不要求也不禁止在action中进行实际的C 语句解析,但建议进行至少涉及对配对括号计数的最小解析。
在Yacc实现中执行完整的解析工作可能不可行,原因如下。解析一个C 语法片段通常需要访问该语法中被引用但在其中未定义的所有标识符的声明。C语言存在需要声明信息来解决的语法歧义。这意味着Yacc需要在解析语法文件时就执行所有头文件的包含,并实现大量工作。尽管如此,Yacc的实现并不要求这样;文件中出现的 #include "foo.h" 可以引用在Yacc处理时并不存在的头文件;只有翻译后的文件(如 y.tab.c)在被C 编译器处理时才需要头文件存在。这两个处理阶段不一定在同一个环境中进行。
一个Yacc实现可能应该在开头的花括号之后对材料进行一些尽量简化的标记化,并计数花括号以确定结束位置。它应能识别字符串字面量、字符字面量以及注释。它不应被像 "Hello, $1" 或 case '}': break; 这样的代码所误导。然而,如果某个action做成类似 { enum foo { $1, $2 }; typedef int $$; } 的事情,那么在那种情况下,我会说,局势就不可预测了。
关于yacc的各种实现(如Berkeley Yacc、Bison)的源代码是可获得的;理解实际的做法是完全可能的。