编译原理课程设计算符优先分析法研究附源程序.docx
《编译原理课程设计算符优先分析法研究附源程序.docx》由会员分享,可在线阅读,更多相关《编译原理课程设计算符优先分析法研究附源程序.docx(33页珍藏版)》请在冰豆网上搜索。
编译原理课程设计算符优先分析法研究附源程序
1课程设计的目的和要求
1.1课程设计的目的
本次设计的时间为1周,目的是通过使用高级语言实现部分算法加强对编译技术和理论的理解。
设计的题目要求具有一定的规模,应涵盖本课程内容和实际应用相关的主要技术。
1.2课程设计的要求
1、文法使用产生式来定义;
2、用大写字母和小写字母分别表示非终结符和终结符;产生式使用->;
3、文法中的空字符串统一使用@表示;
4、分别给出每一个非终结符的FIRSTVT集和LASTVT集;
5、画出算符优先关系表
6、判定给定的文法是否是算符优先文法;
7、给定符号串判定是否是文法中的句子,分析过程用分析表格的方式打印出来。
2系统描述
本次实验使用windowsvista操作系统下visualC++6.0平台,使用C语言,利用读文件方式将待分析的文法读入到程序中,通过定义数组和结构体作为具有一定意义或关系的表或栈,存放FIRSTVT、LASTVT、算符优先关系表的元素。
系统能够对由文件读入的文法进行分析,构造出FIRSTVT表和LASTVT表以及算符优先关系表。
可以根据构造的优先关系表对输入的任意符号串进行分析,判断是否为本文法的句子,若是则打印规约过程。
结果显示到DOS界面上。
2.1自底向上分析方法的描述:
对输入的符号串自左向右进行扫描,并将输入符逐个移入栈中,边移入边分析,一旦栈顶符号串形成某个句型的句柄时(该句柄对应某个产生式的右部),就用该产生式的左部非终结符代替相应右部的文法符号串,这一过程称为规约。
重复这一过程,直到栈中只剩下文法的开始符则分析成功。
2.2算符优先文法的描述:
只规定算符之间的优先关系,也就是说只考虑终结符之间的优先关系。
由于算富有先分析不考虑非终结符之间的优先关系,在规约过程中只要找到最左素短语就可以规约。
如给定一个文法G[S]:
S->#E#
E->E+T
E->T
T->T*F
T->F
F->P/F
F->P
P->(E)
P->i
利用算符优先文法分析过程处理如下:
1)计算给定文法中任意两个终结符对(a,b)之间的优先关系,首先计算两个集合
FIRSTVT(B)={b|B->b…或B->Cb…}
LASTVT(B)={a|B->…a或B->…aC}
表2-1FIRSTVT集和LASTVT集
S
E
T
F
P
FIRSTVT
#
+*/i(
*/i(
/i(
i(
LASTVT
#
+*/i)
*/i)
/i)
i)
2)计算三种优先关系,求出算符优先关系表:
表2-2算符优先关系表
+
*
/
i
(
)
#
+
﹒﹥
﹤﹒
﹤﹒
﹤﹒
﹤﹒
﹒﹥
﹒﹥
*
﹒﹥
﹒﹥
﹤﹒
﹤﹒
﹤﹒
﹒﹥
﹒﹥
/
﹒﹥
﹒﹥
﹤﹒
﹤﹒
﹤﹒
﹒﹥
﹒﹥
I
﹒﹥
﹒﹥
﹒﹥
﹒﹥
﹒﹥
(
﹤﹒
﹤﹒
﹤﹒
﹤﹒
﹤﹒
﹦﹒
)
﹒﹥
﹒﹥
﹒﹥
﹒﹥
﹒﹥
#
﹤﹒
﹤﹒
﹤﹒
﹤﹒
﹤﹒
﹦﹒
3)输入符号串,进行移进-规约分析。
3概要设计
3.1设计思路
1)首先在源程序相同的目录下创建一个txt文档,并在文档中输入待分析的文法。
然后定义一个输入流文件,调用这个流文件中的open函数打开该txt文件,再定义一个二维数组通过循环接收读入的产生式。
2)接着开始构造FIRSTVT、LASTVT、算符优先关系表。
在构造表的时候首先定义了两个重要的结构体。
一个结构体作为存放具有一定关系的一对非终结符和终结符,另一个结构体作为存放上述元素的栈,包括栈顶指针、栈底指针、栈的长度。
既然定义了栈,就存在对栈的初始化、栈是否为空的判断、入栈、出栈操作,利用循环和指针的操作来定义这些函数,以完成元素的进栈和弹出。
定义了这两个结构体,就可以用来构造FIRSTVT、LASTVT、算符优先关系表。
在构造FIRSTVT表时,通过循环找出每条产生式中的非终结符的FIRSTVT集,并把该非终结符和终结符压栈,设置标志位,标志一对非终结符和终结符具有对应关系。
LASTVT表的构造则是将求FIRSTVT的过程翻转过来,可以仅仅将函数中的参数稍作修改就能够完成。
3)构造算符优先关系表。
算符优先关系表是一个二维数组,用来存放任意两个终结符之间的优先关系。
首先构造表头,通过扫描所有产生式将终结符不重复的存放在一个一维数组中并置为优先关系表的行和列,并将优先关系表其他内容全部初始化为空。
接着遍历所有产生式,找出任意两个终结符之间存在的关系(可以没有关系),并判断任意两终结符是否至多存在一种优先关系,如发现优先关系表不为空,则证明该文法不是算符优先文法,否则,将相应的关系填入到相应的行列对应的单元中。
4)输入串分析过程的设计。
首先将大于、小于、等于和无关系分别定义成一种类型的数据表示,通过查询符号栈栈顶以及当前符号之间的优先关系来判断移进和规约的操作。
3.2系统功能结构
图3-1系统功能结构图
函数功能:
Main()函数:
调用主函数,运行程序;
FirstVt()函数:
构造FIRSTVT表;
LastVt()函数:
构造LASTVT表;
OpPrioTable()函数:
构造算符优先关系表;
InputAnalyse()函数:
分析输入串是否为文法中的句子,并给出规约过程。
3.3技术路线或实现方法
算符优先分析法的具体过程如下:
1、首先先输入文件的路径,在readfile(charsen[][col])函数中,将需要分析的文法通过输入流文件打开函数open()复制到sen[row][col]中。
2、然后利用FirstVt()构造产生式sen[row][col]的FirstVt表。
先找出形如A->…a…(a为第一个终结符)的产生式,把(A,a)压入Operator栈中。
从Operator栈顶抛出项(A,a),填入first表相应位置。
在找出形如B->A…的产生式,把(B,a)压入Operator栈中。
循环直到Operator栈为空,此时FirstVt表构造完毕。
3、然后把产生式右部翻转,调用FirstVt函数求出LastVt表。
4、接着调用OpPriotable()构造算符优先关系表opTable。
先把产生式中所有终结符填入opTable表第一行和第一列,然后利用产生式和FirstVt表LastVt表分别找出满足=关系、<关系、>关系的算符填表。
若相应位已有关系,说明两个终结符之间至少有两种优先关系,该文法不是算符优先文法。
5、最后调用InputAnalyse()对输入串进行分析。
初始化分析栈S,依次判断当前输入符a和分析栈中离栈顶最近的终结符S[j]的关系,若S[j]a,则往前找到第一个S[j]>a,将S[j-1]到栈顶S[k]规约,若S[j]=a,如果S[j]=#,则接受,如果S[j]!
=#,则移进。
直到接受或者出错,算符优先分析结束。
3.4开发环境
实验使用windowsvista操作系统下的MicrosoftVisualC++6.0平台,用C语言完成。
4详细设计
4.1模块划分
实验分为五个模块,分别是:
1、文件的导入:
readfile(sen[][]);
2、FirstVt、LastVt集的构造:
FirstVt(sen[][],first[][],sen_len,frist_len);
LastVt(sen[][],last[][],sen_len,frist_len);
3、算符优先关系表OpPriotable的构造:
OpPriotable(sen,first,last,opTable,sen_len,first_len,&opTable_len);
4、算符优先分析过程的实现:
InputAnalyse(opTable[][col],string[col],opTable_len);
5、主函数:
main()。
4.2主要算法的流程图
图4-1算符优先分析法程序流程图
4.3数据分析与定义
1、文法(产生式):
文法使用产生式来定义
charsen[row][col]:
用二维数组表示产生式;
intsen_len:
产生式的个数;
2、FIRSTVT集:
charfirst[row][col]:
用二维数组构造FIRSTVT表
intfirst_len:
Firstvt表的行数;
3、LASTVT集:
charlast[row][col]:
用二维数组构造LASTVT表;
intfrist_len:
LASTVT表的行数;
4、算符优先关系表:
charopTable[row][col]:
用二维数组表示算符优先关系表;
intopTable_len:
算符优先关系表的行数和列数;
5、算符优先分析表
charstring[col]:
用一维数组记录输入串;
charS[SIZE]:
用一维数组表示分析栈;
chara:
当前输入字符;
6、其他数据结构:
typedefstruct
{
charnonterm;//非终结符
charterm;//终结符
}StackElement;
FIRSTVT表或LASTVT表中一个表项(A,a);
7、typedefstruct
{
StackElement*top;
StackElement*bottom;
intstacksize;
}stack;
以形如表项(A,a)为元素的栈,在构造FirstVt集的过程中用到;
4.4系统界面设计
本实验没有考虑界面设计,将结果直接打印输出在DOS界面下。
5测试方法和测试结果
5.1测试用例1
测试目的:
使用算符优先分析法对一个算符文法中的句子进行分析。
读入一个算符优先文法进行分析,给出文件路径D:
\\courses\\C_source_file\\成品\\算符优先文法1.txt。
结果如下:
图5-1测试用例1运行截图1
输入一个字符串,使得该字符串是该文法的一个句子。
则输入字符串i+i*i/(i+i)#时运行结果为:
图5-2测试用例1运行截图2
5.2测试用例2
测试目的:
使用算符优先分析法,分析一个字符串不是该文法的句子,并输出出错信息。
输入一个字符串,使得该字符串不是文法的句子。
图5-3测试用例2运行截图
5.3测试用例3
测试目的:
读入一个文法,判断该文法不是算符优先文法。
读入一个非算符优先文法进行分析,给出文件路径:
D:
\\courses\\C_source_file\\成品\\非算符优先文法1.txt。
运行结果如下:
图5-4测试用例3运行截图
5.4测试用例4
测试目的:
输入一个非算符文法,判断该文法不是算符文法。
读入一个非算符文法,给出路径:
D:
\\courses\\C_source_file\\成品\\非算符文法.txt。
运行结果如下:
图5-5测试用例4运行截图
6结论和展望
结论
本次实验我们基本完成了实验题目的要求。
求出了一个文法中每一个非终结符的FIRSTVT集和LASTVT集,画出算符优先关系表,并判定出给定的文法是否是算符优先文法。
当给定一个符号串时,能够判定是否是文法中的句子,并能够将分析过程打印出来。
通过算符优先分析方法,可以对任意一个文法进行自底向上的分析。
同时,算符优先分析法也存在不足之处,由于忽略文法中的非终结符,会将本不属于文法的句子正确规约,从而引起错误。
展望
本次实验完成了题目的要求,准确把握了将文法通过读文件形式、手动输入分析字符串的题目要求,并在满足要求的基础上进行了创新,添加了对算符文法的判断功能。
实验中,小组成员的分工与合作充分体现了软件工程的思想。
需求分析理解准确,小组成员任务明确,统一函数头、参数,各自完成分内工作,整合工作快速、高效。
同时,实验中还存在很多不足。
调试程序中的错误占用了大部分时间,以至于没有考虑使用界面展示结果。
虽然使用C++开发工具,但实质上还是在使用C编代码。
在今后的实践中还需注意。
学习编译技术课程的体会和对本门课程的评价
在上编译课的时候,小组成员都觉得自己对算符优先文法已经掌握了,但等真正用程序去实现时,才发现有很多细节我当时没有注意到。
也正以为没有注意到这些细节,导致成员们编程时会出现各种错误,大部分都是在循环时下标或者循环次数的控制上出错。
在进行到一定阶段后发现犯的低级错误越来越少了,对算符优先文法也愈发的吃透了,编程水平也有了进一步的提高。
编译原理如果要深入研究,那会是一门非常深奥的学问,对于现阶段只有60多学时的本科生来说,只能涉及到它一些最基本的原理,和最宏观的方法,要想微观的去研究,还需要在以后的时间里去钻研。
在这次实验中,小组成员也只是用高级语言模拟了编译的一个小方法,在完成实验的过程中,小组成员对编译原理有了进一步的了解,更提高了动手编程能力,是一次经验和知识的积累。
7参考文献
[1]张素琴.编译原理(第2版)[M].北京:
清华大学出版社,2005.6,102-121.
[2]陈维兴.C++面向对象程序设计教程[M].北京:
清华大学出版社,2004.8,258-272.
8源代码
#include
#include
#include
#definerow50
#definecol50
#defineSIZE50
//两个重要结构体的定义
//FIRSTVT表或LASTVT表中一个表项(A,a)结构体的初始化
typedefstruct
{
charnonterm;//非终结符
charterm;//终结符
}StackElement;
//存放(A,a)的栈的初始化
typedefstruct
{
StackElement*top;
StackElement*bottom;
intstacksize;
}stack;
//初始化(A,a)栈
voidInitStack(stack&S)
{
S.bottom=newStackElement[SIZE];
if(!
S.bottom)
cout<<"存储空间分配失败!
"<S.top=S.bottom;
S.stacksize=SIZE;
}
//判断(A,a)栈是否为空
boolifEmpty(stackS)
{
if(S.top==S.bottom)returntrue;//如果栈为空,则返回true
elsereturnfalse;//否则不为空,返回false
}
//插入栈顶(A,a)元素
voidInsert(stack&S,StackElemente)
{
if(S.top-S.bottom>=S.stacksize)
cout<<"栈已满,无法插入!
"<else
{
S.top->nonterm=e.nonterm;
S.top->term=e.term;
S.top++;
}
}
//弹出栈顶(A,a)元素
StackElementPop(stack&S)
{
StackElemente;
e.nonterm='\0';
e.term='\0';
if(S.top==S.bottom)
{
cout<<"栈为空,无法进行删除操作!
"<returne;
}
else
{
S.top--;
e.nonterm=S.top->nonterm;
e.term=S.top->term;
returne;
}
}
//终结符与非终结符的判断函数(布尔类型)
boolTerminalJud(charc)
{
if(c>='A'&&c<='Z')returnfalse;//非终结符返回false
elsereturntrue;//终结符返回true
}
//判断非终结符在first表中是否已存在
boolItemJud(charfirst[][col],intfrist_len,charC)
{
for(inti=0;i{
if(first[i][0]==C)
returntrue;//如果first表中已存在此非终结符,则返回true
}
returnfalse;
}
//读文件函数
intreadfile(charsen[][col])
{
charaddr[50];
cout<<"请输入要读文件的地址(\\用\\\\表示):
"<cin>>addr;
ifstreamfin;
fin.open(addr,ios:
:
in);
if(!
fin)
{
cout<<"Cannotopenfile!
\n"<}
for(inti=0;!
fin.eof();i++)
{
fin>>sen[i];
cout<}
returni;
}
//FIRSTVT表和LASTVT表中表项(非终结符)的初始化
voidItemInit(charsen[][col],charfirst[][col],charlast[][col],intsen_len,int&frist_len)
{
inti;
frist_len=1;
first[0][0]=sen[0][0];
last[0][0]=sen[0][0];
for(i=1;i{
if(TerminalJud(sen[i][0])==false&&ItemJud(first,frist_len,sen[i][0])==false)//k是当前first和last表的长度
{
first[frist_len][0]=sen[i][0];
last[frist_len][0]=sen[i][0];
frist_len++;
}
}
}
voidFirstVt(charsen[][col],charfirst[][col],intsen_len,intfrist_len)//frist_len是first表的行数sen_len是产生式的个数
{
StackElementDFS,record[SIZE];
stackOperator;//创建存放(A,a)的栈
InitStack(Operator);
inti,j,r=0;
for(i=0;i{
for(j=3;sen[i][j]!
='\0';j++)
{
if(TerminalJud(sen[i][j])==true)//遇到的第一个终结符压入
{
intexist=0;
DFS.nonterm=sen[i][0];
DFS.term=sen[i][j];
for(inti1=0;i{
if(record[i1].nonterm==sen[i][0]&&record[i1].term==sen[i][j])
{
exist=1;
break;
}
}
record[r].nonterm=sen[i][0];
record[r].term=sen[i][j];
if(exist==0)
{
Insert(Operator,DFS);//A-aBA-aC(A,a)压栈两次?
record[r].nonterm=sen[i][0];
record[r].term=sen[i][j];
r++;
}
break;
}
}
}
intlocation[col];//辅助数组,用来记录first表中放入终结符的位置
for(i=0;ilocation[i]=1;
while(!
ifEmpty(Operator))
{
intexist=0;//标志位,记录即将入栈的元素是否已经存在
StackElementIDElement,DElement;
DElement=Pop(Operator);//弹出栈顶元素
for(i=0;i{
if(first[i][0]==DElement.nonterm)
{
intn=location[i];
first[i][n]=DElement.term;//将终结符填入相应的first表中
location[i]++;
break;
}
}
for(j=0;j{
if(sen[j][3]==DElement.nonterm)//找出能推出当前非终结符的产生式的左部
{
IDElement.nonterm=sen[j][0];
IDElement.term=DElement.term;
//判断将要放进栈里的元素曾经是否出现过,若没有,才压入栈
for(intr0=0;r0{
if(record[r0].nonterm==IDElement.nonterm&&record[r0].term==IDElement.term)
{
exist=1;
break;
}
}
if(exist==0)
{
Insert(Operator,IDElement);
record[r].nonterm=IDElement.nonterm;
record[r].term=IDElement.term;
r++;
}
}//if
}//for
}//while
}
voidLastVt(charsen[][col],charlast[][col],intsen_len,intfrist_len)//firstvt表与lastvt表行数一样firs