解释器构造总结

      解释器构造对于我们来说,事实上只是个课程设计,但是考虑到其触及的东西之多,而且也可以顺便让我复习一下《编译原理》的知识,自己一开始便决定将其作为一个产品来开发,而不是简简单单的一次作业来完成。好在功夫不负有心人,在我们小组的默契配合之下,众志成城(嘿嘿,这个有点小肉,但却是事实。)我们的解释器完全符合我们前期的期望。

      我们给了我们的产品一个名字,叫Pioneer,即先行者,意思是让其能够走在别人前面,尝试别人不敢尝试的东西。我想,我们的开发也正是这样的一个过程吧。在项目开始的初期,我们便很明确的提出了我们的目标:所做的编译器在尽量保留C语言风格的前提之下,首先满足基本的课设要求;其次尝试实现更过更难的功能,而这些功能大致可分为:语句拓展,函数拓展,简约界面,错误机制等;最后就是能够按时交付。

      既然我们的目标确定了,下面就是着手制定文法了。可以说这是至关重要的一步,因为这个一旦确定,那么后期的所有的开发都是基于这个的,而这也隐射出一点:后期即使是对文法做出一点点小的改动,整个程序都必须进行大的整理!事实上,我们之前也认识到这一点,但是,由于在开发前期,大家都对相关知识的认知与考虑并没有达到一个合理的高度,所以造成文法有漏洞也是在所难免的。词法分析倒是干脆简单,毕竟其要做的事很少,只是单纯的扫描和记录Tokens以及一些相关信息。当然,词法分析因为是站在开发的前列,我们开始对后期开发无法有足够的考虑,而且后面的分析又相当依赖于其分析结果,所以在整个开发过程中,词法分析都在根据整个设计与架构的要求进行相应的调整与完善。而在词法分析完之后,接着便是语法分析了。不得不说,这是让我们费尽周折的一个部分。在最开始的计划中,我们是打算用LALR分析器来实现,因为它不但继承了LR(1)文法的强大性,对项目集的数目也进行了一定的优化(虽然这个优化在自己写时是毫无体现,甚至是更麻烦,因为你必须先写出LR(1)的所有项目集,然后再对这些项目集进行一些合并!)还有一点就是,LALR对文法的要求并不高,也就是说,在文法中允许左递归和左公共因子。我想我们当时也就因为这种求成心切,为图一时之快,便不顾整个算法的规模的考虑,一头扎进LALR的深坑中,事实证明,我们的估算是存在很大的偏差的,这也给我们的开发带来了一个低潮期。

      当我们真正开始去做时,我被完全震惊了,这个所谓的优化项目集竟然可以超过400个!而这,还只是我的保守估计。我有些迷茫了,毕竟我们都已经为这个下了很大的功夫了,而我们小组的一些成员,还因为时间紧蹙,好几天强撑到凌晨3点多去做这个,这也包括我自己,这样说放弃是让人不太甘心的。但是,如果我们坚持走下去的话,是会走出来,我相信我们自己的实力,但是这个代价是很大的,而且时间上也不允许。虽说现在我们写出200多个了,但是错误是在所难免的,而且还有近200个在等着我们,那将是前路漫漫啊!在进退两难时,我们还是没有泄气,大家在仔细的商量之后,还是毅然决定放弃LALR,并不是说我们不敢去完成,而是做这个实在不值,我们还只是把这些项目集写一半就花了近一周时间,要是算上之后的一半以及后期把这些项目集转化为两张分析表所需的时间的话,那差不多要1个月!这是不可能允许的,因为语法分析的最终结果要求建好树,如果我们在前期分析基础上就花了超过80%计划的时间,那完全是没有道理的。毕竟方法主要是为了协助分析,而不应该成为分析的绊脚石。

      于是,最终我们选择使用LL(1)文法分析器,而分析过程使用了预测分析法(因为这个牵涉到更多的编译原理的东西)。这个算法很简单,思路也清晰,但是对文法的要求很高,必须去除所有的左递归与左公共因子。所以,我们又花了4天左右来进行文法的修改,而因为我们拓展的功能有点多,所以还是很麻烦的一个过程。到这因为大致的任务也明确了,于是就进行了分工。一人负责写SELECT集,并将将其转化为一张分析表;一人负责界面设计;还有两个人就是负责建树与遍历树,在满足语法分析的基础之上,尽量去设想语义分析对树的要求,这样做到未雨绸缪。令人欣慰的是,这段时间我们的开发速度明显提升了,虽然中间也还是遇到了蛮多问题,但都是很快就被发现与解决了,而最后我们也在定期检查之前把我们的语法分析结果上交了。

       解释器说到底还是为了能够解释并执行一门语言,虽说在语法分析中,语法树是由自己一个人来建的,但是,对语法树的作用还不是很明确,如果说是为建树而建树,那这个工作显得就不是那么的有意义了。而在接下来的语义分析中,自己渐渐地解开了这些谜团。首先面临的就是,我们的解释是否需要去生成中间代码。这个也确实让自己迟疑了一下。如果说能够都解释为中间代码的话,那执行起来应该是很简单也很方便的。但是我们吸取了前期LALR的教训,在具体做出一个决定前,必须去考虑一切相关的问题,否则再好的东西也是不适用的,而我们自己也没有发言权!虽然中间代码形式简单,执行方便,但是去生成这些中间代码岂不又很费事?在这些中间代码生成时,既然都已经有树了,已经具备直接解释的条件,为什么还要去把它转成个中间形式呢?这是否显得多余?况且,我们做的是解释器,并不是编译器,我们需要的是即时的执行与反馈。于是我们并不打算采用书上提供的中间代码解释的方法。当然,在这也必须声明一点,我这样的观点并不是说谁好谁坏,而只是依照我们自己开发的这个东西进行相应的分析与选择。存在即是理由,况且LR的相关算法也确实存在蛮多优于LL的地方。(否则我们前期也不会“栽”进去了,呵呵!~)只是对于我们的设计来说,这个方法是不划算的。

      在分析与总结了我们既成语法树的特点之后,我们决定使用递归的方法来解释执行。因为经受了前几次低估的挫折,这次我们做的很谨慎,也尽量留出更多的空挡来思考设计的问题。这不但包括整个语义分析程序执行的流程演绎,还具体到了哪个栈应该有哪些元素来填充。事实证明,我们的这个选择是正确的,在反复仔细的研究分析思路之后,我们发现了很多问题,考虑的也变的全面很多。而在这些设计确定基本没有问题之后,后期的实现,只花了不到2天的时间,这确实给了我们一个很大的惊喜!这算是给我们付出的一种临时犒劳吧,也更让我们膨胀了拓展的“野心”,不单实现了传说中的各种语句(貌似说switch是最难做的,但我们还是完全支持了!),而且在原先设想的简单函数调用基础之上,增加了函数递归与重载的支持,当然标准库那部分还是有待加强的,嘿嘿!~

       在功能实现的差不多的时候,接着便是整合界面的问题了。在这期间,最让我头痛的一个问题就是VC++ .NET中的enum了。这个东西实在是太诡异了,本来在标准C++和VC++控制台程序他都是正常的,也就是可以当做一个int来使用,但是到了.NET平台下,它却没办法实现自动转换,而如果你要去把它当做一个整数使用,比如说当做一个索引表的下标,那你必须先进行强制转换,否则一个莫名其妙的数就会造成你的访问越界。(据初步研究认为,那个数是你enum元素名字字符串转化为整数的值,超级大,还可能是负数,不越界才怪呢。)

       当然,为了我们程序能够更加强大与稳定,期间我们还做了许多其他的工作。比如说错误处理机制的建立。众所周知,在语法分析中,当遇到一个问题时,整个程序就会卡在那里,如果你不进行特别处理的话,那么分析也就结束,而纵然用户的源程序中还有N多问题,分析器还是强行罢工。为了能够最大限度的包容错误,依据我们采用的预测分析法,我们使用了“同步集”原理。这个算法虽然在思路上很简单,但确实非常的有效!

      按照我们前期的规划,我们在2009年的最后一天对我们的产品进行了演示。可以说,这一天对于我们组的每一个人都非常的重要,虽然前前后后也就三个月的时间,但是这段经历却将我们紧紧地团结在一起,而这也必然是值得我们永远珍藏的一段记忆。当老师一次次发出惊叹的声音时,我们真的是百感交集,这几个月的辛苦与奋斗,终于有了一个交代。当然,结果只是一个必然的东西,过程才是最重要的。在这段时间,自己收获是很大的,不管是经验还是教训,都让我进步。而总结一下,主要有以下几点:

      1. 团队是最重要的!自己是小组长,在我们整个开发过程中,也不断地在思考如何带动每个人参与到其中来。而且大家的水平与特长也都不同,又怎么来做到合理的分工与合作?虽然自己没什么具体经验,但是我还是觉得,将心比心,完全信任是最重要的。当我们遇到一个问题时,首先不是去责怪谁谁谁,而应该是和他去认真分析问题的原因,总结经验教训,这才是相对来说合理的。而当有人出色的完成了任务,那表扬是必须的,虽然看起来这个有点形式化,但是这种形式却是大家都受用的!

      2. 分析与设计是至关重要的,而测试必不可少。对于第一个,我想在这次开发中,我们已经有了正反两方面的教材来说明这个问题了。在最开始时我们我们“盲从”LALR,结果站错队,付出很大代价!而后来在语义时,很重视前期过程,最终早早的完成了预定工作,并得以进行进一步的拓展计划。而说到测试,也确实让我纠结了一下。记得在我们演示的前一天,小组的测试人员因为一时疏忽,忘了在递归调用函数中声明变量来测试,最终导致一些小的bug残留到我们演示前的几个小时,但不幸中的万幸,在真正的交付之前,我们已经完全的调试好了,而我们的演示也相当成功。虽然有惊无险,但还是应该引以为戒的。任何程序都有bug,而不测试的后果就是一些低级错误的残留。

      3. 有些时候,权威并不代表可靠,而小技巧也能派上大用场。这点很是让我小虚荣一把,在我们的文法改为LL过程中,虽然保持原有的功能不变,但是有些产生式提取左公因子是很麻烦的,比如说在expression中的assignment和rvalue间,因为rvalue并不一定就是identifier,所以,这个问题显得很棘手。于是我们选择使用一些特别的方法,在assignment前面加一个特殊的标记,比如说#。(希望不会有人丢板砖!)这个很奏效,但是总不能让用户在写程序时,在每个赋值语句前面都加上一个#吧,那确实蛮烦人的。于是我便去修改我们的词法分析器,在扫描到=或者等号操作,比如说+=,-=时,如果不是声明的话,就在标识符前面自动加上一个#,也就是在修改源程序的token队列。嘿嘿,最终在这种小技巧的帮助之下,我们不但成功的“消除”了这个左公共因子问题,还把全局变量前面的static改为了"不可见",这真是“大快人心”啊!!总之,把工作当做一种创作,那真是一种高档享受!

      4. 注意小工具的使用。这个建议是老师给我们的,我很受启发。在我们制分析表时,完全是手工,这不但会有很多错误,效率也很低。而这个时候完全可以写一些小工具来帮忙的,因为写一个小工具的性价比是非常可观的,而且只要规则制定正确,一般是不会出错的。但是我们毕竟是新手,这个妙招之前是没有想到的,之后我想是可以尝试的,而且这个应该很有用刀之处!

      不管怎么样,最终我们还是交了一份让自己满意,也让老师称赞的答卷。虽然它只是一次平常的课程设计,但是对于我们来说,却显得那么的不一样,我们投入了很多,相应的,收获也非常的大。我要感谢我的组员们,感谢大家对我的包容与配合,这是我新年最好的一份礼物,而这种一起吃苦的幸福也将引导我继续前行!O(∩_∩)O谢谢你们!~

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值