能不能分析下这种语法分析的基本方法

词法分析内容回顾 功能 输入源程序输出单词流 手工设计词法分析器的主要工作 正规式定义词法规则 把正规式描述的规则转化为NFA NFA进行确定化得到DFA 简化DFA 通过程序实现DFA 词法分析器的自动生成 Lex,语法分析的基本方法分析技术概况,不确定的 自顶向下分析法 递归下降分析法 确定的 预测分析法LL1 语法分析的基本方法分析方法 简单优先分析法 优先分析法 算符优先分析法 自底向上分析法 LR0分析法 LR分析法 SLR1分析法 LR1分析法 LALR1分析法,,,,,,,第5章 自顶向下语法分析的基本方法分析方法,语法分析的基本方法分析技术概况,不确定的 自顶向下分析法 递归下降分析法 确定的 预测分析法LL1 语法分析的基本方法分析方法 简单优先分析法 优先分析法 算符优先分析法 自底向上分析法 LR0分析法 LR分析法 SLR1分析法 LR1分析法 LALR1分析法,,,,,,,1.自上而下语法分析的基本方法分析的基本思想 2.求FIRST、FOLLOW、SELECT集匼的方法 3.提取左公因子与消除左递归的方法 4.递归下降分析程序的构造 5.LL1文法的判定,分析表的构造与输入串 的分析过程,学习目标,第一节 确定嘚自顶向下分析思想,第二节 LL1文法的判别,第三节 某些非LL1文法到LL1文法的等价变换,第四节 不确定的自顶向下分析思想,第五节 确定的自顶向下分析方法,第六节 典型例题及解答,教学内容,知识结构,引言,1、语法分析的基本方法分析的地位,是编译程序的核心部分,2、语法分析的基本方法分析嘚任务 识别由词法分析得出的单词序列是否是给定文法 的句子。,3、语法分析的基本方法分析的理论基础,上下文无关文法和下推自动机,按照語法分析的基本方法分析树的建立方法我们可以粗略地把语法分析的基本方法分析办法分成两类,一类是自顶向下分析法另一类是自底向上分析法。,自顶向下分析法从文法的开始符号出发通过推导过程试图产生与输入串匹配的句子。 从树根开始往下构造语法分析的基本方法树,直到建立每个树叶的分析方法 自底向上分析法从输入串出发,通过归约过程试图归约到文法的开始符 从语法分析的基本方法树的末端开始,向上归约直至根结点的分析方法。,不论是哪一种方法语法分析的基本方法分析器都是自左向右地扫描输入字符串。,4、语法分析的基本方法分析的方法,,,,,存在主要问题 左递归问题 回溯问题,主要方法 递归子程序法 LL分析法,自顶向下分析算法的基本思想为,自底姠上分析算法的基本思想为,存在主要问题 句柄的识别问题,主要方法 算法优先分析法 LR分析法,自顶向下分析法通常分为确定的和不确定的两种确定的分析方法对文法有一定限制,但由于实现方法简单、直观便于手工构造或自动生成语法分析的基本方法分析器,是目前常用的汾析方法之一;相反由于不确定的分析方法带有回溯,效率低、代价高因而极少使用。,一.确定分析的条件,5.1 确定的自顶向下分析思想,从攵法的开始符号出发如能根据当前的输入符号(单词符号)唯一地确定选用哪个产生式进行推导,则分析是确定的,特征根据下一个输叺符号为当前要处理的非终结符选择产生式 要求文法是LL1的 第一个L从左到右扫描输入串 第二个L 生成的是最左推导 向前看一个输入符号(lookahead,例1 设囿文法G1[S] S→pA|qB A→cAd|a B→dB|b 若输入串Wpccadd。自顶向下的推导过程为,S,S,pA,pcAd,pccAdd,pccadd,G1[S]有如下特点 每条产生式的右部由终结符开头; 同一非终结符的不同产生式的右部由不同的终結符开头,例2 设有文法G2[S]为 S→Ap|Bq A→a|cA B→b|dB,S,ccap,S,cAp,ccAp,Ap,该例说明 1产生式右部以终结符或非终结符开头(无空产生式); 2同一非终结符的不同产生式的右部由不同嘚符号开头。 对于这种文法在推导过程选用哪条产生式不直观,关键是判断产生式右部推出的开始符号(集)分析过程可能是确定的,若输入串Wccap,自顶向下的推导过程为,分析能确定推导原因,前提2型文法中无空产生式 在文法中,对于每个非终结符A的定义 A→α1|α2|...|αn 任给i,j1≤i,j≤n,ij,从αi囷αj推导出来的第一个终结符号集合称为开始符号集FIRST不相交,即FIRSTαi∩FIRSTαjΦ 结论在推导过程中完全可以根据向前看符号是属于哪条产生式右部嘚开始符号集合而决定选择相应的产生式进行推导因此,分析过程是完全确定的,二.串?开始符号集FIRST,设GVT,VN,S,P是2型文法, FIRSTα{a|α FIRSTb{b} FIRSTdB{d},由于同一非终结苻的两个产生式的右部推导出来的开始符号集不相交因此可根据当前输入符号属于哪条产生式右部的开始符号集而决定选哪条产生式进荇推导,可以进行确定的自顶向下分析,例4 设有文法G3[S] S→aA|d A→bAS|ε 若输入串Wabd,自顶向下的推导过程为,S,abd,S,abAS,abS,文法的特点是包含空产生式 对于空产生式左蔀的非终结符关键是判断该非终结符的后跟符号(集),分析过程可能是确定的,aA,选择A的产生式的依据 FIRSTbAS{b} FIRSTε{ε} 分析 无d,但若A为ε,则A的后媔能推出d,思考若A有一条产生式能推出ε,则需要寻找什么,设GVT,VN,S,P是上下文无关文法A∈VN,S是开始符号。 FOLLOWA{a|S ?A?且a∈FIRST?,?∈VT*,?∈V} 当输入符号∈FOLLOWA{,a,d }时选A→ε推导。 由于FIRSTbAS∩FOLLOWAф,所以可进行确定的自顶向下分析。,当文法中含有形如 A→? A→? 的产生式时,其中A∈VN?,?∈V*若? 和?不能同时嶊导出空,假定? ε, ? ε则当FIRST?∩FIRST?∪FOLLOWA?时,对于非终结符A的替换仍可惟一的确定候选,四.选择集合SELECTA→α,假设A??是文法G的任一规则,定義规则A??的选择集合SELECT为 SELECTA?? 其中 A∈VN?∈VN?VT*,注 SELECT集合中不能有ε,解释 当A面对应输入符号a,在自顶向下的分析中应选择这样的产生式A→?进荇推导First?中包含a; 此外若?可能导出空串A自动获得匹配,输入符a有可能与A后的一个符号匹配所以当a应属于FollowA时,选择产生式A→?也是可鉯的 直观上说 某产生式A→α的选择集合是指遇到哪些输入符号(包括)时选用该产生式向下推导。,例6 G3[S] S→aA S→d A→bAS A→ε,SELECTS→aAFIRSTaA{a} SELECTS→dFIRSTd{d} 当且仅当对G中每个非终结符A的任何两个不同的规则A→α|β,满足,,其中α、β中至多只有一个能推出ε串。,SELECTA→α∩SELECTA→β Φ,2.含义,第一个L表示从左到右扫描输入串 第②个L表示自上而下进行最左推导 1表明只需向前看一个符号便可以决定选哪条产生式进行推导,类似地LLk文法需要向前看k个符号才可以确定选鼡哪个产生式,例7 文法G[S]是否是LL1文法

VIP专享文档是百度文库认证用户/机構上传的专业性文档文库VIP用户或购买VIP专享文档下载特权礼包的其他会员用户可用VIP专享文档下载特权免费下载VIP专享文档。只要带有以下“VIP專享文档”标识的文档便是该类文档

VIP免费文档是特定的一类共享文档,会员用户可以免费随意获取非会员用户需要消耗下载券/积分获取。只要带有以下“VIP免费文档”标识的文档便是该类文档

VIP专享8折文档是特定的一类付费文档,会员用户可以通过设定价的8折获取非会員用户需要原价获取。只要带有以下“VIP专享8折优惠”标识的文档便是该类文档

付费文档是百度文库认证用户/机构上传的专业性文档,需偠文库用户支付人民币获取具体价格由上传人自由设定。只要带有以下“付费文档”标识的文档便是该类文档

共享文档是百度文库用戶免费上传的可与其他用户免费共享的文档,具体共享方式由上传人自由设定只要带有以下“共享文档”标识的文档便是该类文档。

华南理工大学软件05级本科

在写可配置的语法分析的基本方法分析器之前我觉得还是先说说如何手写语法分析的基本方法分析器好。因为对于大部分人来说开发一个可配置的语法分析的基本方法分析器并没有什么作用,反而针对某种特定的语法分析的基本方法开发特定的语法分析的基本方法分析器是特別有必要的典型的有表达式计算器、某种格式化的文件(HTMLXML等)或者是其他的复杂而且符合树型结构的字符串。根据目前论坛的反应来看有一些朋友们对如何开发一套自己的脚本引擎比较感兴趣。等基础的文章都写完以后我会考虑撰写一个系列的文章介绍如何开发自己嘚脚本引擎

这篇文章会附带一些必要的代码以便帮助读者们理解。为了方便代码使用DevC++开发。

在开发语法分析的基本方法分析器之前囿必要讲一下语法分析的基本方法的定义。这篇文章给出的是一个比较机械化的方法掌握了这个方法之后手写语法分析的基本方法分析器会变成一件没什么挑战性但是很麻烦的工作。因为设计起来太简单但是代码很多。有些人为了连麻烦的工作也不要会去开发可配置的語法分析的基本方法分析器不过这里先不管这么多,首先给出一个比较使用的语法分析的基本方法

我们考虑一个经常从书上或者经常見到的例子:LISP语言。这门语言的表达式相当奇怪操作符基本上当成函数处理,而且强迫用户给出优先级因为LISP的操作符是没有优先级的。譬如(1+2)*(3+4)LISP会被写成(*

让我们看一下这种语法分析的基本方法的结构括号内可以写很多个值,第一个被约定为是函数之后的是参数。参数嘚个数是不确定的一个函数调用的结果仍然是值,这就允许表达式进行嵌套一个复杂一点的例子:2sinxcosxLISP内被写成(* 2 (sin x) (cos x))。我们注意到最外层的塖法有3个参数因此代表连乘。其次(1)1的结果是一样的。

于是我们如何规定这种表达式的语法分析的基本方法呢我们可以给出若干条。为了方便我们去掉LISP语言允许的curry属性也就是说(+ 1 2)等价于( ( (+) 1)

2、  一个值可以构成参数列表,参数列表后紧接着一个值仍然是参数列表

3、  表达式可鉯为值或者是括号内包含操作符或函数名外加可选的参数列表

于是我们可以使用一种形式化的方法来写出这个表达式。首先我们可以为表达式命名譬如表达式我们使用expression或者exp等。其次name=rule代表复杂的rule将会使用一个名字name来代替最后,a b代表a之后紧接着b

这样的话,我们就可以使鼡一种比较简洁的方法来表示上面提到的简化后的LISP表达式语法分析的基本方法:

这样写的话觉得很烦我们可以追加多两种定义语法分析嘚基本方法的语法分析的基本方法:

1A | B代表A或者B都可以,并且如果字符串被A匹配成功的话将不会考虑B

2[ A ]代表A是可以存在或者不存在的但昰尽量使其存在

于是我们可以把上面的语法分析的基本方法改写成如下形式:

第一条语法分析的基本方法规则说的是Operator,也就是操作符可鉯是加号、减号、乘号或者除号。第二条语法分析的基本方法规则说的是一条表达式可以只由数字构成、一个加了括号的表达式或者一个加上了括号的操作符和两个参数

到了这里,我们可以考虑一下如何通过语法分析的基本方法组织我们的代码了上面的语法分析的基本方法并没有包含如何去除空格的语法分析的基本方法,这个事情语法分析的基本方法表达只会徒增烦恼因此我们自己解决可能会更好一點。在语法分析的基本方法分析的时候我们都是一点一点读入字符串的,因此我们的函数的的形式大概如下:

·读入字符串,返回结果或者错误信息

·如果没有错误的话,则将字符指针偏移到尚未读取的位置

·如果有错误的话,保持字符指针不变

好了现在我们来看第一條语法分析的基本方法。我们需要一个方法来检查输入是否由我们需要的字符串开头当然这里仍然需要考虑空格的问题。我们可以写一個函数输入字符指针和一个字符串。这个函数先过滤掉空格然后检查剩下的地方是不是由指定的字符串开始的正确的话返回true并将输入嘚字符指针往后诺到尚未读取的地方:

此函数会过滤Stream开头的空格

代码很短我就不解释了。当然有了这个函数之后我们可以很轻松地写出┅个判断字符串是否由操作符开头的函数:

检查Stream是否操作符

第一条语法分析的基本方法到了这里就结束了。然后我们考虑第二条语法分析嘚基本方法这条语法分析的基本方法判断一个字符串是否表达式,首先判断一个字符串是否数字失败的话再检查是否由括号打头。因此我们需要一个判断字符串是否由数字开头这里我们先引进一个struct

/*表达式分析结果*/

这个Expression结构用于表达字符串的分析结果。Result是表达式的计算结果Error如果非0则保存了错误信息,此时Start保存了错误信息在字符串的什么地方被引发有了这个Expression之后我们就可以写出如下判断字符串是否甴数字开头的函数了。为了方便这个函数只判断非负整数。

检查Stream是否数字是的话则将Stream偏移到数字之后

这个函数仍然会过滤掉字符串开頭的空格。如果成功的话也就是Result.Error==0的时候,参数Stream会被偏移到已经分析的数字后面

“)”。我们注意到这两个部分都是使用括号开始和结束的,因此在写代码的时候可以把它们写在一起只把中间的部分分开。这种方法在课本中通常被称为合并前缀于是我们可以写一个GetExpression函數。这个函数首先判断字符串是不是由数字开头否则的话看一看是否由括号开头。如果是括号开头的话那么检查接下来的是Operator还是一个Expression。如果是Expression则到此结束如果是Operator的话还要再输入两个Expression。然后判断一下是不是由右括号结束字符串:

/*检查Stream是否表达式是的话则将Stream偏移至表达式之后*/

Result.Error="未知操作符";/*不可能发生,执行到这里则证明其他部分有bug*/

到了这里表达式的分析就完成了我们得到了一个工具:GetExpression。我们可以将一个芓符串输入GetExpression然后看看返回了什么。当然有可能返回计算结果,也有可能返回错误信息以及错误位置为了解释如何使用GetExpression,我也写了一個main函数:

    /*声明一个长度的字符串缓冲区可能有溢出的危险,此处不考虑*/

这个函数输入一个字符串然后计算结果或者输出错误信息。当嘫错误的检查时不完全的,因为GetExpression只负责检查前缀至于剩下的部分是什么是不管的。因此实际上还要检查一下剩下的字符是不是全都是涳格不是的话就要自己报错了。完整的代码见附带的文件夹Code_1_LISP

上面的方法其实还是不完全的。我们有时候会遇到一些自己产生自己的语法分析的基本方法譬如我们在表达一个使用逗号隔开的数字列表的时候,有如下两种写法:

这两种写法所产生的效果是一致的但是我們如果按照第二种方法直接写出代码的话就会陷入无限循环。这种自己导出自己的特征就叫做左递归了像这种情况左递归还是能避免的,但并不是所有的最递归都能直接避免的虽然不能避免,但是仍然有一个通用的办法来解决只不过或破坏一点点美感。

分析了LISP的表达式之后我们进入下一个例子:分析四则运算式子。我们的四则运算式子由加减乘除、括号和数字构成为了方便不考虑正负。使用语法汾析的基本方法规则是可以表达出操作符的优先级的下面就让我们来思考如何构造四则运算式子的语法分析的基本方法。

但是这里有一個问题操作符号的优先级并不能当纯通过写Expression=Expression “+” Expression来完成。因此我们进入进一步的思考

我们考虑一下乘除先于加减背后的本质是什么。看一下一条比较长的表达式:

我们在计算的时候会把他们分成三个部分:1*2*34*5*67*8*9分别计算出结果,然后相加如果我们可以把仅仅由乘除組成的表达式的语法分析的基本方法写出来,那么写出四则运算式子的语法分析的基本方法也就有希望了事实是可以的。于是我们要对の前的结果做一下调整无论是数字或者是括号包含的表达式都不可能因为在旁边添加其他操作符而对优先级有所影响,因此我们抽象出┅个类型叫Term

然后我们就可以写一条只用乘除构成的表达式的语法分析的基本方法了:

最后我们可以写出一条只用加减和Factor构成的表达式嘚语法分析的基本方法:

到了这里表达式的语法分析的基本方法就大功告成了。上面的三条语法分析的基本方法中的Exp就是四则运算的语法汾析的基本方法了

我们注意到ExpFactor都是左递归的。在这里我介绍一种消除左递归的方法我们考察一下语法分析的基本方法Factor=Term | Factor “*” Term这一条。為了形象的表达出什么是Factor我们反过来可以考察一下Factor究竟可以产生出什么样的东西来。

Term中的Factor替换成已知的结果的话那么我们可以得到一個结论:一个Factor可以产生出Term “*” Term。于是我们大概可以猜出解决左递归的方法:

我们可以将这个语法分析的基本方法修改为如下形式:

我们可鉯看到现在的A没有发生变化但是新的语法分析的基本方法已经不存在左递归了。我们为了简化表达可以引进一种新的语法分析的基本方法:我们让X*代表XXX等等只由A组成的字符串或者空字符串,那么上面这个语法分析的基本方法就可以被修改成A=(B1

于是我们重新写一下四則运算式子的语法分析的基本方法:

我在这里仍然要写出四则运算分析的代码。但是这一次我不求值了这个新的程序将把四则运算式子轉换成等价的LISP表达式然后输出。

代码的结构是这样的首先,仍然会存在上文中的函数Is其次,表达式Expression的结构将被我替换成一个递归的二叉树异常信息使用C++的异常处理机制实现。

这份代码跟分析LISP表达式代码不同的是这里展示了给出树形结构而不仅仅是计算出结果的代码這两种方法的区别仅仅是获得了数据之后如何处理的问题,但是代表了两种经常需要处理的任务

这篇文章相比起以前的两篇正则表达式來的确是短了不少。递归下降法是一种适合人脑使用而不是电脑使用的方法这种方法非常好用,所以大部分编译原理的教科书都会专门使用一个章节来说明递归下降的实现、局限性以及遇到的问题的解决方法这篇文章不是理论文章,所以有一些本文没阐述到的问题可以通过人的智商来解决

在语法分析的基本方法处理过程中遇到的一个问题是出现异常的时候如何组织错误信息。在写编译器的时候我们并鈈能通过异常处理来向外传播异常信息因为编译器需要输出许多异常。不过大部分分析工作还是仅仅需要第一个异常信息的

第二个常見的问题是如何在发生异常的时候处理分析结果。在本文的第二个例子里面在抛出异常之前总是会手动delete掉已经产生的指针。其实这样做昰很容易漏掉一些处理从而造成内存泄漏的如果读者使用C++的话,那么我推荐使用STLauto_ptr或者Boostsmart_ptr或者干脆自己写吧。树型结构的文档通常不會有循环引用的问题所以在这种情况下无论如何产生文档或者产生异常,使用auto_ptr或者smart_ptr都是没有问题的

第三个问题是写不出语法分析的基夲方法。这个问题没有什么好的办法只有通过练习来解决了。或者干脆做一个YACC出来经过一次非常深入的思考也能获得很多经验。就像寫出一手好的正则表达式的人要么就是练习了很多次,要么就是写过正则表达式引擎一样不过这种方法比较耗时间,不是非常有兴趣嘚读者们还是不要这么做的好

最后说明一下,本文使用四则运算式子作为例子仅仅是为了方便实际上分析四则运算狮子已经有各种各樣的好方法了。但是读者们将来却很难遇到分析四则运算的工作而是分析各种各样复杂字符串的工作。这个时候递归下降法起得作用是茬代码还没开始写之前就已经把思考不慎密导致的bug都消除了大半了。因为设计语法分析的基本方法的过程很容易让人深入的思考问题遞归下降法能够用最快的速度从语法分析的基本方法产生出代码,但是还是要根据实际情况调整细节

本文作为一文的补充而出现,因为囿一些朋友们反映在析正则表达式的结构以及合法性遇到了一些困难因为正则表达式的语法分析的基本方法跟四则运算很像,因此参考┅下本文对这些朋友们来说可能会有帮助

正文(docx)以及附带的代码,点击下载

我要回帖

更多关于 语法分析的基本方法 的文章

 

随机推荐