zoukankan      html  css  js  c++  java
  • 语法分析:LL(1)分析

    本篇介绍的LL(1)分析,这是一种自上而下分析的方法,第一个 L 表示从左向右扫描,
    第二个 L 表示分析过程是最左推导,(1)表示每次只向前看一个符号进行分析
    关于语法描述的概念


    本文中,若无特别说明,小写字母[a-z]表示终结符αβ等∈(VT∪VN)*,ABCD等表示非终结符


    自上而下分析

    • 从文法的开始符号出发,向下推导。推出句子
    • 根据输入串,从文法开始符号(根结点)出发,自上而下地为输入串建立语法树

    eg:
    有语法:S → xAy  A → ** | *
    从左向右扫描输入串,取到 x 进行匹配时,分析文法开始符号 S 的产生式,只有一个 S → xAy,
    那么只能将 S 推导为句型 xAy,继续由 xAy 来匹配输入串,
    在这里插入图片描述
    输入符号(终结符)x 与 当前句型中的 x 相匹配,则继续用 Ay 匹配剩下的输入串 *y
    在这里插入图片描述
    对符号 * 进行匹配,遇到非终结符 A,分析 A 可进行的推导,A → ** | *
    按照先后顺序,先尝试 A → **,则现在的的分析树变成
    在这里插入图片描述
    很好,我们使 * 得到了匹配,继续匹配
    在这里插入图片描述
    哟吼,完蛋,非终结符 * 是无法匹配 y 的,匹配失败
    但是我们还有另外一条路 A → * 没试呢
    回到刚刚 A 没有展开,* 等待匹配的状态,将 A 推导为 *
    在这里插入图片描述
    * 得到匹配后,y 也匹配成功,输入串结束且匹配成功,语法分析结束
    这就是LL(1)的推导过程

    那么,这种匹配方式就存在一些限制

    回溯问题
    这是一个非终结符具有多个产生式候选带来的问题
    当一个非终结符用某个候选式进行匹配时,匹配成功可能是暂时的
    如果要遍历所有匹配的可能,在每个多候选式终结符进行匹配时,就要记录下当前等待匹配
    的符号的位置、当前推导句型中非终结符的位置,和该非终结符选择的产生式的序号
    某次匹配失败后,舍弃某些分析结果,回溯到上一个岔路口,走下一条路尝试
    回溯会导致语法分析的效率问题,
    此外,最终分析失败时,没法确定应该在哪个岔路口抛出错误

    还有一个上述推导示例没有反映出来的死循环问题
    文法左递归问题
    假设有文法:S → xAy  A → A* | *
    非终结符 A 的产生式是以 A 开头的串,称为左递归
    在这里插入图片描述
    在上述匹配过程中,就会进入到 A 的左递归的产生式的无限循环中,连回溯的机会都莫得
    在这里插入图片描述

    因此要消除文法的左递归,并避免回溯


    消除左递归

    存在左递归 P → Pα1 | Pα2 |…| Pαm | β1 |…| βn
    (每个α都不等于ε,每个β都不以P开头)
    观察上式,易知 P 推导出的句型为 (β1 |…| βn)(α1 | α2 |…| αm)*
    通过以下方法将左递归转化为右递归(易知右递归不会进入无限循环)
    P → (β1 |…| βn)P/
    P/ → α1P/ | α2P/ |…| αmP/ | ε

    例,有如下文法G(E):
    E → E + T | T
    T → T * F | F
    F → (E) | i
    消除左递归后
    E → TE/
    E/ → + TE/ | ε
    T → FT/
    T/ → * FT/ | ε
    F → (E) | i

    但是有一种特殊的左递归需要注意
    S → Qc | c
    Q → Rb | b
    R → Sa | a
    看起来没有显式的左递归,实际上暗藏玄机
    S → Qc | c ⇒ S → Rbc | bc | c ⇒ S → Sabc | abc | bc | c,实际上 S、Q、R 都是左递归的
    显然这种间接的左递归也需要消除

    下面给出一种左递归消除算法,使用这个算法的前提条件如下:

    • 不含以 ε 为右部的产生式
    • 不含有回路 (非终结符 P 经一次或若干次推导再次得到 P)

    实际上,这两个条件并不苛刻,大多数文法都可以满足

    将文法 G 当前所有的非终结符按某种顺序排列 P1,P2,P3,……,Pn
    并按此顺序将 Pi 改造成 Pi → a | Pi+1 | Pi+2 | … | Pn ,具体做法是{
     将所有的 Pi → Pjγ 的规则改写成 Pi → ( δ1 | δ2 | … | δn )γ ,其中 j < i,且 Pj → δ1 | δ2 | … | δn
     然后消除 Pi 里的直接左递归,得到形如 Pi → a | Pi+1 | Pi+2 | … | Pn 的 Pi
    }
    消除左递归后的文法规则中可能有一些无法到达,删除它们

    另外,由于初始排列顺序的不同,最终消除左递归后的得到的文法规则可能不同,但是它们产生的语言是一样的(证明略)


    消除回溯

    消除回溯即是要保证,匹配某个符号时,可选择的候选式是唯一的、准确的,
    若输入串是合法的句子,所选推导一定可以使输入串分析成功;如果分析不成功,则输入串一定不是合法句子

    那么在匹配符号 a 时,非终结符 P 仅有一个产生式可以推导出以 a 开头的串,就是我们需要的情况

    FIRST集合

    由上面的想法,对不含左递归的文法 G 所有非终结符的每个候选 α 定义终结首符集FIRST集合如下
    FIRST(α) = { a | α ⇒* a…,a ∈ VT },即FIRST(α)中的符号均是候选 α 可以推导出的串的第一个终结符
    特别是,若 α ⇒* ε,规定 ε ∈ FIRST(α)

    那么上述想法用FIRST集合的概念描述就是
    非终结符 A 的任何两个不同的候选 αi、αj,有FIRST(αi) ∩ FIRST(αj) = ∅
    当使用 A 去匹配输入符号 a 时,若某FIRST(α)中包含a,就选择 A 的候选式 α

    斯巴拉西,但是如何保证 FIRST(αi) ∩ FIRST(αj) = ∅ 呢?

    提取左公共因子算法如下

    若关于 A 的语法规则符合如下形式:A → δβ1 | δβ2 | … | δβn | δ | γ1 | γ2 | … | γm
    则提取左公因子 A → δA/ | γ1 | γ2 | … | γm,A/ → β1 | β2 | … | βn | ε |
    继续检查 A 与 A/ 的候选式是否可以继续提取

    反复提取左公共因子(包括新引入的非终结符),就可以使得所有候选首符集两两不相交


    现在我们已经得到不含有左递归、对于任何输入符号都能确定唯一推导过程的文法

    然而,还是有一种情形会使我们的推导有不确定性
    考虑如下文法
    S → AB
    A → a | ε
    B → aC
    C → ……
    当匹配输入串 ab…… 时,取到输入符号 a,将 S 推导为 AB 后,
    出现了无法确定推导过程的情形:

    1. 将 A 推导为 a ,a 匹配成功,进行下一单词的匹配
    2. 将 A 推导为 ε,用 B 去匹配 a ,再继续进行推导

    注意我们是无回溯的,当然不能有岔路

    FOLLOW集合

    为了确定化这种情况(FOLLOW集合的作用:判断能不能将非终结符推导为 ε ),定义FOLLOW集如下
    FOLLOW(A) = { a | S ⇒*…Aa…,a ∈ VT },有些输入串以 # 作为结束标志,因此若 S ⇒*…A,规定 # ∈ FOLLOW(A)
    若某个非终结符 A 的候选首符集FIRST(αi)包含 ε,则要求FIRST(αi)∩FOLLOW(A) = ∅

    也就是说,如果某个等待匹配的输入符号 a,不属于此时的非终结符 A 的FIRST集合,就会有三种情况

    • A 的候选首符集均不包含 ε,a 匹配失败,a 此时在输入串中导致语法错误
    • A 的某个候选首符集包含 ε,且 a ∈ FOLLOW(A),则将 A 推导为 ε,继续由 A 后的推导串来匹配 a
    • A 的某个候选首符集包含 ε,且 a ∉ FOLLOW(A),a 匹配失败,a 此时在输入串导中致语法错误

    okk,罗里吧嗦这么多,终于把适用于LL(1)分析的文法的限制条件讲完辽
    再总结一遍

    • 文法不含左递归
    • 文法中每一个非终结符 A 的各个产生式的候选首符集不相交,即若 A → α1 | α2 | … | αn
      FIRST(αi)∩FIRST(αj) = ∅,i ≠ j
    • 对于文法中的每个非终结符 A,若它的某个候选首符集包含 ε,则 FIRST(αi)∩FOLLOW(A) = ∅

    一个文法,先消除左递归,再提取公共左因子,得到的文法 G 若满足以上条件,
    则称文法 G 为LL(1)文法,可以进行LL(1)分析

    LL(1)分析的过程如下:
    假设当前等待匹配的符号为 a,进行匹配的非终结符为A,A 的所有产生式为 A → α1 | α2 | … | αn

    • 若 a ∈ FIRST(αi),则将 A 推导为 αi,a得到匹配,斯巴拉西,继续分析下一个符号 ①
    • 若 a ∉ FIRST(αi)
      • 若 ε ∈ FIRST(αi),且 a ∈ FOLLOW(A),则将 A 推导为 ε,a 交给 A 后面的串匹配 ②
      • 否则,匹配失败,a 此时在输入串中是语法错误 ③

    如果这个文法是LL(1)文法,则每一步都能且只能在①②③中确定,每一步都只有一种情况。这就是LL(1)分析
    妙呀


    构造FIRST集合和FOLLOW集合

    还有一个问题没有解决,判断文法是否是LL(1)文法,以及分析过程中都要用到FIRST集合和FOLLOW集合

    FIRST集合

    按照FIRST集合的定义,FIRST(α) = { a | α ⇒* a…,a ∈ VT }
    将 α 分情况讨论

    • α = X,X ∈ VT∪VN
    • α = X1X2…Xn,Xi ∈ VT∪VN

    通过反复扫描产生式,运用以下规则构造FIRST集合,直到每个FIRST(α)不再变化

    构造每个文法符号的FIRST集合
    对于每一个X∈VT∪VN

    • 若X∈VT,则FIRST(X) = { X };
    • 若X∈VN
      • 若产生式形如 X → a…,则把 a 加入到FIRST(X)中;
        若 X → ε 也是产生式,则把 ε 也加入到FIRST(X)中;
      • 若 X → Y… 是一个产生式,且 Y∈VN,则把FIRST(Y)中的所有非 ε 元素加入到FIRST(X)中;
      • 若 X → Y1Y2…Yi-1Yi…Yk,Y1 ~ Yi-1 都是非终结符,
        若FIRST(Y1) ~ FIRST(Yi-1)均含有 ε,即 Y1…Yi-1* ε,则将FIRST(Yi)加入FIRST(X);
        若所有 FIRST(Y) 均含有 ε,即 Y1Y2…Yi-1Yi…Yk* ε,则将 ε 加入FIRST(X);

    构造任何符号串 α 的FIRST集合
    对于文法 G 的任何符号串 α = X1X2…Xn 构造集合FIRST(α),

    • 将FIRST(X1) - {ε} 加入FIRST(α);
    • 若FISRT(X1) ~ FIRST(Xi-1)均包含 ε,则将FIRST(Xi) - {ε} 加入FIRST(α);
    • 若FISRT(X1) ~ FIRST(Xn)均包含 ε,则将 ε 加入FIRST(α);
    构造FOLLOW集合

    首先是FOLLOW集合的定义 FOLLOW(A) = { a | S ⇒* …Aa…,a ∈ VT }

    反复使用以下规则,对文法 G 的每个非终结符A构造FOLLOW(A),直至每个FOLLOW不再变化为止

    • 将输入串结束标志 # 加入文法的开始符号S对应的FOLLOW(S)中
    • 若 A → αBβ 是一个产生式,则把 FIRST(β) - {ε} 加入到FOLLOW(B)中
    • 若 A → αB 是一个产生式,或 A → αBβ 是一个产生式而 β ⇒* ε(即 ε ∈ FIRST(β)),则把 FOLLOW(A)加入到FOLLOW(B)中

    注意上述步骤中,α、β ∈ (VT ∩ VN)*,B ∈ VN


    例:文法G(E)为
    E → TE/
    E/ → + TE/ | ε
    T → FT/
    T/ → * FT/ | ε
    F → (E) | i

    则文法 G 的FIRST集合与FOLLOW集合为
    FIRST(E) = { (,i }     FOLLOW(E) = { #,) }
    FIRST(E/) = { +,ε }     FOLLOW(E/) = { #,) }
    FIRST(T) = { (,i }     FOLLOW(T) = { +,#,) }
    FIRST(T/) = { *,ε }     FOLLOW(T/) = { +,#,) }
    FIRST(F) = { (,i }     FOLLOW(F) = { *,+,#,) }


    消除左递归提取公共左因子后,再构造这个文法的FIRST集合和FOLLOW集合,
    判断其是否为LL(1)文法,若是,则可以使用LL(1)的分析方法进行语法分析


    2019/8/10

  • 相关阅读:
    define vs const vs enum
    解决Ubuntu 14.04 LTS 浏览网页速度慢的问题
    C语言两种产生矩阵的方法
    GTK 添加图标
    Unix Socket 端口 reuse
    Linux GTK Hello,World
    插件使用记录
    原型链和new
    each函数循环数据表示列举,列举循环的时候添加dom的方法
    字体圆润属性的使用-webkit-font-smoothing: antialiased
  • 原文地址:https://www.cnblogs.com/kafm/p/12721799.html
Copyright © 2011-2022 走看看