编译原理-学习记录12

ch4.(2)自下而上语法分析(续)

  之前提到,因为在自下而上的语法分析中,只能进行最左直接规约,所以引入句柄的定义便十分重要,因为句柄能够被规约。此外,最左的可以被直接规约的子串是唯一的

  而关于移进-规约思想,这个思想主要面临着两大问题。一个是关于如何帮助程序找到句柄,另一个则是如何帮助分析程序判断句柄应该规约到哪个符号。对于这两个问题,可以用LR(0)项目来表示一个句柄的所有识别状态。除此之外,还应使用“记住历史”的方式,以帮助分析程序进行预测(句柄的识别需要借助实际句型中的所有历史信息)

  由“记住历史”这一思想而引出的“活前缀”定义,也是具有很大的意义的。将历史信息放在活前缀中,历史所对应的状态可以暗含动作走向。在分析过程中,如果得到的当前符号串是活前缀,则说明分析是正确的。而活前缀又有无限多种,现在需要构造能够识别所有规范句型的所有活前缀的程序,则应该使用有穷表达无穷的思想来实现,在这里自然就可以联想到使用有穷自动机来识别

  而在上次提到的有穷自动机的状态闭包时,有提到“如果 A → α . B β A\rightarrow \alpha.B\beta Aα.Bβ属于closure( I I I),则所有 B → . γ B\rightarrow.\gamma B.γ也属于closure( I I I)”,这是非常有道理的,因为在规约B之前,必须要先识别 γ \gamma γ

LR(0)分析表的构造(续)

DFA构造举例

  现有拓广文法G[S]如下:

G [ S ] : S → E E → a A E → b B A → c A A → d B → c B B → d \begin{aligned}G[S]:&\\ S&\rightarrow E\\ E&\rightarrow aA\\ E&\rightarrow bB\\ A&\rightarrow cA\\ A&\rightarrow d\\ B&\rightarrow cB\\ B&\rightarrow d\end{aligned} G[S]:SEEAABBEaAbBcAdcBd

  首先从S开始,构造开始状态 I 0 I_0 I0。最开始时, I 0 I_0 I0中所具有的产生式,是正在面对、准备接受E,打算对E进行规约的: S → . E S\rightarrow .E S.E

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XBWlVYz9-1606534339576)(C:\Users\蔡三圈\AppData\Roaming\Typora\typora-user-images\image-20201127191156219.png)]

  现对此产生式求闭包。根据规则,容易看出应该首先把E位于产生式左部的那些产生式挑出来,加入到 I 0 I_0 I0中(实际上,在规约E之前,的确需要先识别/规约E所对应的产生式的右部的第一个符号):

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PUsYNCvy-1606534339581)(C:\Users\蔡三圈\AppData\Roaming\Typora\typora-user-images\image-20201127191757191.png)]

  由于在新增的两条产生式中,所面临(打点处往后看)的都是终结符,因此在求闭包时就没有其他需要加入的内容了,由此完成 I 0 I_0 I0的构造。现分析 I 0 I_0 I0可能会接收的输入:E、a或b三种,如果接收输入为E,则第一条产生式可以变换为 S → E . S\rightarrow E. SE.,令此产生式位于 I 1 I_1 I1

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eN60PWn4-1606534339584)(C:\Users\蔡三圈\AppData\Roaming\Typora\typora-user-images\image-20201127192454194.png)]

  对 I 1 I_1 I1求闭包后,发现没有其他能添加进来的内容了,故 I 1 I_1 I1最终被确定下来,只含有这一条产生式。实际上,这条产生式中,E也已经被规约完成了,点后已无内容。此后,也无需再从状态 I 1 I_1 I1往后转换了,因为这里的点(分析)已经“到头”了

  回到 I 0 I_0 I0当中,现在探讨输入a后的情景。输入a后, I 0 I_0 I0中的第二条产生式变为 E → a . A E\rightarrow a.A Ea.A,将其放入到 I 2 I_2 I2中:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OEkaWCjI-1606534339590)(C:\Users\蔡三圈\AppData\Roaming\Typora\typora-user-images\image-20201127192734466.png)]

  再求其闭包。将A位于左部的产生式放进来:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-haUNJmVE-1606534339593)(C:\Users\蔡三圈\AppData\Roaming\Typora\typora-user-images\image-20201127192901603.png)]

  放进来后,闭包的构造正好完成,即完成了 I 2 I_2 I2的构造

  以此类推,直到构造完整个系统的所有状态:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-siqv7KZ8-1606534339594)(C:\Users\蔡三圈\AppData\Roaming\Typora\typora-user-images\image-20201127193131068.png)]

  注意到上图中 I 5 I_5 I5 I 8 I_8 I8是有一个自己指向自己的箭头,乍一看十分奇怪,但仔细一看,还是合情合理的:以 I 8 I_8 I8为例,如果输入c,则第二条产生式变为 B → c . B B\rightarrow c.B Bc.B,这又和第一条产生式一样了。而第一条产生式求完闭包后,将会再次得到和 I 8 I_8 I8一模一样的状态

  现在,整个DFA就都被构造完成了,注意到每个状态都是结束状态(即可以停在任何状态上)。有了这个DFA,理论上就已经可以利用符号栈和输入串来实现移进-规约了,以输入串“bccd”为例,初始情况如下(此时对应状态 I 0 I_0 I0):

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hF4rnFZK-1606534339596)(C:\Users\蔡三圈\AppData\Roaming\Typora\typora-user-images\image-20201127195618002.png)]

  现将符号b输入(叫“移进”可能更好一些)到符号栈中,根据DFA的构造,转换到状态 I 3 I_3 I3 t ( I 0 ,   b ) = I 3 t(I_0,\ b)=I_3 t(I0, b)=I3):

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EF9NUkmZ-1606534339597)(C:\Users\蔡三圈\AppData\Roaming\Typora\typora-user-images\image-20201127195642462.png)]

  接下来输入c,转换到状态 I 8 I_8 I8,再输入c,依旧转换到 I 8 I_8 I8

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GYxb86QS-1606534339600)(C:\Users\蔡三圈\AppData\Roaming\Typora\typora-user-images\image-20201127195723759.png)]

  最后输入d,转换到 I 9 I_9 I9

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UXT8yrVe-1606534339601)(C:\Users\蔡三圈\AppData\Roaming\Typora\typora-user-images\image-20201127195805458.png)]

  状态 I 9 I_9 I9对应的产生式为 B → d . B\rightarrow d. Bd.,因此可以将最后输入的那个d规约到B:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qZwHaZFm-1606534339604)(C:\Users\蔡三圈\AppData\Roaming\Typora\typora-user-images\image-20201127195900699.png)]

  现在,第一趟的规约完成了,将这些符号放回到输入串中,并回到状态0开始第二趟:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zqj6kiPJ-1606534339606)(C:\Users\蔡三圈\AppData\Roaming\Typora\typora-user-images\image-20201127200027063.png)]

  使用同样的方法。第二趟结束后,到达状态 I 11 I_{11} I11

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jUa9EnFn-1606534339608)(C:\Users\蔡三圈\AppData\Roaming\Typora\typora-user-images\image-20201127200115375.png)]

  状态 I 11 I_{11} I11对应的产生式为 B → c B . B\rightarrow cB. BcB.,说明cB可以规约到B。因此,将栈顶的cB换为B,并准备第三趟:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rFr9dmSH-1606534339609)(C:\Users\蔡三圈\AppData\Roaming\Typora\typora-user-images\image-20201127200227921.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YCXk2Y03-1606534339611)(C:\Users\蔡三圈\AppData\Roaming\Typora\typora-user-images\image-20201127200248978.png)]

  第三趟结束后,继续将栈顶的cB规约为B:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-evDtjfGF-1606534339613)(C:\Users\蔡三圈\AppData\Roaming\Typora\typora-user-images\image-20201127200310316.png)]

  而第四趟结束后,到达 I 7 I_7 I7 E → b B . E\rightarrow bB. EbB.,将bB规约为E:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZAfa5tNE-1606534339615)(C:\Users\蔡三圈\AppData\Roaming\Typora\typora-user-images\image-20201127200435369.png)]

  第五趟,到达 I 1 I_1 I1 S → E . S\rightarrow E. SE.,将E规约为S:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mmP6tTul-1606534339616)(C:\Users\蔡三圈\AppData\Roaming\Typora\typora-user-images\image-20201127200514149.png)]

  至此,规约完成,语法分析结束

  注意到这样的分析虽然可以顺利进行,但需要进行的趟数实在是太多了:每规约一次都要重头再来,这样的效率显然不是想要的。那么是否可以改成一趟就能解决的算法?答案是:可以。LR分析法的设计者非常聪明地搞了一张“LR分析表”,使用分析表来代替DFA工作时,能够很好地解决DFA的效率低下的问题。接下来,将会说明,如何从DFA变换到LR(0)分析表

  LR(0)项目集规范族的定义:将所有状态给出

构造LR(0)分析表

  LR(0)分析表的构造规则,大致可以由下述五点描述:
  1、若产生式 A → α . a β A\rightarrow \alpha.a\beta Aα.aβ属于状态 I i I_i Ii,且 t ( I i ,   a ) = I j t(I_i,\ a)=I_j t(Ii, a)=Ij,则置表中ACTION[i, a]= S j S_j Sj 回顾上回,在刚开始描述LR分析法的优势时,提到了在分析表中,S代表移进, S j S_j Sj即移进后将新得到的那个符号的状态设置为j。这正好对应了这条产生式所处的状态:即将移进a,到达状态 I j I_j Ij
  2、若 S ′ → S . S'\rightarrow S. SS.属于状态 I i I_i Ii,则置表中ACTION[i, $]=acc。 此产生式对应的状态为:将非终结符S规约到开始符号S’。显而易见,这代表着规约的完成,用acc标识
  3、若 A → α . A\rightarrow \alpha. Aα.属于 I i I_i Ii A → α A\rightarrow \alpha Aα为第j条产生式(事先给文法中的产生式进行编号),则对任意终结符a和$,均置ACTION[i, a]= r j r_j rj或ACTION[i, $]= r j r_j rj 回顾在分析表中,r代表规约, r j r_j rj即表示将句柄(编号为j的产生式右部)规约到编号为j的产生式左部。这么设计的原因在于,当前处于即将进行栈顶符号规约的状态,在规约前不该继续移进,所以便设置了不同于移进S的规约标识r,表示将栈顶的句柄全部替换为规约到的非终结符号,而状态则由句柄前的最后一个符号的状态来决定:
  4、若 t ( I i ,   A ) = I j t(I_i,\ A)=I_j t(Ii, A)=Ij(A为非终结符号),则在表中置GOTO[i, A]=j。 在规约到A的时候,状态由句柄前的最后一个符号所处的状态 I i I_i Ii决定,如果 I i I_i Ii输入A到达 I j I_j Ij,则栈顶A对应的状态便为 I j I_j Ij,因此在表中也填写了GOTO[i, A]=j
  5、其余元素均置“报错标志”。 如果按照上述几个规则,找不到对应的表项,则说明在DFA中走向了一个不存在的状态,从而说明出现了语法错误

  按照上述规则,前面提到的那个DFA便可以转换为如下的LR(0)分析表:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vhm3AWks-1606534339618)(C:\Users\蔡三圈\AppData\Roaming\Typora\typora-user-images\image-20201128105915758.png)]

  看上去已经很完美了。然而,看似完美的LR(0),却仍然有着一个非常致命的缺陷

  构造分析表时,可能会出现一个单元格中填两个元素的情况,这种情况称之为冲突。填表时,一个单元格中的两个元素,其中一个表示移进,另外一个表示规约时,称作移进-规约冲突。类似地,还有规约-规约冲突。而LR分析法又要求不能有冲突,因此LR(0)分析并不是万用的。由此,也有了LR(0)文法定义:在分析表中不含冲突项目

  这显然不是我们想要的情况,我们想要的是一种普适的方法。LR(0)的限制之多,使得这种方法仍需改进。因此,人们提出了一种改进方法:SLR(1)方法

SLR(1)分析表的构造

  LR(0)的“一整行都规约”(填写规约符号r时,总是在表中占满一整行的ACTION),要求太粗糙了。实际上,在语法正确的输入串的过程中,对于一种固定的符号栈栈顶句柄,其右边的输入串栈顶字符必然会出现在待规约到的符号的follow集中。因此,可以在LR(0)的基础上,在填写分析表的规约符号r时,只在出现在follow集中的那几列终结符号上填写r

  因为这种方法需要往前看一个单词,所以叫做SLR(1)分析法。至于在LR前面加了一个S,是因为这个方法还是过于简单(Simple)了,以至于仍然会有出现冲突的可能。这个时候,需要再进一步进行改进:仍然是向前看一个单词,但进行了更进一步的考虑,由此得名:LR(1)方法

LR(1)分析表的构造

  前面提到,即便是SLR(1)按follow集来填表,也会产生冲突,所以需要进一步改进

  回顾之前的两种方法:
  LR(0):只要栈顶像是句柄,一律进行规约
  SLR(1):出现规约项目时,只对follow集符号进行规约

  刚才(SLR(1))是认为当T可以规约为E时,向前看只能是follow集里面的符号。如果仔细一想,还是会发现这个限制有点粗糙了:T后面出现的一定是follow集的符号,但在给定的情况下,并不是follow集的所有符号都可以出现在这个位置(T的后面)

  在LR(1)中,不仅要知道状态(产生式右部打点),还要知道规约的这个句柄所处的环境。为了表示这样的环境,需要将状态中的项目 U → v . w U\rightarrow v.w Uv.w更改为 [ U → v . w ,   a ] [U\rightarrow v.w,\ a] [Uv.w, a],用以表示“已经从输入符号串中获得了句柄vw中的v,还希望从输入符号串中进一步获得w,并且当在分析栈顶获得完整的vw后,只有下一个输入符号是a时,才可将vw归约成U,否则不能归约。”。这样一来,便可以将在表中填写r的范围限制的更小

  因为状态中的项目不再是简单的产生式,而是[产生式, 向前搜索符号],所以对应的DFA也应该进行相应的修改,例如,对于文法:

G [ Z ] Z → S S → L = R S → R R → L L → ∗ R L → i \begin{aligned}G[Z]&\\ Z&\rightarrow S\\ S&\rightarrow L=R\\ S&\rightarrow R\\ R&\rightarrow L\\ L&\rightarrow *R\\ L&\rightarrow i\end{aligned} G[Z]ZSSRLLSL=RRLRi

  对应LR(1)分析的DFA为:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TGNSoLA2-1606534339621)(C:\Users\蔡三圈\AppData\Roaming\Typora\typora-user-images\image-20201128112150057.png)]

  为什么开始时是[S’ → \rightarrow .S, $]?因为项目反映的是句柄S的识别状态,输入串整个规约成S。在栈中得到S后,下一个单词是结束符号$。同一个句柄(的不同识别状态),环境是一样的

  在这里求闭包时,规则有所改变:
  如果[ A → α . B β ,   a A\rightarrow\alpha.B\beta,\ a Aα.Bβ, a]属于closure(I),则所有[ B → . γ ,   b B\rightarrow .\gamma,\ b B.γ, b]也属于closure(I),这里终结符b ∈ \in first( β a \beta a βa)

  为什么[A → α . B β \rightarrow\alpha.B\beta α.Bβ, a]后面是a,而[B → . γ \rightarrow.\gamma .γ, b]的右边却是b?这是因为 α B β \alpha B\beta αBβ都识别时,后面是a。在规约B之前,要先规约 γ \gamma γ γ \gamma γ后面跟着的应该是first( β \beta β),而 β \beta β为空时,跟在 γ \gamma γ后面的就是a

  相较于前两种分析法,LR(1)找到了问题的实质。当然,LR(1)的分析表实在是太大了,状态非常地多,可以考虑简化为LALR(1)

LALR(1)分析表的构造

  LR(1)方法的不足:分析表太大了,状态很多(要将follow集中的状态严格区分开),这便是LR(1)方法带来精确性的同时,所付出的代价

  LR(1)分析表中还是有很多空白(表示不可能出现的情况),可以考虑对不会引起冲突的状态进行合并,从而减少状态——LALR(1)分析表

  什么时候可以合并?为了更好地说明这一点,首先引入同心/同协项目的定义(下述定义摘自https://wenku.baidu.com/view/454474b2b9d528ea81c77943.html):

  定义:
  LR(1)项目的心:如果[A → α β \rightarrow \alpha \beta αβ, ss]是一个LR(1)项目,则其中的LR(0)项目部分称为它的心
  LR(1)自动机状态的心:一个状态所含有的所有LR(1)项目的心
  同心状态:如果两个LR(1)状态具有相同的心,则称这两个状态为同心状态

  在LR(1)的DFA中,如果没有同心状态且没有状态冲突,则该文法是LALR(1)文法。如果有同心状态且合并同心状态后有状态冲突,则是LR(1)文法(https://www.cnblogs.com/Alexkk/p/6033159.html)

  而在合并状态时,只要合并同心状态就行了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值