编译原理实验手册15版.docx
《编译原理实验手册15版.docx》由会员分享,可在线阅读,更多相关《编译原理实验手册15版.docx(25页珍藏版)》请在冰豆网上搜索。
编译原理实验手册15版
兰州大学计算机科学与技术专业
编译原理实验手册(V1.5)
第一节概述2
一、实验目的2
二、实验内容2
三、PASCAL语言子集的文法3
四、实验要求:
6
第二节词法分析7
一、目的与要求7
二、设计步骤7
三、扩充8
第三节语法分析9
一、目的与要求9
二、设计步骤9
三、程序流程示例:
10
四、扩充12
第四节语义分析和翻译13
一、目的与要求13
二、设计步骤13
三、算法示例14
四、建议生成的四元式统一采用如下的形式:
15
五、扩充16
附录一算符优先分析算法示例17
附录二递归下降分析算法示例18
附录三Pascal程序示例20
第一节概述
一、实验目的
编译原理是一门实践性很强的课程,但由于课时所限,只能在课堂上讲授一些通用的原理和方法。
而为了真正学好这门课程,必须自己动手构造出一个编译器,才能对书里讲到的原理、方法和技术有较全面的体会,才能对学生以后的程序设计和解决实际问题的能力有所帮助。
实际的编译程序是十分复杂的,有时由多达十几万条指令组成。
为此,编译原理的实践教学,采用简化编译过程的办法,选择最关键的三个环节──词法分析、语法分析、语义分析和中间代码产生,每个环节作为一个实践课题,逐步深入,扩展功能,直至得到一个简单实用的编译器。
本实验不涉及到优化。
二、实验内容
任何一个实用的高级语言,其语法都比较复杂,如选其作为源语言,很难实践全过程。
故本试验将定义一个简化的语言──PASCAL语言的一个子集作为源语言,分三个课题、一步步地构造出它的编译程序。
所有试验项目前后贯穿这一条主线进行。
本实验共进行6周,每周3学时,共18学时。
本实验主要包括以下三个课题:
Ø词法分析:
以源程序为输入,输出单词符号流;
Ø语法分析:
以源语言的文法为依据,调用词法分析器,使用递归下降分析法或算符优先分析法或SLR
(1)分析法,构造能识别源语言各种语法结构的语法分析器;
Ø语义分析和中间代码产生:
使用语法制导翻译技术,对源语言程序进行简单的翻译,输出四元式序列。
在本节的第三部分给出了两个PASCAL语言的子集的文法,对这些文法稍加变换,即可获得用于语法分析的LL
(1)文法或SLR
(1)文法。
学生可以直接选择一个作为编译器的源语言,也可以对这些文法进行改造,以获得能力更为强大的源语言。
学生也可以自己设计源语言,来完成这些题目;唯一的要求是源语言必须包含三种基本的程序设计结构(顺序、选择、循环)和至少两种不同的数据类型。
本实验要求:
Ø所有的输入输出均采用文件形式。
Ø独立完成。
Ø语言不限,开发工具不限;但必须有可运行的程序和规范的注释。
三、PASCAL语言子集的文法
由于Pascal语言结构严谨,层次清晰,语法与C语言接近,也便于理解,因此本实验抽取Pascal语言的一个子集,稍加改造,作为源语言,姑且命名为LittleP。
一个LittleP程序由一系列全局数据声明和一个主程序体组成。
所有数据采用静态存储分配,没有I/O,只支持一种基本数据类型:
无符号整数。
1.LittleP的文法:
〈程序〉→〈程序首部〉〈程序体〉.
〈程序首部〉→program〈程序名〉;
〈程序体〉→〈变量声明〉〈复合语句〉
〈变量声明〉→var〈变量定义列表〉|〈空〉
<变量定义列表>→〈变量定义列表〉;〈变量定义〉|〈变量定义〉
〈变量定义〉→〈变量名列表〉:
<类型>
<变量名列表>→〈变量名列表〉,〈变量名〉|〈变量名〉
<类型>→integer
〈复合语句〉→begin〈语句块〉end
〈语句块〉→〈语句〉|〈语句块〉;〈语句〉
〈语句〉→〈赋值语句〉|〈条件语句〉|〈循环语句〉|〈复合语句〉|〈空〉
〈赋值语句〉→〈左部〉:
=〈右部〉
〈左部〉→〈变量名〉
〈右部〉→〈算术表达式〉
〈条件语句〉→if(〈关系表达式〉)then〈语句〉else〈语句〉
〈循环语句〉→while(〈关系表达式〉)do〈语句〉
<关系表达式>→〈算术表达式〉〈关系运算符〉〈算术表达式〉
<算术表达式>→〈项〉|〈算术表达式〉〈加运算符〉〈项〉
<项>→〈因子〉|〈项〉〈乘运算符〉〈因子〉
〈因子〉→〈变量名〉|(〈算术表达式〉)|〈整数〉
〈程序名〉→〈标识符〉
〈变量名〉→〈标识符〉
〈标识符〉→〈字母〉|〈标识符〉〈字母〉|〈标识符〉〈数字〉
〈整数〉→〈数字〉|〈整数〉〈数字〉
〈关系运算符〉→<|<=|>|>=|=|<>
〈加运算符〉→+|-
〈乘运算符〉→*|/
〈字母〉→a|b|…|x|y|z
〈数字〉→1|2|3|4|5|6|7|8|9|0
2.在此基础上加以扩充,可得功能较强的一个LittleP语言的超集:
LittleP+。
该语言引入了布尔型、一维数组和过程、函数的定义,参数传递采用传值方式。
另外,加入了I/O支持,编译器提供两个系统函数:
read()和write()。
〈程序〉→〈程序首部〉〈程序体〉.
〈程序首部〉→program〈程序名〉;
〈程序体〉→〈变量声明〉<分程序声明>〈复合语句〉
〈变量声明〉→var〈变量定义列表〉|〈空〉
<变量定义列表>→〈变量定义列表〉;〈变量定义〉|〈变量定义〉
〈变量定义〉→〈变量名列表〉:
<类型>
<变量名列表>→〈变量名列表〉,〈变量名〉|〈变量名〉
<类型>→<基本类型>|<数组>
<基本类型>→integer|boolean
<数组>→array[〈下界〉..〈上界〉]of<基本类型>
<下界>→<整数>
<上界>→<整数>
<分程序声明>→〈分程序〉〈分程序声明〉|〈空〉
〈分程序〉→〈分程序首部〉〈变量声明〉〈复合语句〉
〈分程序首部〉→procedure〈过程名〉(<形参列表>);
|function〈函数名〉(<形参列表>):
<基本类型>;
<形参列表>→〈形参定义〉,〈形参列表〉|〈形参定义〉|〈空〉
〈形参定义〉→〈变量名〉:
<基本类型>
〈复合语句〉→begin〈语句块〉end
〈语句块〉→〈语句〉|〈语句块〉;〈语句〉
〈语句〉→〈赋值语句〉|〈条件语句〉|〈循环语句〉
|〈过程调用语句〉|〈复合语句〉|〈读写语句〉|〈空〉
〈赋值语句〉→〈左部〉:
=〈右部〉
〈左部〉→〈变量名〉|〈变量名〉[算术表达式]
〈右部〉→〈算术表达式〉
〈条件语句〉→if(〈逻辑表达式〉)then〈语句〉else〈语句〉
〈循环语句〉→while(〈逻辑表达式〉)do〈语句〉
〈过程调用语句〉→〈过程名〉(<实参列表>)
|〈函数名〉(<实参列表>)
<实参列表>→〈算术表达式〉|〈算术表达式〉,〈实参列表〉|〈空〉
〈读写语句〉→read(<变量名列表>)|write(<变量名列表>)
<逻辑表达式>→〈逻辑项〉|〈逻辑项〉〈逻辑运算符〉〈逻辑项〉
<逻辑运算符>→and|or
<逻辑项>→<关系表达式>|〈变量名〉|not<逻辑项>|true|false
<关系表达式>→〈算术表达式〉〈关系运算符〉〈算术表达式〉
<算术表达式>→〈项〉|〈算术表达式〉〈加运算符〉〈项〉
<项>→〈因子〉|〈项〉〈乘运算符〉〈因子〉
〈因子〉→〈变量名〉|(〈算术表达式〉)|〈函数名〉(<实参列表>)
|〈变量名〉[算术表达式]|〈整数〉
〈程序名〉→〈标识符〉
〈变量名〉→〈标识符〉
〈过程名〉→〈标识符〉
〈函数名〉→〈标识符〉
〈标识符〉→〈字母〉|〈标识符〉〈字母〉|〈标识符〉〈数字〉
〈整数〉→〈数字〉|〈整数〉〈数字〉
〈关系运算符〉→<|<=|>|>=|=|<>
〈加运算符〉→+|-
〈乘运算符〉→*|/
〈字母〉→a|b|…|x|y|z
〈数字〉→1|2|3|4|5|6|7|8|9|0
3.对源程序语法的其他说明:
∙出现在{}里的所有字符作为注释跳过。
∙各单词符号之间的空格可有可无,但关键字和标识符必须分隔开来。
∙分号作为语句之间的分割符;逗号作为语句内的分割符;一个完整的程序以点号作为结束符号。
∙过程没有返回值,只能出现在过程调用语句中;
函数有且只有1个返回值,只能出现在算术表达式中或作为赋值语句的右部。
函数返回值通过函数名带回,因此在函数体内必须给函数名赋值。
∙标识符的长度不得超过8个字符。
∙关键字保留,不得用做标识符。
∙read和write以缺省库函数的形式实现,对stdin和stdout进行读写。
四、实验要求:
每个课题完成后写出实验报告。
实验报告应该包括:
∙程序设计时考虑的算法和主要的数据结构;
∙可执行的程序
∙至少2个测试用例,包括:
▪至少1个合法的源程序及其运行结果;
▪至少1个非法的源程序及其错误报告。
第二节词法分析
一、目的与要求
1.目的
通过设计、调试词法分析程序,实现从源程序中分离出各种单词的方法;加深对课堂教学的理解,尤其是对正规式、有穷自动机的原理和用途的理解;为以后软件开发过程中设计高效率的扫描器打下基础。
2.要求
∙应有适当的预处理。
∙输入源程序,输出定长单词符号流,均采用文件形式。
∙针对选定的源语言,构造识别其合法单词符号的词法分析器。
∙应考虑到后续阶段的需要,合理设计词法分析器的结构。
∙本实验应在一周内完成。
二、设计步骤
1.问题分析:
●分析源语言的文法,找出各种词法单位的构词规则。
●工作流程:
构词规则→正规式→NFA→DFA→状态转换图→程序;
构词规则→状态转换图→程序。
●预处理:
有哪些预处理工作要做。
2.总体设计:
●输入输出缓冲区,输出格式(等长二元式序列);
●表格设计(设计几张表,每张表登记什么信息):
✓关键字表
✓算符、分隔符表
✓变量表:
简单变量、数组、过程与函数
●出错处理。
3.程序流程设计:
明确程序所使用的主要算法,一般以伪代码或程序流程图表示。
图1给出了一个程序流程图的例子,作为参考。
4.编码与测试:
编写程序并调试通过,然后自己设计至少3个测试用例。
测试用例包括两部分:
输入(源程序代码)和预期结果(运行结果或错误信息)。
5.编写实验报告。
图1词法分析程序流程图
三、扩充
有余力的同学,可适当扩大分析对象。
譬如:
1.加入更多的数据类型,如:
字符型和字符串型的常量、变量。
2.加入一元算术运算符,如:
取负运算或自增自减运算。
第三节语法分析
一、目的与要求
1.目的
通过设计、编写、调试一个典型的语法分析程序,实现对词法分析程序所提供的单词序列进行语法分析和检查,进一步掌握常用的语法分析方法的实现技术。
2.要求
∙调用词法分析器,分析源程序的语法结构(识别出各种语法结构),指出源程序中是否含有语法错误。
∙推荐使用算符优先分析算术表达式,用递归子程序法分析其他各种语法结构,例如各种语句等。
也可以使用LR分析法完成这些工作。
∙输入文本文件形式的源程序;输出分析结果(正确与错误)到文件。
∙本课题应在两周内完成。
因时间紧张,建议采用增量开发模式:
从简单到复杂、能力逐渐增强。
二、设计步骤
1.问题分析:
✓使用哪种语法分析方法?
单独使用递归下降分析法可以完成任务吗?
✓从文法中提炼出主要的语法结构,如:
程序、变量声明、分程序声明、复合语句、IF语句、算术表达式等,认真分析他们的语法结构。
✓如何利用前面构造的词法分析器和相关表格?
✓考虑错误处理的方法。
2.总体设计:
✓对文法进行必要的等价变换,使之符合所选语法分析方法的要求。
✓若有必要,对上次实验后得到的词法分析器及相关符号表进行调整。
✓针对产生算术表达式(关系表达式)的文法,构造算符优先关系表,在此基础上,编写一小段程序,分析这类语法结构。
(算法示例见附录一)
✓针对各种语法结构,逐一产生其递归下降分析的状态转换图,再一一翻译为程序段。
(算法示例见附录二)
✓设计输出的形式。
✓将前面的工作组合起来,形成一个完整的语法分析器。
3.程序流程设计。
4.编码和测试:
编写代码,调试通过,并设计测试用例。
5.编写实验报告。
三、程序流程示例:
题目:
递归下降法分析表达式(此题目仅供参考,而且不完全准确)
1.分析对象的BNF定义如下:
〈算术表达式〉→〈项〉|〈算术表达式〉+〈项〉|〈算术表达式〉-〈项〉
〈项〉→〈因式〉|〈项〉*〈因式〉|〈项〉/〈因式〉
〈因式〉→〈变量〉│(〈算术表达式〉)
〈变量〉→〈字母〉
〈字母〉→A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|T|U|V|W|X|Y|Z
⒉用递归下降法分析上述算术表达式的框图,如图2所示。
(a)
(b)(c)
(d)
(e)(f)
图2递归下降法分析表达式之框图
(a)ZC过程;(b)E过程;(c)T过程;
(d)F过程;(e)函数过程SYM;(f)过程ADVANCE
这里,ZC过程为总控程序,主要完成:
⑴通知外界键入算术表达式;
⑵控制E过程分析算术表达式;
⑶根据分析结果之正误,分别通知外界不同的信息。
ZC过程被设计成可以分析无穷多个算术表达式。
E、T和F三个过程分别对应〈算术表达式〉、〈项〉和〈因式〉三个产生式的处理。
它们用到两个公共过程。
一个是函数过程SYM,它负责从输入字符串ST中取出下一个字符,并存入SYM中等待分析。
另一个过程ADVANCE负责剔除ST中的首字符。
四、扩充
有余力的同学,可适当扩大分析对象。
譬如:
1.加入for循环语句:
fori:
=1to5dobegindosomething()end;
2.引入二义性文法:
stmt→ifcondthenstmtelsestmt|ifcondthenstmt
3.加强语法检查,尽量多和确切地指出各种错误。
第四节语义分析和翻译
一、目的与要求
1.目的
通过上机实习,加深对语法制导翻译和运行时存储空间分配的理解,掌握将语法分析所识别的语法范畴变换为某种中间表示的语义翻译方法。
2.要求
●采用语法制导翻译方法。
●实现简单的静态语义检查。
●将通过检查的源程序翻译成中间表示。
●中间表示可以选用:
三元式、四元式或抽象语法树。
●以两周内完成为宜。
二、设计步骤
1.问题分析:
✓要做哪些静态语义检查工作,可以从以下几点来考虑:
i.标识符必须先说明,再使用;
ii.同一作用域内不得重复定义(不能重名);
iii.操作数与操作符的类型匹配
a)定义在integer上的运算:
+-*/和关系运算符;
b)定义在boolean上的运算:
notorand;
c)赋值运算符:
=两端的类型应该相同。
iv.还可以考虑数组下标越界和函数的形、实参匹配的问题;
v.对于不满足上述规则的,应给出错误提示;
vi.检查工作可以安排成单独一遍:
在语法分析之后,翻译之前;也可以“语法、检查、翻译”三项工作一遍完成,把语义检查和翻译的代码插入语法分析过程之中。
✓如何把语法分析和语义分析结合起来(把语义子程序加入语法分析中);
✓如何设计语义子程序来产生中间表示:
∙声明语句:
只操作符号表,不翻译代码;
∙数组元素:
地址计算;
∙算术表达式及赋值:
直接翻译;
∙条件语句:
▪先求值(同算术表达式)再判断;按照短路法则判断;
▪否定判断:
源代码中的条件是否不成立?
▪跳转目标:
拉链返填
∙循环语句:
同上;
∙子程序结构:
先传参,再转移;
∙读写语句:
特殊处理,可参照C语言的库函数方式。
2.总体设计:
✓对文法进行必要的等价变换,并为每条产生式添加语义规则。
✓将语义规则转换成语义子程序,生成中间表示;
✓分析各种语法结构,把语义子程序加入到语法分析代码的合适位置;
✓把四元式列表输出到文件。
3.程序流程设计:
核心是语义子程序的设计。
4.编码与测试。
5.编写实验报告。
三、算法示例
⒈题目:
在对简化的算术表达式进行语法分析的同时生成四元式(仅作参考)。
⒉四元式生成程序的核心部分(指表达式、项和因式的处理)的算法,可描述如下:
PROCEDUREE;
BEGIN
E1PLACE:
=T;
WHILESYM='+'OR'-'DO
BEGIN
ADVANCE;
E2PLACE:
=T;
T1:
=NEWTEMP;
GEN(±,E1PLACE,E2PLACE,T1);
E1PLACE:
=T1
END;
RETURN(E1PLACE)
END;
PROCEDURET;
BEGIN
T1PLACE:
=F;
WHILESYM='*'OR'/'DO
BEGIN
ADVANCE:
T2PLACE:
=F;
T1:
=NEWTEMP;
GEN(*/,T1PLACE,T2PLACE,T1);
T1PLACE:
=T1
END;
RETURN(T1PLACE)
END;
PROCEDUREF:
BEGIN
IFSYM=标识符THEN
BEGIN
ADVANCE;
RETURN(ENTRY(i))
END
ELSE
IFSYM='('THEN
BEGIN
ADVANCE;
PLACE:
=E;
IFSYM=')'THEN
BEGIN
ADVANCE;
RETURN(PLACE)
END
ELSEERROR
END
ELSEERROR
END.
这里:
E──表达式;T──项;F──因子;
ADVANCE──将输入串指针调整至指向下一个输入字符;
NEWTEMP──分配一个新的工作单元;
GEN──将一个四元式填入四元式表;
ENTRY──查找变量名表,并获得名字所在位置值。
四、建议生成的四元式统一采用如下的形式:
Ø赋值:
⏹形如x:
=y的赋值语句,x,y采用内存直接寻址。
⏹形如x:
=y[i]和y[i]:
=x的赋值语句。
y是基址,i是偏移量。
Ø控制流程转移:
⏹无条件跳转gotoL,L是接下来要执行的四元式的编号。
⏹条件跳转jumpxL,若x为真,则跳转至L所指的四元式;
否则顺序执行。
x指向内存中某个boolean型的值。
Ø过程调用:
先传参,后转子。
⏹传参数paramx,有n个参数就生成n条传参数四元式;
⏹过程调用callp,n,其中:
p是过程或函数名,n表示参数的个数;
⏹返回值returny。
五、扩充
1.在C语言中数组名是一个常量,代表数组的首地址;而Pascal语言中数组名代表一个数组变量,因而可以用以下用法:
定义a,b:
array[-7..16]ofboolean;
引用a:
=true;b:
=a;
考虑实现这种用法。
(深层次考虑,每种工具中一些东西的实现方法,找出各种工具的优势)
2.熟悉汇编的同学,也可以考虑一步到位,直接从源代码翻译成某种汇编。
附录一算符优先分析算法示例
Treeterm2Rest(Treet,intminprec){//minprecisthelowestprecedenceofallbinaryoperators.
Tree[]odStack=newOdStack();//stackofoperands;
int[]opStack=newOpStack();//stackofoperators.
inttop=0;//toppointerofstack.
odStack[0]=t;
intstartPos=S.pos;//Sisascanner.Itreturnsatokenanditspositioneverytime.
//topOpisalwaysthetopelementofopStack.Itsinitialvalueis‘error’.
inttopOp=ERROR;
while(prec(S.token)>=minprec){
//移进
opStack[top]=topOp;
top++;
topOp=S.token;
intpos=S.pos;
S.nextToken();
odStack[top]=term();//term()分析表达式中的“项”
//规约
while(top>0&&prec(topOp)>=prec(S.token)){
//Youcandosomethinghere,suchascreatingasyntaxtreeorcalculatingthevalue.
odStack[top-1]=makeop(pos,topOp,odStack[top-1],odStack[top]);
top--;
topOp=opStack[top];
}
}
......
}
#
附录二递归下降分析算法示例
SyntaxTree*Parser:
:
Statement(){
SyntaxTree*tree=NULL;
switch(currentToken.type){
......
caseID:
this->nextToken();
tree=Assign();
if(tree!
=NULL){
tree->addLeft(ID);
}
break;
caseWHILE:
this->nextToken();
tree=While();
break;
caseBEGIN:
this->nextToken();
tree=Block();
if(currentToken.type!
=END){
tree=NULL;
this->printError("ERROR!
beginwithoutend");
}
this->currentToken.type=SEMI;
break;
caseIF: