算符优先分析文法
一、写在前面
算符优先分析文法是一种工具,在编译的过程中,隶属于语法分析环节,却又与中间代码的生成息息相关,编译可以分为五个阶段:词法分析、语法分析、语义分析(中间代码的生成)、代码优化、目标代码生成。语法分析是指:在词法分析基础上,将单词符号串转化为语法单位(语法范畴)(短语、子句、句子、程序段、程序),并确定整个输入串是否构成语法上正确的程序。也就是说语法分析是检验输入串的语法是否正确,注意这里的语法正确,只是简单地符合自己定义的规范,而不能检测出运行时错误,比如"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 //若遇上‘|’或'
'则求这段右部对应的firstVT()
20 for(i=4;i<20;i++)
21 {
22 if(INPUT[T][i]=='|'||INPUT[T][i]=='
')
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]!='