词法分析程序+语法语义分析四元式生成+实验报告.docx
《词法分析程序+语法语义分析四元式生成+实验报告.docx》由会员分享,可在线阅读,更多相关《词法分析程序+语法语义分析四元式生成+实验报告.docx(52页珍藏版)》请在冰豆网上搜索。
词法分析程序+语法语义分析四元式生成+实验报告
《编译原理》实验报告
本文档集合了编译原理大作业的实验报告加代码
实验主要内容为用C++实现了词法分析程序;语法语义以与四元式生成程序
代码见附录,复制进VS后程序绝对可编译执行。
文档代码为原创,谨慎使用〔姚砺的大作业〕
实验设计一
[一、实验名称]
词法分析程序
[二、实验目的]
〔1〕设计一个词法分析程序,每调用一次就从源程序文件中顺序识别出一个单词符号,并返回该单词符号的内部编码、单词符号自身、行列位置信息。
〔2〕要能处理单行注释。
[三、实验内容与要求]
单词种类与识别规则
(1)标识符:
首字符为字母或下划线,其后由字母、数字或下划线组成、
长度不超过255个字符;
(2)整数:
由1到8个数字组成。
(3)小数:
数字串1.数字串2,其中:
数字串1由1-8个数字符组成;
数字串2由0-8个数字符组成,即:
数字串2可以为空。
(4)字符串:
由一对“〞括起来的符号串,长度不超过255个字符;
(5)保留字:
if、else、while、do、integer、float、string、input、output、
and、or、function、end、def、as、begin
(6)数学运算符:
+、-、*、/、=
(7)比较运算符:
<、<=、>、>=、<>、==
(8)逻辑运算符:
and、or
(9)分隔符:
{、}、(、)、;、,
[四、实验环境]
操作系统:
Win7/其他
编译工具:
VC++6.0/CFree/VS2012
[五、设计]
1设计大体思路
将读取的文件采用一遍扫描的方法,即从左到右只扫描一次源程序,将读取的数据存放在一个二维数组里。
然后通过扫描函数scan,再从数组中一行一行的读取数据,每调用其依次返回一个单词的类型,同时单词本身以与行列号存放在全局变量中。
而说词法分析作为语法分析的一个子程序,故在编写词法分析程序时,将会反复调用scan函数来获取一个个单词信息。
3设计流程图
4函数设计
/*词法分析函数*/
intscan(strings,intline)
框架:
{
初始化工作
是空格直接跳过,知直到读取到一个字符
if(是字母)
{
查表判断是否为关键字
判断是否为逻辑运算符and或or
Else
则为标识符
}
elseif(是否为数字)
{
判断是整数
Else
是小数
}
Else
{
其余情况判断是否为运算符,字符串等
}
elseif(getchar==’/’)
{
if(content[line][i+1]=='/')//向前看一个,确定是否为行注释;
如果是,则游标指向行末,跳过行注释
if(content[line][i+1]=='/*')
如果向前看一个发现时块注释
则一直向前扫描直到出现“*/〞停止,略过块注释
如果都不是则
Else
判断为除号,返回运算符类型
}
}
2对其中部分实现的说明
(1)数字识别
while(content[line][i]>='0'&&content[line][i]<='9')//判断是否为数字
{
text+=content[line][i];i++;flag=1;
}
if(flag==1)
{
if(content[line][i]=='.')
{
text+=content[line][i];i++;
while(content[line][i]>='0'&&content[line][i]<='9')//判断是否为数字
{
text+=content[line][i];i++;
}
return2;//整数
}
return3;//小数
}
每读入一个字符,判断是否数字,然后找小数点,找到即为小数
(2)标识符处理
while((content[line][i]>=65&&content[line][i]<=90)||(content[line][i]>=91&&content[line][i]<=122)||content[line][i]>='0'&&content[line][i]<='9')//判断是否为数字或者字母或者下划线
{
text+=content[line][i];i++;
}
for(j=0;j<=13;j++)
if(text==key[j])//查表判断是否为保留字
return5;
检查到读取的字符为字母时,进行查表判断,找到即说明为关键字
(3)空格,注释,行号的处理
if(mode==0)
{if(ielse
if(mode==-1)//如果为块注释找回行号
k=templine;
[六、程序源代码]
见源代码文件。
[七、实验数据、结果]
测试数据见文件
[八、总结]
之前的每周作业曾经写过一个词法分析程序,所以这次写词法分析程序没什么太多困难。
在读取源文件时,最初的想法是一行一行的读取,读一次就存一行,这样可以节省空间资源,提高效率。
但是仔细一想考虑到块注释分布在不同的行中,而且这样对行列的操作十分麻烦,而且许多地方涉与到要向前看,〔比如判断注释与/除号运算符时〕,最后只好一次全部读完,存在一个二维数组中。
再用scan函数来处理。
这样一来行列的处理就自由多了。
第二点,在写scan函数的时候,最初的想法是每调用一次,返回一个单词,然后在语法分析时不断调用它。
但是仅仅返回单词是不够的,于是打算返回一个结构体,把单词信息行列号什么的全部存进去,这个在后来的语法语义分析中可以看到,这里任然采用的是返回一个单词类型。
单词本身,和行列号作为全局变量保存。
然后只要按照流程图一步一步判断,慢慢写就行了,没有太多难度,唯一要注意的是单词位置行列的计算一定要细心。
这个是要穿插在整个词法分析的全部地方的。
所有涉与到读入一个字符的地方都需要考虑到如何修改行列变量。
尤其是本程序拓展了块注释,要注意行列变化。
实验设计二
注:
由于语义分析和四元式生XX是在语法分析的基础上拓展得到的,这一章实验报告囊括了语法,语义和四元式。
[一、实验名称]
语法语义以与四元式分析程序
[二、实验目的]
设计一个语法语义以与生成四元式的分析程序
[三、实验内容与要求]
设计要求:
构造相应文法的语法分析程序,应能指出源程序中出现的错误。
为语法分析程序中添加类型检查功能,包括:
变量重复定义;
变量未定义就使用;
变量未赋值就引用;
将语法正确的源程序翻译成四元式。
文法见附录
[四、实验环境]
操作系统:
Win7/其他
编译工具:
VC++6.0/CFree/VS2012
[五、设计]
1.语法分析设计
(1)相关数据机构
structToken
{
stringvalue;//值
intmode;//类型
introw;//行
intcol;//列
}token;
说明:
这本应是词法分析时的结构,但是scan函数返回的是但单一值,于是又设计了Token函数,对scan函数进行扩充,每个单词的信息均保存一次在结构体中,方便错误报告函数进行处理。
〔2〕各个非终结符函数形式
//**********************************************************************************************************************
/*语法语义各产生式函数*/
voidchengxu();
voidhanshukuai();
voidhanshu();
voidyujukuai();
voidyuju();
voidbianliangdingyiyuju();
voidshujuleixing();
voidshuruyuju();
voidshuchuyuju();
voidfuzhiyuju();
voidfenzhiyuju();
voidxunhuanyuju();
voidbiaodashi();
voidxiang();
voidyinzi();
voidbuerbiaodashi();
voidguanxibiaodashi();
voidguanxi();
其他函数:
voiderror();
voidwarning();
每个函数具体作用在代码中都有注释说明
〔3〕语法分析整体设计思路
采用递归下降的方法,为每个非终结符设计一个函数,在函数中对此处可能出现的语法错误进行处理。
处理也语法错误的思路主要分两种:
1.查找关键字
由于进行语法分析时检查到错误不可能就此停下,要一次性尽量找到所有的错误,而且测试数据的语法错误形式千奇百怪,这就要求发现错误之后如何找到继续进行分析的位置。
这里对于一些不常见的错误采取查找关键字的方法,一旦出现错误在相应函数中查找下一个出现的关键字的位置,然后继续分析。
2.跳过错误单词,直接分析下一个单词
由于查找关键字方法会跳过许多代码,从而有可能造成一些信息不必要的遗漏。
尤其是对于漏分号这种错误,与是这里采取,直接当前错误,默认后续单词可能就是正确单词,继续分析。
但是这个方法在一些地方并不适用,可能会导致一连串的错误出现,于是在使用时要考虑好取舍。
编译错误处理函数:
voiderror(Tokentoken,char*msg)//编译错误处理函数
{
cout<<"编译错误:
";printf("%s",msg);cout<第"<"<cout<errornum++;
}
每出现错误时,通过传递错误信息参数以与单词信息,调用编译错误处理函数进行实时报错。
〔3〕测试数据和结果
见后文语义分析部分
(4)语法分析部分的小总结
原本打算试试自底向上归约方法设计的,结果想了想发现实在麻烦,老师给的文法又挺庞杂的,创建分析表什么的就要好久。
后来一想用递归下降方法好处很多,做到之后的语义分析和四元式生成的时候在语法分析的基础上拓展很方便,整体框架非常清晰。
在错误处理方面考虑的还是挺合理的,对于一些常见错误都能有效指出。
并且程序只有一个出口,任何代码输入都会从那一个出口结束,不会引起程序崩溃。
2、语义分析设计
语义分析主要实现3个功能
变量重复定义;
变量未定义就使用;
变量未赋值就引用;
(1)相关数据结构
structSymtable//符号表结构
{
stringname;
inttype;
boolvalue;
}S;
structcompare:
binary_function//仿函数和绑定器
{
booloperator()(Symtables,stringstr)const
{
if(s.name==str)
returntrue;
else
returnfalse;
}
};
(2)相关函数
voidpush_in_token(Tokentoken)//进表操作
{
S.name=token.value;
S.type=-1;
S.value=false;
ST.push_back(S);
}
vector:
:
iteratorfind_token(Tokentoken)//查表操作
{
it=find_if(ST.begin(),ST.end(),bind2nd(compare(),token.value));
returnit;
}
(2)测试数据和结果
〔4〕设计思路
语义部分主要采用符号表结构,符号表中记录了每个非终结符的信息。
〔名称,类型,是否赋值〕。
然后再程序中每次调用Token函数发现读取到标识符时都进行查表操作。
根据查表结果进行相关分析。
在赋值函数中,如果查表找到其信息就说明重复定义。
再其余函数中
1.没有找到说明为声明就使用。
2.找到发现未赋值说明未赋值就引用。
〔5〕语义部分小总结
假如只实现以上3个功能语义部分并不难,关键是符号表的构建以与进表查表等操作。
在这一部分我使用了仿函数和绑定器进行查表。
由于我把相应表结构都存在vector内,导致查找结构内成员信息的不便,但是自己写一套查找操作代码估计效率不高,索性使用仿函数进行查找,泛型操作一般效率较高而且代码较少也很清晰。
假如时间更充足一点其实还可以把数据类型匹配这一部分拓展一下,毕竟也是查表比较,但是时间有限只好先完成要求的3个任务。
3、四元式分析设计
〔1〕相关数据结构
structQuaternary//四元式结构
{
intserial;
stringop;
stringv1;
stringv2;
stringresult;
}Q;
stackoperator_stack;//操作数堆栈
stackoperand_stack;//操作符堆栈
〔2〕相关函数
QuaternaryQuaternary_generater(intserial,stringop,stringv1,stringv2,stringresult)//四元式生成函数
{
Q.serial=serial;
Q.op=op;
Q.v1=v1;
Q.v2=v2;
Q.result=result;
returnQ;
}
voidQuaternary_maker()
{
while(!
operand_stack.empty()&&!
operator_stack.empty())
{
op=operator_stack.top();
operator_stack.pop();
v1=operand_stack.top();
operand_stack.pop();
v2=operand_stack.top();
operand_stack.pop();
res=result[r];r++;
Q=Quaternary_generater(serial,op,v1,v2,res);serial++;
QV.push_back(Q);
////////////////
for(it2=QV.begin();it2!
=QV.end();it2++)//找回真出口
if((*it2).result=="null")
{
stringstreamss;
ss<strings=ss.str();
(*it2).result=s;
break;
}
///////////////
operand_stack.push(res);
}
}
voidQuaternary_output()
{
while(!
QV.empty())
{
cout<it2=QV.begin();
QV.erase(it2);
}
}
(3)测试数据和结果
〔4〕设计思路
赋值运算部分整体结构流程如下:
说明:
首先建立操作符栈和运算符栈。
每次扫描遇到操作符遍进栈,遇到运算符如果当前栈空则进栈,否则和栈顶操作符的优先级进行比较,如果小于等于其优先级,则pop栈顶符号,进行四元式生成,再将四元式压入四元式队列,然后继续与栈顶元素比较直至其优先级大于其优先级或栈空则进栈。
If语句部分
在对布尔表达式进行判断时,先让操作符和运算符进栈,转移序号先赋为null
等到扫描到ifthen之后的部分通过对其四元式生成确定了序号,这时候再查找队列进行回填序号。
函数具体结构:
voidbiaodashi()
{
xiang();
while(token.value=="+"||token.value=="-")
{
///////////////////////////////////////////////四元式
while(!
operator_stack.empty()&&operator_stack.top()!
="=")//此处要注意用while循环,if会出错,因为需要不断判断直至栈顶的符号优先级小于需要压栈的符号
{
此处生成四元式,四元式进队列
for(it2=QV.begin();it2!
=QV.end();it2++)//找回真出口
if((*it2).result=="null")
{
stringstreamss;
ss<strings=ss.str();
(*it2).result=s;
break;
}
}
operator_stack.push(token.value);//如果栈空或者准备进栈的符号的优先级大于栈顶的优先级则进栈
token=getToken();
xiang();
}
}
〔5〕四元式的总结:
这部分是我觉得此次试验最难的一部分。
而且也是我花费时间最多的一部分。
在写赋值运算的那部分时,把一个while循环写成了if〔我一开始以为运算符进栈只需判断一次〕导致测试数据结果出错找了好久错误才找到。
If语句那部分的四元式的编写也十分困难,尤其是找回真假出口让我考虑了很久。
而且测试数据整个代码有许多这种语句,应当在每个语句结束时就输出一次四元式,不然会导致次序的混乱。
〔一开始我没发现,是把所有四元式进队列到最后程序结束时一起输出的〕,最后调试了许久,才发现问题,于是在语句结果判断分号时,就调用四元式输出函数进行输出。
[六、整体总结]
这次编译原理大作业让真的我受益良多,整个近千行代码基本都是我一点一点慢慢敲出来的,过程固然辛苦但是学到了不少。
尤其是在做语法分析和四元式生成的部分,代码量较大,一开始思路也不太清晰,但是还是一点一点硬着头皮往下写,结果在写的过程中一些思路就出来了,看来有时候还是要多动手,光苦思冥想并不行。
对于这次作业我自己是比较满意的但是还是有一些不足:
语法部分本来想对文法进行拓展把布尔表达式那部分的附加题解决掉,无奈时间有限,实在来不与写,可能留到暑假自己再完善吧。
四元式部分其实做得并不完完善,有些测试数据会出现错误结果,主要是还是因为布尔表达式那部分文法没有拓展,在进行复杂布尔表达式判断时会出错,而且if语句真假口如果遇到复杂的算数运算可能会出口回填错误。
总体来说,这次作业基本任务还是都完成了,通过这次大作业也对整个编译器的设计有了比以前更深的了解和体会,看来还是实践才能出真知。
附录
文法如下:
1.<程序>—><函数块>
2.<函数块>—><函数>[<函数>]
3.<函数>—>functionid()<语句块>endfunction
4.<语句块>—>begin语句[语句]end
5.<语句>—><分支语句>|<赋值语句>|<循环语句>|
<输入语句>|<输出语句>|<变量定义语句>
6.<变量定义语句>—>defid[,id]as<数据类型>;
7.<数据类型>—>integer|float|string
8.<输入语句>—>inputid[,id];
9.<输出语句>—>output<表达式>[,<表达式>];
10.<赋值语句>—>id=<表达式>;
11.<分支语句>—>if<布尔表达式><语句块>{else<语句块>}
12.<循环语句>—>while<布尔表达式>do<语句块>
13.<表达式>—><项>[+|-<项>]
14.<项>—><因子>[*|/<因子>]
15.<因子>—>id|con|deci|(<表达式>)
14.<布尔表达式>—><关系表达式>[and|or<布尔表达式>]
15.<关系表达式>—><表达式><关系><表达式>
16.<关系>—><|<=|>|>=|==|<>
附录:
词法分析完整代码〔复制可执行〕
/*
词法分析程序
BY谢卓函
ONJune20th
*/
#include
#include
#include
#include
#include
#defineMAXLINE30
usingnamespacestd;
//////////////////////////
//标识符1,整数2,小数3,字符串4,保留字5,数学运算符6,比较运算符7,逻辑运算符8,分隔符9
stringkey[14]={"if","else","while","do","float","string","begin","end","def","integer","input","output","as","function"};//保留字
stringborder[5]={";","{","}","(",")"};//分隔符5
stringarithmetic[9]={"+","-","*","/","<","<=","=",">",">="};//运算符6
stringtext;//记录标识符1
stringcontent[MAXLINE];//保存从文件读取的内容
inti=0;//i为字符游标
inttempline=0;
intscan(strings,intline)
{
intj,flag=0;
text="";//赋空text
while(content[line][i]=='')//判断空格
i++;//是空格跳过
if((content[line][i]>=65&&content[line][i]<=90)||(content[line][i]>=91&&content[line][i]<=122))//判断是否为字母或者下划线
{
text+=content[line][i];i++;
while((content[line][i]>=65&&