马少华 计算机1725011213218编译原理资料.docx
《马少华 计算机1725011213218编译原理资料.docx》由会员分享,可在线阅读,更多相关《马少华 计算机1725011213218编译原理资料.docx(28页珍藏版)》请在冰豆网上搜索。
马少华计算机1725011213218编译原理资料
塔里木大学信息工程学院
编译原理课程论文
LL
(1)语法分析器
所属学院信息工程学院
指导老师史召峰
班级计算机17-2
学生姓名马少华
学号5011213218
目录
1、需求分析......................................................................3
2、概要设计......................................................................4
2.1LL
(1)......................................4
2.1.1LL
(1)文法定义.....................................4
2.1.2文法的左递归.......................................4
2.1.3直接左递归.........................................4
2.1.4间接左递归..........................................4
2.2设计数据结构...................................5
2.2.1FIRST、FOLLOW集合的存储...........................5
2.2.2终结符集合的存储...................................5
2.2.3文法产生式的存储...................................5
2.2.4文法的存储.........................................6
2.2.5预测分析表的存储....................................6
2.3LL
(1)文法的判别.................................6
2.4预测分析程序——张表(分析表),一个栈..............6
2.5程序流程图......................................7
三、详细设计....................................8
3.1预测分析表的构造......................................8
3.2构造FOLLOW(A)......................................9
3.3构造First集模块设计...................................11
3.4消除左递归模块设计...................................13
3.5判断文法是否为LL
(1)文法...........................14
3.6判断输入的句型是否为给用户所输入文法的句型............16
3.7LL
(1)分析中的错误处理................................18
4、程序调试.....................................18
五、总结分析.....................................22
1.需求分析
语法分析是编译过程的核心部分。
它的任务是在词法分析识别出单词符号串的基础上,分析并判定程序的语法结构是否符合语法规则。
语法分析器在编译程序中的地位如图1所示:
图1语法分析器在编译程序中的地位
语言的语法结构是用上下文无关文法描述的。
因此,语法分析器的工作本质上就是按文法的产生式,识别输入符号串是否为一个句子。
这里所说的输入串是指由单词符号(文法的终结符)组成的有限序列。
对一个文法,当给你一串(终结)符号时,怎样知道它是不是该文法的一个句子呢?
这就要判断,看是否能从文法的开始符号出发推导出这个输入串。
或者,从概念上讲,就是要建立一棵与输入串相匹配的语法分析树。
自顶向下分析法就是语法分析办法中的一类。
顾名思义,自顶向下就是从文法的开始符号出发,向下推导,推出句子。
这种方法是带“回溯”的。
自顶向下分析的主旨是,对任何输入串,试图用一切可能的办法,从文法开始符号(根结)出发,自上而下地为输入串建立一棵语法树。
或者说,为输入串寻找一个最左推导。
这种分析过程本质上是一种试探过程,是反复使用不同产生式谋求匹配输入串的过程。
实现这种自顶向下的带回溯试探法的一个简单途径是让每个非终结符对应一个递归子程序。
每个这种子程序可作为一个布尔过程。
一旦发现它的某个候选与输入串相匹配,就用这个候选去扩展语法树,并返回“真”值;否则,保持原来的语法树和IP值不变,并返回“假”值。
对于给定的分析文法对象,构造它的预测分析程序;并任意给一算术表达式进行分析测试,本预测分析程序能够使用分析表和栈联合控制实现LL
(1)分析,本文将就编译原理中比较常用的一个表达式文法,通过递归下降语法分析法来编写分析器。
文中将为您提供如何通过FIRST、FOLLOW和SELECT集合来判断LL
(1)方法,然后如何用递归下降语法分析法分析LL
(1)方法的基本递归流程,以及用C++语言来编程实现分析器。
本程序需要首先构造文法,根据文法判断是否正确,消除左递归,判断是否为LL
(1) 文法,然后用户输入句型,根据句型判断是否为该文法的句型。
2.概要设计
自顶向下的分析算法通过在最左推导中描述出各个步骤来分析记号串输入。
之所以称这样的算法为自顶向下是由于分析树隐含的编号是一个前序编号,而且其顺序是由根到叶自顶向下的分析程序有两类:
回溯分析程序(backtrackingparser)和预测分析程序(predictiveparser)。
预测分析程序试图利用一个或多个先行记号来预测出输入串中的下一个构造,而回溯分析程序则试着分析其他可能的输入,当一种可能失败时就要求输入中备份任意数量的字符。
虽然回溯分析程序比预测分析程序强大许多,但它们都非常慢,一般都在指数的数量级上,所以对于实际的编译器并不合适。
递归下降程序分析和LL
(1)分析一般地都要求计算先行集合,它们分别称作First集合和Follow集合。
由于无需显式地构造出这些集合就可以构造出简单的自顶向下的分析程序。
2.1LL
(1)文法设计
2.1.1LL
(1)文法定义
LL
(1)文法是一类可以进行确定的自顶向下语法分析的文法。
就是要求描述语言的文法是无左递归的和无回溯的。
根据LL
(1)文法的定义,对于同一非终结符A的任意两个产生式A:
=a和A:
=b,都要满足:
SELECT(A:
=a)∩SELECT(A:
=b)=Ø。
这样,当前非终结符A面临输入符a时,如果a∈SELECT(A:
=a),则可以选择产生式A:
=a去准确匹配。
如本程序中举例说明的a.txt的文法就是一个LL
(1)文法:
S:
=aBc|bAB
A:
=aAb|b
B:
=b|0
2.1.2文法的左递归
当一个文法是左递归文法时,采用自顶向下分析法会使分析过程进入无穷循环之中。
所以采用自顶向下语法分析需要消除文法的左递归性。
文法的左递归是指若文法中对任一非终结符A有推导AA…,则称该文法是左递归的。
左递归又可以分为直接左递归和间接左递归。
2.1.3直接左递归
若文法中的某一产生式形如A→Aα,α∈V*,则称该文法是直接左递归的。
消除直接左递归的方法:
设有产生式是关于非终结符A的直接左递归:
A→Aα|β(α,β∈V*,且β不以A开头)
对A引入一个新的非终结符A′,把上式改写为:
A→βA′
A′→αA′|ε
2.1.4间接左递归
若文法中存在某一非终结符A,使得AA…至少需要两步推导,则称该文法是间接左递归的。
消除间接左递归的方法:
【方法一】采用代入法把间接左递归变成直接左递归。
【方法二】直接改写文法:
设有文法G10[S]:
S→Aα|β⑴
A→Sγ⑵
因为SAαSγα,所以S是一个间接递归的非终结符。
为了消除这种间接左递归,将⑵式代入⑴式,即可得到与原文法等价的文法(可以证明):
S→Sγα|β⑶
⑶式是直接左递归的,可以采用前面介绍的消除直接左递归的方法,对文法进行改写后可得文法:
S→βS′
S′→γαS′|ε
2.2设计数据结构
2.2.1FIRST、FOLLOW集合的存储
由于FIRST集合和FOLLOW集合里都是一个一个的字符,所以用字符数组来存储比较合适。
具体如下:
charFIRST[NUMVN];
charFOLLOW[NUMVN];
2.2.2终结符集合的存储
该集合存储着文法中一个一个的终结符,为了便于某一个终结符集中的查找定位,采用字符数组来存放是做适当不过的。
具体如下:
charVTSET[NUMVT];
2.2.3文法产生式的存储
文法的产生式都是形如A->a|b,因此比较复杂,此处,我自定义了一个结点类型。
具体如下:
StructVNNODE
{charv;
charfirstexpress[MAX];
charrightexpress[MAX];
char*firstptr;
char*followptr;
};
其中v存放着产生式左部的非终结符;
字符数组firstptr和rightptr分别存放该终结符的两个产生式,若只有一个产生式,则rightexpress为“ERROR”若该非终结符有ε产生式则将ε产生式固定的存放在rightexpress中。
字符指针firstptr、followptr分别指向该非终结符的FIRST集合和FOLLOW集合;
MAX为常数255;
2.2.4文法的存储
在此考虑到一般文法的产生式不是很多,采用数组存放是比较方便的。
文法的基本单位是产生式。
因此数组的每一个元素为上述的VNNODE类型。
具体如下:
VNNODEGrammer[NUMVN+1];
至此,我们可以看到设计这样的存储结构,便于算法的实现,但是也存在着一些局限性。
即每个非终结符的产生式的个数最多只允许两个.
2.2.5预测分析表的存储
预测分析表可以被视为一个二维数组,它的每一行与文法的一个非终结符号相关联,而其每一列则与一个终结符号或界符‘#’相关联。
而数组中的每一个元素又是一个复合结构类型,为了将数组的第一行(终结符或‘#’)和第一列(非终结符)与数组内部的产生式相协调,我将数组的每一个元素定义为字符指针。
具体如下:
char*M[NUMVN+1][NUMVT+1];
其中NUMVN、NUMVT分别为文法的非终结符、终结符的最大个数,在此用CONST修饰并定义为254;
2.3LL
(1)文法的判别:
一个文法中含有左递归和左公共因子绝对不是LL
(1)文法,所以也就不可能用确定的自顶向下分析法,对此结论可以证明。
然而,某些含有左递归和左公共因子的文法在通过等价变换把它们消除以后可能变为LL
(1)文法,但需要用LL
(1)文法的定义判别,也就是说文法中不含左递归和左公共因子,只是LL
(1)文法的必要条件。
2.4预测分析程序——张表(分析表),一个栈:
预测分析表是一个M[A,a]形式的矩阵,a是终结符或“#”(结束符),文法E->E+T|T,T->T*F|F,F->(E)|i是LL
(1)分析表,栈STACK存放文法符号,栈底先放一个“#”,每个输入串后接一个“#”,设当前栈顶符号为X,当前输入符号为a,则对应的LL
(1)预测分析表如下:
i
+
*
(
)
#
E
ETE
ETE
E
E+TE
E
E
T
TFT
TFT
T
T
T*FT
T
T
F
Fi
F(E)
若X=a=‘#’,则分析成功,并停止分析过程;若X=a‘#’,则把X从STACK栈顶逐出,让a指向下一个输入符号;若X是一个非终结符,则查表M。
若M[A,a]中存在产生式,则逐出X,然后把产生式右部符号串反序入栈(若右部符号为,则意味不推什么东西进栈),同时应做这个产生式相应的语义动作(目前暂且不管)。
若M[A,a]中存放出错标志,则调用ERROR。
2.5程序流程图
3.详细设计
3.1预测分析表的构造
对G中每个文法符号XVT∪VN,构造FIRST(X)。
连续使用下述规则,直至每个FIRST集合不再增大:
若XVT,则FIRST(X)={X};
若XVN,且有产生式Xa…,则把a加入FIRST(X);若X也是一条产生式,则把也加入;若XY…是一个产生式且YVN,则把FIRST(Y)中的所有非元素都加入FIRST(X)中;若XY1Y2…Yk是一个产生式,Y1,Y2,…,Yi–1都是非终结符,而且,对任意j(1≤j≤i–1),FIRST(Yj)都含有,则把FIRST(Yi)中的所有非元素加入FIRST(X)中;特别是,若所有的FIRST(Yj)均含有,j=1,2,…,k,则把加入FIRST(X)中。
构造M[A,a]对文法G的每个产生式A,执行第2步和第3步;对每个终结符aFIRST(),把A加入M;若FIRST(),则对任何bFOLLOW(A)把A加入M;所有无定义的M[A,a]标上“出错标志”。
程序模块实现如下:
voidMake_M()
{
inti,j,k,m;
for(i=0;i<=19;i++)
for(j=0;j<=19;j++)
M[i][j]=-1;
i=strlen(termin);
termin[i]='#';/*将#加入终结符数组*/
termin[i+1]='\0';
for(i=0;i<=count-1;i++)
{
for(m=0;;m++)
if(non_ter[m]==left[i])
break;/*m为产生式左部非终结符的序号*/
for(j=0;j<=strlen(select[i])-1;j++)
{
if(in(select[i][j],termin)==1)
{
for(k=0;;k++)
if(termin[k]==select[i][j])
break;/*k为产生式右部终结符的序号*/
M[m][k]=i;
}
}
}
}
3.2构造FOLLOW(A)
对G中每个非终结符A,构造FOLLOW(A)。
连续使用下述规则,直至每个FOLLOW不再增大。
对文法的开始符号S,置#于FOLLOW(S)中;若AB是一个产生式,则把FIRST()\{}加入FOLLOW(B);若AB是一个产生式,或AB是一个产生式而,则把FOLLOW(A)加入FOLLOW(B)。
对文法(:
ETETFTE+TE|F(E)|iTFT|其first和follow集如下:
FIRST(E)={(,i}FOLLOW(E)={),#}
FIRST(E)={+,}FOLLOW(E)={),#}
FIRST(T)={(,i}FOLLOW(T)={+,),#}
FIRST(T)={*,}FOLLOW(T)={+,),#}
FIRST(F)={(,i}FOLLOW(F)={,+,),#}
程序代码模块实现如下:
voidFOLLOW(inti)
{
intj,k,m,n,result=1;
charc,temp[20];
c=non_ter[i];/*c为待求的非终结符*/
temp[0]=c;
temp[1]='\0';
merge(fo,temp,1);
if(c==start)
{/*若为开始符号*/
temp[0]='#';
temp[1]='\0';
merge(follow[i],temp,1);
}
for(j=0;j<=count-1;j++)
{
if(in(c,right[j])==1)/*找一个右部含有c的产生式*/
{
for(k=0;;k++)
if(right[j][k]==c)
break;/*k为c在该产生式右部的序号*/
for(m=0;;m++)
if(v[m]==left[j])
break;/*m为产生式左部非终结符在所有符号中的序号*/
if(k==strlen(right[j])-1)
{/*如果c在产生式右部的最后*/
if(in(v[m],fo)==1)
{
merge(follow[i],follow[m],1);
continue;
}
if(F[m]=='0')
{
FOLLOW(m);
F[m]='1';
}
merge(follow[i],follow[m],1);
}
else
{/*如果c不在产生式右部的最后*/
for(n=k+1;n<=strlen(right[j])-1;n++)
{
empt[0]='\0';
result*=_emp(right[j][n]);
}
if(result==1)
{/*如果右部c后面的符号串能推出^*/
if(in(v[m],fo)==1)
{/*避免循环递归*/
merge(follow[i],follow[m],1);
continue;
}
if(F[m]=='0')
{
FOLLOW(m);
F[m]='1';
}
merge(follow[i],follow[m],1);
}
for(n=k+1;n<=strlen(right[j])-1;n++)
temp[n-k-1]=right[j][n];
temp[strlen(right[j])-k-1]='\0';
FIRST(-1,temp);
merge(follow[i],TEMP,2);
}
}
}
F[i]='1';
}
3.3构造First集模块设计
FIRST(α)={a|α=*=>aβ,a∈VT,α,β∈V*}若α=*=>ε,则规定ε∈FIRST(α).当一个文法中相同左部非终结符的右部存在能=*=>ε的情况则必须知道该非终结符的后跟符号的集合中是否含有其它右部开始符号集合的元素。
本程序设计代码如下:
voidfirst2(inti)
{/*i为符号在所有输入符号中的序号*/
charc,temp[20];
intj,k,m;
charch='^';
c=v[i];
emp(ch);
if(in(c,termin)==1)/*若为终结符*/
{
first1[i][0]=c;
first1[i][1]='\0';
}
elseif(in(c,non_ter)==1)/*若为非终结符*/
{
for(j=0;j<=count-1;j++)
{
if(left[j]==c)
{
if(in(right[j][0],termin)==1||right[j][0]=='^')
{
temp[0]=right[j][0];
temp[1]='\0';
merge(first1[i],temp,1);
}
elseif(in(right[j][0],non_ter)==1)
{
if(right[j][0]==c)
continue;
for(k=0;;k++)
if(v[k]==right[j][0])
break;
if(f[k]=='0')
{
first2(k);
f[k]='1';
}
merge(first1[i],first1[k],2);
for(k=0;k<=strlen(right[j])-1;k++)
{
empt[0]='\0';
if(_emp(right[j][k])==1&&k{
for(m=0;;m++)
if(v[m]==right[j][k+1])
break;
if(f[m]=='0')
{
first2(m);
f[m]='1';
}
merge(first1[i],first1[m],2);
}
elseif(_emp(right[j][k])==1&&k==strlen(right[j])-1)
{
temp[0]='^';
temp[1]='\0';
merge(first1[i],temp,1);
}
else
break;
}
}
}
}
}
f[i]='1';
}
3.4消除左递归模块设计
欲构造行之有效的自上而下分析器,必须消除回溯。
为了消除回溯就必须保证:
对文法的任何非终结符,当要它去匹配输入串时,能够根据它所面临的输入符号准确地指派它的一个候选去执行任务,并且此候选的工作结果应是确信无疑的。
在本程序里的设计思路如下:
voidrecur(char*point)
{/*完整的产生式在point[]中*/
intj,m=0,n=3,k;
chartemp[20],ch;
ch=c();/*得到一个非终结符*/
k=strlen(non_ter);
non_ter[k]=ch;
non_ter[k+1]='\0';
cout<<"此文法含有左递归,现在开始分改此含左递归的产生式:
"<for(j=0;j<=strlen(point)-1;j++)
{
if(point[n]==point[0])
{/*如果‘|’后的首符号和左部相同*/
for(j=n+1;j<=strlen(point)-1;j++)
{
while(point[j]!
='|'&&point[j]!
='\0')
temp[m++]=point[j++];
left[c