[编译原理-语法分析(一)] 提取左公因子和消除左递归

前言

写起来真是脑壳疼,没有看到有blog详解和代码的,所以又双叒来了!
代码400行,注释就有200行(可能是我写过注释占比例最多的一次)。

项目地址

github

后续会更新LL,LR文法,提取左公因子和消除左递归在 src/struct/CFG.java

参考链接

在学习的过程中也发掘到几篇不错的blog

自顶向下分析方法之表驱动LL(1)分析程序

前文

在正文之前,先说几个封装的结构,以文法E举例:

E → E + T    ∣    E − T E → E + T \ \ | \ \ E - T EE+T    ET

ProductionSet一个产生式体集合 ; E E E 对应的ProductionSet有:{ E + T E + T E+T E − T E - T ET }.

BodyItem一个产生式体 ; E E E有两个产生式体: E + T E + T E+T E − T E - T ET.

一个产生式子项, E + T E + T E+T有三个子项,分别是: E E E + + + T T T ; 每个子项都有一个子项属性, 此项是非终结符还是终结符 :


之所以封装成这样的结构,是为了在提取和消除时的方便,不然check时都是以字符串的形式,这样效率很低。

回顾单个文法的提取公因子和消除左递归


文法G,提取左公因子:
A → α β 1 ′    ∣   α β 2 ′    ∣   α β 3 ′    ∣   . . .   ∣ α β n ′    ∣   γ A →αβ_1' \ \ | \ αβ_2' \ \ | \ αβ_3' \ \ | \ ... \ |αβ_n' \ \ | \ γ Aαβ1   αβ2   αβ3   ... αβn   γ
替换为:
A → α A ′ ∣ γ A → β 1 ′    ∣   β 2 ′    ∣   β 3 ′    ∣   . . .   ∣   β n ′ A →αA' | γ \\ A →β_1' \ \ | \ β_2' \ \ | \ β_3' \ \ | \ ... \ | \ β_n' AαAγAβ1   β2   β3   ...  βn


文法G,消除左递归:
A → A α    ∣   β A →Aα \ \ | \ β AAα   β
替换为:
A → β A ′ A ′ → α A ′   ∣   ε A →βA' \\ A'→αA' \ | \ ε AβAAαA  ε

正文

举例文法G:
E − > E + T   ∣   E − T   ∣   ε T − > T ∗ F   ∣   T   /   F   ∣   F F − > (   E   )   ∣   i d E -> E + T \ | \ E - T \ | \ ε \\ T -> T * F \ | \ T \ / \ F \ | \ F \\ F -> ( \ E \ ) \ | \ id E>E+T  ET  εT>TF  T / F  FF>( E )  id

一、 提取左公因子
1. 归类

对一条产生式集合(ProductionSet)进行归类,存在相同左公因子的归为一类:

对于产生式头部 E E E,则有:
{   E + T   ,   E − T   } {   ε   } \{ \ E + T \ , \ E - T \ \} \\ \{ \ ε \ \} { E+T , ET }{ ε }
对于产生式头部 T T T,则有:
{   T ∗ F   ,   T   /   F   } {   F   } \{ \ T * F \ , \ T \ / \ F \ \} \\ \{ \ F \ \} { TF , T / F }{ F }

2. 提取

对归类的每个类别进行分解提取左公因子; 注意,提取时是取当前类别所有项的最长公因子:

假设存在类别E,对其提取左公因子:
{   E + T   ,   E − T   } \{ \ E + T \ , \ E - T \ \} { E+T , ET }
左公因子是: E E E


假设存在类别S,对其提取左公因子:
{   S + V   ,   S + (   V   )   ,   S − V   } \{ \ S + V \ , \ S + ( \ V \ ) \ , \ S - V \ \} { S+V , S+( V ) , SV }
左公因子是: S S S
有人可能认为类别S的左公因子是 S   + S \ + S + (笑,刚开始做的时候我就是这样认为的…),如果变成 S + S + S+,则 S − V S - V SV不能正确提取左公因子。


想想为什么要提取左公因子?
当选择产生式时,可能会面临不知道选哪个,提取左公因子的目的是为了将选择这个问题推迟到下一步,获取到足够的信息做出正确的选择。


那么,对于类别S,提取左公因子 S S S就算完结了吗?
S → {   S   S ′   } S ′ → {   + V   ,   + (   V   )   ,   − V   } S →\{ \ S \ S' \ \} \\ S' → \{ \ + V \ , \ + ( \ V \ ) \ , \ - V \ \} S{ S S }S{ +V , +( V ) , V }
S ′ S' S依然不能做出正确的选择,所以还需要对 S ‘ S‘ S进行一次归类提取才可。

提取左公因子就这两步就可以了,剩下就是一些零碎的判断了,比如一个类别只有一项肯定是不能提取左公因子的,还有其他等等。关于提取的算法想了两个,但是好像复杂度都比较高,详细的步骤想法有写在注释中(CFG/extract()函数的注释) ; 如果有更好的算法欢迎交流~

我还是喜欢匹配current个子项的那个想法,因为结构类似与这样:

提取左公因子就像找到这样一个结构的最靠近开始的那个节点。

二、消除左递归

举例文法G:
E → E + T   ∣   E − T   ∣   F T → T ∗ F   ∣   T   /   F   ∣   F F → (   E   )   ∣   D s   ∣   E D s → D   D s   ∣   D D → 0 ∣ 1 ∣ 2 ∣ 3 ∣ 4 ∣ 5 ∣ 6 ∣ 7 ∣ 8 ∣ 9 E→ E + T \ | \ E - T \ | \ F \\ T→ T * F \ | \ T \ / \ F \ | \ F \\ F → ( \ E \ ) \ | \ Ds \ | \ E \\ Ds→ D \ Ds \ | \ D \\ D → 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 EE+T  ET  FTTF  T / F  FF( E )  Ds  EDsD Ds  DD0123456789

这个没什么好说的,龙书有详细的算法原理,通俗易懂(还是读了很多遍的)。

先看算法:
按照某个顺序将非终结符符号排序为 A 1 , A 2 , A 3 . . . .   A n A_1,A_2,A_3.... \ A_n A1,A2,A3.... An.
f o r for for (从 1 1 1 n n n的每个 i i i) {
   f o r for for(从 1 1 1 i − 1 i - 1 i1的每个 j j j) {
    将每个形如 A i   → A j γ A_i \ → A_jγ Ai Ajγ的产生式替换为产生式组 A i   → δ 1 γ   ∣   δ 2 γ   ∣   δ 3 γ   . . .   ∣   δ k γ A_i \ → δ_1γ \ | \ δ_2γ \ | \ δ_3γ \ ... \ | \ δ_kγ Ai δ1γ  δ2γ  δ3γ ...  δkγ
    其中 A i   → δ 1   ∣   δ 2   ∣   δ 3   . . .   ∣   δ k A_i \ → δ_1 \ | \ δ_2 \ | \ δ_3 \ ... \ | \ δ_k Ai δ1  δ2  δ3 ...  δk是所有的 A j A_j Aj产生式
  }
  消除 A i A_i Ai产生式之间的立即左递归;
}


i = 1 i = 1 i=1时(第一次迭代时),不进入内层循环,然后消除 A i A_i Ai的所有立即左递归;
因此余下的所有形如 A 1   → A l α A_1 \ → A_lα A1 Alα的产生式都满足 l > 1 l > 1 l>1 ;
例如文法 G G G E → E + T   ∣   E − T   ∣   F E→ E + T \ | \ E - T \ | \ F EE+T  ET  F 前两项消除立即左递归,余下的 E → F E→ F EF 满足 F F F的索引比 E E E大 ;


当外层循环 i − 1 i - 1 i1次迭代之后,所有非终结符号 A k ( k < i ) A_k(k< i) Ak(k<i)都被处理过 ; 任何产生式 A 1 k   → A l α A_1k \ → A_lα A1k Alα的产生式都满足 l > k l > k l>k ;


结果,在第 i i i次迭代时,内层循环提升所有形如 A i → A m α A_i → A_mα AiAmα的产生式中的 m m m的下界,直到 m ≥ i m≥i mi成立; 然后消除 A i A_i Ai产生式中的立即左递归,保证 m > i m>i mi成立。
例如文法 G G G:当执行 F → (   E   )   ∣   D s   ∣   E F → ( \ E \ ) \ | \ Ds \ | \ E F( E )  Ds  E 时,比 F F F索引小的(例如 E E E)检查替换 E E E后是否会产生左递归,如果是则提升 E E E,提升后为 F E ’ F E’ FE, 此时下界成立。

代码也很简单,检查能否左递归,如果能则替换产生式,然后执行一些零碎的操作,每一步的注释也比较详细。对于上面的算法我也只是理解的七七八八,如果有问题请帮忙指出,谢谢~

标签

语法分析 提取左公因子和消除左递归 JAVA实现

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值