一.实验目的
1.掌握LR(1)分析法的基本原理
2.掌握LR(1)分析表的构造方法
3.掌握LR(1)驱动程序的构造方法
二.实验内容及要求
构造LR(1)分析程序,利用它进行语法分析,判断给出的符号串是否为该文法识别的句子,了解LR(K)分析方法是严格的从左向右扫描,和自底向上的语法分析方法。
根据某一文法编制调试LR(1)分析程序,以便对任意输入的符号串进行分析。本次实验的目的主要是加深对LR(1)分析法的理解。
程序输入/输出示例:
对下列文法,用LR(1)分析法对任意输入的符号串进行分析:
(1)E->E+T
(2)E->E—T
(3)T->T*F
(4)T->T/F
(5)F->(E)
(6)F->i
输出的格式如下:
(1)LR(1
(2)输入一以#结束的符号串(包括+—*/()i#)
(3)输出过程如下:
步骤状态栈符号栈剩余输入串动作
1 0 # i+i*i# 移进
(或者为合法符号串)
备注:(1)在“所用产生式”一列中如果对应有推导则写出所用产生式;如果为匹配终结符则写明匹配的终结符;如分析异常出错则写为“分析出错”;若成功结束则写为“分析成功”。
注意:1.表达式中允许使用运算符(+-*/)、分割符(括号)、字符i,结束符#;
2.如果遇到错误的表达式,应输出错误提示信息(该信息越详细越好);
3.对学有余力的同学,测试用的表达式事先放在文本文件中,一行存放一个表达式,同时以分号分割。同时将预期的输出结果写在另一个文本文件中,以便和输出进行对照;
4.可采用的其它的文法。
三.实验过程
1、使用LR(1)的优点:
(1)LR分析器能够构造来识别所有能用上下文无关文法写的程序设计语言的结构。
(2)LR分析方法是已知的最一般的无回溯移进-归约方法,它能够和其他移进-归约方法一样有效地实现。
(3)LR方法能分析的文法类是预测分析法能分析的文法类的真超集。
(4)LR分析器能及时察觉语法错误,快到自左向右扫描输入的最大可能。
为了使一个文法是LR的,只要保证当句柄出现在栈顶时,自左向右扫描的移进-归约分析器能够及时识别它便足够了。当句柄出现在栈顶时,LR分析器必须要扫描整个栈就可以知道这一点,栈顶的状态符号包含了所需要的一切信息。如果仅知道栈内的文法符号就能确定栈顶是什么句柄。LR分析表的转移函数本质上就是这样的有限自动机。不过,这个有限自动机不需要根据每步动作读栈,因为,如果这个识别句柄的有限自动机自底向上读栈中的文法符号的话,它达到的状态正是这时栈顶的状态符号所表示的状态,所以,LR分析器可以从栈顶的状态确定它需要从栈中了解的一切。
2、LR分析器由三个部分组成:
(1)总控程序,也可以称为驱动程序。对所有的LR分析器总控程序都是相同的。
(2)分析表或分析函数,不同的文法分析表将不同,同一个文法采用的LR分析器不同时,
分析表将不同,分析表又可以分为动作表(ACTION)和状态转换(GOTO)表两个部分,它们都可用二维数组表示。
(3)分析栈,包括文法符号栈和相应的状态栈,它们均是先进后出栈。
分析器的动作就是由栈顶状态和当前输入符号所决定。
LR分析器结构:
其中:SP为栈指针,S[i]为状态栈,X[i]为文法符号栈。状态转换表用GOTO[i,X]=j 表示,规定当栈顶状态为i,遇到当前文法符号为X时应转向状态j,X为终结符或非终结符。
ACTION[i,a]规定了栈顶状态为i时遇到输入符号a应执行。动作有四种可能:(1)移进:
action[i,a]= Sj:状态j移入到状态栈,把a移入到文法符号栈,其中i,j表示状态号。
(2)归约:
action[i,a]=r k:当在栈顶形成句柄时,则归约为相应的非终结符A,即文法中有A->B的产生式,若B的长度为R(即|B|=R),则从状态栈和文法符号栈中自顶向下去掉R个符号,即栈指针SP减去R,并把A移入文法符号栈内,j=GOTO[i,A]移进状态栈,其中i为修改指针后的栈顶状态。
(3)接受acc:
当归约到文法符号栈中只剩文法的开始符号S时,并且输入符号串已结束即当前输入符是'#',则为分析成功。
(4)报错:
当遇到状态栈顶为某一状态下出现不该遇到的文法符号时,则报错,说明输入端不是该文法能接受的符号串。
3、LL(1)分析法实验设计思想及算法
4.实验的源程序代码如下:
#include
#include
char *action[10][3]={"S3#","S4#",NULL, /*ACTION表*/
NULL,NULL,"acc",
"S6#","S7#",NULL,
"S3#","S4#",NULL,
"r3#","r3#",NULL,
NULL,NULL,"r1#",
"S6#","S7#",NULL,
NULL,NULL,"r3#",
"r2#","r2#",NULL,
NULL,NULL,"r2#"};
int goto1[10][2]={1,2, /*QOTO表*/
0,0,
0,5,
0,8,
0,0,
0,0,
0,9,
0,0,
0,0,
0,0};
char vt[3]={'a','b','#'}; /*存放非终结符*/ char vn[2]={'S','B'}; /*存放终结符*/ char *LR[4]={"E->S#","S->BB#","B->aB#","B->b#"};/*存放产生式*/
int a[10];
char b[10],c[10],c1;
int top1,top2,top3,top,m,n;
void main(){
int g,h,i,j,k,l,p,y,z,count;
char x,copy[10],copy1[10];
top1=0;top2=0;top3=0;top=0;
a[0]=0;y=a[0];b[0]='#';
count=0;z=0;
printf("请输入表达式\n");
do{
scanf("%c",&c1);
c[top3]=c1;
top3=top3+1;
}while(c1!='#');
printf("步骤\t状态栈\t\t符号栈\t\t输入串\t\tACTION\tGOTO\n"); do{
y=z;m=0;n=0; /*y,z指向状态栈栈顶*/ g=top;j=0;k=0;
x=c[top];
count++;
printf("%d\t",count);
while(m<=top1){ /*输出状态栈*/ printf("%d",a[m]);
m=m+1;
}
printf("\t\t");
while(n<=top2){ /*输出符号栈*/
printf("%c",b[n]);
n=n+1;
}
printf("\t\t");
while(g<=top3){ /*输出输入串*/
printf("%c",c[g]);
g=g+1;
}
printf("\t\t");
while(x!=vt[j]&&j<=2) j++;
if(j==2&&x!=vt[j]){
printf("error\n");
return;
}
if(action[y][j]==NULL){
printf("error\n");
return;
}
else
strcpy(copy,action[y][j]);
if(copy[0]=='S'){ /*处理移进*/ z=copy[1]-'0';
top1=top1+1;
top2=top2+1;
a[top1]=z;
b[top2]=x;
top=top+1;
i=0;
while(copy[i]!='#'){
printf("%c",copy[i]);
i++;
}
printf("\n");
}
if(copy[0]=='r'){ /*处理归约*/ i=0;
while(copy[i]!='#'){
printf("%c",copy[i]);
i++;
}
h=copy[1]-'0';
strcpy(copy1,LR[h]);
while(copy1[0]!=vn[k]) k++;
l=strlen(LR[h])-4;
top1=top1-l+1;
top2=top2-l+1;
y=a[top1-1];
p=goto1[y][k];
a[top1]=p;
b[top2]=copy1[0];
z=p;
printf("\t");
printf("%d\n",p);
}
}while(action[y][j]!="acc");
printf("acc\n");
}
5.程序的运行结果如下:
四.实验总结(心得)
经过本实验的学习,主要掌握了LR(1)算法的含义,结合书本知识,让我更加清
楚此算法实现的主要过程,以及使用该算法的好处,通过对程序实现的分析,对于LR (1)算法的细节了解的更加清楚,从中学到了很多的东西,让我更加系统的了解编译原理的具体用途和以后的使用价值。