基于VC++的LL1语法分析器设计与实现doc.docx
- 文档编号:25911178
- 上传时间:2023-06-16
- 格式:DOCX
- 页数:19
- 大小:159.47KB
基于VC++的LL1语法分析器设计与实现doc.docx
《基于VC++的LL1语法分析器设计与实现doc.docx》由会员分享,可在线阅读,更多相关《基于VC++的LL1语法分析器设计与实现doc.docx(19页珍藏版)》请在冰豆网上搜索。
基于VC++的LL1语法分析器设计与实现doc
基于VC++的LL
(1)语法分析器设计与实现
作者姓名:
晏丽智指导老师:
王一宾
摘要:
语法分析是编译过程的核心部分,可以粗略的分为自上而下分析法和自下而上分析法。
LL
(1)文法是一类可以进行确定的自上而下语法分析的文法。
本文首先阐述了LL
(1)文法的基本理论,然后着重讨论了LL
(1)语法分析器的设计,最后用VC++实现了LL
(1)语法分析器。
关键词:
LL
(1)文法,FIRST集,FOLLOW集,预测分析表
0引言
语法分析是编译过程的核心部分,它的任务是在词法分析识别出单词符号串的基础上,分析并判定程序的语法结构是否符合语法规则。
LL
(1)文法是一类可以进行确定的自上而下语法分析的文法。
本文讨论了LL
(1)语法分析器的工作原理和过程,重点说明了FIRST集、FOLLOW集以及预测分析表的构造。
1LL
(1)语法分析器的基本理论
1.1理论基础
语法分析是编译过程的核心部分,它的任务是在词法分析识别出单词符号串的基础上,分析并判定程序的语法结构是否符合语法规则。
语法分析器工作本质:
按文法的产生式,识别输入符号串是否为一个句子,判断是否能从文法的开始符号出发推导出这个输入串。
LL
(1)文法是一类可以进行确定的自上而下语法分析的文法。
自上而下分析方法的基本思想是从文法的开始符号出发,向下推导,推出句子;即对任何输入串,试图用一切可能的办法,从文法开始符号出发,自上而下地为输入串建立一棵语法树[1]。
实现这种自上而下分析法存在许多困难,首先是递归问题,一个文法是含有左递归的,如果存在非终结符P,有PPa,则含有左递归的文法将使上述的自上而下的分析过程陷入无限循环。
因此,使用自上而下分析法必须消除文法的左递归。
其次是回溯问题。
由于回溯造成的匹配虚假现象,把已经做的一大堆语义工作推倒重来。
这些事情既麻烦又费时间,所以,最好应设法消除回溯。
1.2左递归的消除
直接消除见诸于产生式中的左递归是比较容易的。
假定关于非终结符P的规则为 P→Pα|ß
其中,ß不以P开头。
那么我们可以把P的规则改写为如下的非直接左递归形式:
P→ßP’
P’→αP’|ε(ε为空字)
这种形式和原来的形式是等价的,也就是说,从P推出的符号串是相同的。
一般而言,假定关于P的全部产生式是
P→Pα1|Pα2|…|Pαm|β1|β2|…βn
其中α都不等于ε,且每个β不以P开头。
则改写后为 P→β1P´|β2P´|…|βnP´
P´→α1P´|α2P´|…|αmP´|ε
使用这个方法,我们容易把见诸于表面的所有直接左递归都消除掉,也就是说,把直接左递归都改成直接右递归。
对于间接左递归的消除可以采用代人法把间接左递归变成直接左递归[2]。
下面给出消除左递归的算法:
如果一个文法不含回路,也不含以ε为右部的产生式。
(1)排序
把文法G的所有非终结符按任一种顺序排列P1、P2、…、Pn按序执行。
(2)代入及消除左递归
for(i=1;i<=n;i++)
for(j=1;j<=i-1;j++)
把形如Pi→Pjγ的规则改写为
Pi→δ1γ|δ2γ|…|δkγ
(其中Pj→δ1|δ2|…|δk是关于Pj的所有规则)
消除关于Pi规则的直接左递归性
(3)化简
化简由
(2)所得文法,即去除那些从开始符号出发永远无法到达的非终结符的产生式规则。
1.3消除回溯、提左因子
为了消除回溯就必须保证:
对文法的任何非终结符,当要它去匹配输入串时,能够根据它所面临的输入符号准确地指派它的一个候选去执行任务,并且次候选的工作结果是确信无疑的。
也就是说,若此候选获得成功匹配,那么,这种匹配不会是虚假的;若此候选无法完成任务,则任何其它候选也肯定也无法完成任务。
换句话说,假定现在轮到非终结符A去执行匹配任务,A共有n个候选α1,α2,……αn,即A→α1|α2|……αn。
A能够根据不同的输入符号指派相应的αi作为全权代表去执行任务,那就肯定无需回溯了。
在这里A已不再是让某个候选去试探地执行任务,而是根据所面临的输入符号α准确地指派唯一的一个候选。
其次,被指派候选的工作成败也完全代表了A[3]。
假定关于A的规则是A→δβ1|δβ2|……|δβn|γ1|γ2|……|γm(其中γi不以δ开头),提取公共左因子,改写为
A→δA′|γ1|γ2|……|γm
A′→β1|β2|……|βn
2LL
(1)语法分析器的设计
首先判别一个文法是否是LL
(1)文法,如果不是LL
(1)文法则需要转化为LL
(1)文法。
要判别一个文法是否为LL
(1)文法,首先需要给出FIRST集合、FOLLOW集合。
2.1构造FIRST集
根据定义:
FIRST(α)={a|αa...,a∈VT},特别是,若α=>ε,则规定ε∈FIRST(α)。
对于文法符号串X∈(VN∪VT)*,其办法是连续使用下面的规则,直至没个集合不在增大为止。
(1)若X∈VT,则FIRST(X)={X}。
(2) 若X∈VN,且有产生式X→a…,则把a加入到FIRST(X)中;若X→ε也是一条产生式则把ε也加到FIRST(X)中。
(3) 若X->Y…是一个产生式且Y∈VN,则把FIRST(Y)中的所有非ε元素都加到FIRST(X)中;若X→Y1Y2…Yk是一个产生式,Y1…Yi-1都是非终结符,而且,对于任何j,1≤j≤i-1,FIRST(Yj)都含有ε,则把FIRST(Yj)中的所有非ε元素都加到FIRST(X)中;特别是,若所有的FIRST(Yj)均含有ε,j=1,2,…,k,则把ε加到FIRST(X)中[4]。
2.2构造FOLLOW集
根据定义:
FOLLOW(A)={a|S…Aa…,a∈VT},特别是,若S…A,则规定#∈FOLLOW(A)。
对文法中每一非终结符A,构造FOLLOW(A)的算法如下:
反复使用如下产生式,直至FOLLOW集不在增大为止。
(1)对于文法的开始符号S,置#于FOLLOW(S)中
(2)若A→αBβ是一个产生式,则把FIRST(β)\{ε}加到FOLLOW(B)中。
(3)若A→αB是一个产生式,或A→αBβ且β=>ε时(即ε∈FIRST(β))则把FOLLOW(A)加到FOLLOW(B)中。
(4)重复
(2)、(3),直到FOLLOW集不再增大为止。
在构造FOLLOW集的算法中,将第(3)条产生式解释一下:
如果有句型…Ba…,显然a是B的后随符号,a∈FOLLOW(B),而A→αB,用它进行推导有S=>…Aa…=>…αBa…,于是a也是B的后随符号,a∈FOLLOW(B),即FOLLOW(A)中的后随符号都是FOLLOW(B)中的后随符号。
通过上面一系列讨论,我们可以找出满足构造不带回溯的LL
(1)文法的条件:
(1)文法不含左递归。
(2)对于文法中每一个非终结符A的各个产生式的候选首符集两两不相交。
即,若A→α1|α2|…|αn
则 FIRST(αi)∩FIRST(αj)=Ф (i≠j)
(3)对文法中的每个非终结符A,若它存在某个候选首符集包含ε,则
FIRST(A)∩FOLLOW(A)=Ф
如果一个文法G满足以上条件,则称该文法G为LL
(1)文法。
2.3预测分析表的构造
LL
(1)分析表可用一个矩阵(或二维数组)来表示,它概括了相应文法的全部信息。
LL
(1)分析表的每一行对应文法的一个非终结符,而每一列对应一个终结符或输入结束符“#”。
LL
(1)分析表可用数组M[A,a]表示,其中A表示非终结符(即为分析栈中的符号),a表示终结符或“#”(即为输入符号)。
数组的各个入口M[A,a]中存放着面临输入符号a时,所应选择A的某条产生式,即指出当前推导所使用的产生式。
数组中的空白入口中应填入适当的出错子程序名或编号,即此中情况下存在语法错误。
数组的大小为m×n,其中m是文法中非终结符,n是终结符数(外加一个结束符“#”)。
预测分析程序对每个输入串的分析在总控程序控制之下进行。
具体分析时是根据当前输入符号ai和分析栈顶符号Xj来决定是否查LL
(1)分析表,从而根据LL
(1)表决定所选的产生式,以及应该进行的相应操作[5]。
预测分析表的构造:
预测分析法的实现关键在于预测分析表,预测分析表是指分析栈中的文法符号与输入串中的符号的一种匹配关系记为M[A,a]其中A为分析栈中的符号,a为输入符号。
构造预测分析表算法的基本思想并不复杂。
例如,设A->α是一个产生式,a∈FIRST(α)。
那么,当A呈现于分析栈栈顶,且a是当前的输入符号时,α应当是A唯一合适的候选式。
因此,M[A,a]中应放进产生式A→α。
若α=>ε,而当前面临的输入符号a属于FOLLOW(A),那么,A→α就认为已自动得到匹配,因而,应把A→α放在M[A,a]中。
根据这个基本思想,对于给定的LL
(1)文法,在求出其各个产生式的可选集之后,可以按照如下的方法构造预测分析表,其构造算法是:
(1)对文法G的每个产生式A→α执行第2步和第3步;
(2)对每个终结符a∈FIRST(α),把A→α加至M[A,a]中;
(3)若ε∈FIRST(α),则对任何b∈FOLLOW(A)把A→α加至M[A,a]中;
(4)把所有无定义的,M[A,a]均填上“出错标志”。
上述算法可用于任何文法G以构造它的分析表M。
但对于某些文法,有些M[A,a]中可能有若干个产生式,或者说有些M[A,a]可能是多重定义的。
如果G是左递归的或回溯的,那么M至少含有一个多重定义人口。
因此,消除左递归和提取公共左因子将有助于获得无多重定义的分析表M[6]。
2.4预测分析程序
LL
(1)语法分析器的核心是预测分析程序。
一个预测分析程序是由三个部分组成:
总控程序;预测分析表;先进后出的语法分析栈。
预测分析程序实际上是一个下推自动机,其中,输入串是待分析的符号串,“#”作为其结束符号。
分析栈中存放当前句型中尚待分析的文法符号,包括终结符和非终结符。
栈底用“#”标记结束。
在各种分析技术的实现中,总是让输入符号串后面紧跟一个“#”号,标志输入串的结束。
在输入符号串之前也有标志符号“#”,预测分析程序的总控程序将把“#”事先推人分析栈。
因此“#”被称为左右端标志符号,它不是文法符号,是由分析程序自动添加的[7]。
预测分析的工作流程如下:
分析开始时,首先将栈底符号“#”及文法的开始符号S依次推人分析栈的栈底,并对各条指示器置初值,此时分析栈和输入串的格局为(用箭头表示输入串指针和栈顶指针当前所指向的位置)。
#S a1…aiai+1…an#
此时,可根据栈顶的文法符号Xm的不同情况,分别处理:
(1)若Xm为终结符,即Xm∈VT,且Xm=ai≠“#”时,则表明栈顶符号已与当前正扫视的输入符号想匹配,此时应将Xm从栈中弹出,而且将输入指针向前移动一个位置至ai+1,从而得到如下格局:
#X1X2…Xm-1 a1…aiai+1…an#
(2)若Xm为非终结符,即Xm∈VN,则以Xm及ai组成符号对(Xm,ai)查分析表M,假设M[Xm,ai]中存放着关于Xm的一个产生式Xm->ABC,此时,首先将Xm从分析栈中弹出,并将该产生式右部ABC的逆替换栈顶符号,即把ABC按逆序推人栈中(此即用该产生式推导了一步)。
输入指针不动,从而得到新的如下:
#X1X2…Xm-1CBA a1…aiai+1…an#
否则出错,即M[Xm,ai]=“ERROR”,则调用出错程序来进行处理或宣告分析失败。
(3)若Xm=ai=“#”,即输入串与分析栈均为空,则表明输入符号串已完全得到匹配,此时可宣告分析成功,结束工作,接受输入串。
此时的格局如下:
# a1…aiai+1…an#
若用算法来描述,则预测分析程序的总控程序的算法如下[8]:
BEGIN
首先把‘#’然后把文法开始符号推进STACK栈;
把第一个输入符号读进a;
FLAG:
=TRUE;
WHILEFLAGDO
BEGIN
把STACK栈顶符号上托出去并放在X中;
IFX∈VTTHEN
IFX=aTHEN把下一个输入符号读进a
ELSEERROR
ELSEIFX=‘#’THEN
IFX=aTHENFLAG:
=FALSEELSEERROR
ELSEIFM[A,a]={X→X1X2…Xk}THEN
把Xk,Xk-1,…,X1一一推进STACK栈
ELSEERROR
ENDOFWHILE;
STOP
END
2.5预测分析方法举例
把上述算法应用于下列文法,可以为该文法构造预测分析表,并且对该文法利用预测分析法进行语法分析。
文法G:
E->E+T|T
T->T*F|F
F->i|(E)
用预测分析法分析的步骤如下:
(1)构造FIRST集和FOLLOW集
由于上述文法中含有左递归,所以必须先消除左递,使文法改写为:
E->TEˊ
Eˊ->+TEˊ|ε
T->FTˊ
Tˊ->*FTˊ|ε
F->i|(E)
各非终结符的FIRST集如下:
FIRST(E)={(,i)}
FIRST(Eˊ)={+}
FIRST(T)={(,i}
FIRST(Tˊ)={*}
FIRST(F)={(,i}
各非终结符的FOLLOW集为:
FOLLOW(E)={#,)}
FOLLOW(Eˊ)={#,)}
FOLLOW(T)={+,),#}
FOLLOW(Tˊ)={+,),#}
FOLLOW(F)={*,+,),#}
(2)构造预测分析表
根据构造预测分析表的算法,可得上述文法的预测分析表如表1所示:
表1LL
(1)预测分析表
i
+
*
(
)
#
E
E->TEˊ
E->TEˊ
Eˊ
Eˊ->+TEˊ
Eˊ->ε
Eˊ->ε
T
T->FTˊ
T->FTˊ
Tˊ
Tˊ->ε
Tˊ->*FTˊ
Tˊ->ε
Tˊ->ε
F
F->i
F->(E)
(3)对输入串进行分析
下面用预测分析法的总控程序、分析栈和预测分析表对输入串i+i*i进行分析,给出输入串T的分析过程如表2所示:
表2输入串i+i*i的分析过程
步骤
分析栈
(栈顶符号,当前输入符)
剩余输入串
所用产生式
1
#E
(E,i) 查表
i+i*i#
E->TEˊ
2
#EˊT
(T,i) 查表
i+i*i#
T->FTˊ
3
#EˊTˊF
(F,i)查表
i+i*i#
F->i
4
#EˊTˊi
(i,i)i匹配
i+i*i#
5
#EˊTˊ
(Tˊ,+)查表
+i*i#
Tˊ->ε
6
#Eˊ
(Eˊ,+)查表
+i*i#
Eˊ->+TEˊ
7
#EˊT+
(+,+)+匹配
+i*i#
8
#EˊT
(T,i)查表
i*i#
T->FTˊ
9
#EˊTˊF
(F,i)查表
i*i#
F->i
10
#EˊTˊi
(i,i)i匹配
i*i#
11
#EˊTˊ
(Tˊ,*)查表
*i#
Tˊ->*FTˊ
12
#EˊTˊF*
(*,*)*匹配
*i#
13
#EˊTˊF
(F,i)查表
i#
F->i
14
#EˊTˊi
(i,i)i匹配
i#
15
#EˊTˊ
(Tˊ,#)查表
#
Tˊ->ε
16
#Eˊ
(Eˊ,#)查表
#
Eˊ->ε
17
#
#
#
3LL
(1)语法分析器的实现
3.1总体设计
该程序可分为如下几步,流程图如图1:
(1)读入文法;
(2)判断正误;
(3)若无误,判断是否为LL
(1)文法;
(4)若是,构造分析表;
(5)由总控算法判断输入符号串是否为该文法的句型。
是
是
图1程序流程图
主函数设计如下:
voidmain()
{
inti,j;
start=grammer(termin,non_ter,left,right);/*读入一个文法*/
printf("count=%d",count);
printf("\nstart:
%c",start);
strcpy(v,non_ter);
strcat(v,termin);
printf("\nv:
%s",v);
printf("\nnon_ter:
%s",non_ter);
printf("\ntermin:
%s",termin);
printf("\nright:
");
for(i=0;i<=count-1;i++)
printf("%s",right[i]);
printf("\nleft:
");
for(i=0;i<=count-1;i++)
printf("%c",left[i]);
if(validity==1)
validity=judge();
printf("\nvalidity=%d",validity);
if(validity==1)
{
printf("\n文法有效");
ll=ll1();
printf("\nll=%d",ll);
if(ll==0)
printf("\n该文法不是一个LL1文法!
");
else
{
MM();
printf("\n");
for(i=0;i<=19;i++)
for(j=0;j<=19;j++)
if(M[i][j]>=0)
printf("M[%d][%d]=%d",i,j,M[i][j]);
printf("\n");
menu();
}
}
}
3.2详细设计
主要的程序代码说明:
(1)文法的存储类型
intcount=0;/*分解的产生式的个数*/
intnumber;/*所有终结符和非终结符的总数*/
charstart;/*开始符号*/
chartermin[50];/*终结符号*/
charnon_ter[50];/*非终结符号*/
charv[50];/*所有符号*/
charfirst[50][50],follow[50][50];/*各产生式右部的FIRST和左部的FOLLOW集合*/
charfirst1[50][50];/*所有单个符号的FIRST集合*/
charselect[50][50];/*各单个产生式的SELECT集合*/
charf[50],F[50];/*记录各符号的FIRST和FOLLOW是否已求过*/
charempty[20];/*记录可直接推出^的符号*/
charTEMP[50];/*求FOLLOW时存放某一符号串的FIRST集合*/
intvalidity=1;/*表示输入文法是否有效*/
intll=1;/*表示输入文法是否为LL
(1)文法*/
intM[20][20];/*分析表*/
charchoose;/*用户输入时使用*/
charempt[20];/*求_emp()时使用*/
charfo[20];/*求FOLLOW集合时使用*/
文法的终结符和非终结符分别存放在termin[50]和non_ter[50]中,number是所有非终结符合终结符个数总和,start存放文法的开始符号,产生式个数存放在count中。
(2)产生式存储类型
charleft[50];/*左部*/
charright[50][50];/*右部*/
文法产生式的左部非终结符存放在字符型数组left[50]中,产生式右部符号串存放在字符型数组right[50][50]中。
读入一个文法时将产生式放在P[50][50]数组中,传值到形参point中,分解产生式时完整的产生式在point[]中。
(3)FIRST和FOLLOW集的构造
voidfirst2(inti)和voidFIRST(inti,char*p)分别是求单个符号的FIRST和各产生式右部的FIRST。
这里因为前面已有一个first数组,而且又用first1数组存放所有单个符号的FIRST集合,为了避免重名,命名first2。
求各产生式左部的FOLLOW用voidFOLLOW(inti)。
其中i为符号在所有输入符号中的序号。
(4)LL
(1)分析表的构造
voidMM()
{
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;
}
}
}
}
(4)其他函数设计
voidrecur(char*point)用于分解含有左递归的产生式;
voidemp(charc)是用于求所有能直接推出‘^’的符号;
int_emp(charc)用于求某一符号能否推出‘^’,若能推出,返回1,否则返回0;
intjudge()用于判断读入的文法是否正确;voidsyntax()是总控程序;
3.3相关的运行界面截图及说明
本程序的运行坏境为VC++,执行文件为LL
(1).exe。
进入程序后即提示信
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 基于 VC LL1 语法 分析器 设计 实现 doc