编译原理是计算机专业的一门重要专业课,旨在介绍编译程序构造的一般原理和基本方法。内容包括语言和文法、词法分析、语法分析、语法制导翻译、中间代码生成、存储管理、代码优化和目标代码生成。 编译原理是计算机专业设置的一门重要的专业课程这门课在理论、技术、方法上都对学生提供了系统而有效的训练,有利于提高软件人员的素质和能力。
所谓LR(K)分析,是指从左至右扫描和自底向上的语法分析,且在分析的每一步,只须根据分析栈当前已移进和归约出的全部文法符号,并至多再向前查看K个输入符号,就能确定相对于某一产生式左左部符号的句柄是否已在分析栈的顶部形成,从而也就可以确定当前所应采取的分析动作(是移进还是按某一产生式进行归约等)。
已知有如下的布尔表达式文法:
B ®B and T | T
T®T or F | F
F®notF|true|false |(B)|i rop i
利用LR分析法编制、调试其语法及语义分析程序,生成的中间代码为四元式。编制好分析程序后计若干用例,上机测试并通过所设计的分析程序。
布尔表达式的LR分析分为扩展文法,构造识别活动前缀的DFA图,判断有误冲突,若有冲突,则消除冲突和构造LR分析表等步骤。
l 首先要拓广文法:
非二义性文法如下:
(0) B’ ® B
(1) B ® B and T
(2) B ® T
(3) T ® T or F
(4) T ® F
(5) F ® not F
(6) F ® ( B )
(7) F ® true
(8) F ® false
(9) F ® i rop i
l 构造识别活动前缀的DFA图
l 判断有无冲突
LR(0)分析时有移进—规约冲突,但冲突可以由SLR(1)分析解决。
l 构造LR分析表
状态 Si |
ACTION |
GOTO |
|||||||||||
and |
or |
not |
true |
false |
( |
) |
i |
rop |
# |
B |
T |
F |
|
0 |
|
|
S4 |
S5 |
S6 |
S7 |
|
S8 |
|
|
1 |
2 |
3 |
1 |
S9 |
|
|
|
|
|
R2 |
|
|
ACC |
|
|
|
2 |
R2 |
S10 |
|
|
|
|
R2 |
|
|
R2 |
|
|
|
3 |
R4 |
R4 |
|
|
|
|
R4 |
|
|
R4 |
|
|
|
4 |
|
|
S4 |
S5 |
S6 |
S7 |
|
S8 |
|
|
|
|
11 |
5 |
R7 |
R7 |
|
|
|
|
R7 |
|
|
R7 |
|
|
|
6 |
R8 |
R8 |
|
|
|
|
R8 |
|
|
R8 |
|
|
|
7 |
|
|
S4 |
S5 |
S6 |
S7 |
|
S8 |
|
|
12 |
2 |
3 |
8 |
|
|
|
|
|
|
|
|
S13 |
|
|
|
|
9 |
|
|
S4 |
S5 |
S6 |
S7 |
|
S8 |
|
|
|
14 |
3 |
10 |
|
|
S4 |
S5 |
S6 |
S7 |
|
S8 |
|
|
|
|
15 |
11 |
R5 |
R5 |
|
|
|
|
R5 |
|
|
R5 |
|
|
|
12 |
S9 |
|
|
|
|
|
S16 |
|
|
|
|
|
|
13 |
|
|
|
|
R3 |
|
|
S17 |
|
|
|
|
|
14 |
R1 |
S10 |
|
|
|
|
R1 |
|
|
R1 |
|
|
|
15 |
R3 |
R3 |
|
|
|
|
R3 |
|
|
R3 |
|
|
|
16 |
R6 |
R6 |
|
|
|
|
R6 |
|
|
R6 |
|
|
|
17 |
R9 |
R9 |
|
|
|
|
R9 |
|
|
R9 |
|
|
|
编译器设计的编译程序涉及到编译五个阶段中的三个,即词法分析器、语法分析器和中间代码生成器。编译程序的输出结果为中间代码即逆波兰式。整个编译程序分为三部分:词法分析部分、语法分析处理及逆波兰式生成部分、输出显示部分。
编译程序需要在单词级别上来分析和翻译源程序,所以首先要识别出单词,而词法分析部分的任务是:从左至右扫描源程序的字符串,按照词法规则(正则文法规则)识别出一个个正确的单词,并转换成该单词相应的二元式(种别码、属性值)交给语法分析使用。因此,词法分析是编译的基础。执行词法分析的程序称为词法分析器。
语法分析是编译程序的核心部分,其主要任务是确定语法结构,检查语法错误,报告错误的性质和位置,并进行适当的纠错工作。
语法分析中主要以二元式作为输入部分,所以输出显示部分的任务是将二元式通过LR分析表对语法分析处理过程进行控制,使逆波兰式翻译的工作有条不紊的进行,同时识别语法分析中的语法错误。
词法分析是编制一个读单词的过程,从输入的源程序中,识别出各个具有独立意义的单词,即基本保留字、标识符、常数、运算符、分隔符五大类。并依次输出各个单词的内部编码及单词符号自身值。程序语言的单词符号一般分为五种:关键字(保留字/基本字)、标识符、常数、运算符、界限符。
词法分析的功能是输入源程序,输出单词符号。词法分析的单词符号常常表示成二元式(单词种别码,单词符号的属性值)。
词法分析器的设计方法有如下四个步骤:
1、写出该语言的词法规则。
2、把词法规则转换为相应的状态转换图。
3、把各转换图的初态连在一起,构成识别该语言的自动机。
4、设计扫描器;把扫描器作为语法分析的一个过程,当语法分析需要一个单词时,就调用扫描器。扫描器从初态出发,当识别一个单词后便进入终态,送出二元式
语法分析是编译程序的核心部分,其主要任务是确定语法结构,检查语法错误,报告错误的性质和位置,并进行适当的纠错工作.法分析的方法有多种多样,常用的方法有递归子程序方法、运算符优先数法、状态矩阵法、LL(K)方法和LR(K)方法。归纳起来,大体上可分为两大类,即自顶向下分析方法和自底向上分析方法. Syntax进行语法分析。对于语法分析,这里采用LR(1)分析法,判断程序是否满足规定的结构.构造LR(1)分析程序,利用它进行语法分析,判断给出的符号串是否为该文法识别的句子,了解LR(K)分析方法是严格的从左向右扫描,和自底向上的语法分析方法。
进入编译程序的第三阶段:中间代码产生阶段。为了使编译程序有较高的目标程序质量,或要求从编译程序逻辑结构上把与机器无关和与机器有关的工作明显的分开来时,许多编译程序都采用了某种复杂性介于源程序语言和机器语言之间的中间语言。常用的几种中间语言有: 逆波兰式、四元式、三元式、树表示。本课程设计主要实现逆波兰式的生成。
逆波兰式定义: 将运算对象写在前面,而把运算符号写在后面。用这种表示法表示的表达式也称做后缀式。逆波兰式的特点在于运算对象顺序不变,运算符号位置反映运算顺序。采用逆波兰式可以很好的表示简单算术表达式,其优点在于易于计算机处理表达式。
Windows环境下使用Visualstudio2012
算法是对问题求解过程的一种描述,是为解决一个或一类问题给出的一个确定的、有限长的操作序列。严格说来,一个算法必须满足以下五个重要特性:
l 有穷性:对于任意一组合法的输入值,在执行有穷步骤之后一定能结束。
l 确定性:对于每种情况下所应执行的操作,在算法中都有确切的规定,使算法的执行者或阅读者都能明确其含义及如何执行。并且在任何条件下,算法都只有一条执行路径。
l 可行性:算法中的所有操作都必须足够基本,都可以通过已经实现的基本操作运算有限次实现之。
l 有输入:作为算法加工对象的量值,通常体现为算法中的一组变量。但有些算法的字面上可以没有输入,实际上已被嵌入算法之中。
l 有输出:它是一组与“输入”有确定关系的量值,是算法进行信息加工后得到的结果,这种确定关系即为算法的功能。
在设计算法时,通常应考虑以下原则:首先说设计的算法必须是“正确的”,其次应有很好的“可读性”,还必须具有“健壮性”,最后应考虑所设计的算法具有“高效率与低存储量”。
所谓算法是正确的,除了应该满足算法说明中写明的"功能"之外,应对各组典型的带有苛刻条件的输入数据得出正确的结果。在算法是正确的前提下,算法的可读性是摆在第一位的,这在当今大型软件需要多人合作完成的环境下是换重要的,另一方面,晦涩难读的程序易于隐藏错误而难以调试。算法的效率指的是算法的执行时间,算法的存储量指的是算法执行过程中所需最大存储空间。
算法是程序设计的另一个不可缺的要素,因此在讨论数据结构的同时免不了要讨论相应的算法。这里有两重意思,即算法中的操作步骤为有限个,且每个步骤都能在有限时间内完成。确定性表现在对算法中每一步的描述都没有二义性,只要输入相同,初始状态相同,则无论执行多少遍,所得结果都应该相同。
在程序中,我们使用两个二维数组存储SLR分析表,并初始化,初始化SLR表,其中action表中:100表示acc,除了100以外整数表示移进状态;负数表示用对应产生式进行规约。
int action[18][10]={ { 0 , 0 ,4 ,5 , 6 ,7 , 0 , 8 ,0 , 0},//0
{9 , 0 ,0 ,0 ,0 ,0 ,-2 , 0 ,0 ,100},//1
{-2 ,10 ,0 ,0 , 0 ,0 ,-2 ,0 , 0 ,-2},//2
{-4 ,-4 ,0 ,0 , 0 ,0 ,-4 , 0 ,0 , -4},//3
{0 , 0 ,4 ,5 ,6 ,7 ,0 , 8 ,0 , 0},//4
{-7 ,-7 ,0 ,0 , 0 ,0 ,-7 , 0 ,0 , -7},//5
{-8 ,-8 ,0 ,0 , 0 ,0 ,-8 , 0 ,0 , -8},//6
{0 , 0 ,4 ,5 ,6 ,7 ,0 , 8 ,0 , 0},//7
{0 , 0 ,0 ,0 ,0 ,0 ,0 , 0 ,13 , 0},//8
{0 , 0 ,4 ,5 ,6 ,7 ,0 , 8 ,0 , 0},//9
{0 , 0 ,4 ,5 ,6 ,7 ,0 , 8 ,0 , 0},//10
{-5 ,-5 ,0 ,0 , 0 ,0 ,-5 ,0 , 0 ,-5},//11
{9 , 0 ,0 ,0 ,0 ,0 ,16 , 0 , 0 , 0},//12
{0 , 0 ,0 ,0 ,0 ,0 ,0 ,17 ,0 , 0},//13
{-1 ,10 ,0 ,0 , 0 ,0 ,-1 , 0 ,0 , -1},//14
{-3 ,-3 ,0 ,0 , 0 ,0 ,-3 , 0 ,0 , -3},//15
{-6 ,-6 ,0 ,0 , 0 ,0 ,-6 , 0 ,0 , -6},//16
{-9 ,-9 ,0 ,0 , 0 ,0 ,-9 , 0 ,0 , -9}};//17
int gotol[18][3]={ {1 ,2 , 3},{0 ,0 , 0},{0 ,0 , 0},{0 ,0 , 0}, {0 ,0 ,11},
{0 ,0 , 0},{0 ,0 ,0},{12 ,2 ,3},{0 ,0 ,0},{0 ,14 , 3},
{0 ,0 ,15},{0 ,0 , 0},{0 ,0 , 0},{0 ,0 , 0},{0 ,0 , 0},
{0 ,0 ,0},{0 ,0 ,0},{0 ,0 ,0}};
用三个数组来存储和符号以及产生式相关的信息:endls数组存储终结符、noends数组存储非终结符、products数组存储产生式信息。
//终结符集合
stringendls[10]={"and","or","not","true","false","(",")", "i","rop","#" };
//非终结符集合
stringnoends[3]={"B","T","F"};
//产生式集合
stringproducts[10]={"B","B and T", "T","T orF","F","not F","( B )","true","false","i rop i"};
我们用类来模拟状态栈和符号栈。
这是状态栈:
classstatestack
{
private:
int*base;//栈底指针
int*top;//栈顶指针
intsize;//栈内元素个数
intstacksize;//栈的大小
public:
statestack()
{
size=0;
stacksize=20;
base=newint[stacksize];;
top=base;
}
intgetTop()//获取栈顶的元素。
{
if(base==top)
{
return-1;
}
else
{
return*(top-1);
}
}
boolstatePush(int elem)//元素入栈
{
++size;
(*top)=elem;
++top;
returntrue;
}
voidstatePop(int time)//元素出栈
{
for(inti=0;i