编译原理实验报告3LL文法构造.docx
- 文档编号:11639920
- 上传时间:2023-03-29
- 格式:DOCX
- 页数:12
- 大小:239.55KB
编译原理实验报告3LL文法构造.docx
《编译原理实验报告3LL文法构造.docx》由会员分享,可在线阅读,更多相关《编译原理实验报告3LL文法构造.docx(12页珍藏版)》请在冰豆网上搜索。
编译原理实验报告3LL文法构造
编译原理实验报告3-LL
(1)文法构造(总18页)
实验3LL
(1)文法构造
一、实验目的
熟悉LL
(1)文法的分析条件,了解LL
(1)文法的构造方法。
二、实验内容
1、编制一个能够将一个非LL
(1)文法转换为LL
(1)文法;
2、消除左递归;
3、消除回溯。
三、实验要求
1、将一个可转换非LL
(1)文法转换为LL
(1)文法,要经过两个阶段,1)消除文法左递归,2)提取左因子,消除回溯。
2、提取文法左因子算法:
1)对文法G的所有非终结符进行排序
2)按上述顺序对每一个非终结符Pi依次执行:
for(j=1;j 将Pj代入Pi的产生式(若可代入的话); 消除关于Pi的直接左递归: Pi->Piα|β,其中β不以Pi开头,则修改产生式为: Pi—>βPi′ Pi′—>αPi′|ε 3)化简上述所得文法。 3、提取左因子的算法: A—>δβ1|δβ2|…|δβn|γ1|γ2|…|γm (其中,每个γ不以δ开头) 那么,可以把这些产生式改写成 A—>δA′|γ1|γ2…|γm A′—>β1|β2|…|βn 4、利用上述算法,实现构造一个LL (1)文法: 1)从文本文件中读入文法,利用实验1的结果,存入实验1设计的数据结构; 2)设计函数remove_left_recursion()和remove_left_gene()实现消除左递归和提取左因子算法,分别对文法进行操作,消除文法中的左递归和提出左因子; 3)整理得到的新文法; 4)在一个新的文本文件输出文法,文法输出按照一个非终结符号一行,开始符号引出的产生式写在第一行,同一个非终结符号的候选式用“|”分隔的方式输出。 四、实验环境 PC微机 DOS操作系统或Windows操作系统 TurboC程序集成环境或VisualC++程序集成环境 五、实验步骤 1、学习LL (1)文法的分析条件; 2、学习构造LL (1)文法的算法; 3、结合实验1给出的数据结构,编程实现构造LL (1)文法的算法; 4、结合实验1编程和调试实现对一个具体文法运用上述算法,构造它的LL (1)文法形式; 5、把实验结果写入一个新建立的文本文件。 六、测试数据 输入数据: 编辑一个文本文文件,在文件中输入如下内容: S->Qc|c|cab; Q->Rb|b; R->Sa|a; 正确结果: 本实验的输出结果是不唯一的,根据消除左递归是选择非终结符号的顺序不同,或选择新的非终结符号的不同,可能会得到不同的结果,下面只是可能的一个结果: S->Qc|cT; T->@|ab;//由于无法输出ε,用@代替 Q->Rb|b; R->bcaU|caU|cabaU|aU; U->bcaU|@; 七、实验报告要求 实验报告应包括以下几个部分: 1、满足LL (1)文法的分析条件; 转换前要求文法中不含回路(经过推导有形如P->P之类的),也不含以ε为右部的产生式。 一个文法要能进行LL (1)分析,那么这个文法应该满足: 无二义性,无左递归,无左公因子。 首先需要定义一些规则: 1.在程序运行前文法就必须输入进文本文件中,输入的文法只包含其中的所有产生式,并且默认其为可转换的非LL (1)文法,即通过消除左递归和反复提取公共左因子,就转换成了LL (1)文法。 2.输入的产生式为原实验1的结果,即一个非终结符只有一条产生式,候选之间用“|”隔开。 3.产生式与产生式之间只能以换行符或分号隔开。 4.开始符号对应的产生式必须第一个输入,即默认输入的第一个产生式的左部的大写字母是开始符号。 5.输入与输出都会保存在文本文件中文件名分别是和,本实验测试数据时,将两个文本放在了桌面。 6.ε用@代替,输入与输出都使用@。 7.新产生的非终结符统一使用没有在非终结符集合中出现的大写字母。 8.规定产生式最多只有20个。 2、构造LL (1)文法的算法; 算法: 1)从文本文件中读入文法,存入结构体中。 将第一个读到的大写字母记为开始符号S,将读到的包括开始符号在内的所有大写字母判定为非终结符,并将第一次出现的存入文法的非终结符集合中,终结符小写字母也一样。 将以换行符或分号隔开的字符串判断为一条产生式存入文法中。 实现函数是scanP()。 2)对文法中的产生式消除左递归。 实现函数是remove_left_recursion()。 3)对文法中的产生式反复提取公共左因子。 实现函数是remove_left_gene()。 4)向中输出文法的所有产生式。 3、消除左递归文法和提取左因子算法实现方法; 消除左递归文法(包括其中用到其它的子函数): /*字符串分割函数,将产生式右部的候选返回,识别‘|’,从pos位开始分割*/ stringstrsplit(stringstrTok,intpos){ stringstr; size_tposition; position=("|",pos); if(position! =string: : npos){ize();j++){ for(k=0;k if(gp->P[sVn[i]][j]<'A'||gp->P[sVn[i]][j]>'Z')break;lear(); gp->P[o-1].swap(gp->P[o]); } m--; } } } voidremove_left_recursion(structgrammar*gp){ize()+1){ str_i[num_i]=strsplit(gp->P[i-1],ipos); restr_i[num_i]=str_i[num_i]; ipos=ipos+str_i[num_i].size()+1; num_i++; } for(j=1;j<=i-1;j++){ if(! repeat){ num_i=0,ipos=3; ch_i=gp->Vn[i-1];ize()+1){ str_i[num_i]=strsplit(gp->P[i-1],ipos); restr_i[num_i]=str_i[num_i]; ipos=ipos+str_i[num_i].size()+1; num_i++; } } repeat=true; stringstr_j[20]; intl; jpos=3; num_j=0; ch_j=gp->Vn[j-1];ize()+1){ str_j[num_j]=strsplit(gp->P[j-1],jpos); jpos=jpos+str_j[num_j].size()+1; num_j++; } for(l=0;l if(pos==string: : npos){continue;}ubstr(1,str_i[l].size()-1);wap(change);ize()-1){ubstr(0,str_i[l].size()-1); str_i[l].swap(change); intlink=0; while (1){ if(link==num_j)break; str_j[link]=s+str_j[link]; if(link==num_j-1)str_i[l]+=str_j[link]; elsestr_i[l]=str_i[l]+str_j[link]+"|"; link++; } } else{ubstr(0,pos-1);ubstr(pos+1,str_i[l].size()-pos-1);wap(change); intlink=0; while (1){ if(link==num_j)break; str_j[link]=s1+str_j[link]+s2; if(link==num_j-1)str_i[l]+=str_j[link]; elsestr_i[l]=str_i[l]+str_j[link]+"|"; link++; } } } stringstri="->"; (0,1,ch_i); intindex=0; while (1){ize()+1; if(ps==gp->P[i-1].size()+1)break; s++; } stringPi="->",Pichange="->"; Pi=ch_i+Pi; intlink=0,flag=-1; boolflagpos[30]; charnewWord; for(;link<=s;link++){ind(ch_i); if(posi==0){ubstr (1)+newWord; flagpos[link]=false; gp->P[m-1]=Pichange+splitstr[link]+"|"; } if(flag>0){ splitstr[link]=splitstr[link].substr (1)+newWord; flagpos[link]=false; gp->P[m-1]=gp->P[m-1]+splitstr[link]+"|"; } } } nsert(0,1,ch_i); for(;h<=s;h++){ if(flagpos[h]){ splitstr[h]+=newWord; gp->P[i-1]=gp->P[i-1]+splitstr[h]+"|"; } } gp->P[m-1]+="@"; gp->P[i-1].erase(gp->P[i-1].size()-1,1); } } simplify(gp);wap(str[j]); } } } /*子函数,提取左因子*/ voidremove_left_gene(structgrammar*gp){ intrule_a,i,j,k,l,matchnum,oldmatchnum,resize,size; charch,newWord; for(rule_a=0;rule_a num++; } str_sort(str,num);ize(); for(j=0;j renum++; } } /*将已经提取过左因子的以候选为单位的字符串重新组合成产生式(包括新产生式)*/ for(l=0;l<=i-oldpo+oldmatchnum;l++){ if(l>=i-oldpo){ size+=restr[l].size(); can=restr[l].substr(j); if(can=="")can="@"; if(l==i-oldpo+oldmatchnum)newP+=can; elsenewP=newP+can+"|"; gp->P[m-1]=newP; } else{ resize+=restr[l].size(); resize++; } } gp->P[rule_a].replace(resize+3,size+oldmatchnum,repstr);ata(),fpo); fputs("\n",fpo); } fclose(fpo);//关闭fpi指向的文件 fpo=NULL;//避免指向非法内存 } 4、整个测试程序的流程; 5、程序的测试结果和问题; 实验源文法: 其它文法: 书上的文法,不过调换了顺序,且改变开始符号为R,结果会删除关于Q的无用产生式: 实验中遇到的问题: 1.开始设计程序的时候,并没有很好的运用数据结构来简化问题,只是使用了实验一的数据结构,构造了一个结构体。 实验大部分的处理过程需要用到文法的产生式,但是结构体中产生式只是用字符串数组存储,其中夹杂着”->”和”|”,分析时要将这些符号去除(迭代时前面有过变化,还要重新分割得到候选),得到候选,给处理过程增加了复杂度,如果能对产生式另外设计一个结构体,只将左部的非终结符和右部的所有候选及候选的个数进行封装,将极大地简化代码,又或者将其直接定义为类,这样还可以使用其中的成员函数,过程将更加清晰有条理。 2.代码的重用性问题,由于缺乏对C++编程的经验,几乎没有使用类、对象这些更优越的技术来实现。 另外有些地方的,诸如重新对候选进行分割,另外设置存储位置作为缓冲等等,没有设置子函数去处理,使得主过程变得冗余。 3.对C的字符数组运用的不熟练,用其来存储字符串时,不能灵活的使用相应的库函数来处理问题,不得已使用了C++的string类,其实关于字符数组的许多库函数非常有用,高效。 4.编程时使用的VS2015,由于之前一直使用DevC++,所以编程时对一些软件功能不熟悉,浪费了时间在查看说明,认识快捷键上。 今后还是需要多在VS下编写C。 5.编程经验不足导致的查阅或者大量修改,降低了效率。 6.调试程序时变量太多,查错费了不少时间,这也是没有多多使用子函数带来的弊端。 6、实验总结。 实验本身是比较麻烦的,好在关键的remove_left_recursion()和remove_left_gene()两个子函数提供了算法再加上本身理论课提供的大量额外知识,很容易有思路。 主要就在于实现这两个子函数的具体过程。 而这两个函数的实现过程将与文法的数据结构关系密切,如果能首先在设计数据结构上下工夫,能够设计得到一个好的数据结构,绝对能事半功倍! 所以以后对于相对复杂的问题,要首先设计好数据结构,其次慢慢完成算法。 完成算法及其代码的编写后,就是调试了。 要高效完成这项工作,需要了解IDE关于调试的各种功能,这样借助软件的这些功能,调试会更加容易,当然其中免不了会发现错误,需要修改程序的时候,我觉得特别要注意修改,这就像是一个状态转换的过程,你在修改前程序在一个过程,修改后是另一个过程,但到达的过程究竟是是否是你要的,就在于你的修改,修改时要特别将需要修改的部分用软件本身的自动将相同变量调亮的功能进行标记,免得漏改,一旦漏改,很容易进入思维误区,觉得刚才的修改方法出错,但其实不过是漏改了,这点错误在变量达到十几二十个时很容易犯,所以调试时要重点关注修改! 八、思考题 1、是不是所有的文法都可以通过上述程序构造LL (1)文法? 答: 不是;文法本身要能够被转换,在经过转换前要不含回路,不含以空字为右部的产生式,转换时,在提取公共左因子时还存在某些文法不能在有限步骤内提取完左公因子。 例如: S->Ap|Bq A->aAp|d B->aBq|e 那么在转换后还要在经过判断才能最终确认是否是LL (1)文法。 2、LL (1)文法在整个语法分析中的作用? 答: 语法分析包括两类: 一类是自上而下分析法,一类是自下而上分析法 自上而下的主旨是,对任何输入串,试图用一切可能的办法,从文法开始符号出发,自上而下的为输入串建立一棵语法树。 或者说,为输入串寻找一个最左推倒,这种分析过程的本质是一种试探过程,是反复使用不同产生式谋求匹配输入串的过程我主要是自上而下的过程。 而在自上而下的分析法中,主要是研究LL (1)分析法。 3、实验1中设计的文法数据结构对本实验的影响? 答: 使用数据结构本身是能够简化问题,可以使具体算法的思路有倾向性,也可以使得程序处理过程更加有条理,另外实验1的数据结构,完整的将开始符号,终结符,非终结符及所有产生式存储,对于消除左递归和提取左因子,可以对非终结符和产生式有进行更加有效的处理,较之没有使用数据结构或没有存储非终结符而言可以在迭代时单个处理,而不需要再所有步骤完成后才进行统一处理。 4、如何更好地组合实验1和实验3,使之具有更高的效率? 答: 使用结构体来定义文法,包括文法的四个部分,终结符和非终结符用string类,以便于可以调用string类的成员函数处理问题,产生式用C++类来定义,在存储文法时得到其左部的非终结符和右部的所有候选,并将候选按ASCII码排好序再存储,其数据有非终结符,所有候选,以及候选的个数,成员函数要能够查看,增加,修改,删除所有的成员,如果可以候选最好能使用链表,这样增加,替换,删除将更加简单,高效。 在实验1中还要将有回路的和右部有空字的产生式进行判断,提示输入的文法不合理,另外再将由实验3定义的一些规定转接到实验1,这样文法的结构定义,文法的完整输入以及对可转换的非LL (1)文法的判断,以及其它输入的提示及出错检测全部集中在原来实验1要完成的部分,原来实验3的部分只关注将文法进行转换和输出,即消除左递归、提取左因子和输出到文本文件中。
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 编译 原理 实验 报告 LL 文法 构造