编译原理实验指导书.docx
《编译原理实验指导书.docx》由会员分享,可在线阅读,更多相关《编译原理实验指导书.docx(46页珍藏版)》请在冰豆网上搜索。
编译原理实验指导书
《编译原理》上机指导
编者:
郭文宏
二零零三年十月第一版
前言
编译原理是计算机专业的主干课和必修课,由于这门课程相对抽象且内容较复杂,一直是比较难学的一门课程。
在编译原理的学习过程中,实验非常重要,只有通过上机实验,才能使学生对比较抽象的课程内容产生一个具体的感性认识。
但是,目前国内市场上很少有较详细且比较适合我院实际的实验指导书,为此,我们特编了这份指导书,希望能对我院的《编译原理》教学工作有所帮助。
本书实验环境主要为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下调试通过.
由于编者水平有限,本书中必然存在着不少缺点,在此恳请大家给予批评和指正,我们将尽力纠正。
如对本书有批评指正,请Email至guowh2003@。
在此特对关心支持编写本书的院系领导表示感谢。
本书中关于LEX和YACC的部份大量参考引用了何炎祥老师主编,华中理工大学出版社出版的《编译原理》一书,在此表示衷心的感谢。
郭文宏
2001年10月12日
实验一 手工生成PL/0语言词法分析器-----------------1
实验二 熟悉FLEX使用方法--------------------------11
实验三 用FLEX自动生成PL/0词法分析器--------------12
实验四 用递归下降法进行表达式分析------------------20
实验五 用算符优先法进行表达式分析------------------22
实验六 利用BISON生成逆波兰表示计算器--------------27
实验七 利用BISON生成中缀表示计算器----------------28
附录一词法分析器生成工具FLEX简介----------------29
附录二语法分析器生成工具YACC简介----------------37
实验一 手工生成PL/0语言词法分析器
(1)实验目的
1、掌握手工生成词法分析器的方法,了解词法分析器的内部工作原理。
2、掌握C环境下工程文件的用法
(2)实验要求
1、手工编制PL/0语言词法分析函数intGetsym(void);每调用此函数一次,从当前待编译文件中识别出一单词,并给出其类型和值。
关于PL/0的说明参见<<编译原理>>第二章(清华大学吕映芝编)。
2、生成一工程文件,调用1中生成的函数Getsym(),对一指定的文件进行词法分析,要求分析出单词的类型和值,对于词法错误之处,要给出错误所在位置。
并将分析结果存入一文件Mydata.dat中。
3、实验前请仔细阅读实验预习提示,提示中程序仅供参考。
4、本实验建议上机时间4-6学时。
(3)实验预习提示
1、词法分析器的功能和输出格式
词法分析器的功能是输入源程序,输出单词符号。
词法分析器的单词符号常常表示成以下的二元式(单词种别码,单词符号的属性值)。
本实验中,采用的是一符一种别码的方式。
2、单词的EBNF(扩展巴科斯范式)表示
下面是对PL/0进行词法分析所用到的部份EBNF。
<标识符>:
:
=<字母>{<字母>|<数字>|<下划线>}
<无符号整数>:
:
=<数字>{<数字>}
<加法运算符>:
:
=+
<减法运算符>:
:
=-
.
.
.
<大于关系运算符>:
:
=>
<大于等于关系运算符>:
:
=>=
.
.
.
3、“超前搜索”方法
词法分析时,常常会用到超前搜索方法。
如当前待分析字符串为“a>+”,当前字符为’>’,此时,分析器到底是将其分析为大于关系运算符还是大于等于关系运算符呢?
显然,只有知道下一个字符是什么才能下结论。
于是分析器读入下一个字符’+’,这时可知应将’>’解释为大于运算符。
但此时,超前读了一个字符’+’,所以要回退一个字符,词法分析器才能正常运行。
在分析标识符,无符号整数等时也有类似情况。
4、工程文件结构
由于在以后的实验中,还会用到本次实验的结果。
为提高代码的复用性,必须采用模块化设计的方法。
本例中,用到了自编的四个文件,即Basedata.h,Symbol.h,Symbol.c,Testsym.c。
其中,两个.h头文件中存放的是一些宏定义和自定义数据类型,Symbol.h中还存放有词法分析器要用到的一些自定义函数的声明。
在Symbol.c中存放的是对这些函数的定义部份。
Testsym.c中存放的是主函数部份,在此处调用词法分析器对待分析PL/0文件进行分析。
现建一工程文件,名为test.prj,将文件Basedata.h,Symbol.h,Symbol.c,Testsym.c加入工程文件中,编译链接后生成可执行文件Test.exe,运行之即可对指定的源文件进行词法分析。
5、参考源程序
[1]Basedata.h
#ifndef_BASETYPE_H
#define_BASETYPE_H
#defineFALSE0
#defineTRUE1
#defineSPACE0x20
#defineBACKSPACE0x08
#defineENTER0x0d
#defineESC0x1b
#defineTABLE0x09
#defineENDFILE-1
#endif
[2]Symbol.h
#ifndef_SYMBOL_H
#define_SYMBOL_H
#include
#include
#defineWORDLEN13//保留字个数
#defineMAXIDLEN50//标识符最长长度
#defineSYMBOLNUM32//种别码个数
typedefenumSYMBOL
{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}SYMBOL;//定义种别码
voidGetchar(void);
//取下一个字符
voidGetbc(void);
//如当前字符为白字符,则读字符至不为白字符时停止
voidConcat(void);
//将当前字符加入token字
voidRetract(void);
//回退一字符
intReserve(void);
//判断token字中单词是否是保留字
intGetsym(void);
//从当前文件中识别出一单词,并给出其类型和值
voidErrorsym(void);
//打印错误信息
#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;i{
temp[i]=toupper(token[i]);//将当前token字以大写形式存入temp中
}
temp[i]='\0';
for(i=0;i{
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)实验目的
1、掌握FLEX基本使用方法
2、掌握如何将通过FLEX生成的C语言模块加入到自已的程序中
(2)实验要求
1、编制FLEX源程序,分别统计文本文件a.txt中出现的标识符和整数个数,并显示之。
标识符定义为字母开头,后跟若干个字母,数字或下划线。
整数可以带+或-号,也可不带,且不以0开头。
非单词和非整数则忽略不记,将之滤掉不显示。
2、编制一FLEX源程序,分别求出文件hh.c中字母,数字,回车符的个数。
3、思考:
若main函数不在FLEX中实现,应该如何实现?
4、本次实验建议学时2学时。
(3)实验预习提示
参见附录一。
在看懂的基础上将之调试通过。
实验三 用FLEX自动生成PL/0词法分析器
(1)实验目的
熟练掌握FLEX,并通过其生成一个词法分析器
(2)实验要求
1、通过FLEX生成一词法分析器函数intGetsym(),其功能同实验一中词法分析器函数类似。
2、生成一工程文件,调用1中生成的函数Getsym(),对一指定的文件进行词法分析,要求分析出单词的类型和值。
并将分析结果存入一文件Mydata.dat中。
3、本实验建议学时4学时。
(3)实验预习提示
1、FLEX可自动生成函数intyylex(),则intGetsym()可通过调用yylex()实现。
2、由于FLEX生成的C程序模块lex.yy.c过于复杂,基本不可读,所以不要直接修改它,可将它看成一个“黑箱”,即不需要清楚知道其内部结构,只需要知道其接口即可。
可通过修改FLEX源程序间接修改之。
关于lex.yy.c中常用变量和函数,在附录中有详细说明。
3、编制一FLEX源程序,不妨取名为sym.l,通过FLEX生成lex.yy.c,并将之加入到工程文件中。
4、工程文件结构
生成一工程文件,不妨取名为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,LSS,LEQ,GTR,GEQ,LPAREN,
RPAREN,COMMA,SEMICOLON,PERIOD,BECOMES,
BEGINSYM,ENDSYM,IFSYM,THENSYM,WHILESYM,
WRITESYM,READSYM,DOSYM,CALLSYM,CONSTSYM,
VARSYM,PROCSYM}SYMBOL;/*定义种别码*/
intReserve(void);
/*判断token字中单词是否是保留字*/
intGetsym(void);
/*从当前文件中识别出一单词,并给出其类型和值*/
voidMyError(void);
/*打印错误信息*/
#endif
[3]symbol.c
#include"Basedata.h"
#include"Symbol.h"
#include
#include
#include
char*WORD[WOR