《编译原理》实验

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

《编译原理》实验
《编译原理》是国内外各高等院校计算机科学技术类专业,特别是计算机软件专业的一门重要专业课程。

该课程系统地向学生介绍编译程序的结构、工作流程及编译程序各组成部分的设计原理和实现技术。

由于该课程理论性和实践性都比较强,内容较为抽象复杂,涉及到大量的软件设计算法,因此,一直是一门比较难学的课程。

为了使学生更好地理解和掌握编译技术的基本概念、基本原理和实现方法,实践环节非常重要,只有通过上机进行程序设计,才能使学生对比较抽象的教学内容产生具体的感性认识,增强学生综合分析问题、解决问题的能力,并对提高学生软件设计水平大有益处。

本实验内容可在《编译原理》课程教学的同时,安排学生进行相关的实验。

实验平台可选择在MS-DOS或Windows操作系统环境,使用C/C++的任何版本作为开发工具。

学生在做完试验后,应认真撰写实验报告,内容应包括实验名称、实验目的、实验要求、实验内容、测试或运行结果等。

实验一词法分析
1.实验目的
对C语言的一个子集设计并实现一个简单的词法分析器,掌握利用状态转换图设计词法分析器的基本方法。

2.实验要求
利用该词法分析器完成对源程序字符串的词法分析。

输出形式是源程序的单词符号二元式的代码,并保存到文件中。

3.实验内容
(1) 假设该语言中的单词符号及种别编码如下表所示。

(2) 关键字main int char if else for while都是小写并都是保留字。

算符和界符= + -* / & <<=>>===!=&& || , : ; { } [ ] ( ) ID和NUM的正规定义式为:
ID→letter(letter | didit)*
NUM→digit digit*
letter→a | … | z | A | … | Z
digit→ 0 | … | 9
如果关键字、标识符和常数之间没有确定的算符或界符作间隔,则至少用一个空格作间隔。

空格由空白、制表符和换行符组成。

(3) 设计词法分析器的步骤:
①首先根据上面单词符号表及ID和NUM的正规定义式,构造出状态转换图;
②定义相关的变量和数据结构。

关键字作为特殊标识符处理,把它们预先安排在一
张表格中(称为关键字表),当扫描程序识别出标识符时,查关键字表。

如能查
到匹配的单词,则该单词为关键字,否则为一般标识符。

关键字表为一个字符串
数组,其描述如下:
char *KEY_WORDS[7]={″main″,″int″,″char″,″if″,″else″,″for″,″while″};
用以存放单词符号二元式的数据结构可如下定义:
#define MAXLENGTH 255 /* 一行允许的字符个数 */
union WORDCONTENT { /* 存放单词符号的内容 */
char T1[MAXLENGTH];/* 存放标识符及由两个(以上)字符组成的符号 */
int T2; /* 存放整型常数的拼数 */
char T3; /* 存放其他符号 */
};
typedef struct WORD { /* 单词符号二元式 */
int code; /* 存放种别编码 */
union WORDCONTENT value;
} WORD;
③按照编译程序一遍扫描的要求,把词法分析器Scaner作为一个独立的子程序来
设计,通过对Scaner的反复调用识别出所有的单词符号;
④当Scaner识别出一个单词符号时,则将该单词符号的二元式写入到输出文件中。

若Scaner无法识别出一个单词符号时,则调用错误处理程序PrintError,显示当
前扫描到的字符及其所在行、列位置,并跳过该字符重新开始识别单词符号。

(4) 测试该设计词法分析器,可对下面的源程序进行词法分析:输出如下二元式代码序列: main()
{
int i = 10;
while(i) i = i - 1;
}
输出如下二元式代码序列:
(1,main) (26,() (27,)) (30,{) (2,int) (10,i) (21,=) (20,10) (34,;)
(7,while) (26,() (10,i) (27,)) (10,i) (21, =) (10,i) (23,-) (20,1) (34,;)
(31,})
实验二NFA的确定化
1.实验目的
设计并实现将NFA确定化为DFA的子集构造算法,从而更好地理解有限自动机之间的等价性,掌握词法分析器自动产生器的构造技术。

该算法也是构造LR分析器的基础。

2.实验要求
设计并实现计算状态集合I的ε闭包的算法ε_Closure(I)和转换函数Move(I,a),并在此基础上实现子集构造算法Subset_Construction。

利用该从NFA到DFA的转换程序Subset_Construction,任意输入一个NFA N=(S,Σ,δ,s0,F),输出一个接收同一语言的DFA M=(S’,Σ,δ’,s0’,F’)。

3.实验内容
(1)令I是NFA N的状态集S的一个子集,I的ε闭包的ε_Closure(I)构造规则如下:
(a)若s∈I,则s∈ε_Closure(I);
(b)若s∈ε_Closure(I)且δ(s, ε)=s’而s’∉ε_Closure(I) ,则s’∈ε
_Closure(I)
根据上面的规则,下面给出了一个计算I的ε闭包的算法ε_Closure(I)。

SET S;
SETε_Closure(input)
SET *input;
{
S=input; /* 初始化 */
push(); /* 把输入状态集中的全部状态压入栈中 */
while(栈非空){
Nfa_state i;
pop(); /* 把栈顶元素弹出并送入i */
if(存在δ(i, ε)=j)
if(j不在S中) {
把i加到S中;
把j压入栈中;
}
}
return S; /* 返回ε_Closure(input)集合 */
}
完成上述算法的设计。

(2)令I是NFA N的状态集S的一个子集,a∈Σ, 转换函数Move(I,a)定义为:
Move(I,a)= ε_Closure(J)
其中,J={s’|s∈I且δ(s,a)=s’}
转换函数Move(I,a)的设计通过调用ε_Closure(input)实现,完成该函数的设计。

(3)从NFA N构造一个与其等价的DFA M的子集构造算法,就是要为DFA M构造状态转
换表Trans,表中的每个状态是NFA N状态的集合,DFA M将“并行”地模拟NFA N 面对输入符号串所有可能的移动。

下面给出了子集构造算法Subset_Construction 的框架,请完成其设计过程。

有关数据结构:
States[] 是一个M的数组,每个状态有两个域,set域存放N的状态集合,
flg域为一标识。

Trans[] 是M的转移矩阵(输入字母表Σ元素个数×最大状态数),
Trans[i][a]=下一状态。

i M的当前状态号
a 输入符号,a∈Σ
Nstates[] M的下一新状态号
S 定义M的一个状态的N的状态集
初始化:
States[0].set=ε_Closure({N的初态})
States[0].flg=FALSE
Nstates=1
i=0
S=Ф
Trans初始化为无状态’-’
while(States[i]的flg为FALSE){
States[i].flg=TRUE;
for(每个输入符号a∈Σ){
S=ε_Closure(Move(States[i].set,a));
if(S非空)
if(States中没有set域等于 S的状态){
States[Nstates].set=S;
States[Nstates].flg=FALSE;
Trans[i][a]= Nstates++;
}
else
Trans[i][a]= States中一个set域为S的下标;
}
}
此算法的输出M主要由Trans矩阵描述,其中省略了每个状态是否为终态的描述,应加以完善。

(4)测试用例
NFA N的初态为12,DFA M的初态为ε_Closure({12})。

DFA M的状态转换图如下。

实验三非递归预测分析
1.实验目的
设计一个非递归预测分析器,实现对表达式语言的分析,理解自上而下语法分析方法的基本思想,掌握设计非递归预测分析器的基本方法。

2.实验要求
建立文法及其LL(1)分析表表示的数据结构,设计并实现相应的预测分析器,对源程序经词法分析后生成的二元式代码流进行预测分析,如果输入串是文法定义的句子则输出“是”,否则输出“否”。

3.实验内容
(1)文法描述及其LL(1)分析表
表达式语言(XL)的语法规则如下:
1.程序→表达式;
2. |表达式;程序
3.表达式→表达式 + 项
4. |项
5.项→项 * 因式
6. |因式
7.因式→ num_or_id
8. |(表达式)
将该语言的文法转换为如下的LL(1)文法:
1prgm → expr;prgm’ 8 term → factor
term’
2prgm’→ prgm 9 term’→ *factor term’
3prgm’→ε 10 term’→ε
4expr → term expr’ 11 factor → (expr)
5expr →ε 12 f actor → num
6expr’→ +term expr’ 13 system_goal → prgm
7expr’→ε
该LL(1)文法的LL(1)分析表如下:
(2)文法及其LL(1)分析表的数据结构
文法的产生式可用数组Yy_pushtab[]存放。

数组的第一个下标是产生式号,第一个产生式的序号为0;每列按逆序存放该产生式右部各符号的常数值,并以0结束。

对于该表达式语言XL的LL(1)分析表,可用数组Yy_d[]存放。

第一个下标是非终结符数值,第二个下标是终结符数值,数组元素的值为:0(表示接受),1(表示产生式号),-1(表示语法错)。

数组Yy_pushtab[]的具体内容及表示如下:
数组Yy_d[]的具体内容及表示如下:
0 1 2 3 4 5 6
rgm 256
prgm’ 257
expr 258
expr’ 260
factor 261
term’ 262
system_goal 263
(3)预测分析器总控程序结构
预测分析器总控程序使用上面的两个表Yy_pushtab、Yy_d和一个分析栈(元素类型为int),其结构如下:
初始化;/* 把开始符号的常数值压入分析站,输入指向第一个输入符号 */ while(分析栈非空) {
if(栈顶常数表示一个终结符)
if(该常数与输入符号的常数不等)
报语法错;
else {
把一个数从栈顶弹出;
advance读下一输入符号;
}
else { /* 栈顶的常数表示一个非终结符 */
what_to_do=Yy_d[栈顶常数][当前输入符号的常数];
if(what_to_do= = -1)
报语法错;
else {
把栈顶元素弹出栈;
把Yy_pushtab[what_to_do]中列出的全部常数压入分析栈;
}
}
}
请实现该程序。

在程序中添加输出栈内容的功能,以便和手工模拟分析过程作比较。

(4)用预测分析器和手工模拟两种方式对文法的句子1+2;进行分析。

综合分析过程可用下表表示。

实验四LR分析
1.实验目的
设计一个LR分析器,实现对表达式语言的分析,加深对LR语法分析方法的基本思想的理解,掌握LR分析器设计与实现的基本方法。

2.实验要求
建立文法及其LR分析表表示的数据结构,设计并实现一个LALR(1)的分析器,对源程序经词法分析后生成的二元式代码流进行分析,如果输入串是文法定义的句子则输出“是”,否则输出“否”。

3.实验内容
(1)文法描述及其LALR(1)分析表
描述表达式语言的文法G如下:
0.S → E
1. E → E+T
2. E → T
3.T → T*F
4.T → F
5. F → (E)
6. F → ID
该文法的LALR(1)分析表如下:
(2)LR分析器总控程序框架如下:
push(0);
advance();
while(Action[tos][sym]!=accept)
if(Action[tos][sym]= =’-’)
error();
else
if (Action[tos][sym]= =SN) {
push(N);
advance();
}
else if(Action[tos][sym]= =RN){
act(N);
pop(产生式N的右部的符号个数);
push(Goto[新tos][ 产生式N的左部符号]); accept();
上述算法中的有关函数与符号的意义如下:
accept() 返回成功状态,LR分析器停止工作
act(N) 执行利用产生式N的归约的动作,通常为产生代码
advance() 丛输入流读下一单词到sym
error() 出错处理
pop(N) 从栈顶弹出N个符号(状态)
push(N) 把状态N压入状态栈
sym 当前输入的单词符号
tos 栈顶状态号
(3)存放LR分析表的数据结构
①实现方法一:用一个二维整数数组表示
数组元素为表示动作的整数。

数组的行下标为状态号,列下标用来表示终结符与非终结符的整数表示。

数组元素可作如下约定:
正整数:表示移进动作,如S6用数6表示;
负整数:表示归约动作,如R5用数-5表示;
0:表示接受,通常为按产生式0归约;
状态号也用整数表示;
用不可能是状态号的较大的整数表示错误转移。

请将上述LALR(1)分析表用这种表示方法,完成LR分析器的程序设计,并添加输出状态栈内容的功能。

用上述表达式文法G的一个句子作为输入,进行测试。

②实现方法二:采用压缩表示法
动作Action表的每一行用一个数组表示,数组的第一个元素是本数组中存放的数偶个数,第二个元素到最后一个元素都以[终结符,动作]的数偶的形式存放。

再用一个以状态号为下标的下标数组,每个元素含一个指向数偶数组的指针。

若数
组元素的值为NULL,则表示从此状态无转移弧发出。

若分析表有几行相同,则只
需保存一行,其它元素则都指向存放这一行表的数组即可。

转移Goto表也按同样
方式组织,只是这个行数组的数偶为[非终结符,下一状态号]。

每个行数组Yyan表示动作表Yy_action的一行,每个行数组Yygn表示转移表Yy_goto的一行。

假定上述表达式文法G中终结符及非终结符的整数值为:终结符: # ID + * ( ) 非终结符: S E T F
整数值: 0 1 2 3 4 5 整数值: 0 1 2 3 Yy_action数组是以状态号为下标的下标数组,每个元素含有指向数组Yyan的指针;下标数组Yy_goto的每个元素含有指向数组Yygn的指针。

表达式文法G的LALR(1)分析表的具体压缩表示如下:
Yy_action
11
Yy_goto
以上分析表用C 语言程序描述如下:
/*
* Yy_action 表
*/
int Yya000[]={2,4,2,1,1};
int Yya001[]={4,5,-6,3,-6,2,-6,0,-6}; int Yya003[]={2,0,0,2,7};
int Yya004[]={4,5,-2,2,-2,0,-2,3,8}; int Yya005[]={4,5,-4,3,-4,2,-4,0,-4};
int Yya006[]={2,5,9,2,7};
int Yya009[]={4,5,-5,3,-5,2,-5,0,-5};
int Yya010[]={4,5,-1,2,-1,0,-1,3,8};
int Yya011[]={4,5,-3,3,-3,2,-3,0,-3};
int Yy_action[]=
{
Yya000, Yya001, Yya000, Yya003, Yya004, Yya005,
Yya006, Yya000, Yya000, Yya009, Yya010, Yya011
};
/*
* Yy_goto表
*/
int Yyg000[]={3,3,5,2,4,1,3};
int Yyg002[]={3,3,5,2,4,1,6};
int Yyg007[]={2,3,5,2,10};
int Yyg008[]={1,3,11};
int Yy_goto[]=
{
Yyg000, NULL, Yyg002, NULL, NULL, NULL,
NULL, Yyg007, Yyg008, NULL, NULL, NULL
};
/*
* 为了进行归约,使用一个Yy_lhs[]数组,其值为代表产生式左部符号的
* 整数,数组的下标为产生式号
*/
int Yy_lhs[7]=
{
/* 0 */ 0,
/* 1 */ 1,
/* 2 */ 1,
/* 3 */ 2,
/* 4 */ 2,
/* 5 */ 3,
/* 6 */ 3
};
/*
* Yy_reduce[]数组元素的值为产生式右部符号的个数,
* 以产生式号为数组的下标索引
*/
int Yy_reduce[]=
{
/* 0 */ 1,
/* 1 */ 3,
/* 2 */ 1,
/* 3 */ 3,
/* 4 */ 1,
/* 5 */ 3,
/* 6 */ 1
};
根据以上数组结构,构造函数Yy_next(),其功能是在给定状态和输入符号下,求出应采取的动作或转向的下一状态。

int Yy_next(table,cur_state,symbol)
int **table; /* 要查的表 */
int cur_state; /* 行号 */
int symbol; /* 列号 */
{
int *p=table[cur_state];
int i;
if(p)
for(i=(int)*p++ ; --i>0 ; p+=2)
if(symbol = = p[0])
return(p[1]);
return YYF; /* 出错指示 */
};
指针p指向Yyan数组或Yygn数组,由参数table的值而定。

如果table指向Yy_action,则p指向Yyan数组;若table指向Yy_goto,则p指向Yygn数组据。

根据上述LALR(1)分析表压缩表示方法,完成LR分析器的程序设计,并添加输出状态栈内容的功能。

用上述表达式文法G的一个句子作为输入,进行测试。

实验五语义分析和中间代码生成
1.实验目的
通过设计一个简单的语义分析和中间代码生成器,加深对语法制导翻译方法的理解,掌握将语法分析所识别的语法范畴变换为中间代码的语义翻译方法。

2.实验要求
采用递归下降语法制导翻译方法,构造语义分析和中间代码生成器,实现对算术表达式、赋值语句、条件语句、循环语句进行语义分析,生成四元式中间代码的序列。

3.实验内容
(1)待分析的语言为C语言的一个子集,其文法用扩充的BNF描述如下:程序→ main( ) 语句块
语名块→′{′语句串′}′
语句串→语句 { ;语句 } ;
语句→赋值语句 | 条件语句 | 循环语句
赋值语句→ ID = 表达式
条件语句→ if (条件)语句块
循环语句→ while(条件)语句块
条件→表达式关系运算符表达式
表达式→项 { + 项 | - 项 }
项→因子 { * 因子 | / 因子 }
因子→ ID | NUM |(表达式)
关系运算符→ < | <= | > | >= | == | !=
上面语名块产生式中的{和}是终结符号,加引号以示区别。

(2)通过为上面文法中的每一个非终结符构造一个递归子程序,设计一个递归下降分析器。

(3)在条件、表达式、赋值语句、条件语句、循环语句的递归子程序中,加入语义分析和生成四元式的代码。

构造下面的语义过程:
int gen(op,arg1,arg2,result)
该函数是将四元式(op, arg1,arg2,result)送到四元式表中。

char * newtemp( )
该函数回送一个新的临时变量名,临时变量名产生的顺序为T1,T2……
int merg(p1,p2)
该函数将以p1和p2为头指针的两条四元式链合并为一条链,合并后的链首为返
回值。

int bp(p,t)
该函数的功能是把指针p所链接的每个四元式的第四区段都填为t。

(4)实验的输入和输出
输入是语法分析提供的正确的单词串,输出是四元式序列。

例如,对于语句串
i = 2 * 3 + 4;
if ( i>8 ) j = 10;
while( j>0){
k = k + 1;
j = j - 1;
}
输出的四元式序列如下:
1.(*,2,3,T1)
2.(+,T1,4,T2)
3.(=,T2,,i)
4.(j>,i,8,6)
5. (j,,,7)
6.(=,10,,j)
7.(j>,j,0,9)
8.(j,,,14)
9.(+,k,1,T3)
10.(=,T3,,k)
11.(-,j,1,T4)
12.(=,T4,,j)
13.(j,,,7)
14.……
实验六基于DAG的基本块优化
1.实验目的
了解基本块的DAG表示及其应用,掌握局部优化的基本方法。

2.实验要求
设计一个转换程序,把由四元式序列表示的基本块转换为DAG,并在构造DAG的过程中,进行合并已知量、删除无用赋值及删除公共子表达式等局部优化处理。

最后再从所得到的DAG出发,按原来生成DAG各个结点的顺序,重建四元式序列形式的基本块。

3.实验内容
(1)DAG的结点类型只考虑0型、1型和2型,如下表所示。

(2)由基本块构造DAG算法如下:
while(基本块中还有未处理过的四元式) {
取下一个四元式Q;
newleft=newright=0;
if(getnode(B)= =NULL){
makeleaf(B);
newleft=1;
}
switch(Q的类型){
case 0 : n= getnode(B);
insertidset(n,A);
break;
case 1: if(isconsnode(B)){
p=calcons(Q.op,B);
if(newleft= =1) /* getnode(B)是处理Q时新建结点 */
delnode(B);
if((n=getnode(p))= =NULL){
makeleaf(p);
n=getnode(p);
}
} else{
if((n=findnode(Q.op,B))= =NULL)
n=makenode(Q.op,B);
}
insertidset(n,A);
break;
case 2: if(getnode(C)= =NULL){
makeleaf(C);
newright=1;
}
if(isconsnode(B) && isconsnode(C)){
p=calcons(Q.op,B,C);
if(newleft= =1) /* getnode(B)是处理Q时新建结点 */
delnode(B);
if(newright= =1) /* getnode(C)是处理Q时新建结点 */
delnode(C);
if((n=getnode(p))= =NULL){
makeleaf(p);
n=getnode(p);
}
} else{
if((n=findnode(Q.op,B,C))= =NULL)
n=makenode(Q.op,B,C);
}
insertidset(n,A);
break;
}
}
上述算法中应设置如下的函数:
getnode(B) :返回B(可以是标记或附加信息)在当前DAG中对应的结点号。

makeleaf(B):构造标记为B的叶子结点。

isconsnode(B):检查B对应的结点是否为标记为常数的叶子结点。

calcons(Q.op,B):计算op B 的值(即合并已知量)。

它的另一种调用形式是 calcons(Q.op,B,C),计算B op C 的值。

delnode(B):删除B(结点的标记)在当前DAG中对应的结点。

findnode(Q.op,B):在当前DAG中查找并返回这样的结点:标记为op,后继
为getnode(B)(即查找公共子表达式op B)。

它的另一种调用
形式是findnode (Q.op,B,C) (即查找公共子表达式B op C)。

makenode(Q.op,B,C):构造并返回标记为op,左右后继分别为getnode(B)、
getnode(C)的内部结点。

insertidset(n,A):若getnode(A)=NULL,则把A附加到结点n;否则,若A
在getnode(A)的附加标识符集中,且getnode(A)无前驱或虽
有前驱但getnode(A) 附加标识符集中符号数大于1,则把A
从getnode(A)的附加标识符集中删除(即删除无用赋值)。

请实现上述基本块的DAG构造算法,并添加从所得DAG按原来生成DAG各个结点的顺序,重建四元式序列的功能。

(3)测试用例
用下面的基本块作为输入:
(1) T1 = A * B
(2) T2 = 3 / 2
(3) T3 = T1 ― T2
(4) X = T3
(5) C = 5
(6) T4 = A * B
(7) C = 2
(8) T5 = 18 + C
(9) T6 = T4 * T5
(10) Y = T6
基本块的DAG如下:
⑤T3,X

⑨T6,Y
③T1,T4 *
*
①②④T2⑧T5⑥⑦C
A B 1.5 20 5 2
按生成DAG各个结点的顺序,重建四元式序列如下:
(1) T1 = A * B
(2) T2 = 1.5
(3) T3 = T1 ― 1.5
(4) X = T3
(5) T4 = T1
(6) C = 2
(7) T5 = 20
(8) T6 = T1 * 20
(9) Y = T6。

相关文档
最新文档