96778997973编译原理实验指导书新.docx
《96778997973编译原理实验指导书新.docx》由会员分享,可在线阅读,更多相关《96778997973编译原理实验指导书新.docx(21页珍藏版)》请在冰豆网上搜索。
96778997973编译原理实验指导书新
前言
编译原理是计算机科学与技术、软件工程等专业的主干课和必修课,由于这门课程相对抽象且内容较复杂,一直是比较难学的一门课程。
在编译原理的学习过程中,实验非常重要,只有通过上机实验,才能使学生对比较抽象的课程内容产生一个具体的感性认识。
但是,目前国内市场上很少有较详细且比较适合我院实际的实验指导书,为此,我们特编了这份指导书,希望能对我院的《编译原理》教案工作有所帮助。
本书实验环境主要为C环境(由于兼容性问题,建议使用Turboc2.0>及一个词法分析器自动生成工具FLEX和一个语法分析器自动生成工具BISON。
书中给出的参考源程序也是C源程序,但由于实验者熟悉精通的语言工具不尽相同,因而强求采用统一的编程语言编程是不现实的。
实验者在掌握了编译程序各个阶段的功能和原理之后,不难借助使用其他自己熟悉的语言实现相关功能。
实验者在实验过程中应该侧重写出自己在算法分析、设计思路、实现功能或程序代码等方面的特色,写出设计和实现过程中遭遇到的难点和解决办法,可以不拘泥于实验指导给出的参考性设计思路,尽可能在深度和广度上加以拓展。
只有这种各具特色的实验报告,才将更有利于体现实验者在创新思维和动手能力上的差异。
通过这些实验,能使学生对这些部份的工作机理有一个详细的了解,达到“知其然,且知其所以然”的目的。
并可在C环境下对自动生成工具生成的词法、语法分析器进行编译调试。
由于手工生成词法和语法分析器的工作量太大,在实际中常用自动生成工具来完成之。
这些工具中最著名的当属贝尔实验室的词法分析器生成工具LEX和语法分析器生成工具YACC。
它们现已成为UNIX的标准应用程序同UNIX一起发行。
与此同时GNU推出与LEX完全兼容的FLEX,与YACC完全兼容的BISON。
这两个程序都在Internet上以源代码的形式免费发行,所以很容易在其它操作系统下重新编译安装。
我们实验采用的就是fordos的FLEX和BISON。
本书有关的编译工具及其源程序例子,可到BISON的网站上下载。
关于FLEX和BISON的用法简介,参见附录,如需更详细的介绍,请参阅编译工具中帮助文件。
关于实验学时和安排,任课教师可根据实际情况,选做其中的一部份。
由于这门课实验难度较大,所以希望任课教师在实验前安排好学生的预习工作。
在上机前要求学生写好实验预习报告。
本书中c程序均在Turboc2.0下调试通过.LEX和YACC源程序均在FLEX和BISON下调试通过.
由于编者水平有限,本书中必然存在着不少缺点,在此恳请大家给予批评和指正,我们将尽力纠正。
在此特对关心支持编写本书的院系领导表示感谢。
本书中关于LEX和YACC的部份大量参考引用了何炎祥老师主编,华中理工大学出版社出版的《编译原理》一书,在此表示衷心的感谢。
周鹏 杨亚会梅琴赵榕
2006年8月
实验一 词法分析器设计
【实验目的】
1.掌握生成词法分析器的方法,加深对词法分析原理的理解。
2.掌握设计、编制并调试词法分析程序的思想和方法。
3.本实验是高级语言程序设计、数据结构和编译原理中词法分析原理等知识的综合。
【实验内容及要求】
1.选择一种熟悉的高级语言<如C语言,C++,VB或VC等),设计、编写、调试一个词法分析子程序。
2.待分析的源程序为一个简单的C语言程序,如下所示:
main(>
{intx,a,b。
floaty,c,d。
x=a+b。
y=c/d。
if(x>y>
x=10。
else
y=100。
}
将该源程序的源文件经词法分析后输出以二元组形式表示的单词符号序列。
3.编写的程序具有一定的查错能力。
提交的实验报告中要有实验名称、实验目的、实验内容、实验程序清单、调试过程和运行结果,程序的主要部分作出功能说明,并有实验收获体会或改进意见等内容。
4.实验前请仔细阅读实验预习提示,提示中程序仅供参考。
5.本实验建议学时数为4学时。
【实验预习提示】
1.词法分析器的功能和输出格式
词法分析器的功能是输入以字符串表示的源程序,输出单词符号或单词符号序列。
词法分析器的单词符号常常表示成以下的二元组(单词的种别码,单词符号的属性值>。
本实验中,采用的是一符一种种别码的方式。
2.调试程序文法的EBNF<扩展巴科斯范式)表示如下
<程序>:
:
=main(><语句串>
<语句串>:
:
=<语句>{;<语句>}
<语句>:
:
=<赋值语句>|<条件语句>|<循环语句>
<赋值语句>:
:
=<变量>=<表达式>
<条件语句>:
:
=if<条件>then<语句>
<循环语句>:
:
=while<条件><语句串>
<条件>:
:
=<表达式><关系运算符><表达式>
<表达式>:
:
=<项>{+<项>|—<项>}
<项>:
:
=<因子>{*<因子>|/<因子>}
<因子>:
:
=<变量>|<无符号整数>|<表达式>
<无符号整数>:
:
=<数字>{<数字>}
<变量>:
:
=<标识符>
<标识符>:
:
=<字母>{<字母>|<数字>|<下划线>}
<关系运算符>:
:
=>|<|>=|<=|==|!
=
<字母>:
:
=A|B|C|……|Z|a|b|c|……|z(要求不区分大小写>
<数字>:
:
=0|1|2|3|4|5|6|7|8|9
3.“超前搜索”方法
词法分析时,常常会用到超前搜索方法。
如当前待分析字符串为“a>+”,当前字符为’>’,此时,分析器倒底是将其分析为大于关系运算符还是大于等于关系运算符呢?
显然,只有知道下一个字符是什么才能下结论。
于是分析器读入下一个字符’+’,这时可知应将’>’解释为大于运算符。
但此时,超前读了一个字符’+’,所以要回退一个字符,词法分析器才能正常运行。
在分析标识符,无符号整数等时也有类似情况。
4.词法分析程序主程序的算法思想
算法的基本思想是根据扫描到单词符号的第一个字符的种类,拼写出相应的单词符号,其实
现的基本任务是从字符串表示的源程序中识别出相应的具有独立意义的单词符号。
主程序示
意图如图1.1所示。
图1.1词法分析主程序示意图
其中设置初始值包括两个方面:
<1)关键字表的初值
关键字作为特殊标识符处理,把它们预先安排在一张表中,称这张表为关键字表,当扫描程序
识别出标识符时,查关键字表,若能查找到匹配的单词,则该单词是关键字,否则为一般标识
符。
关键字表为一个字符串数组,其描述为:
char*KEY_WORDS[8]={“main”,”int”,”char”,”if”,”else”,”while”,”for”}。
<2)程序中需要用到的主要变量为syn,token和sum。
5.参考部分源程序
[1]globals.h/*本头文件定义分析器需要的一些数据结构和宏等*/
#ifndef_GLOBALS_H
#define_GLOBALS_H
#include
#include
#include
#define_SYN_MAIN1
#define_SYN_INT2
#define_SYN_CHAR3
#define_SYN_IF4
#define_SYN_ELSE5
#define_SYN_FOR6
#define_SYN_WHILE7
/*以上为关键字的单词种别码*/
#define_SYN_ID10/*标识符的单词种别码*/
#define_SYN_NUM11/*整数的单词种别码*/
#define_SYN_PLUS13/*算术运算符“+”的单词种别码*/
#define_SYN_MINUS14/*算术运算符“-”的单词种别码*/
#define_SYN_TIMES15/*算术运算符“*”的单词种别码*/
#define_SYN_DIVIDE16/*算术运算符“/”的单词种别码*/
#define_SYN_ASSIGN17/*=*/
#define_SYN_SEMICOLON18/*。
*/
#define_SYN_LT20/*<*/
#define_SYN_NE21/*!
=*/
#define_SYN_LE22/*<=*/
#define_SYN_LG23/*>*/
#define_SYN_ME24/*>=*/
#define_SYN_EQ25/*=*/
#define_SYN_LPAREN28/*(*/
#define_SYN_RPAREN29/*>*/
#define_SYN_OVER0/*#即源程序结束标志*/
#defineMAXLENGTH255/*一行允许的字符个数*/
unionWORDCONTENT{/*存放单词内容的联合*/
charT1[MAXLENGTH]。
intT2。
charT3。
}。
typedefstructWORD{/*单词二元组*/
intsyn。
unionWORDCONTENTvalue。
}WORD。
#endif
[2]scan.h/*本头文件定义词法分析器的接口*/
#ifndef_SCAN_H
#define_SCAN_H
#define_TAB_LEGNTH4/*一个TAB占用的空格数*/
#define_KEY_WORD_END"waitingforexpanding"/*关键字结束标志*/
voidScaner(void>。
/*函数scaner得到源程序里的下一个单词符号*/
#endif
[3]Symbol.c
#include"basedata.h"
#include"symbol.h"
#include
#include
#include
char*WORD[WORDLEN]={"BEGIN","CALL","CONST","DO",
"END","IF","ODD","PROCEDURE",
"READ","THEN","VAR","WHILE",
"WRITE"
}。
//保留字字符串表,用于将保留字种别码转为字符串输出
SYMBOLWSYM[WORDLEN]={BEGINSYM,CALLSYM,CONSTSYM,
DOSYM,ENDSYM,IFSYM,ODDSYM,
PROCSYM,READSYM,THENSYM,
VARSYM,WHILESYM,WRITESYM}。
//保留字种别码表
char*SNAME[SYMBOLNUM]=
{"NOL","IDENT","NUMBER","PLUS","MINUS","TIMES",
"SLASH","ODDSYM","EQL","NEQ","LSS","LEQ","GTR",
"GEQ","LPAREN","RPAREN","COMMA","SEMICOLON",
"PERIOD","BECOMES","BEGINSYM","ENDSYM","IFSYM",
"THENSYM","WHILESYM","WRITESYM","READSYM",
"DOSYM","CALLSYM","CONSTSYM","VARSYM","PROCSYM"}。
//单词字符串表,用于将保留字种别码转为字符串输出
SYMBOLsym。
//最近已识的单词种别码
chartoken[MAXIDLEN+1]。
//最近已识别的单词
intnum。
//最近已识别的数字值
charch。
//最近已识别的字符
intcol=1,row=1。
//当前行和列值
FILE*fd。
//指向待编译文件
externFILE*fout。
//指向存放结果文件
voidGetchar(void>
{
ch=fgetc(fd>。
if(ch!
=EOF&&ch!
='\n'>
col++。
return。
}
voidGetbc(void>
{
while(ch==SPACE||ch==TABLE||ch=='\n'>
{
if(ch=='\n'>{row++。
col=1。
}
Getchar(>。
}//为空字符则一直读至不为空字符
}
voidRetract(void>
{
fseek(fd,-1l,SEEK_CUR>。
col--。
}
voidConcat(void>
{
chartemp[2]。
temp[0]=ch。
temp[1]='\0'。
strcat(token,temp>。
}
intReserve(void>
{
inti,j。
chartemp[60]。
j=strlen(token>。
for(i=0。
ii++>
{
temp[i]=toupper(token[i]>。
//将当前token字以大写形式存入temp中
}
temp[i]='\0'。
for(i=0。
ii++>
{
if(!
strcmp(WORD[i],temp>>
break。
}//判断当前token是否是保留字
if(i>=WORDLEN>i=-1。
returni。
}
voidErrorsym(void>
{
fprintf(fout,
"Thereiserror@row:
%5d,@col:
%5d",
row,col>。
}
intGetsym(void>
{
intk。
intflag=TRUE。
Getchar(>。
Getbc(>。
//滤掉白字符
strcpy(token,"">。
if(isalpha(ch>>
{
//以字母开头则是标识符
num=0。
Concat(>。
Getchar(>。
while(isalnum(ch>>
{
Concat(>。
Getchar(>。
}
Retract(>。
//由于超前搜索了,所以回退一个字符
k=Reserve(>。
//判断此标识符是否是保留字
if(k!
=-1>
{
sym=WSYM[k]。
//将保留字种别码存入sym中
}
else
{
sym=IDENT。
//将一般标识符种别码存入sym中
}//endelsek!
=-1。
}//endofifisalpha
elseif(isdigit(ch>>
{
//以数字开头则为无符号整数
Concat(>。
Getchar(>。
while(isdigit(ch>>
{
Concat(>。
Getchar(>。
}
if(isalpha(ch>>
{
flag=FALSE。
while(isalnum(ch>>
{
Concat(>。
Getchar(>。
}
}//endofflag=FALSE
Retract(>。
//回退
if(flag>//若是无符号整数,则将整数值存于num中
{sym=NUMBER。
num=atoi(token>。
}
}//endofifisdigit
else
{
num=0。
switch(ch>
{
case'+':
Concat(>。
sym=PLUS。
break。
case'-':
Concat(>。
sym=MINUS。
break。
case'*':
Concat(>。
sym=TIMES。
break。
case'/':
Concat(>。
sym=SLASH。
break。
case'(':
Concat(>。
sym=LPAREN。
break。
case'>':
Concat(>。
sym=RPAREN。
break。
case'=':
Concat(>。
sym=EQL。
break。
case'#':
Concat(>。
sym=NEQ。
break。
/*
ODDSYM,EQL,NEQ,LSS,LEQ,GTR,GEQ,LPAREN,
RPAREN,COMMA,SEMICOLON,PERIOD,BECOMES,
*/
case',':
Concat(>。
sym=COMMA。
break。
case'.':
Concat(>。
sym=PERIOD。
break。
case'。
':
Concat(>。
sym=SEMICOLON。
break。
case'>':
Concat(>。
Getchar(>。
if(ch!
='='>//若后不跟'=',则回退
{sym=GTR。
Retract(>。
}
else
{Concat(>。
sym=GEQ。
}
break。
case'<':
Concat(>。
Getchar(>。
if(ch!
='='>
{sym=LSS。
Retract(>。
}
else
{Concat(>。
sym=LEQ。
}
break。
case':
':
Concat(>。
Getchar(>。
if(ch!
='='>
{flag=FALSE。
Retract(>。
}
else
{Concat(>。
sym=BECOMES。
}
break。
default:
Concat(>。
flag=FALSE。
break。
}//endofswitchelsechar
}//endofelsechar
returnflag。
}
[4]Testsym.c
#include"basedata.h"
#include"symbol.h"
#include
externchar*WORD[WORDLEN]。
externintWSYM[WORDLEN]。
externchar*SNAME[SYMBOLNUM]。
externSYMBOLsym。
//lastreadedwordtype。
externchartoken[MAXIDLEN+1]。
//lastreadedword
externintnum。
//lastreadednum。
externcharch。
//lastreadedchar。
externintcol,row。
externFILE*fd。
FILE*fout。
voidInit(void>。
voidQuit(void>。
voidmain(>
{
intflag。
Init(>。
fprintf(fout,"\nTOKENSYMNUM">。
do{
flag=Getsym(>。
if(flag>
{
fprintf(fout,"\n%10s%10s%d",token,SNAME[sym],num>。
}
elseif(ch!
=EOF>
{
fprintf(fout,"\n%10s",token>。
Errorsym(>。
}
}while(ch!
=EOF>。
//反复调用Getsym(>识别单词,将输出结果存入fout中
Quit(>。
}
//======================================
voidInit(void>
{
chartemp[30]。
printf("\nPleaseinputyourfilename:
">。
gets(temp>。
if((fd=fopen(temp,"rt">>
==NULL>
{
fprintf(stderr,"Cannotopeninputfile%s.\n",temp>。
getch(>。
return。
}//将fd指针指向待分析源文件
if((fout=fopen("mydata.dat","wt">>
==NULL>
{
fprintf(stderr,"Cannotopeninputfile.\n">。
getch(>。
return。
}//将fout指向文件mydata.dat
}
voidQuit(void>
{
fclose(fd>。
fclose(fout>。
}
实验二 熟悉FLEX使用方法
【实验目的】
1.掌握FLEX基本使用方法
2.掌握如何将通过FLEX生成的C语言模块加入到自已的程序中
【实验要求】
1.编制FLEX源程序,分别统计文本文件a.txt中出现的标识符和整数个数,并显示之。
标识符定义为字母开头,后跟若干个字母,数字或下划线。
整数可以带+或-号,也可不带,且不以0开头。
非单词和非整数则忽略不记,将之滤掉不显示。
2.编制一FLEX源程序,分别求出文件hh.c中字母,数字,回车符的个数。
3.思考:
若main函数不在FLEX中实现,应该如何实现?
4.本次实验建议学时2学时。
【实验预习提示】
参见附录一。
在看懂的基础上将之调试通过。
实验三 用FLEX自动生成PL/0词法分析器
【实验目的】
熟练掌握FLEX,并通过其生成一个词法分析器
【实验要求】
1.通过FLEX生成一词法分析器函数intGetsym(>,其功能同实验一中词法分析器函数类似。
2.生成一工程文件,调用1中生成的函数Getsym(>,对一指定的文件进行词法分析,要求分析出单词的类型和值。
并将分析结果存入一文件Mydata.dat中。
3.本实验建议学时4学时。
【实验预习提示】
FLEX可自动生成函数intyylex(>,则intGetsym(>可通过调用yylex(>实现。
1.由于FLEX生成的C程序模块lex.yy.c过于复杂,基本不可读,所以不要直接修改它,可将它看成一个“黑箱”,即不需要清楚知道其内部结构,只需要知道其接口即可。
可通过修改FLEX源程序间接修改之。
关于lex.yy.c中常用变量和函数,在附录中有详细说明。
2.编制一FLEX源程序,不妨取名为sym.l,通过FLEX生成lex.yy.c,并将之加入到工程文件中。
3.工程文件结构
生成一工程文件,不妨取名为test.prj,将文件Symbol.c,lex.yy.c,testsym.c加入之。
源程序参考如下:
[1]Basedata.h
同实验一中Basedata.h
[2]Symbol.h
#include
#include
#defineWORDLEN13/*保留字个数*/
#defineMAXIDLEN50/*标识符最长长度*/
#defineSYMBOLNUM32/*种别码个数*/
typedefenumSYMBOL
{NOL,IDENT,NUMBER,PLUS,MINUS,TIMES,SLASH,
ODDSYM,EQL,NEQ,L