编译原理——算符优先分析文法(附源代码)
Posted 精心出精品
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了编译原理——算符优先分析文法(附源代码)相关的知识,希望对你有一定的参考价值。
算符优先分析文法
一、写在前面
算符优先分析文法是一种工具,在编译的过程中,隶属于语法分析环节,却又与中间代码的生成息息相关,编译可以分为五个阶段:词法分析、语法分析、语义分析(中间代码的生成)、代码优化、目标代码生成。语法分析是指:在词法分析基础上,将单词符号串转化为语法单位(语法范畴)(短语、子句、句子、程序段、程序),并确定整个输入串是否构成语法上正确的程序。也就是说语法分析是检验输入串的语法是否正确,注意这里的语法正确,只是简单地符合自己定义的规范,而不能检测出运行时错误,比如"X/0",空指针错误,对象未初始化等错误。在这一个实验中,我将通过算符优先分析文法这一个工具,在语法分析的时候,顺便进行语义分析,也就是识别出语法单位,同时简要的将识别出的中间代码进行计算(目标代码的生成+运行),得到相应的结果,来检验自己设计的正确性。可以说题目虽然叫做算符优先分析文法,其实却是一个贯穿了“词法分析+语法分析+语义分析+中间代码优化+目标代码生成+运行”全过程的一个极具概括性的程序。如果能将这个程序得心应手的完成出来,我相信诸位对编译原理的掌握也算是炉火纯青了。时隔将近两年再来整理自己以前写过的实验报告,还是挺有感慨的,对一件东西感兴趣,原来影响还会如此深远,还记得自己当时连续六个小时全神贯注写出的实验报告,现在看看竟然写了五六十页,核心内容也有三四十页,不觉的感慨当年充满热情的时代慢慢的竟走出许久~~
二、算符优先分析文法实验
2.1、任务
实验目的:
1.了解掌握算符优先分析的基本方法、内容;
2.学会科学思考并解决问题,提高程序设计能力。
实验内容与要求:
用算符优先分析方法设计一个分析解释程序,对输入的赋值语句、输出语句、清除语句进行词法分析、语法分析、表达式求值并存储于指定变量中;若存在错误,提示错误相关信息。
文法表示:
S→v=E|E?|clear
E→E+T|E-T|T
T→T*F|T/F|F
F→ (E)|v|c
单词种别码设计:
= 1
? 2
+ 3
- 4
* 5
/ 6
( 7
) 8
v 9
c 10
clear 11
# 12
N 13
可归约串语义解释:
变量归约;常量归约;运算归约;括号归约;
赋值语句;输出语句;清除语句。
演示示例:
a=5
b=a+10
b?
b+a*a?
a=a+b
2.2、分析与设计
首先,这是一个很好的题目,从知识的理解、将形式化的东西转化成具体的过程、编程能力、编程技巧、调试改错能力等多个方面考察了我们的学习情况。 算符优先文法是一种自下而上的文法分析方法,这种方法的用处十分广泛,虽然有的文法不能用算符优先分析文法,如类似…PQ…..(P,Q为非终结符)这样形式的产生式,但是对于大部分文法这种分析方法还是十分有用的。
其次,对于本程序中的文法,实质上是算数表达式的计算。用这种文法就是再好不过了,作为从算符文法抽象出来的算符优先文法当然继承了算符文法的特性。下面就切入正题了,我将详细介绍一下我对于这个文法的思考出发点和分层分析的方法。
模块一:构建firstVT()和lastVT()这两个集合
基于“优先”这两个字,有效的回避了左递归(自上而下文法分析)和二义性的问题。关键是如何体现“优先”这两个字。这就需要firstVT()和lastVT()集合了。
firstVT(E)={a|E→a…..|E→Qa….|firstVT(Q)},从这个定义可以看到,firstVT()主要找到是本产生式中的第一个非终结符和若第一个是非终结符则包含该非终结符的firstVT()集,因为firstVT有可能要求Q的firstVT()集,因此有可能要用递归才能求尽所有的firstVT()集。同理,lastVT(E)={a|E→….a|E→…….aQ},可见这两个关系好像反对称的影像。说了这么多,还是没有说到这两个集合要干什么用。让我们想象一个句型…aQ…..
在这个句型中我们知道只有等Q的短语规约为Q了,才有可能将…aQ….再次向上规约,因此a的优先级要小于Q产生式的firstVT(Q)集,因为我们可以断定a必定是比Q中第一个出现的终结符优先级低的,也就是说优先级a<优先级firstVT(Q),至于第二个,第三个终结符。。。我们不敢判定。于是才要费尽心思地构造firstVT()这样的一个集合。同理,对于lastVT(),让我们想一下这种情况…..Qa…..,对于这个句型我们知道只有当Q的短语归约成了Q,我们才敢将….Qa……向上归约。这样的话就是说Q的产生式中最后出现的一个终结符的优先级必定是比a的优先级高的,也就是优先级lastVT(Q)>优先级a,同样的对于倒数第二个,倒数第三个终结符。。。我们不敢判定。说了这么多我想应该能够理解这两个集合存在的必要性和决定性了。
因为是程序,我就说一下程序如何实现这两个集合的求解。
首先是一些数据结构和意义:
char FIRSTVT[20][20];
存储firstVT()集,第二维代表第几个产生式,第一维代表集合中的第几个元素
char LASTVT[20][20];
存储lastVT()集,第二维代表第几个产生式,第一维代表集合中的第几个元素
char INPUT[20][20];
按照一定的形式存储文法中的所有产生式。
然后是几个函数:
1. void setFIRSTVT(char X,int T);
这个函数的目的是将求出的终结符X,存放到第T条产生式的左部对应的firstVT集合中。
2. void getFIRSTVT(char X,int S)
S标记产生式的位置,X为将要计算的产生式的左部。这个函数就比较复杂了,它将完成求出一个产生式左部的firstVT的终结符的重要任务,可以说是主控程序了。它的算法是逐个遍历产生式,对每个产生式求出该产生式的直接a,并且若有E→Qa…还要递归的求出Q的firstVT()将其并入firstVT(E)的集合中。
3. 同理void setLASTVT(char X,int T)
4. void getLASTVT(char X,int S)和上面类似。
5. void DisplayFirstVT_LasVT()
这个函数也是主程序开始时要进入的位置。它主要提示用户输入文法(产生式组),然后调用上面的函数计算并显示两种集合。
下面是void getFIRSTVT(char X,int S)的函数。
1 //找出FIRSTVT集元素
2 void getFIRSTVT(char X,int S)
3 {
4 int i,j=0,k;//j当前数组指针
5 int T=0;//X position
6 int L=0;//X offspring length
7 char C[20];
8
9 for(i=0;i<20;i++)
10 {
11 if(INPUT[i][0]==X)//找到将要处理的产生式
12 {
13 T=i; //标记产生式的位置
14 break;
15 }
16 }
17 //按照规则从产生式的右部第一个字符开始处理,直至遇上\'/n\'
18 //每一次都判断指针j指向的字符若满足p-->w中w的规定则放进C[]
19 //若遇上‘|’或\'\\n\'则求这段右部对应的firstVT()
20 for(i=4;i<20;i++)
21 {
22 if(INPUT[T][i]==\'|\'||INPUT[T][i]==\'\\n\')
23 {//刚开始走不到这里
24 L=j;//j指针所指位置为C[]中字符串的长度
25 j=0;//交给L后就清零,以供下一次使用
26 for(k=0;k<L;k++)
27 {//要是数组C[]中的终结符,如小写字母\'a\'~\'z\',加减乘除,乘方,#
28 //则归入fisrstVT集中
29 //若是Aab...则a成为F()一部分,b被忽略,A也不用求first集???需要求!!!除非A==X
30 //若是QRa...,则不是算符优先文法,故不可能
31 //若是a...则只是填进a
32
33 if((C[k]>=97&&C[k]<=122)||C[k]==\'+\'||C[k]==\'*\'||C[k]==\'-\'||C[k]==\'/\'||C[k]==\'!\'||C[k]==\'(\'||C[k]==\')\'||C[k]==\'#\'||C[k]==\'\\?\'||C[k]==\'=\')
34 {//只能用一次,因是算符优先文法,故前两个中必会至少存在一个终结符
35 setFIRSTVT(C[k],S);//存入FIRSTVT中,S标记产生式的位置
36 break;//跳出循环保证存入的是最左边的终结符
37 }
38 }
39 if(C[0]>=65&&C[0]<=90&&C[0]!=X)
40 {//若C[0]中为A~Z,并且C[0]不是X(否则将无限循环),则递归的进行填充
41 int flag=0,count;
42 for(count=0;count<20;count++)
43 {
44 if(INPUT[count][0]==C[0])//找到将要处理的产生式
45 {
46 flag=1;//若存在,则填充
47 }
48 }
49 if(flag==1)
50 {//递归,所用极妙,画龙点睛
51 getFIRSTVT(C[0],S);//S为子集中的元素填入父集中指明了方向
52 }
53 }
54 }
55 else if(INPUT[T][i]!=\'|\'&&INPUT[T][i]!=\'\\0\')//该行没结束,过滤\'\\0\'
56 {
57 C[j]=INPUT[T][i];//j从0开始
58 j++;//移进
59 }
60 if(INPUT[T][i]==\'\\n\')
61 {//该行结束
62 break;
63 }
64 }
65 }
比如说对于这个文法分析出的集合如下:
模块二:构建优先符号表
为了体现“优先”的关系,只是构建出了上面的两种集合是不够的,由上面分析我们知道了这两个集合的用处。说白了就是为了构造出算符优先分析表。因为关系无非四类1.等于,2.大于,3.小于,4没有关系。注意这里的“没有关系”也是一种关系。
而由第一个阶段产生的两个集合就可以找到所有的大于和小于关系,那就只剩下了等于关系,如果等于关系找到了,剩余的没有填写的关系就是没有关系。等于关系是这样的:如果存在这样的句型.....ab....或.......aQb.....则关系(a==b)。这样的结果也是显而易见的,因为a和b注定要共同归约,没有谁先谁后,因此是等于关系。
好了现在所有关系都搞请了,就可以建表了。
还是首先解释一下新定义的一个数据结构:
char PriorityTable[20][20];
优先关系表,其中存放着终结符以及终结符对应的关系。
然后是一些函数:
1.int IsVT(char ch)
判断ch是否为终结符,若是则返回1,否则返回0
2.int SearchTbl(char ch)
搜索终结符ch在符号表中的行列数,用来定位将要填写的位置。
3.int FL_map(char ch)
这个映射既是查找产生式左部的位置,若存在则返回该产生式的条数,即是第几个产生式,失败则返回-1这个出错标记。
4.void createPriorityTable()
这个是建表的主控程序,用来填写所有关系与表中。遍历所有的产生式,填写所有的关系。这里主要解释一下程序是如何找到横纵坐标并且将关系填写进去的。对于简单的情况只要拿一个终结符来使用SearchTbl(终结符)就可以找到横(纵)坐标了,但是因为对于大小关系我们往往要填的是“终结符<firstVT()”或“lastVT()>终结符”这样的情况,因此势必要从集合中取出终结符,并用该终结符来映射坐标,即是用到了类似这样的转换:
temp_column=FIRSTVT[FL_map(C[2])][count_1];
tbl_column=SearchTbl(temp_column);//将上述结果再次转换
或者temp_row=LASTVT[FL_map(C[1])][reg];
tbl_row=SearchTbl(temp_row);//map
这样的映射。知道了这些程序不难读懂。
5.void DisplayPriorityTable()
这个函数就是显示优先关系表的内容的。
下面是 void createPriorityTable() 函数的实现过程:
1 void createPriorityTable()
2 {//创建并填写优先级表
3 //对每个产生式遍历,求出四种关系1.=,2.<,3.>,4.没有关系
4 char temp[13]={\'+\',\'-\',\'*\',\'/\',\'(\',\')\',\'v\',\'c\',\'=\',\'?\',\'#\',\'\\0\'};
5 int j,L,i;
6 int tbl_row,tbl_column;//表的元素坐标
7 char C[20];
8 for(int r=1;r<12;r++)
9 {
10 PriorityTable[0][r]=temp[r-1];//初始化行头,第0行
11 PriorityTable[r][0]=temp[r-1];//初始化第0列
12 }
13 //扫描产生式的右部,如果发现终结符且该终结符周围有其他字符
14 //若该其他字符为终结符,则这两者关系为相等
15 //若该其他字符为非终结符,则根据非终结符的firstVT,LastVt填表
16 for(int p=0;p<4;p++)
17 {
18 j=0;
19 for(i=4;i<20;i++)
20 {//注意,此处因为时间有限考虑不是很全面,只是对老师给定的文法进行了周密的部署
21 //在别的文法上可能需要少许加工,不是问题
22 if(INPUT[p][i]==\'|\'||INPUT[p][i]==\'\\n\')
23 {//刚开始走不到这里
24 L=j;//j指针所指位置为C[]中字符串的长度
25 j=0;//交给L后就清零,以供下一次使用
26 if(L>1)//大于一则处理,否则不关心
27 { //对于清零指令l自动忽略
28 if(IsVT(C[0])&&IsVT(C[1])||(L==3)&&IsVT(C[0])&&IsVT(C[2])&&(FL_map(C[1])!=-1))
29 {//若为终结符因至少有两个,若出现两个终结符则C[0]\'==\'C[1]||C[2],注意这是此文法的情况
30 //则需要填表,查表找到C[0]的行数,C[1],C[2]的列数进行填表
31 tbl_row=SearchTbl(C[0]);//记录行数
32 if(IsVT(C[1]))
33 {//列数,若是终结符 v=
34 tbl_column=SearchTbl(C[1]);
35 }
36 if(IsVT(C[2])&&(L==3))
37 {//列数 (E)
38 tbl_column=SearchTbl(C[2]);
39 }
40 PriorityTable[tbl_row][tbl_column]=\'=\';//填表
41
42 if((L==3)&&IsVT(C[0])&&IsVT(C[1])&&(FL_map(C[2])!=-1))
43 {//v=E,这种情况
44 int count_1=0;
45 char temp_column;
46 tbl_row=SearchTbl(C[1]);// =<firstVT(E)
47 while(FIRSTVT[FL_map(C[2])][count_1]!=\'\\0\')
48 {//填写小于关系
49 temp_column=FIRSTVT[FL_map(C[2])][count_1];//映射,仔细体会
50 tbl_column=SearchTbl(temp_column);//将上述结果再次转换
51 PriorityTable[tbl_row][tbl_column]=\'<\';//填写关系
52 count_1++;//准备填写下一个
53 }
54 count_1=0;//清零
55 }
56 if((L==3)&&IsVT(C[0])&&IsVT(C[2])&&(FL_map(C[1])!=-1))
57 {//弥补(E),针对本文法
58 //首先填写<关系
59 char temp_row,temp_column;
60 int reg=0;
61 tbl_row=SearchTbl(C[0]);
62 while(FIRSTVT[FL_map(C[1])][reg]!=\'\\0\')
63 {//填写小于关系 \'(\'<firstVT(E)
64 temp_column=FIRSTVT[FL_map(C[1])][reg];
65 tbl_column=SearchTbl(temp_column);//皆是映射
66 PriorityTable[tbl_row][tbl_column]=\'<\';
67 reg++;
68 }
69 reg=0;//清零
70 tbl_column=SearchTbl(C[2]);
71 while(LASTVT[FL_map(C[1])][reg]!=\'\\0\')
72 {//填写大于关系 lastVT(E)>\')\'
73 temp_row=LASTVT[FL_map(C[1])][reg];
74 tbl_row=SearchTbl(temp_row);//map
75 PriorityTable[tbl_row][tbl_column]=\'>\';
76 reg++;
77 }
78 reg=0;//reset
79 }
80 }
81 else if((FL_map(C[0])!=-1)&&IsVT(C[1]))
82 {//C[0]肯定为非终结符lastVT(C[0])>C[1]
83 //填写大于关系
84 char temp_row,temp_column;
85 int count=0;
86 tbl_column=SearchTbl(C[1]);
87 while(LASTVT[FL_map(C[0])][count]!=\'\\0\')
88 {//填写大于关系
89 temp_row=LASTVT[FL_map(C[0])][count];
90 tbl_row=SearchTbl(temp_row);//同上
91 PriorityTable[tbl_row][tbl_column]=\'>\';
92 count++;
93 }
94 count=0;
95 if(L==3)
96 {//在这种情况下C[2]必为非终结符,求C[2]的firstVT(),填写小于关系
97 //E+T,E-T,T*F,T/F等情况
98 tbl_row=SearchTbl(C[1]);
99 while(FIRSTVT[FL_map(C[2])][count]!=\'\\0\')
100 {//填写小于关系
101 temp_column=FIRSTVT[FL_map(C[2])][count];
102 tbl_column=SearchTbl(temp_column);//map
103 PriorityTable[tbl_row][tbl_column]=\'<\';
104 count++;
105 }
106 count=0;
107 }//end if
108 }
109 }
110 }
111 else if(INPUT[p][i]!=\'|\'&&INPUT[p][i]!=\'\\0\')//该行没结束,过滤\'\\0\'
112 {
113 C[j]=INPUT[p][i];//j从0开始
114 j++;//移进
115 }
116 if(INPUT[p][i]==\'\\n\')
117 {//该行结束
118 break;
119 }
120 }
121 }
122 //补上#的关系
123 for(int m1=1;m1<11;m1++)
124 {
125 PriorityTable[SearchTbl(\'#\')][m1]=\'<\';//这个容易理解,补行
126 }
127 for(int m2=1;m2<11;m2++)
128 {
129 PriorityTable[m2][SearchTbl(\'#\')]=\'>\';//补列
130 }
131 PriorityTable[SearchTbl(\'#\')][SearchTbl(\'#\')]=\'=\';
132 }
比如说对于 以上是关于编译原理——算符优先分析文法(附源代码)的主要内容,如果未能解决你的问题,请参考以下文章