ImageVerifierCode 换一换
格式:DOC , 页数:18 ,大小:225.04KB ,
资源ID:2742504      下载积分:8 金币
验证码下载
登录下载
邮箱/手机:
验证码: 获取验证码
温馨提示:
支付成功后,系统会自动生成账号(用户名为邮箱或者手机号,密码是验证码),方便下次登录下载和查询订单;
特别说明:
请自助下载,系统不会自动发送文件的哦; 如果您已付费,想二次下载,请登录后访问:我的下载记录
支付方式: 支付宝    微信支付   
验证码:   换一换

开通VIP
 

温馨提示:由于个人手机设置不同,如果发现不能下载,请复制以下地址【https://www.zixin.com.cn/docdown/2742504.html】到电脑端继续下载(重复下载【60天内】不扣币)。

已注册用户请登录:
账号:
密码:
验证码:   换一换
  忘记密码?
三方登录: 微信登录   QQ登录  
声明  |  会员权益     获赠5币     写作写作

1、填表:    下载求助     留言反馈    退款申请
2、咨信平台为文档C2C交易模式,即用户上传的文档直接被用户下载,收益归上传人(含作者)所有;本站仅是提供信息存储空间和展示预览,仅对用户上传内容的表现方式做保护处理,对上载内容不做任何修改或编辑。所展示的作品文档包括内容和图片全部来源于网络用户和作者上传投稿,我们不确定上传用户享有完全著作权,根据《信息网络传播权保护条例》,如果侵犯了您的版权、权益或隐私,请联系我们,核实后会尽快下架及时删除,并可随时和客服了解处理情况,尊重保护知识产权我们共同努力。
3、文档的总页数、文档格式和文档大小以系统显示为准(内容中显示的页数不一定正确),网站客服只以系统显示的页数、文件格式、文档大小作为仲裁依据,个别因单元格分列造成显示页码不一将协商解决,平台无法对文档的真实性、完整性、权威性、准确性、专业性及其观点立场做任何保证或承诺,下载前须认真查看,确认无误后再购买,务必慎重购买;若有违法违纪将进行移交司法处理,若涉侵权平台将进行基本处罚并下架。
4、本站所有内容均由用户上传,付费前请自行鉴别,如您付费,意味着您已接受本站规则且自行承担风险,本站不进行额外附加服务,虚拟产品一经售出概不退款(未进行购买下载可退充值款),文档一经付费(服务费)、不意味着购买了该文档的版权,仅供个人/单位学习、研究之用,不得用于商业用途,未经授权,严禁复制、发行、汇编、翻译或者网络传播等,侵权必究。
5、如你看到网页展示的文档有www.zixin.com.cn水印,是因预览和防盗链等技术需要对页面进行转换压缩成图而已,我们并不对上传的文档进行任何编辑或修改,文档下载后都不会有水印标识(原文档上传前个别存留的除外),下载后原文更清晰;试题试卷类文档,如果标题没有明确说明有答案则都视为没有答案,请知晓;PPT和DOC文档可被视为“模板”,允许上传人保留章节、目录结构的情况下删减部份的内容;PDF文档不管是原文档转换或图片扫描而得,本站不作要求视为允许,下载前自行私信或留言给上传者【w****g】。
6、本文档所展示的图片、画像、字体、音乐的版权可能需版权方额外授权,请谨慎使用;网站提供的党政主题相关内容(国旗、国徽、党徽--等)目的在于配合国家政策宣传,仅限个人学习分享使用,禁止用于任何广告和商用目的。
7、本文档遇到问题,请及时私信或留言给本站上传会员【w****g】,需本站解决可联系【 微信客服】、【 QQ客服】,若有其他问题请点击或扫码反馈【 服务填表】;文档侵犯商业秘密、侵犯著作权、侵犯人身权等,请点击“【 版权申诉】”(推荐),意见反馈和侵权处理邮箱:1219186828@qq.com;也可以拔打客服电话:4008-655-100;投诉/维权电话:4009-655-100。

注意事项

本文(基于编译原理的计算器设计和实现.doc)为本站上传会员【w****g】主动上传,咨信网仅是提供信息存储空间和展示预览,仅对用户上传内容的表现方式做保护处理,对上载内容不做任何修改或编辑。 若此文所含内容侵犯了您的版权或隐私,请立即通知咨信网(发送邮件至1219186828@qq.com、拔打电话4008-655-100或【 微信客服】、【 QQ客服】),核实后会尽快下架及时删除,并可随时和客服了解处理情况,尊重保护知识产权我们共同努力。
温馨提示:如果因为网速或其他原因下载失败请重新下载,重复下载【60天内】不扣币。 服务填表

基于编译原理的计算器设计和实现.doc

1、基于编译原理计算器设计与实现 一方面看一下这个计算器功能:CALC set a = 1;b = 2CALC set c = 3CALC calc (10 + pow(b,c) * sqrt(4) - 135.0CALC exit如上所示,这个计算器功能非常简朴:1. 用set命令设立上下文中变量。 2. 用calc命令计算一种表达式值。 3. 用exit命令退出计算器。 咱们把编译重点放在calc命令背面计算表达式解析,其他某些咱们可以简朴解决(如set命令可以这样简朴解决:先按分号分隔得到各种赋值解决,再按等号分隔就可以在上下文中设立变量了,并不需要复杂编译过程)。如上演示例子中,咱们使用编

2、译技术解决某些是(10 + pow(b,c) * sqrt(4) - 1,其他某些咱们只使用简朴文本解决。麻雀虽小,但五脏俱全,这个计算器包括编译技术中最必要某些。虽然这次咱们只是实现了一种计算器,但所使用技术足以实现一种简朴脚本语言解释器了。这个计算器分为如下几种某些:词法分析:把表达式文本,解析成词法元素列表(tokenList)。 语法分析:把tokenList解析成语法树(syntaxTree)。 语义分析:把语法树转成汇编语言代码(asm) 汇编器:把汇编代码翻译为机器码(字节码)。 虚拟机:执行字节码。 普通编译步聚中不包括“汇编器”和“虚拟机”,而这里之因此包括这两个某些是由于:

3、普通编译器会直接由中间代码生成机器码,而不是生成汇编代码,而这里我之因此要生成汇编代码因素是“调试时候汇编可读性较好”,如果直接生成目的代码,则会非常难用肉眼来阅读。 自己实现虚拟机因素是:既有机器(涉及物理机和虚拟机以及模仿器)指令虽然也很丰富,但似乎都没有直接计算“乘方”或“开方”指令,自已实现虚拟机可以任意设计计算指令,这样可以减少整个程序复杂度。 因汇编器与虚拟机并不是编译原理某些,所如下文中并不会描述其实现细节,但由于计算器代码编译后目的代码就是汇编代码,因此需要把汇编指令做一下阐明(如下把这个汇编语言简称为ASM)。ASM指令简介指令助记符 操作数 指命阐明 storenumber

4、把number放入栈顶add从栈顶取出两个数相加,并把成果压回栈中sub从栈顶取出一种数做为被减数,再取一种做为减数,相减之后成果入栈mul从栈顶取出两个数相乘,并把成果入栈div从栈顶取出一种数做为除法分子,再取出一种做为除法分母,相除成果入栈pow从栈顶取出一种数做为底,再取出一种做为幂,计算成果入栈sqrt从栈顶取出一种数,把这个数开平方后成果入栈print在控制台打印栈顶数字这个虚拟机是基于栈来设计,所有计算指令操作数都从栈中取,store命令向栈顶添加数据。print指令用于打印当前栈顶数据,在咱们编译汇编代码要做到:对的计算出成果,且计算完毕之后成果要刚好在栈顶,这样最后调用一种p

5、rint指令即可以控制台看到计算成果。ASM举例:例1,如果咱们要计算1-2*3,则咱们写出汇编代码如下(行号是为下文解释代码以便而放上去,不是代码一某些):点击(此处)折叠或打开 store 3store 2mulstore 1sub print 对这段代码阐明如下:前两行向栈顶添加两个数字,先压入3再压入2,这样栈顶数字是2,第二个数字是3。 第三行mul会从栈顶弹出两个数字(2和3)计算乘法,并把成果(6)再压入栈中,此时栈中只有一种数字6。 第四行向栈顶压入一种数字1,此时栈顶为1,第二个数字是6。 第五行sub指令从栈顶取出两个数字,第一种数字1做为被减数,第二个数字6做为减数,即计

6、算1-6,并把成果压入栈中,此时栈中只有一种数字-5。 最后一行print指令不对栈做写操作,只读取栈顶数字,并打印出来。 在这里,咱们用到两个运算,mul和sub,这两个运算都是二元运算,因我在设计指令时候,先取出来数字是第一种操作数,因此先压入应当是第二个操作数,这也是为什么代码中先压入是3,之后是2,最后才是1。 例2,如果咱们要计算(10 + pow(2,3) * sqrt(4) - 1,则咱们写出汇编代码如下(行号是为下文解释代码以便而放上去,不是代码一某些):点击(此处)折叠或打开 store 1store 4sqrtstore 3store 2powstore 10addmuls

7、ubprint 对这段代码阐明如下:这段代码稍有点复杂,但有前一段代码经验,咱们可以看到,所有操作数先后顺序是从右向左store,因此store指令顺序是固定下来,剩余核心是操作指令应当放在哪里。 操作指令也是有一种规律,即:当前栈顶数据刚刚好满足某运算时,则操作指令就放在哪里,如: store 1时候没有任何操作操作数都在栈中。 store 4时候,刚刚好sqrt操作数都在栈中,则此时加入sqrt指令。 store 3 store 2时,刚刚好可以计算pow。 store 10之后,就可以计算加法了,因此此时加入add指令。 add计算完毕之后,再加上之前已计算完sqrt指令,则此时乘法所有

8、操作数都在栈中了,则此时加入mul指令。 最后减操作也可以计算了,则加上sub指令。 所有计算完毕之后打印出成果。 在这个例子中,我所说“规律”其实就是“后缀表达式”。咱们寻常写算术表达式是“中缀”,即符号在操作数中间,如1 + 2, + 在1和2中间,转为后缀形式即为1 2 +这里由于我对于参数顺序设计是与“正常”顺序相反,因此1 + 2对于这个汇编器来说,其后缀形式就应当为2 1 +人们是可以按照这个规律,相对简朴实现这个计算器只要做好词法分析就可以按照后缀表达式规律直接由tokenList生成汇编代码了但咱们目是用这个计算器例子来讲编译,因此还是按步就班来讲。词法分析词法分析目是把一段文

9、本分解成词法元素列表,例如(10 + pow(2,3) * sqrt(4) - 1,做词法分析之后会分解成如下几种词法元素: (10+pow(2,3)*sqrt(4)-1这里只是做了一次文本解决在解决之前,咱们手里有东西就是一串字符构成字符串,在解决之后,咱们按照一定“规则”分解为各种单词。算法是各种各样,有创造力程序员会想出各种办法来解决这个单词分解问题。在编译原理中,普遍方式是用如下一种状态转换图来实现在图中,“椭圆形”是状态,状态与状态之间有一条有方向线,这个线代表从一种状态到另一种状态途径,在这条线上面有一种花括号代表从前一种状态到达 后一种状态输入(为以便表达,0-9表达从0到9十个

10、数字,a-z表达从a到z二十六个字母等),如从START状态开始,输入一种数字1,就会到达 INT状态。图中蓝色状态是终结状态,如果从START状态通过若干输入后到达终结状态,则这些输入字符可合并为词法合法单词这里需要额外阐明一点:在对输入进行匹配状态时采用贪婪方式,即:尽量输入更多字符。在辨认到一种合法词法单元之后,状态回到START继续辨认下一种元素,直到没有新元素为止。这个状态转换图在编译原理中有一种专有名词称呼它“拟定有限状态自动机”,英文简称DFA。这里“拟定”意思是每一种状态通过一种输入字符可以拟定只有 一条途径到达另一种状态,“有限”意思是,状态个数是有限。对于一种复杂语言状态数

11、量是这个状态机几种数量级大小。但咱们当前计算器只需要 这几种状态就够了。普通DFA会由工具读取正则办法描述而生成,而不是直接手工构造,但对咱们当前设计计算器来说,其DFA非常小,手工构造是很以便,因此就不用工具了。此外,如果使用工具话,我这篇文章也不会使用既有工具,而是自己实现一种工具。下面举个例子:咱们有一种表达式12.3 + abc,下面我来描述一下DFA运营过程:定义一种变量s,来表达当前状态机所在状态(初始时s = START)。 输入第一种字符1,此时START状态可接受这个输入1,到达INT状态,则变量s赋值为INT状态,此时可看到INT状态颜色是蓝色表达当前可辨以为一种合法词法元

12、素,但由于咱们规则是贪婪匹配,因此咱们还要看看与否还可以匹配更多字符。 输入第二个字符2,此时INT状态也可以接受这人输入,并到达INT状态(转了一种圈又回到本来状态),此时变量s赋值为INT状态。 再输入第三个字符 . ,此时INT状态可接受 . 到达NUMBER状态,此时变量s赋值为NUMBER状态。 此时咱们再向前看一种字符 + ,此时状态NUMBER无法接受这个字符了,同步咱们可在看到NUMBER状态颜色是蓝色,表达当前状态可辨认一种合法词法元素,即:从 START开始到当前咱们一共经历输入为12.3,则这个单词做为第一种合法词法元素。 第一种词法元素辨认成功之后,变量s要回到STAR

13、T状态,并继续输入 + ,此时从START状态可到达ADD状态,且ADD状态并不容许接受任何输入,同步ADD状态颜色也是蓝色,则咱们又辨认出第二个词法元素 + 。 在辨认到第二个词法元素之后,变更s要回到START状态,并继续输入a,此时START状态可由a指向途径到达ID状态,此时s赋值为新状态ID。 当前状况与辨认NUMBER状况类似,当前状态也是终结状态,但按贪婪匹配原则还要继续看看与否可以匹配到新输入。 背面b和c都在ID一种状态里转圈,我就不在赘述了,这样咱们就辨认到了最后一种词法元素abc了。 用如上过程办法可以辨认任何对的算术表达式,但尚有几点需要特别阐明: 如何辨认错误:当前我

14、见过语言规范都在描述如何对的编译一种语言,但没有一种规范有描述如何报错,因此咱们当前能做是只要按正常走法走不通了,那就是错了,就要报错,并尽量提供详细错误信息。 对空白辨认:我在DFA中并没有画辨认空白某些,因素是对于计算器程序来说,空白完全没有用处,因此我只是在代码中直接忽视这个输入,以免状态机无法 辨认空白,同步在辨认到词法元素之后,去掉单词先后空白。对于某些语言来说,空白是故意义,需要做为词法元素辨认出来,不能忽视掉。 对于词法元素表达:普通咱们会用一种类型Token来表达一种词法元素,Token中有两个属性,一种用于表达这个Token类型,另一种用于表达内 容,只有数字与标记符才需要使

15、用Token类型内容属性,其他类型由于同一类型只有一种表达也许,因此就不需要再把内容保存下来了(如ADD内容 一定是+)。 关于标记符:DFA中ID状态用于辨认标记符,这里标记符涉及自定义变量,也涉及函数名。在DFA设计过程中,咱们可以选取把普通标记符与保存字 做为不同状态,也可以用使用同一种状态。咱们当前设计就使用了一种ID状态表达所有标记符,而在辨认到一种ID之后,咱们在看与否是一种保存字,这 样在返回Token对象时设立不同类型。 对于INT和NUMBER:这个计算器所有计算都使用是double类型数字,所在虽然咱们词法可以辨认到INT,但咱们定义Token类型 时,就只定义一种NUMB

16、ER类型,INT或NUMBER状态拟定单词都返回NUMBER这个类型Token对象。 前面逻辑中有一种贪婪批配原则,这个原则在某些语言词法中会有某些特例不合用,例如在C+和JAVA中均有一种运算符“”,这个运算符代表右移,但在C+11 原则中可以写出这样代码std:vectorstd:vector,在JDK5及以上版本也可以这样写 ListList,这里如果按贪婪批配就错了。因此必要在词法分析中加入上下文判断才干决定是按两 个来辨认还是按一种来辨认上下文判断是语法分析某些了,但对于复杂词法构造在没有一种统一词法解析算法可以解决情 况下就得借助更高档办法了。 当前剩余就是写代码了。 如果我把代码

17、贴在这里就太长了,不太适当。所如下面我只描述一下描述DFA思路: 思路一:直接静态代码来描述,类似这样方式把状态机每个途径都描述出来:IF S = START AND c = 1 THEN S = INT . ELSIF .,这样就可以输入运营了。 思路二:表格驱动式,例如列出下面表格即可懂得哪个状态通过哪个输入之后到达哪个新状态了下表左标题是当前状态,上标题是在当前状态上输入,表格中内容是通过途径到达状态。 思路三:结合前两个思路先写一种代码生成工具,工具读取“思路二”中表格中内容,并生成“思路一”中静态代码。0-9 . _a-zA-Z + - * / ( ) ,START INT POIN

18、T ID ADD SUB MUL DIV LBT RBT COMMA INT INT NUMBER POINT NUMBER NUMBER NUMBER ID ID ID ADD SUB MUL DIV LBT RBT COMMA 下面举一下DFA返回Token对象类型:NUM,FUN,VAR,ADD,SUB,MUL,DIV,LBT,RBT,COMMA这里前三个与DFA状态名不同:NUM代表INT状态和NUMBER状态两个状态辨认出词法元素。 FUN和VAR都是ID状态辨认出元素,如果这个ID是一种函数名,则Token为FUN类型,否则即为VAR类型。 其他类型与DFA状态是一一相应。 最后说

19、一下DFA接口:假设DFA有一种办法叫做parse,则这个办法参数只有一种字符串用于传入表达式即可。如果咱们写DFA是用于解析长文本(而不但仅是解析这个简短算术表达式),则可以考虑参数为输入流。 parse办法返回可以考虑返回一种Token游标,游标供语法分析程序调用;也可以考虑解析完所有词法元素返回一种Token列表。由于当前比较通用语法分析算法只需要向前看一种Token对象,因此游标就足够了。 由于语法分析程序有也许会把已读出来Token放回去,下次再用,因此游 标可考虑加一种putBack办法也可以考虑不实现这个办法,而由语法分析程序自己缓存当前用不到词法元素如果DFA返回是list就简

20、朴一 些,语法分析器可此先后先后移动offset即可。 DFA返回list方式实现虽然简朴一点,但对于要解析数据非常大状况就不合用了特别数据是从网络上以流方式传过来,这样咱们主线不懂得什 么时候这个流会结束,因此还是需要一种元素就返回一种元素做法比较安全对于咱们当前做计算器程序来说,随便怎么做都可以了。 语法分析语法分析是把词法分析过程返回tokenList转换为语法树过程。词法分析成果是为语法分析服务,语法分析自然也是为下一步语义分析做准备,在这一节中,咱们只讲一下语法树是怎么构造,下一节语义分析某些再讲如何使用语法树。语法树是一种树形数据构造,树每个根节点表达一种语法构造,子节点表达构成根

21、这个大语法构造小语法构造。这样不断划分更小语法构造直到无法再分子节点,其实就是一种整体与某些关系。因树形构造是要画图,但咱们普通工具是文本工具,因此咱们通惯用类似如下文本形式来表达树形构造:NODE1 -NODE2 NODE3NODE2 - NODE4这个表达意思是:NODE1为根节点,NODE1有两个子节点NODE2和NODE3,NODE2又有一种子节点NODE4。这样即可用文本形式表达树形构造了。这种表达形式叫“巴科斯范式”,英文简称为BNF下文中有需要表达这个名称地方,我就直接用BNF三个字母来代替了。下面咱们看一下这个计算器BNF(对于复杂语言BNF描述要写几十页,但咱们计算器就只有这

22、样几行):点击(此处)折叠或打开 exp - term exp - term +exp exp - term -exp term - factor term - factor*term term - factor /term factor - varName factor - number factor - -number factor - funCall factor - ( exp ) funCall- funName ( params ) params - exp params - exp ,params 下面对这个语法构造描述一下:exp为算术表达式,即为咱们要分析表达式整体。 ter

23、m是可做加法或减法项。 咱们可看到exp有三行表达基构造,这三行是或关系,即:exp也许是一种term,也也许是一种term加上一种exp,也也许是一种term减掉一种exp。也就是说,exp这个根节点子节点也许只有一种节点,也也许有三个节点。 factor是可做乘法或除法项。 term继续分解过程与exp是完全同样,这里就不再赘述。 这里把加减法与乘除法分开为两种语法构造因素是,乘法与除法优先级高于加法与减法,按照咱们当前表达,在计算加法或减法之前必要先计算term,这样因term是先计算,因此优先级就表达出来了。 varName是变量名,number是数字,funCall是函数调用 对于f

24、actor构成某些就可理解为:可觉得一种变量,或一种数字,或一种减号连着一种数字(负数),或一次函数调用,或用小括号括起来表达式。 funName是函数名,params是函数调用参数。 funCall构造为:函数名后跟着一种括号,再跟着一种或各种参数,再跟着一种括回。 params由两个产生式,因每个参数都可以是单独算术表达式,因此一种exp节点即可表达一种参数,如果有各种参数,则exp后成要跟着一种逗号,之后再跟着其他参数。 这种表达办法相对于树形表达来说,有一种长处,即:相对比较容易表达“或”,例如factor这个节点也许由变量名来表达,也也许由数字来表达,如也许是,这样咱们如果画图话,如

25、何表达各种也许中只选其中一种状况呢?上面表达是对语法构造抽象表达,抽象东西还是详细化为咱们代码中数据构造。咱们目是把咱们算术表达式变成符合语法树描述样子,举个例子来说:(1 + 2) * 3转成语法语法树就是下图所示样子:图中蓝色某些为语法树叶子节点,也就是不能再分解节点。这些叶子节点正是词法分析某些词法元素。下面咱们看一下来看一下构造这个树环节:上 面图中只有三种非叶子节点类型(exp、term、factor)以及几种叶子节点类型number、*、+、(、),所如下面我只以这几种为 例做一下描述,其他节点类型解析环节是类似因+*()这几种符号在描述中会不太好看,所如下文中我改用add、mul

26、、lbt、rbt来表达。 节点类型: 咱们可觉得每种节点类型单独创立一种类,如:exp类型节点即为Exp类型对象,term类型节点即为Term类型对象 也可以考虑只用一种类型TreeNode,而用Node中一种属性type来表达不同节点类型。 我选取用第二种方式来表达节点类型,这样对于子节点表达也相对简朴直接用一种list即可。 对于number类型节点,还需要一种属性来表达数字内容,因此TreeNode类中除了type字段之外,还要有一种content字段varName或funName类型也是需要content字段,用于表达变量名或函数名。 为每个非叶子节点写一种函数来解析这个语法构造,如:

27、parseExp、parseTerm、parseFactor,由于咱们每个语法构造(或子语法构造)都是TreeNode类型对象为根树,因此这几种函数返回值类型为TreeNode。 每个函数内构造化代码与BNF中描述可完全相应得上,例如: exp有三个产生式:exp - term和exp - term + exp和exp - term - exp,则parseExp代码可以按下面环节来写: 创立一种类TreeNode对象expNode,并指定其类型为exp。 调用parseTerm函数解析出expNode第一种子元素termNode。 向前看下一种词法元素(如果尚有下一种话): 如果为add或s

28、ub类型Token,则创立第二个子元素opNode(这个元素类型为add或sub),之后再递归调用parseExp解析第三个子元素。 如果不为add或sub类型Token,则一定是出错了。 反回expNode。 term有三个产生式:term - factor和term - factor * term和term - factor / term,这三个产生式与exp三个产生式是同一种格式,因此代码也几乎是同样。 factor有五个产生式,但咱们当前这个例子用不到变更和函数,因此只看其中三个:factor - number和factor - - number和factor - ( exp ),则p

29、arseFactor代码可以这样写: 创立一种类TreeNode对象factorNode,并指定其类型为factor。 向前看一种词法元素: 如果是num类型Token,则创立一种number类型TreeNode对象做为factorNode子节点(这个节点content值为当前这个Token值)。 如果是sub类型Token,则再从tokenList中读出一种num,创立一种number类型TreeNode做为factorNode子节点(这个节点content值为当前这个Token值把符号位反过来)。 如果是lbt类型,则先创立一种lbt类型TreeNode做为factorNode第一种子节点

30、,再调用parseExp函数来获得第二个子节 点,再向前看一种Token,如果是rbt类型,则创立一种rbt类型Token做为第三个子节点(如果看到不是rbt类型就要报语法错误了)。 返回factorNode。 按上面描述逻辑实当代码是要不了多少行代码就可以实现这个计算器语法解析了。尚有两个funCall和params,这两个类型解析与exp或term或factor差不多,就不再描述了。下面还要说一点额外注意事项:咱们当前写代码这种方式叫做LL(1)型,也叫递归向下型语法分析,在这种 语法分析方式中,咱们总是先创立根节点,再创立子节点,在创立子节点时,咱们总是由最左边节点开始一种一种去创立(如

31、parseExp中,咱们先创立出 来就是expNode,然后再创立是termNode,之后如果尚有元素话就会创立opNode和下一种expNode),这种语法解析方式是最容 易用代码实现,因此我才使用这种方式来做,但这种解析方式有一种局限性:语法BNF描述中不能有左递归。何为左递归呢?举个例子来说:exp产生式 中两个exp - term + exp和exp - term - exp,如果改为exp - exp +term和exp -exp - term,也是对,但按照这样产生式来写代码时,第一种要解析就不是term,则还是一种exp,要解析第一种TreeNode就要递归调用parseExp函

32、数了,这样就会形成无限递归了。因此咱们在设计LL(1)型文法描述时要避免左递归。 LL(1)文法设计在解析复杂语言时会有语法描述上局限性,如:C语言 一条语句开头如果为ABC,则这个ABC也许一种变量名,也也许是行号,此时必要再向前看一种Token才干懂得应当使用哪个产生式,这就变成 LL(2)型了LL背面括号中数字代表咱们在辨认语法产生式时,要向前看几种Token这样语法解析代码就复杂诸多了。那么,有无实现简 单,且语法描述能力又强解析办法呢?答案为:是有。但本文只需要实现计算器语法,就没有必要喧宾夺主来耗费大量篇幅来写其他语法解析方式。后来 如果有时间可专门再写一种博文来讲语法解析。 语法

33、分析有时也是要判断上下文,例如:假设在C+代 码中有这样一句:f(c);这句代码在没有上下文状况下是不能判断是什么,它也许是一种函数调用(f是函数名,A和B是范形参数,c是函数参数),也也许是一 个逗号表达式(f,A,B,c都是变量或值,这一名就表达为f不大于A,B不不大于c)其实这是C+语法上一种考虑不周之处,这给编译器设计者带来了很大麻烦,JAVA对于范型办法调用就避免了C+尴尬:JAVA中范型参数位置不在办法名与参数之间,而在办法名之前,如: f(c),这样就不会与其他JAVA语法冲突了。从这也可看出,语法设计对语法解析程序编写影响是非常直接。最后再讲一点稍微偏门一点东西:上面解说析过程

34、是正统语法解析方式,用这样方式来解析计算器算术表达式有点杀鸡用牛刀感觉。对于算术表达式语法树可以用以更简朴构造,例如:表达式(10 + pow(2,3) * sqrt(4) - 1语法树可以表达到下面图形:这个图显然比正统语法解析方式成果树要小诸多由于这个树中只有终结符号,exp、term、factor等非终结符号是不存在。对于这样语法树语义分析也更简朴,背面讲语义分析时再讲一下如何解析这个袖珍版本语法树。这个树构造即:所有叶子节点都是操作数,所有根节点都是操作符同步人们也可以注意到括号不见了,事实上括号在语法树中也是不必要。至于这个语法树如何构造就留个悬念,不在这里讲了。语义分析语义分析是把

35、语法树转成中间代码,再由中间代码转成目的代码。但对于简朴分析来说,咱们省略中间代码环节,直接读语法树生成目的代码(目的代码即为前面讲过ASM代码)。虽然对于复杂语言来说语义分析这个某些是非常复杂,但对于语法与设计都很简朴语言来说语义分析这个某些简直简朴到可以合并到语法分析过程中去做了,只是咱们当前目是用计算器例子来讲编译过程,因此这个某些还是简朴讲一讲。我之因此说这个计算器语义分析可以合并到语法分析中是由于计算器构造中没有上下文判断,因此语法分析不报错话,语义分析就一定没有问题了。咱们还是以在语法分析中 (1 + 2) * 3 例子来讲语义分析,这个表达式语法树还是这个:语法分析是构造语法树,

36、语义分析是读语法树,因此语义分析代码与语法分析代码是相通,读这个语法树咱们也是需要parseExp、parseTerm、parseFactor三个函数,只是这三个函数参数不再是tokenList,而是TreeNode,返回值不再是 TreeNode,而要返回ASM代码(直接返回字符串文本即可但是直接返回一种字符串列表也许更好某些,这样海汇编器直接受到就是一种个汇编表 示了)。下面分别描述一下parseExp、parseTerm、parseFactor三个函数逻辑:parseExp:检查一下exp节点子节点有几种,如果只有一种,则这一种一定是一种term构造,则把这个节点传给parseTerm得

37、到成果并返回即可;如果有三个,则对第三个参数调用parseExp得到一种asmCode,把第一种参数传给parseTerm得到一串汇编指令,并加到asmCode背面做为新asmCode,最后,判断第二个参数是add还是sub,直接向asmCode背面加上一种add指令或sub指令作为新asmCode,最后返回asmCode即可。 这里要阐明一种在解析三个子节点状况下为什么要从第三个开始,然后第一种,最后第二个呢?是由于: 咱们当前汇编器设计中,指令要从本中取操作数,因此要先把所有操作数搞定才干搞写指令,因此第二个操作数解决要放到最后。 咱们当前汇编器设计中,操作数出栈顺序是按咱们正常表达式从左

38、到右顺序,因此入线顺序就要从右到左,因此第三个操作数先解决,然后才是第一种。 parseTerm:与parseExp也几乎是同样,也不赘述了。 parseFactor:在语法解析中咱们只将了这三个产生式:factor - number和factor - - number和factor - ( exp ),因此在这里咱们还是继续以此为例。 如果factor第一种子节点是一种number类型,则直接返回store + number指令即可。 如果factor第一种子节点是一种sub类型,则直接返回store - + number指令即可。 如果factor第一种子节点是lbt类型,则对第二个参数调用parseExp函数得到asmCode,并返回asmCode即可因而时第三个操作数一定是rbt,因此此时不论它也可以,其实在语法分析时本就可以直接不构造lbt和rbt这两个节点。 就这样,咱们就可以得到ASM代码了为了在控制台看到计算构造,咱们还要在ASM代码结尾加一种print指令。最后咱们要做事情是调用已经存在汇编器和虚拟机来运营了。 到当前所涉及到技术对于解决复杂语言是远远不够,但也足可以用这些知识来设计语法简朴,但功能强劲语言解释器,对于普通领域特定语言语法解决都是足够了。后来有机会也许会再写其他关于编译技术文章。

移动网页_全站_页脚广告1

关于我们      便捷服务       自信AI       AI导航        获赠5币

©2010-2024 宁波自信网络信息技术有限公司  版权所有

客服电话:4008-655-100  投诉/维权电话:4009-655-100

gongan.png浙公网安备33021202000488号   

icp.png浙ICP备2021020529号-1  |  浙B2-20240490  

关注我们 :gzh.png    weibo.png    LOFTER.png 

客服