LR0文法分析报告Word格式.docx
《LR0文法分析报告Word格式.docx》由会员分享,可在线阅读,更多相关《LR0文法分析报告Word格式.docx(25页珍藏版)》请在冰豆网上搜索。
从直观意义上讲,一个LR(0)项目指明了在分析过程中的某一步我们看到产生式的多大部分被识别,LR(0)项目中的圆点可看成是分析栈栈顶与输入串的分界线,圆点左边为已进入分析栈的部分,右边是当前输入或继续扫描的符号串。
不同的LR(0)项目,反映了分析栈顶的不同情况。
我们根据LR(0)项目的作用不同,将其分为四类:
(1)归约项目:
表现形式:
A→a.
这类LR(0)项目表示句柄a恰好包含在栈中,即当前栈顶的部分内容构成了所期望的句柄,应按A→a进行归约。
(2)接受项目:
→a.
其中
是文法惟一的开始符号。
这类LR(0)项目实际是特殊的归约项目,表示分析栈中内容恰好为a,用
→a进行归约,则整个分析成功。
(3)移进项目:
(b
VT)
这类LR(0)项目表示分析栈中是不完全包含句柄的活前缀,为构成恰好有句柄的活前级,需将b移进分析栈。
(4)待约项目:
A→α.Bβ(B
VN)
这类LR(0)项目表示分析栈中是不完全包含句柄的活前缀,为构成恰好有句柄的活前缀,应把当前输入字符串中的相应内容先归约到B。
在给出LR(0)项目的定义和分类之后,我们从这些LR(0)项目出发,来构造能识别文法所有前缀的有限自动机。
其步骤是:
首先构造能识别文法所有活前缀的非确定的有限自动机,再将其确定化和最小化,最终得到所需的确定的有限自动机。
由文法G的LR(0)项目构造识别文法G的所有活前缀的非确定有限自动机的方法:
(1)规定含有文法开始符号的产生式(设
→A)的第一个LR(0)项目(即
→.A)为NFA的惟一初态。
(2)令所有LR(0)项目分别对应NFA的一个状态且LR(0)项目为归约项目的对应状态为终态。
(3)若状态i和状态j出自同一文法G的产生式且两个状态LR(0)项目的圆点只相差一个位置,即:
若i为X→X1X2·
…Xi-1·
Xi…Xn,j为X→X1X2…Xi·
Xi+1…Xn,则从状态i引一条标记为Xi的弧到状态j。
(4)若状态i为待约项目(设X→α·
Aβ),则从状态i引ε弧到所有A→·
r的状态。
为了使“接受”状态易于识别,我们通常将文法G进行拓广。
假定文法G是一个以S为开始符号的文法,我们构造一个
,它包含了整个G,但它引进了一个不出现在G中的非终结符
,并加进一个新产生式
→S,以
→S
为开始符号。
那么,我们称
是G的拓广文法。
这样,便会有一个仅含项目
→S的状态,这就是惟一的“接受”态。
如果I是文法G'的一个项目集,定义和构造I的闭包CLOSURE(I)如下:
(1)I的项目都在CLOSURE(I)中。
(2)若A→.B属于CLOSURE(I),则每一形如B→.的项目也属于CLOSURE(I)。
(3)重复
(2)直到CLOSURE(I)不再扩大。
定义转换函数如下:
GO(I,X)=CLOSURE(J)
其中:
I为包含某一项目集的状态,X为一文法符号,J={A→X.|A→.X∈I}。
圆点不在产生式右部最左边的项目称为核,惟一的例外是S′→.S,因此用GOTO(I,X)状态转换函数得到的J为转向后状态闭包项目集的核。
使用闭包函数(CLOSURE)和转换函数(GO(I,X))构造文法G’的LR(0)的项目集规范族,步骤如下:
(1)置项目S′→.S为初态集的核,然后对核求闭包CLOSURE({S′→.S})得到初态的闭包项目集。
(2)对初态集或其他所构造的项目集应用转换函数GO(I,X)=CLOSURE(J)求出新状态J的闭包项目集。
(3)重复
(2)直到不出现新的项目集为止。
计算LR(0)项目集规范族C={I0,I1,...In}的算法伪代码如下:
Procedureitemsets(G’);
BeginC:
={CLOSURE({S’.S})}
Repeat
ForC中每一项目集I和每一文法符号X
DoifGO(I,X)非空且不属于C
Then把GO(I,X)放入C中
UntilC不再增大
End;
一个项目集可能包含多种项目,若移进和归约项目同时存在,则称移进-归约冲突,若
归约和归约项目同时存在,则称归约-归约冲突。
下面看一个具体的例子:
我们希望能根据识别文法的活前缀的DFA建立LR分析器,因此,需要研究这个DFA的每个项目集(状态)中的项目的不同作用。
我们说项目A→β1.β2对活前缀αβ1是有效的,其条件是存在规范推导
。
一般而言,同一项目可能对几个活前缀都是有效的(当一个项目出现在几个不同的集合中时便是这种情形)。
若归约项目A→β1.对活前缀
是有效的,则它告诉我们应把符号串
归约为A,即把活前缀
变成αA。
若移进项目A→β1.β2对活前缀
是有效的,则它告诉我们,句柄尚未形成,因此,下一步动作应是移进。
但是,可能存在这样的情形,对同一活前缀,存在若干项目对它都是有效的。
而且它们告诉我们应做的事情各不相同,互相冲突。
这种冲突通过向前多看几个输入符号,或许能够获得解决。
对于每个活前缀,我们可以构造它的有效项目集。
实际上,一个活前缀γ的有效项目集正是从上述的DFA的初态出发,经读出γ后而到达的那个项目集(状态)。
换言之,在任何时候,分析栈中的活前缀X1X2…Xm的有效项目集正是栈顶状态Sm所代表的那个集合。
这是LR分析理论的一条基本定理。
实际上,栈顶的项目集(状态)体现了栈里的一切有用信息——历史。
前面我们已经对LR(0)文法进行了定义,下面我们来看一下LR(0)分析表是如何构造的。
对于LR(0)文法,我们可以直接从它的项目集规范族C和活前缀识别自动机的状态转换函数GO构造出LR分析表。
下面是构造LR(0)分析表的算法。
假定C={I0,I1,…,In},令每个项目集Ik的下标k为分析器的一个状态,因此,G'
的LR(0)分析表含有状态0,1,…,n。
令那个含有项目S'
→.S的Ik的下标k为初态。
ACTION子表和GOTO子表可按如下方法构造:
(1)若项目A→α.aβ属于Ik且GO(Ik,a)=Ij,a为终结符,则置ACTION[k,a]为“把状态j和符号a移进栈”,简记为“sj”;
(2)若项目A→α.属于Ik,那么,对任何终结符a,置ACTION[k,a]为“用产生式A→α进行规约”,简记为“rj”;
其中,假定A→α为文法G'
的第j个产生式;
(3)若项目S'
→S.属于Ik,则置ACTION[k,#]为“接受”,简记为“acc”;
(4)若GO(Ik,A)=Ij,A为非终结符,则置GOTO[k,A]=j;
(5)分析表中凡不能用上述1至4填入信息的空白格均置上“出错标志”。
按上述算法构造的含有ACTION和GOTO两部分的分析表,如果每个入口不含多重定义,则称它为文法G的一张LR(0)分析表。
具有LR(0)表的文法G称为一个LR(0)文法,LR(0)文法是无二义的。
例如,文法G(E)的拓广文法如下:
(0)S'
→E
(1)E→aA
(2)E→bB
(3)A→cA
(4)A→d
(5)B→cB
三、实验内容及其代码如下所示:
#include<
iostream>
stdio.h>
fstream>
malloc.h>
usingnamespacestd;
#defineOK1
#defineERROR0
#defineN50
#defineY20
intvtnum,vnnum,pronum;
//依次是终结符个数,非终结符个数,产生式个数
charvt[N];
//终结符集
charvn[N];
//非终结符集
charold[N][N]={'
/0'
};
//用于存储文法
charoldz[N][N]={'
//用于存储增广文法
intACTION[N][N]={0};
//动作表
intGOTO[N][N]={0};
//状态转换表
typedefstructSqE{
intt;
//状态编号
charc1;
}SqE;
//堆栈元素
typedefstructitem{
intf;
//项目前部,表示产生式编号
intl;
//项目后部,表示停顿点在产生式的位置
}item;
//定义项目
typedefstructlink{
//连接前部,表示所用符号的编号,非终结符编号=在vn[]中的下标+100
//连接后部,即状态编号
}link;
//定义状态之间的连接
typedefstructcd{
intitem_num;
//状态中的项目数
intlink_num;
//状态的连接数
itemw[N];
//项目集
linku[N];
//连接集
}cd;
//定义状态
typedefstructDFA{
intcd_num;
//状态个数
cds[N+1];
//状态集
}DFA;
//定义规范LR(0)项目族,D.s[N]用作状态转换函数go_switch()的存储空间
DFAD;
voiddfa();
//求规范LR(0)项目族
voidclosure(int);
//求闭包
voidgo_switch(int,int);
//求转换
inttest_go_switch();
voidadd_go_switch();
//增加新状态
voiddel_go_switch();
//清空状态转换函数的存储空间
voidaction();
//构造ACTION表
voidgo_answer();
//构造GOTO表
intcontrol();
//总控程序
intlength(int);
//返回增广文法产生式右部的长度
inttest(char);
voidprintf_ag();
//输出ACTION表和GOTO表
booltest_link(inti,intnum);
voidmain()
{
inti,j;
ifstreamin("
input1.txt"
ios_base:
:
in);
//读文件,从文件中读入pronum,vtnum,vnnum以及产生式
in>
>
pronum>
vnnum>
vtnum;
vn;
vt;
for(i=1;
i<
=pronum;
i++)
old[i];
//将产生式存入old[][],old[1]为第一个产生式
for(j=0;
old[i][j]!
='
;
j++)
oldz[i][j]=old[i][j];
//将产生式从old[][]录入oldz[][]
oldz[0][0]='
P'
oldz[0][1]=old[1][0];
//加入P->
S,将原文法扩充,使其变为增广文法
vt[vtnum]='
$'
//把结束符'
加入终结符集
D.cd_num=0;
for(i=0;
=N;
{
D.s[i].item_num=0;
D.s[i].link_num=0;
}//初始化状态个数、连接个数、项目个数
dfa();
action();
go_answer();
printf_ag();
control();
}
//求一个状态的闭包
voidclosure(inti)
intj,k,m,x,flag;
do
j=D.s[i].item_num;
//j是本轮循环开始前的项目数
for(k=0;
k<
D.s[i].item_num;
k++)
for(m=0;
m<
m++)
if(oldz[m][0]==oldz[D.s[i].w[k].f][D.s[i].w[k].l])//对当前状态i中的每个项目,查询每个产生式,例如:
A->
a.Ab应找到A->
...
flag=0;
//判断该项是否在当前状态i中,即检查(m,1)是否存在于状态i中,保证求闭包时加入的新项目和原项目集不重合
for(x=0;
x<
x++)
if(D.s[i].w[x].f==m&
&
D.s[i].w[x].l==1)
flag=1;
break;
}
if(flag==0)//如果该项不在当前状态i中,将其加入状态i
D.s[i].w[D.s[i].item_num].f=m;
D.s[i].w[D.s[i].item_num].l=1;
D.s[i].item_num++;
}while(j!
=D.s[i].item_num);
//当一轮没有新的项目加入i时,结束循环
//状态转换函数,i是状态编号,num是符号编号,状态转换函数的结果存于s[N]
voidgo_switch(inti,intnum)
{intj;
j<
if(test(oldz[D.s[i].w[j].f][D.s[i].w[j].l])==num)
D.s[N].w[D.s[N].item_num].f=D.s[i].w[j].f;
D.s[N].w[D.s[N].item_num].l=D.s[i].w[j].l+1;
D.s[N].item_num++;
closure(N);
//返回终结符和非终结符的标号,判断符号的类型,0<
=终结符返回值<
100,100<
=非终结符返回值<
200,错误符号返回值=200
inttest(charc)
{inti,j;
{if(vt[i]==c)
if(i<
vtnum)
returni;
else
vnnum;
if(vn[j]==c)
if(j<
vtnum)return(100+j);
elsereturn200;
//检验状态转换函数的结果
inttest_go_switch()
{inti,j,k,flag;
if(D.s[N].item_num==0)//判断状态转换的结果是否为空,即当前状态可否接收该字符
return0;
{for(i=0;
D.cd_num;
i++)//选定一状态,对s[N]中的每个项目进行循环,如果存在一个项目在当前状态中未找到,即flag=0,立即跳至下一状态
//判断状态转换函数的结果是否已经完全包含于某一现有状态中,例如go_switch(状态4,符号a)依然存在于状态4中,go_switch(状态4,符号c)已经存在于状态5中
D.s[N].item_num;
j++)//如果在当前状态i中找不到s[N]的当前项目j,就跳至下一状态
if(D.s[i].w[k].f==D.s[N].w[j].f&
D.s[i].w[k].l==D.s[N].w[j].l)
if(k>
=D.s[i].item_num)
{flag=0;
if(flag==1)return1000+i;
return1;
//状态转换函数的结果未被任何现有状态完全包含,完全满足建立新状态的条件
//把状态转换函数的结果加入DFA,即当建立新状态的条件符合时,建立新的状态s[D.cd_num]
voidadd_go_switch()
inti;
D.s[D.cd_num].w[D.s[D.cd_num].item_num].f=D.s[N].w[i].f;
D.s[D.cd_num].w[D.s[D.cd_num].item_num].l=D.s[N].w[i].l;
D.s[D.cd_num].item_num++;
D.cd_num++;
voiddel_go_switch()
D.s[N].item_num=0;
//构造规范LR(0)项目族
voiddfa()
inti,j,k;
D.s[0].w[0].f=0;
D.s[0].w[0].l=1;
D.s[0].item_num++;
closure(0);
//把P->
S加入初状态,并求其闭包
do{i=D.cd_num;
//本轮循环开始时状态数
j++)//对每个状态进行循环
k++)//对当前状态,每个非终结符
if(!
test_link(j,k+100))//检验当前状态用当前非终结符构造的连接是否已经存在,若存在,则跳至下一非终结符,这样可以防止do_while()二次执行时,将连接集重新加入当前状态,比如s[0]
go_switch(j,k+100);
//对当前状态,当前符号调用状态转换
if(test_go_switch()==1)//如果符合建立新状态的条件
add_go_switch();
D.s[j].u[D.s[j].link_num].f=k+100;
//建立当前状态和新状态的连接
D.s[j].u[D.s[j].link_num].l=D.cd_num-1;
D.s[j].link_num++;
else
if(test_go_switch()>
=1000)//如果状态转换的结果包含于某一现有状态
//建立当前状态和该现有状态的连接
D.s[j].u[D.s[j].link_num].l=test_go_switch()-1000;
del_go_switch();
//清空
for(k=0;
k++)//对当前状态,每个终结符
test_link(j,k))
go_switch(j,k);
if(test_go_switch()==1)
D.s[j].u[D.s[j].link_num].f=k;
=1000)
}while(i!
=D.cd_num);
//当一轮没有新的状态产生时,结束
voidaction()
{inti,j,k;
i++)//对每个状态循环
D.s[i].link_num;
j++)//S,对每个状态的每个连接循环,如果找到当前状态用终结符建立的连接,则ACTION[当前状态号][对应终结符编号]=连接另一端状态号
if(D.s[i].u[j].f<
100)