zhen's blog

编译思想,编译人生

S_型文法到q_型文法再到LL(1)型文法演进笔记

S_型文法到q_型文法再到LL(1)型文法演进笔记

S_型文法(简单的确定性文法)

  • 每个产生式的右部都以终结符开始

  • 同一非终结符的各个候选式的首终结符都不同

针对第一条的理解是,只要右部都是终结符开始,那么对于串当前的读入字符,我们可以很容易的直接根据右部开头的终结符来进行判断匹配,而无需进行产生式的推导;针对第二条的理解是,因为首终结符都不一样,所以根据当前串读入的字符,我们只会匹配到一个确定的产生式。

举例,现有文法如下:

1.SaBC2.Bb3.Cc\begin{aligned} &1. \quad S \rightarrow aBC\\ &2. \quad B \rightarrow b\\ &3. \quad C \rightarrow c\\ \end{aligned}

现有字符串abcabc,我们从左部第一个字符开始输入,因为字符aa能够被产生式1匹配,因此我们首先选择产生式1.SaBC1. \quad S \rightarrow aBC;接下来输入第二个字符bb,于是我们从产生式1到3种进行匹配判断,找到左部非终结符为BB(找BB的原因是上一步我们已经匹配到了产生式1,产生式1的右部现在已经匹配上了aa字符,我们接下来需要推导BB,所以要找左部为非终结符BB的产生式),右部首终结符为bb的产生式,于是找到产生式2。以此类推,我们最后能够如下的推导:

SaBCabCabcS \rightarrow aBC \rightarrow abC \rightarrow abc

个人理解觉得,针对S_型文法的特征是:我们总是能够根据读入的字符,直接匹配找到一个确定的产生式

然而,该文法的局限性非常的大,最基本的一点就是该文法不包含ε\varepsilon的产生式。例如,现有下列文法:

1.SaBC2.BbC3.BdB4.Bε5.Cc6.Ca7.De\begin{aligned} &1. \quad S \rightarrow aBC \\ &2. \quad B \rightarrow bC \\ &3. \quad B \rightarrow dB \\ &4. \quad B \rightarrow \varepsilon \\ &5. \quad C \rightarrow c \\ &6. \quad C \rightarrow a \\ &7. \quad D \rightarrow e \\ \end{aligned}

对于字符串adeade,首先还是产生式1没得说;接下来,我们需要寻找的是左部为BB,右部首终结符为dd的产生式,于是我们选择产生式3,此时产生式推到得到:

1.a,选择产生式1,得到aBC2.d,选择产生式3,得到adBC3.e,因为产生式23的首位非终结符不是e,这里选择ε,即选择产生式4,得到adC\begin{aligned} & 1. \Rightarrow a,选择产生式1,得到 aBC \\ & 2. \Rightarrow d,选择产生式3,得到 adBC \\ & 3. \Rightarrow e,因为产生式2、3的首位非终结符不是e,这里选择\varepsilon,即选择产生式4,得到 adC \\ \end{aligned}

因为此时输入字符为ee,且当前推导的式子中还存在非终结符CC,于是我们继续应用上述规则进行匹配选择(即,我们结束第3轮,进入第4轮准备开始查找),然而我们无法找到形如Ce...C \rightarrow e...的产生式,此时报错。

尽管最终报错了,但是当前的文法存在这样一个问题:因为ε\varepsilon产生式的存在,本该在第3轮中就该发现的再无匹配的问题,在第4轮的检查过程中才被发现。为什么说第3轮就该发现呢?我们回到第3轮的检查中,上面说“因为产生式2、3的首位非终结符不是ee,我们选择BεB \rightarrow \varepsilon,但是选择这个产生式真的正确吗?事实上,当产生式右部是ε\varepsilon的时候,我们应该要考虑空串之后紧跟着的非终结符是什么,如果我们知道紧跟着的非终结符也和当前的输入符号不匹配的话,我们立刻就能知道选择了空串后的下一步必然是无法匹配的。这里ε\varepsilon后面紧跟的等价于BB紧跟的,那么通过产生式1我们知道BB后面紧跟的是CC,而CC能够推导出ccaa,故在第3轮中,我们首先知道已经无法选择产生式2和3了,接下来我们判断产生式4是否满足,因为当前的输入ee依然无法和ccaa匹配,所以在第3轮我们已经无法继续进行下去,就应该报错了。

而我们上面所说的“紧跟着的…”也就是接下来要引入的一个概念:FOLLOWFOLLOW集。

FOLLOWFOLLOW 集(后继符号集)

非终结符AA的后继符号集
可能在某个句型中紧跟在AA后边的终结符aa的集合,记为FOLLOW(A)FOLLOW(A)

FOLLOW(A)={aSαAaβ,aVT,α,β(VTVN)}FOLLOW(A)=\{a|S \Rightarrow^* \alpha Aa\beta, \quad a \in V_T, \quad \alpha, \beta \in (V_T \cup V_N)^*\}

有了FOLLOWFOLLOW集这个东西之后,再回头看待ε\varepsilon产生式就变得很明朗了。

如果当前某非终结符AA的产生式右部的首字符与输入aa不匹配的时候,若存在AεA \rightarrow \varepsilon,则我们检查aa是否存在于AA的后继符号集FOLLOW(A)FOLLOW(A)中,若在其中,则匹配继续,否则程序报错。

于是,对于上面的判断流程的第3步中,FOLLOW(B)={a,c}FOLLOW(B) = \{a, c\},而当前我们遇到的符号是ee,不在对应的集中,于是该步骤已经结束。

SELECTSELECT 集(产生式可选集)

我们综合上述的对于非ε\varepsilon产生式和ε\varepsilon产生式,可以定义产生式的可选集这一概念。

产生式AβA \rightarrow \beta的可选集是指可以选用该产生式进行推导时对应的输入符号的集合,记为SELECT(Aβ)SELECT(A \rightarrow \beta)。例如:

SELECT(Aαβ)={α}SELECT(A \rightarrow \alpha\beta) = \{\alpha\}

SELECT(Aε)=FOLLOW(A)SELECT(A \rightarrow \varepsilon) = FOLLOW(A)

q_文法

每个产生式的右部或为ε\varepsilon或以终结符开始

具有相同左部的产生式有不可相交的可选集

我们称这样的文法为q_型文法。为什么引入q_型文法,因为前面的FOLLOWFOLLOW集概念的引入,解决ε\varepsilon产生式的问题,而这一点可以很好的地对照在q_型文法的第一条定义。尽管它对于S_型文法限制有了略微的放宽,支持了ε\varepsilon的产生式,可是还是有一定的限制:q_文法不含右部以非终结符打头的产生式。

在前文中,我们总是在限制产生式的右部,要么首字符是一个终结符,要么是ε\varepsilon空串,之所以这样是因为我们总是希望在进行输入的时候右部具有确定的信息(这里就是一个首终结字符)。然而,当右部打头的是非终结符的时候,我们就无从下手了(至少目前是),因为非终结符是不确定的(至少一下子无法知道),它最终能够推导成什么样子的串,似乎变数太多,要是我们事先有一个集合,已经存放好了产生式右部的串(无论它是ε\varepsilon,还是终结符,还是非终结符打头)的首非终结符,每次进行输入匹配的时候,看一下是不是在这里面,问题就迎刃而解了。于是,我们引入概念:串首终结符集。

串首终结符集

给定一个文法符号串α\alphaα\alpha的串首终结符集FIRST(α)FIRST(\alpha)被定义为:可以从α\alpha推导出的所有串首终结符的集合。此外,如果αε\alpha \Rightarrow^* \varepsilon,那么εFIRST(α)\varepsilon \in FIRST(\alpha)。即:

对于α(VT,VN)+FIRST(α)={aaβ,aVT,β(VTVN)}如果αε,那么εFIRST(α)\begin{aligned} & 对于 \forall \alpha \in (V_T, V_N)^+,FIRST(\alpha) = \{ a | a\beta, \quad a \in V_T, \quad \beta \in (V_T \cup V_N)^* \} \\ & 如果\alpha \Rightarrow^* \varepsilon,那么\varepsilon \in FIRST(\alpha) \\ \end{aligned}

至于这个串首终结符集如何生成,我们在后文再议。

有了串首终结符集的定义后,我们再次回头看一下产生式AαA \rightarrow \alpha的可选集SELECT(Aα)SELECT(A \rightarrow \alpha)。我们先给出定义:

\begin{equation*} SELECT(A \rightarrow \alpha) = \begin{cases} FIRST(\alpha), & \text{if} \quad \varepsilon \notin FIRST(\alpha) \\ (FIRST(\alpha) - \{\varepsilon\}) \cup FOLLOW(A), & \text{if} \quad \varepsilon \in FIRST(\alpha) \end{cases} \end{equation*}

我们首先解读这个定义,先看第一种情况,因为εFIRST(α)\varepsilon \notin FIRST(\alpha),即α\alpha无法通过任意步骤推导得到空串,即产生式AαA \rightarrow \alpha的左部AA是无法推导得到空串,那么该产生式的可选集是FIRST(α)FIRST(\alpha)确实是合理的,试想,现在我有个输入字符c,因为我知道εFIRST(α)\varepsilon \notin FIRST(\alpha),那么我检查字符c是不是在FIRST(α)FIRST(\alpha)中就可以做出判断报错还是继续,而无需担心因为左部AA可以产生空串进而还要考虑AA的后继符号集;

对于第二种情况,其实就是把空串的情况也考虑了:不仅仅要判断输入字符是不是在右部的串首终结符集中,还因为左部AA能够推导出空串,而判断是不是在左部的后记符号集中。当然这里排除串首终结符中的空串的含义也是显而易见的,正式因为AαεA \rightarrow \alpha \Rightarrow^* \varepsilon,我们才要判断是否在FOLLOW(A)FOLLOW(A)中,这里自然要排除空串了。

LL(1)型文法

在上述理论基础上,我们现在将产生式的右部已经扩展到了任意形式了。于是,我们引入LL(1)型文法。它的定义如下:

文法G是LL(1)的,当且仅当G的任意两个具有相同左部的产生式AαβA \rightarrow \alpha|\beta满足如下的条件:

  1. 如果α\alphaβ\beta均不能推导出ε\varepsilon,则FIRST(α)FIRST(β)=FIRST(\alpha) \cap FIRST(\beta) = \emptyset
  2. α\alphaβ\beta至多有一个能推导出ε\varepsilon。如果βε\beta \Rightarrow^* \varepsilon,则FIRST(α)FOLLOW(A)=FIRST(\alpha) \cap FOLLOW(A) = \emptyset;如果αε\alpha \Rightarrow^* \varepsilon,则FIRST(β)FOLLOW(A)=FIRST(\beta) \cap FOLLOW(A) = \emptyset

对于情况1,对于α\alphaβ\beta都无法推导出空集的时候,假设两者的串首终结符集的交集不为空,那么对于某个输入字符c,我到底该选择右部为α\alpha的产生式还是β\beta的呢?很显然出现了二义性,所以我们有了交集为空集的条件;

对于情况2,首先解释为什么至多有一个能推到出ε\varepsilon。假设都能推导出空集,根据前面产生式的可选集对于右部推导有空集的情况,可选集会包含左部的后记符号集,在这里也就是说,SELECT(Aα)SELECT(A \rightarrow \alpha)SELECT(Aβ)SELECT(A \rightarrow \beta)都有FOLLOW(A)FOLLOW(A),当输入字符c属于FOLLOW(A)FOLLOW(A)的时候,我们应该选择哪一个产生式呢?于是又出现了二义性。所以我们限定只能至多一个能够推导出ε\varepsilon。再进一步,当其中一个右部(这里以β\beta为例)能够推导出ε\varepsilon,那么SELECT(Aβ)={FIRST(β){ε}}FOLLOW(A)SELECT(A \rightarrow \beta) = \{FIRST(\beta) - \{\varepsilon\}\} \cup FOLLOW(A),而α\alpha无法推导出ε\varepsilon,所以SELECT(Aα)=FIRST(α)SELECT(A \rightarrow \alpha) = FIRST(\alpha),为了保证不会出现二义性,我们需要SELECT(Aβ)SELECT(Aα)=SELECT(A \rightarrow \beta) \cap SELECT(A \rightarrow \alpha) = \emptyset,这里FIRST(α)FIRST(\alpha)FIRST(β)FIRST(\beta)的交集不为空是一个隐藏的条件,因为是情况1的情形;于是,我们还需要保证FIRST(α)FOLLOW(A)=FIRST(\alpha) \cap FOLLOW(A) = \emptyset