Skip to content

第四章:语法分析 2 ​

4.4 FIRSTFIRST 集和 FOLLOWFOLLOW 集的计算 ​

4.4.1 文法符合 XX 的 FIRSTFIRST 集 ​

FIRST(X)FIRST(X) :所有可以从 XX 推导的所有 串首终结符 构成的集合。

如果 X⇒∗εX \Rightarrow^* \varepsilon ,那么 ε∈FIRST(X)\varepsilon \in FIRST(X) 。

例如:

G:  E  →TE′E′→+TE′∣εT  →FT′T′→∗FT′∣εF  →(E)∣id\begin{aligned} G:\; & E \;\rightarrow TE' \\ & E' \rightarrow +TE' \mid \varepsilon \\ & T \;\rightarrow FT' \\ & T' \rightarrow *FT' \mid \varepsilon \\ & F \;\rightarrow (E) \mid \text{id} \end{aligned}

推导一下 FIRSTFIRST 集

G:  E  →TE′FIRST(E)={(, id}E′→+TE′∣εFIRST(E′)={+, ε}T  →FT′FIRST(T)={(, id}T′→∗FT′∣εFIRST(T′)={∗, ε}F  →(E)∣idFIRST(F)={(, id}\begin{aligned} G:\; & E \;\rightarrow TE' & FIRST(E) &= \{(,\,\text{id}\} \\ & E' \rightarrow +TE' \mid \varepsilon & FIRST(E') &= \{+,\,\varepsilon\} \\ & T \;\rightarrow FT' & FIRST(T) &= \{(,\,\text{id}\} \\ & T' \rightarrow *FT' \mid \varepsilon & FIRST(T') &= \{*,\,\varepsilon\} \\ & F \;\rightarrow (E) \mid \text{id} & FIRST(F) &= \{(,\,\text{id}\} \end{aligned}

4.4.2 计算文法符合 XX 的 FIRSTFIRST 集 ​

【算法】


不断应用下列规则,直到没有新的终结符或 ε\varepsilon 可以被加入到任何 FIRSTFIRST 集合中为止。

  • 如果 XX 是一个终结符,那么 FIRST(X)={X}FIRST(X) = \{X\}
  • 如果 XX 是一个非终结符
    • 如果 X→Yi⋯Yk∈P (k≥1)X \rightarrow Y_i\cdots Y_k \in P\,(k \ge 1) ,那么如果对于某个 ii , aa 在 FIRST(Yi)FIRST(Y_i) 中且 ε\varepsilon 在所有的 FIRST(Y1), ⋯ , FIRST(Yi−1)FIRST(Y_1),\,\cdots,\,FIRST(Y_{i-1}) 中(即 Y1⋯Yi−1⇒∗εY_1\cdots Y_{i-1} \Rightarrow^* \varepsilon ),就把 aa 加入到 FIRST(X)FIRST(X) 中
    • 如果对于所有的 j=1, 2, ⋯ , kj=1,\,2,\,\cdots,\,k , ε\varepsilon 在 FIRST(Yj)FIRST(Y_j) 中,那么将 ε\varepsilon 加入到 FIRST(X)FIRST(X)
  • 如果 X→ε∈PX \rightarrow \varepsilon \in P ,那么将 ε\varepsilon 加入到 FIRST(X)FIRST(X) 中

4.4.3 计算串 X1X2⋯XnX_1X_2\cdots X_n 的 FIRSTFIRST 集 ​

  • 向 FIRST(X1X2⋯Xn)FIRST(X_1X_2\cdots X_n) 加入 FIRST(X1)FIRST(X_1) 中所有的非 ε\varepsilon 符号
  • 如果 ε\varepsilon 在 FIRST(X1)FIRST(X_1) 中,再加入 FIRST(X2)FIRST(X_2) 中所有非 ε\varepsilon 符号
  • 如果 ε\varepsilon 在 FIRST(X1)FIRST(X_1) 和 FIRST(X2)FIRST(X_2) 中,再加入 FIRST(X3)FIRST(X_3) 中的所有非 ε\varepsilon 符号,以此类推
  • 如果所有的 ii , ε\varepsilon 都在 FIRST(Xi)FIRST(X_i) 中,那么将 ε\varepsilon 加入到 FIRST(X1X2⋯Xn)FIRST(X_1X_2\cdots X_n) 中

4.4.4 计算非终结符 AA 的 FOLLOW(A)FOLLOW(A) ​

FOLLOW(A)FOLLOW(A) 可能在某个句型中紧跟在 AA 后边的终结符 aa 的集合。

FOLLOW(A)={a∣S⇒∗αaβ, a∈VT, α, β∈(VT∪VN)∗}FOLLOW(A) = \{ a \mid S \Rightarrow^* \alpha a \beta,\,a\in V_T,\,\alpha,\,\beta \in (V_T \cup V_N)^* \}

如果 AA 是某个句型的最右符号,则将结束符 $\$ 添加到 FOLLOW(A)FOLLOW(A) 中。

例如:

G:  E  →TE′FIRST(E)={(, id}FOLLOW(E)={$, )}E′→+TE′∣εFIRST(E′)={+, ε}FOLLOW(E′)={$, )}T  →FT′FIRST(T)={(, id}FOLLOW(T)={+, $, )}T′→∗FT′∣εFIRST(T′)={∗, ε}FOLLOW(T′)={+, $, )}F  →(E)∣idFIRST(F)={(, id}FOLLOW(F)={∗, +, $, )}\begin{aligned} G:\; & E \;\rightarrow TE' & FIRST(E) = \{(,\,\text{id}\} & FOLLOW(E) = \{\$,\,)\} \\ & E' \rightarrow +TE' \mid \varepsilon & FIRST(E') = \{+,\,\varepsilon\} & FOLLOW(E') = \{\$,\,)\} \\ & T \;\rightarrow FT' & FIRST(T) = \{(,\,\text{id}\} & FOLLOW(T) = \{+,\,\$,\,)\} \\ & T' \rightarrow *FT' \mid \varepsilon & FIRST(T') = \{*,\,\varepsilon\} & FOLLOW(T') = \{+,\,\$,\,)\} \\ & F \;\rightarrow (E) \mid \text{id} & FIRST(F) = \{(,\,\text{id}\} & FOLLOW(F) = \{*,\,+,\,\$,\,)\} \end{aligned}

【算法】


不断应用下列规则,直到没有新的终结符可以被加入到任何 FOLLOWFOLLOW 集合中为止

  • 将 $\$ 放入 FOLLOW(S)FOLLOW(S) 中,其中 SS 是开始符号, $\$ 是输入右端的结束标记
  • 如果存在一个产生式 A→αBβA \rightarrow \alpha B \beta ,那么 FIRST(β)FIRST(\beta) 中除 ε\varepsilon 之外的所有符号都在 FOLLOW(B)FOLLOW(B) 中
  • 如果存在一个产生式 A→αBA \rightarrow \alpha B ,或存在产生式 A→αBβA \rightarrow \alpha B \beta 且 FIRST(β)FIRST(\beta) 包含 ε\varepsilon ,那么 FOLLOW(A)FOLLOW(A) 中的所有符号都在 FOLLOW(B)FOLLOW(B) 中

4.4.5 表达式文法各个产生式的 SELECTSELECT 集 ​

XXFIRST(X)FIRST(X)FOLLOW(X)FOLLOW(X)
EE(, id(,\,\text{id}$, )\$,\,)
E′E'+, ε+,\,\varepsilon$, )\$,\,)
TT(, id(,\,\text{id}+, ), $+,\,),\,\$
T′T'∗, ε*,\,\varepsilon+, ), $+,\,),\,\$
FF(, id(,\,\text{id}∗, +, ), $*,\,+,\,),\,\$
产生式SELECTSELECT 集
E→TE′E \rightarrow TE'SELECT(1)={(, id}SELECT(1) = \{ (,\,\text{id} \}
E′→+TE′E' \rightarrow +TE'SELECT(2)={+}SELECT(2) = \{ + \}
E′→εE' \rightarrow \varepsilonSELECT(3)={$, )}SELECT(3) = \{ \$,\,) \}
T→FT′T \rightarrow FT'SELECT(4)={(, $}SELECT(4) = \{ (,\,\$ \}
T′→∗FT′T' \rightarrow *FT'SELECT(5)={∗}SELECT(5) = \{ * \}
T′→εT' \rightarrow \varepsilonSELECT(6)={+, ), $}SELECT(6) = \{ +,\,),\,\$ \}
F→(E)F \rightarrow (E)SELECT(7)={(}SELECT(7) = \{ ( \}
F→idF \rightarrow \text{id}SELECT(8)={id}SELECT(8) = \{ \text{id} \}

这是一个 LL(1)LL(1) 文法,可以根据产生式的 SELECTSELECT 集构造预测分析表。

将每个非终结符作为行,每个输入符号作为列,那么每个产生式就是其元素。

预测分析表如下:

非终结符 / 输入符号id\text{id}++∗*(())$  \$\;
EEE→TE′E \rightarrow TE'E→TE′E \rightarrow TE'
E′E'E′→+TE′E' \rightarrow +TE'E′→εE' \rightarrow \varepsilonE′→εE' \rightarrow \varepsilon
TTT→FT′T \rightarrow FT'T→FT′T \rightarrow FT'
T′T'T′→εT' \rightarrow \varepsilonT′→∗FT′T' \rightarrow *FT'T′→εT' \rightarrow \varepsilonT′→εT' \rightarrow \varepsilon
FFF→idF \rightarrow \text{id}F→(E)F \rightarrow (E)

4.4.6 LL(1)LL(1) 文法的分析方法 ​

  • 递归的预测分析法
  • 非递归的预测分析法

4.5 递归的预测分析法 ​

4.5.1 递归的预测分析法定义 ​

递归的预测分析法 是指在递归下降分析中,编写每一个非终结符对应的过程时,根据预测分析表进行产生式选择。

4.5.2 示例分析 ​

1.  <PROGRAM>→program <DECLIST>:<TYPE>:<STLIST> end2.  <DECLIST>→id <DECLISTN>3.  <DECLISTN>→, id <DECLISTN>4.  <DECLISTN>→ε5.  <STLIST>→s <STLISTN>6.  <STLISTN>→; s <STLISTN>7.  <STLISTN>→ε8.  <TYPE>→real9.  <TYPE>→int\begin{aligned} 1.\;& \text{<PROGRAM>} &&\rightarrow \text{program <DECLIST>:<TYPE>:<STLIST> end} \\ 2.\;& \text{<DECLIST>} &&\rightarrow \text{id <DECLISTN>} \\ 3.\;& \text{<DECLISTN>} &&\rightarrow \text{,\,id <DECLISTN>} \\ 4.\;& \text{<DECLISTN>} &&\rightarrow \varepsilon \\ 5.\;& \text{<STLIST>} &&\rightarrow \text{s <STLISTN>} \\ 6.\;& \text{<STLISTN>} &&\rightarrow \text{; s <STLISTN>} \\ 7.\;& \text{<STLISTN>} &&\rightarrow \varepsilon \\ 8.\;& \text{<TYPE>} &&\rightarrow \text{real} \\ 9.\;& \text{<TYPE>} &&\rightarrow \text{int} \end{aligned}

SELECT(4)={:}SELECT(7)={end}\begin{aligned} SELECT(4) &= \{ : \} \\ SELECT(7) &= \{\text{end}\} \end{aligned}

各个分析过程的伪代码如下:

pascal
program DESCENT;
begin
    GETNEXT(TOKEN);
    PROGRAM(TOKEN);
    GETNEXT(TOKEN);
    if TOKEN != '$' then ERROR;
end
pascal
procedure PROGRAM(TOKEN);
begin
    if TOKEN != 'program' then ERROR;
    GETNEXT(TOKEN);
    DECLIST(TOKEN);

    if TOKEN != ':' then ERROR;

    GETNEXT(TOKEN);
    TYPE(TOKEN);

    GETNEXT(TOKEN);
    if TOKEN != ';' then ERROR;

    GETNEXT(TOKEN);
    STLIST(TOKEN);

    if TOKEN != 'end' then ERROR;
end
pascal
procedure DECLIST(TOKEN);
begin
    if TOKEN != 'id' then ERROR;
    GETNEXT(TOKEN);
    DECLISTN(TOKEN);
end
pascal
procedure DECLISTN(TOKEN);
begin
    if TOKEN = ',' then
        begin
            GETNEXT(TOKEN);
            if TOKEN != 'id' then ERROR;

            GETNEXT(TOKEN);
            DECLISTN(TOKEN);
        end
    else if TOKEN != ':' then ERROR;
end
pascal
procedure STLIST(TOKEN);
begin
    if TOKEN != 's' then ERROR;
    GETNEXT(TOKEN);
    STLISTN(TOKEN);
end
pascal
procedure STLISTN(TOKEN);
begin
if TOKEN = ';' then
    begin
        GETNEXT(TOKEN);
        if TOKEN != 's' then ERROR;

        GETNEXT(TOKEN);
        STLISTN(TOKEN);
    end
    else if TOKEN != 'end' then ERROR;
end
pascal
procedure TYPE(TOKEN);
begin
    if TOKEN != 'real' or TOKEN != 'int'
        then ERROR;
end

4.6 非递归的预测分析法 ​

4.6.1 下推自动机 ​

非递归的预测分析法 不需要为每个非终结符编写递归下降过程,而是根据预测分析表构造一个自动机,也叫表驱动的预测分析。

这是一个 下推自动机(Push Down Automata, PDA),下推自动机比有穷自动机的识别能力更强,因为它的记忆功能更强。

例如:

L={anbn∣n≥1}L = \{a^nb^n \mid n \ge 1\}

有穷自动机不具备专门的存储器,有穷自动机不会知道语言 LL 中 nn 的值。有穷自动机无法识别这样的语言。

因为 nn 可以趋于无穷,而有穷自动机只有有穷个状态,这就产生了矛盾,故有穷自动机不能识别语言 LL 。

非递归预测分析

例如下面的预测分析表:

非终结符 / 输入符号id\text{id}++∗*(())$  \$\;
EEE→TE′E \rightarrow TE'E→TE′E \rightarrow TE'
E′E'E′→+TE′E' \rightarrow +TE'E′→εE' \rightarrow \varepsilonE′→εE' \rightarrow \varepsilon
TTT→FT′T \rightarrow FT'T→FT′T \rightarrow FT'
T′T'T′→εT' \rightarrow \varepsilonT′→∗FT′T' \rightarrow *FT'T′→εT' \rightarrow \varepsilonT′→εT' \rightarrow \varepsilon
FFF→idF \rightarrow \text{id}F→(E)F \rightarrow (E)

分析过程:

栈剩余输入输出
E$  E\$\;id+id∗id$  \text{id}+\text{id}*\text{id}\$\;
TE′$  TE'\$\;id+id∗id$  \text{id}+\text{id}*\text{id}\$\;E→TE′E \rightarrow TE'
FT′E′$  FT'E'\$\;id+id∗id$  \text{id}+\text{id}*\text{id}\$\;T→FT′T \rightarrow FT'
idT′E′$  \text{id}T'E'\$\;id+id∗id$  \text{id}+\text{id}*\text{id}\$\;F→idF \rightarrow \text{id}
T′E′$  T'E'\$\;+id∗id$  +\text{id}*\text{id}\$\;
E′$  E'\$\;+id∗id$  +\text{id}*\text{id}\$\;T′→εT' \rightarrow \varepsilon
+TE′$  +TE'\$\;+id∗id$  +\text{id}*\text{id}\$\;E′→+TE′E' \rightarrow +TE'
TE′$  TE'\$\;id∗id$  \text{id}*\text{id}\$\;
FT′E′$  FT'E'\$\;id∗id$  \text{id}*\text{id}\$\;T→FT′T \rightarrow FT'
idT′E′$  \text{id}T'E'\$\;id∗id$  \text{id}*\text{id}\$\;F→idF \rightarrow \text{id}
T′E′$  T'E'\$\;∗id$  *\text{id}\$\;
∗FT′E′$  *FT'E'\$\;∗id$  *\text{id}\$\;T′→∗FT′T' \rightarrow *FT'
FT′E′$  FT'E'\$\;id$  \text{id}\$\;
idT′E′$  \text{id}T'E'\$\;id$  \text{id}\$\;F→idF \rightarrow \text{id}
T′E′$  T'E'\$\;$  \$\;
E′$  E'\$\;$  \$\;T′→εT' \rightarrow \varepsilon
$  \$\;$  \$\;E′→εE' \rightarrow \varepsilon

4.6.2 表驱动的预测分析法 ​

【输入】一个串 ww 和文法 GG 的分析表 MM

【输出】如果 ww 在 L(G)L(G) 中,输出 ww 的最左推导,否则给出错误指示

【算法】最初,语法分析器的格局如下:输入缓冲区中是 w$ w\$\, , GG 的开始符号位于栈顶,其下面是 $ \$\, 。下面的程序使用预测分析表 MM 生成了处理这个输入的预测分析过程

cpp
设置 ip 使他指向 w 的第一个符号,其中 ip 是输入指针;
X = 栈顶符号;
while (X != $) { /* 栈非空 */
    if (X == ip 所指向的符号 a)
        弹出栈,ip 向前移动一个位置;
    else if (X 是一个终结符号)
        throw ERROR;
    else if (M[X, a] 是一个报错条目)
        throw ERROR;
    else if (M[X, a] = X -> Y[1]Y[2]...Y[k] ) {
        输出产生式 X -> Y[1]Y[2]...Y[k];
        弹出栈顶符号;
        将 Y[k],Y[k+1]...Y[1] 入栈,其中 Y[1] 位于栈顶;
    }
    X = 栈顶符号;
}

4.6.3 递归的预测分析法 VS 非递归的预测分析法 ​

-递归的预测分析法非递归的预测分析法
程序规模程序规模较大,不需要载入分析表主程序规模较小,需要载入分析表
直观性较好较差
效率较低分析时间大约正比于待分析程序的长度
自动生成较难较易

4.6.4 预测分析的实现步骤 ​

步骤:

  1. 构造文法
  2. 改造文法:消除二义性、消除左递归、消除回溯
  3. 求每个变量的 FIRSTFIRST 集和 FOLLOWFOLLOW 集,从而求得每个候选式的 SELECTSELECT 集
  4. 检查是否为 LL(1)LL(1) 文法,若是,构造预测分析表
  5. 预测分析
    • 对于递归的预测分析,根据预测分析表为每一个非终结符编写一个过程
    • 对于非递归的预测分析实现表驱动的预测分析算法

4.7 预测分析中的错误处理 ​

4.7.1 预测分析中的错误检测 ​

两种情况下可以检测到错误:

  • 栈顶的终结符和当前输入符号不匹配
  • 栈顶非终结符与当前输入符号在预测分析表对应项中的信息为空

4.7.2 预测分析中的错误恢复 ​

恐慌模式:

  • 忽略输入中的一些符号,直到输入中出现由设计者选定的 同步词法单元(Synchronizing Token)集合中的某个词法单元
    • 其效果依赖于同步集合的选取。集合的选取应该使得语法分析器能从实际遇到的错误中快速恢复
    • 例如可以把 FOLLOW(A)FOLLOW(A) 中的所有终结符放入非终结符 AA 的同步记号集合
  • 如果终结符在栈顶而不能匹配,一个简单的办法就是弹出此终结符

例如,预测分析表如下:

非终结符 / 输入符号id\text{id}++∗*(())$  \$\;
EEE→TE′E \rightarrow TE'E→TE′E \rightarrow TE'SynchSynch
E′E'E′→+TE′E' \rightarrow +TE'E′→εE' \rightarrow \varepsilonE′→εE' \rightarrow \varepsilon
TTT→FT′T \rightarrow FT'SynchT→FT′T \rightarrow FT'SynchSynch
T′T'T′→εT' \rightarrow \varepsilonT′→∗FT′T' \rightarrow *FT'T′→εT' \rightarrow \varepsilonT′→εT' \rightarrow \varepsilon
FFF→idF \rightarrow \text{id}SynchSynchF→(E)F \rightarrow (E)SynchSynch

Synch 表示根据相应非终结符的 FOLLOWFOLLOW 集得到的同步词法单元。

XXFOLLOW(X)FOLLOW(X)
EE$, )\$,\,)
E′E'$, )\$,\,)
TT+, ), $  +,\,),\,\$\;
T′T'+, ), $  +,\,),\,\$\;
FF∗, +, ), $  *,\,+,\,),\,\$\;

分析表的使用方法:

  • 如果 M[A, a]M[A,\,a] 是空,表示检测到错误,根据恐慌模式,忽略输入符号 aa
  • 如果 M[A, a]M[A,\,a] 是 Synch,则弹出栈顶的非终结符 AA,试图继续分析后面的语法成分
  • 如果栈顶的终结符和输入符号不匹配,则弹出栈顶的终结符

分析过程:

栈剩余输入输出
E$  E\$\;+id∗+id$  +\text{id}*+\text{id}\$\;忽略 ++
E$  E\$\;id∗+id$  \text{id}*+\text{id} \$\;
TE′$  TE'\$\;id∗+id$  \text{id}*+\text{id} \$\;
FT′E′$  FT'E'\$\;id∗+id$  \text{id}*+\text{id} \$\;
idT′E′$  \text{id}T'E'\$\;id∗+id$  \text{id}*+\text{id} \$\;
T′E′$  T'E'\$\;∗+id$  *+\text{id} \$\;
∗FT′E′$  *FT'E'\$\;∗+id$  *+\text{id} \$\;
FT′E′$  FT'E'\$\;+id$  +\text{id}\$\;报错
T′E′$  T'E'\$\;+id$  +\text{id} \$\;
E′$  E'\$\;+id$  +\text{id} \$\;
+TE′$  +TE'\$\;+id$  +\text{id} \$\;
TE′$  TE'\$\;id$  \text{id} \$\;
FT′E′$  FT'E'\$\;id$  \text{id} \$\;
idT′E′$  \text{id}T'E'\$\;id$  \text{id} \$\;
T′E′$  T'E'\$\;$  \$\;
E′$  E'\$\;$  \$\;
$  \$\;$  \$\;