📄 fu.htm.bak
字号:
<br>
2.4.2 语义动作<br>
<br>
当语法分析程序识别出某个句型时,它即用相应的语法规则进行归约,yscc在进行归约之前,先完成用户提供的语义动作,这些语义动作可以是返回语法符号的语义值,也可以是求某些语法符号的语义值,或者是其他适当的动作如建立语法树,产生目标代玛,打印有关信息等。终结符的语义值是通过词法分析程序返回的,这个值由全局变量(yacc自动定义的) yylval带回,如果用户在词法分析
程序识别出某终结符时,给yylval赋与相应的值,这个值就自动地作为该终结符的语义值。当语义值的类型不是int时,要注意yylval的值的类型须与相应的终结符的语义值类型一致。语义动作是用C语言的语句写成的,跟在相应的语法规则后面,用花括号括起来.例如:<br>
<center>
<table>
<tr>
<td>
A:'('B')'<br>
{hello(l,“abc”);}<br>
XXX:YYY ZZZ<br>
{printf(“a message\n”);<br>
flag=25;<br>
}<br>
:<br>
</td>
</tr>
</table>
</center>
<br>
要存取语法符号的语义值,用户要在语义动作中使用以$开头的伪变量,这些伪变量是yacc内部提供的,用户不用定义。伪变量$$代表产生式左部非终结符的语义值,产生式右部各语法符号的语义值按从左到右的次序为$1,$ 2,…
例如在下面的产生式中:<br>
<br>
A :B C D<br>
;<br>
<br>
A的语义值为$$,B、C、D的语义值依次为$ 1,$2,$3。<p>
为说明伪变量的作用,请看下例:有产生式<br>
<br>
expr:'('expr')'<br>
;<br>
<br>
左的边的exPr的值应该等于右连的expr的值,表示这个要求的语义动作为,<br>
expr: '('expr')'<br>
{$$=$2;}<br>
;
<br>
<br>
如果在产生式后面的语义动作中没有为伪变量$$赋值, yaCC自动把它置为产生式右部第一个语法符号的值(即$1)有较复杂的应用中,往往需要在产生式右部的语法符号之间插入语义动作.这意味着使语法分析器在识别出句型的一部分时就完成这些动作。请看下例:<br>
<br>
A:B<br>
<br>
{$$=1;}<br>
<br>
C<br>
{X=$2; y=$3;}<br>
<br>
例中x 的值最后为1而y的值量为符号C的语义值,注意B后面的语义动作$$=1并非将符号A的语义值置为1,这是因为上面的例子是按下面的方式实现的。<br>
<br>
$ACT:/*empty。/<br>
{$$=1;}<br>
;<br>
A:B$ACTC<br>
{X=$2;y=$3;}<br>
;<br>
<br>
即 yacc自动设置一个非终结符$ ACT及一个空产生式用以完成上述语义动作。关于语义动作的实例请读者详细阅读6中的两个例子。<br>
<br>
2.4.3 yacc解决二义性和冲突的方法<br>
<br>
在2.3.8中已涉及到二义性和冲突的问题,这里再集中介绍一下,这在写Yacc源程序时会经常碰到。二义性会带来冲突。在2.3.8中我们介绍了yacc可以用为算符确定优先级和结合规则解决由二义性造成的冲突,但是有一些由二义性造成的冲突不易通过优先级方法解决,<br>
<br>
如有名的例子:<br>
<br>
stat:IF bexp THEN stat<br>
<br>
|IF bexp THEN stat ELSE<br>
stat<br>
;<br>
<br>
对于这样的二义性造成的冲突和一些不是由二义性造成的冲突,Yacc提供了下面两条消除二义性的规则:<br>
<br>
A1.出现移进/归约冲突时,进行移进;<br>
<br>
A2. 出现归约/归约冲突时,按照产生式在yacc源程序中出现的次序,用先出现的产生式归约。<br>
<br>
我们可以看出用这两条规则解决上面的IF语句二义性问题是合乎我们需要的。所以用户不必将上述文法改造成无二义性的。当Yacc用上述两条规则消除了二义性,它将给出相应信息
。<br>
<br>
下面再稍微严格地介绍一下Yacc如何利用优先级和结合性来解决冲突的。<br>
<br>
Yacc源程序中的产生式也有一个优先级和结合性.这个优先级和结合性就是该产生式右部最后一个终结符或文字字符的优先级和结合性,当使用了%Prec子句时,该产生式的优先级和结合性由%Prec子句决定。当然如果产生式右部最后一个终结符或文字字符没有优先级或结合性,则该产生式也没有优先级或结合性。<br>
<br>
根据终结符(或文字字符)和产生式的优先级和结合性,Yacc又有两个解决冲突的规则:<br>
<br>
P1. 当出现移进/归约冲突或归约/归约冲突,而当时输入符号和语法规则(产生式)均没有优先级和结合性,就用 AI和A2来解决这些冲突。<br>
<br>
P2.当出现移进/归约冲突时,如果输入符号和语法规则(产生式)都有优先级和结合性,那么如果输入符号的优先级大于产生式的优先级就移进如果输入符号的优先级小于产生式的优先级就归约。如果二者优先级相等,则由结合性决定动作,左结合则归约,右结合则移进,无结合性则出错。<br>
<br>
用优先级和结合性能解决的冲突,yacc不报告给用户。<br>
<br>
2.4.4 语法分析中的错误处理<br>
<br>
当进行语法分析时发现输入串有语法错误,最好能在报告出错信息以后继续进行语法分析,以便发现更多的错误。<br>
<br>
yacc处理错误的方法是:当发现语法错误时,yacc丢掉那些导致错误的符号适当调整状态栈。然后从出错处的后一个符号处或跳过若干符号直到遇到用户指定的某个符号时开始继续分析。<br>
<br>
Yacc内部有一个保留的终结符error,把它写在某个产生式的右部,则Yacc就认为这个地方可能发生错误,当语法分析的确在这里发生错误时,Yacc就用上面介绍的方法处理,如果没有用到 error的产生式,则 Yacc打印出“Syntax
error”,就终止语法分析。<br>
<br>
下面看两个使用error的简单例子:<br>
<br>
1.下面的产生式<br>
<br>
stat: error<br>
;<br>
<br>
使yacc在分析stat推导出的句型时,遇到语法错误时跳过出错的部分,继续分析(也会打<br>
<br>
印语法错信息)<br>
<br>
2.下面的产生式<br>
<br>
stat: error ';'<br>
;<br>
<br>
使yacc碰到语法错时,跳过输入串直到碰到下一个分号才继续开始语法分析。<br>
<br>
如果语法分析的输入串是从键盘上输入的(即交互式),那么某一行出错后,希望重新<br>
<br>
输入这一行,并使yacc立即开始继续分析,这只要在语义动作中使用语句yyerror即可,如下例:<br>
<br>
<center>
<table border=0>
<tr>
<td>
input: error‘\n’<br>
{yyerror;<br>
printf (“Reenter last line:”);}<br>
input<br>
{$$=$4;}<br>
;<br>
</td>
</tr>
</table>
</center>
<br>
关于错误处理请参看[2]和6的例子。<br>
<br>
2.5 程序段部分<br>
<br>
程序段部分主要包括以下内容:主程序 main();错误信息执行程序 yyerror(s);词法分析程序yylex();用户在语义动作中用到的子程序,下面分别介绍。<br>
<br>
2.5.l主程序<br>
<br>
主程序的主要作用是调用语法分析程序yyparse(),yyparse()是yacc 从用户写的yacc源程序自动生成的,在调用语法分析程序yyparse()之前或之后用户往往需要做一些其他处理,这些也在main()中完成,如果用户只需要在main()中调用yyparse(),则也可以使用Unix的yacc库(一ly)中提供的main()而不必自己写。库里的main()如下:<br>
<br>
<center>
<table border=0>
<tr>
<td>
main() {<br>
return(yyparse());<br>
}<br>
</td>
</tr>
</table>
</center>
<br>
2.5.2 错误信息报告程序<br>
<br>
yacc的库也提供了一个错误信息报告程序,其源程序如下:<br>
<br>
<center>
<table border=0>
<tr>
<td>
#include <stodio. h><br>
yyerror (s) char * s{<br>
fprintf (stderr, “%s\n”,s);<br>
}<br>
</td>
</tr>
</table>
</center>
<br>
如果用户觉得这个yyerror(s)太简单。也可以自己提供一个,如在其中记住输入串的行号并当yyerror(s)被调用时,可以报告出错行号。<br>
<br>
2.5.3 词法分析程序<br>
<br>
词法分析程序必须由用户提供,其名字必须是yylex,调法分析程序向语法分析程序提供当前输入的单词符号。yylex提供给yyparse的不是终结符本身,而是终结符的编号,即token number,如果当前的终结符有语义值,yylex必须把它赋给yylval。<br>
<br>
下面是一个词法分析程序例子的一部分。<br>
<br>
<center>
<table border=0>
<tr>
<td>
yylex(){<br>
extern int yylval<br>
int c;<br>
…<br>
c= getchar();<br>
…<br>
switch(c){<br>
…<br>
case ‘0’:<br>
case ‘1’:<br>
...<br>
case “9”<br>
yylval=c-'0'<br>
return (DIGIT);<br>
…<br>
}
<br>
…<br>
</td>
</tr>
</table>
</center>
<br>
上述词法分析程序碰到数字时将与其相应的数值赋给yylval,并返回DIGIT的终结符编号,注意DIGIT代表它的编号(如可
以通过宏来定义)。<br>
<br>
用户也可以用Lex为工具编写记号词法分析程序,如果这样,在yacc源程序的程序段部分就只需要用下面的语句来代替词
法分析程序:<br>
<br>
#include "lex.yy.c"<br>
<br>
为了清楚lex与yacc的关系,我们用下图表示lex与yacc配合使用的情况;<p align="center">
<img border="0" src="f3.gif" tppabs="http://162.105.30.75/materialroot/ug3/compiler_project/f3.gif" width="449" height="176">
</p>
<p>
在Unix系统中,假设lex源程序名叫plo.l.yace源程序名叫plo.y,则从这些源程序得到可用的词分析程序中语法分析程序依次使用下述三个命令:<br>
<br>
<center>
<table border=0>
<tr>
<td>
Lex plo.l<br>
<br>
yacc plo,y<br>
<br>
cc y,tab,c -ly-ll<br>
</td>
</tr>
</table>
<br>
第一条命令从lex源程序plo.l产生词法分析程序,文件名为lex.yy.c第二命令从yacc源程序plo.y产生语法分析程序,文件名为<br>
<br>
y.tab.c;第三条命令将y.tab.c这个c语言的程序进行编译得到可运行的目标程序。<
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -