yacc 理论(转载)

 YACC
理论
yacc 的文法由一个使用BNF 文法(BackusNaur
form)的变量描述。 BNF 文法规则最初由 John
Backus 和 Peter Naur 发明,并且用于描述Algol60 语言。 BNF 能够用于表达上下文无关语言。现代
程序语言中的大多数结构可以用BNF 文法来表达。例如,数值相乘和相加的文法是:
E >
E + E
E >
E * E
E >
id
上面举了三个例子,代表三条规则(依次为 r1,r2,r3)。像 E (表达式)这样出现在左边的结构叫
非终结符(nonterminal)。像 id(标识符)这样的结构叫终结符(terminal,由lex 返回的标记),它
们只出现在右边。这段文法表示,一个表达式可以是两个表达式的和、乘积,或者是一个标识符。
我们可以用这种文法来构造下面的表达式:
E >
E * E (r2)
>
E * z (r3)
>
E + E * z (r1)
>
E + y * z (r3)
>
x + y * z (r3)
每一步我们都扩展了一个语法结构,用对应的右式替换了左式。右面的数字表示应用了哪条规则。
为了剖析一个表达式,我们实际上需要进行倒序操作。不是从一个简单的非终结符开始,根据语法
生成一个表达式,而是把一个表达式逐步简化成一个非终结符。这叫做“自底向上”或者“移进归
约”分析法,这需要一个堆栈来保存信息。下面就是用相反的顺序细述了和上例相同的语法:
1 . x + y * z 移进
2 x . + y * z 归约 (r3)
3 E . + y * z 移进
4 E + . y * z 移进
5 E + y . * z 归约 (r3)
6 E + E . * z 移进
7 E + E * . z 移进
8 E + E * z . 归约 (r3)
9 E + E * E . 归约(r2) 进行乘法运算
10 E + E . 归约(r1) 进行加法运算
11 E . 接受
点左面的结构在堆栈中,而点右面的是剩余的输入信息。我们以把标记移入堆栈开始。当堆栈顶部
和右式要求的记号匹配时,我们就用左式取代所匹配的标记。概念上,匹配右式的标记被弹出堆
栈,而左式被压入堆栈。我们把所匹配的标记认为是一个句柄,而我们所做的就是把句柄向左式归
约。这个过程一直持续到把所有输入都压入堆栈中,而最终堆栈中只剩下最初的非终结符。在第1
步中我们把x 压入堆栈中。第2 步对堆栈应用规则 r3,把x 转换成 E 。然后继续压入和归约,直到
堆栈中只剩下一个单独的非终结符,开始符号。在第9 步中,我们应用规则 r2 ,执行乘法指令。同
样,在第10 步中执行加法指令。这种情况下,乘法就比加法拥有了更高的优先级。
考虑一下,如果我们在第6 步时不是继续压入,而是马上应用规则r1 进行归约。这将导致加法比
乘法拥有更高的优先级。这叫做“移进
归约”冲突(shiftreduce
conflict )。我们的语法模糊不
清,对一个表达式可以引用一条以上的适用规则。在这种情况下,操作符优先级就可以起作用了。
举另一个例子,可以想像在这样的规则中
E >
E + E
是模糊不清的,因为我们既可以从左面又可以人右面递归。为了挽救这个危机,我们可以重写语法
规则,或者给yacc 提供指示以明确操作符的优先顺序。后面的方法比较简单,我们将在练习段中
进行示范。
下面的语法存在“归约
归约”冲突 (reducereduce
conflict)。当堆栈中存在id 是,我们既可以归约
为 T,也可以归约为 E 。
E >
T
E >
id
T >
id
当存在冲突时, yacc 将执行默认动作。当存在“移进
归约”冲突时,y acc 将进行移进。当存在
“归约
归约”冲突时, yacc 将执行列出的第一条规则。对于任何冲突,它都会显示警告信息。只
有通过书写明确的语法规则,才能消灭警告信息。后面的章节中我们将会介绍一些消除模糊性的方
法。

 

练习,第一部分
... 定义 ...
%%
... 规则 ...
%%
... 子程序 ...
yacc 的输入文件分成三段。“ 定义”段由一组标记声明和括在“%{”和“%}”之间的C 代码组
成。B NF 语法定义放在“规则”段中,而用户子程序添加在“子程序”段中。
构造一个小型的加减法计算器可以最好的说明这个意思。我们要以检验lex 和yacc 之间的联系开始
我们的学习。下面是yacc 输入文件的定义段:
%token INTEGER
上面的定义声明了一个INTEGER 标记。当我们运行yacc 时,它会在y.tab.c 中生成一个剖析器,
同时会产生一个包含文件 y.tab.h :
#ifndef YYSTYPE
#define YYSTYPE int
#endif
#define INTEGER 258
extern YYSTYPE yylval;
lex 文件要包含这个头文件,并且使用其中对标记值的定义。为了获得标记,y acc 会调用 yylex。
yylex 的返回值类型是整型,可以用于返回标记。而在变量yylval 中保存着与返回的标记相对应的
值。例如,
[09]+
{ yylval = atoi(yytext);
return INTEGER; }
将把整数的值保存在yylval 中,同时向yacc 返回标记 INTEGER。y ylval 的类型由 YYSTYPE
决定。由于它的默认类型是整型,所以在这个例子中程序运行正常。 0255
之间的标记值约定为字
符值。例如,如果你有这样一条规则
[+]
return *yytext; /* 返回操作符 */
减号和加号的字符值将会被返回。注意我们必须把减号放在第一位心避免出现范围指定错误。
由于lex 还保留了像“文件结束”和“错误过程”这样的标记值,生成的标记值通常从258 左右开
始。下面是为我们的计算器设计的完整的lex 输入文件:
%{
#include <stdlib.h>
void yyerror(char *);
#include "y.tab.h"
%}
%%
[09]+
{
yylval = atoi(yytext);
return INTEGER;
}
[+/
n] return *yytext;
[ /t] ; /* skip whitespace */
. yyerror("invalid character");
%%
int yywrap(void) {
return 1;
}
yacc 在内部维护着两个堆栈;一个分析栈和一个内容栈。分析栈中保存着终结符和非终结符,
并且代表当前剖析状态。内容栈是一个YYSTYPE 元素的数组,对于分析栈中的每一个元素都保存
着一个对应的值。例如,当yylex 返回一个INTEGER 标记时,y acc 把这个标记移入分析栈。同
时,相应的yylval 值将会被移入内容栈中。分析栈和内容栈的内容总是同步的,因此从栈中找到对
应于一个标记的值是很容易实现的。下面是为我的计算器设计的yacc 输入文件:
%{
int yylex(void);
void yyerror(char *);
%}
%token INTEGER
%%
program:
program expr '/n' { printf("%d/n", $2); }
|
;
expr:
INTEGER { $$ = $1; }
| expr '+' expr { $$ = $1 + $3; }
| expr ''
expr { $$ = $1 $
3; }
;
%%
void yyerror(char *s) {
fprintf(stderr, "%s/n", s);
return 0;
}
int main(void) {
yyparse();
return 0;
}
规则段的方法类似前面讨论过的BNF 文法。规则第一条叫command 规则。其中的左式,或都
称为非终结符,从最左而开始,后面紧跟着一个自己的克隆。后面跟着的是右式。与规则相应的动
作写在后面的花括号中。
通过利用左递归,我们已经指定一个程序由0 个或更多个表达式构成。每一个表达式由换行结
束。当探测到换行符时,程序就会打印出表达式的结果。当程序应用下面这个规则时
expr: expr '+' expr { $$ = $1 + $3; }
在分析栈中我们其实用左式替代了右式。在本例中,我们弹出“expr '+' expr” 然后压入
“expr”。 我们通过弹出三个成员,压入一个成员缩小的堆栈。在我们的C 代码中可以用通过相对
地址访问内容栈中的值,“ $1”代表右式中的第一个成员,“ $2”代表第二个,后面的以此类推。“ $
$ ”表示缩小后的堆栈的顶部。在上面的动作中,把对应两个表达式的值相加,弹出内容栈中的三
个成员,然后把造得到的和压入堆栈中。这样,分析栈和内容栈中的内容依然是同步的。
当我们把INTEGER 归约到expr 时,数字值开始被输入内容栈中。当NTEGER 被移分析栈中
之后,我们会就应用这条规则
expr: INTEGER { $$ = $1; }
INTEGER 标记被弹出分析栈,然后压入一个 expr。 对于内容栈,我们弹出整数值,然后又把
它压回去。也可以说,我们什么都没做。事实上,这就是默认动作,不需要专门指定。当遇到换行
符时,与expr 相对应的值就会被打印出来。当遇到语法错误时,y acc 会调用用户提供的yyerror 函
数。如果你需要修改对yyerror 的调用界面,改变yacc 包含的外壳文件以适应你的需求。你的 yacc
文件中的最后的函数是main ... 万一你奇怪它在哪里的话。这个例子仍旧有二义性的语法。 yacc 会
显示“移进归
约”警告,但是依然能够用默认的移进操作处理语法。

阅读更多
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页