S_型文法到q_型文法再到LL(1)型文法演进笔记
S_型文法(简单的确定性文法)
-
每个产生式的右部都以终结符开始
-
同一非终结符的各个候选式的首终结符都不同
针对第一条的理解是,只要右部都是终结符开始,那么对于串当前的读入字符,我们可以很容易的直接根据右部开头的终结符来进行判断匹配,而无需进行产生式的推导;针对第二条的理解是,因为首终结符都不一样,所以根据当前串读入的字符,我们只会匹配到一个确定的产生式。
举例,现有文法如下:
1.S→aBC2.B→b3.C→c
现有字符串abc,我们从左部第一个字符开始输入,因为字符a能够被产生式1匹配,因此我们首先选择产生式1.S→aBC;接下来输入第二个字符b,于是我们从产生式1到3种进行匹配判断,找到左部非终结符为B(找B的原因是上一步我们已经匹配到了产生式1,产生式1的右部现在已经匹配上了a字符,我们接下来需要推导B,所以要找左部为非终结符B的产生式),右部首终结符为b的产生式,于是找到产生式2。以此类推,我们最后能够如下的推导:
S→aBC→abC→abc
个人理解觉得,针对S_型文法的特征是:我们总是能够根据读入的字符,直接匹配找到一个确定的产生式。
然而,该文法的局限性非常的大,最基本的一点就是该文法不包含ε的产生式。例如,现有下列文法:
1.S→aBC2.B→bC3.B→dB4.B→ε5.C→c6.C→a7.D→e
对于字符串ade,首先还是产生式1没得说;接下来,我们需要寻找的是左部为B,右部首终结符为d的产生式,于是我们选择产生式3,此时产生式推到得到:
1.⇒a,选择产生式1,得到aBC2.⇒d,选择产生式3,得到adBC3.⇒e,因为产生式2、3的首位非终结符不是e,这里选择ε,即选择产生式4,得到adC
因为此时输入字符为e,且当前推导的式子中还存在非终结符C,于是我们继续应用上述规则进行匹配选择(即,我们结束第3轮,进入第4轮准备开始查找),然而我们无法找到形如C→e...的产生式,此时报错。
尽管最终报错了,但是当前的文法存在这样一个问题:因为ε产生式的存在,本该在第3轮中就该发现的再无匹配的问题,在第4轮的检查过程中才被发现。为什么说第3轮就该发现呢?我们回到第3轮的检查中,上面说“因为产生式2、3的首位非终结符不是e,我们选择B→ε,但是选择这个产生式真的正确吗?事实上,当产生式右部是ε的时候,我们应该要考虑空串之后紧跟着的非终结符是什么,如果我们知道紧跟着的非终结符也和当前的输入符号不匹配的话,我们立刻就能知道选择了空串后的下一步必然是无法匹配的。这里ε后面紧跟的等价于B紧跟的,那么通过产生式1我们知道B后面紧跟的是C,而C能够推导出c和a,故在第3轮中,我们首先知道已经无法选择产生式2和3了,接下来我们判断产生式4是否满足,因为当前的输入e依然无法和c、a匹配,所以在第3轮我们已经无法继续进行下去,就应该报错了。
而我们上面所说的“紧跟着的…”也就是接下来要引入的一个概念:FOLLOW集。
FOLLOW集(后继符号集)
非终结符A的后继符号集
可能在某个句型中紧跟在A后边的终结符a的集合,记为FOLLOW(A)
FOLLOW(A)={a∣S⇒∗αAaβ,a∈VT,α,β∈(VT∪VN)∗}
有了FOLLOW集这个东西之后,再回头看待ε产生式就变得很明朗了。
如果当前某非终结符A的产生式右部的首字符与输入a不匹配的时候,若存在A→ε,则我们检查a是否存在于A的后继符号集FOLLOW(A)中,若在其中,则匹配继续,否则程序报错。
于是,对于上面的判断流程的第3步中,FOLLOW(B)={a,c},而当前我们遇到的符号是e,不在对应的集中,于是该步骤已经结束。
SELECT集(产生式可选集)
我们综合上述的对于非ε产生式和ε产生式,可以定义产生式的可选集这一概念。
产生式A→β的可选集是指可以选用该产生式进行推导时对应的输入符号的集合,记为SELECT(A→β)。例如:
SELECT(A→αβ)={α}
SELECT(A→ε)=FOLLOW(A)
q_文法
每个产生式的右部或为ε或以终结符开始
具有相同左部的产生式有不可相交的可选集
我们称这样的文法为q_型文法。为什么引入q_型文法,因为前面的FOLLOW集概念的引入,解决ε产生式的问题,而这一点可以很好的地对照在q_型文法的第一条定义。尽管它对于S_型文法限制有了略微的放宽,支持了ε的产生式,可是还是有一定的限制:q_文法不含右部以非终结符
打头的产生式。
在前文中,我们总是在限制产生式的右部,要么首字符是一个终结符,要么是ε空串,之所以这样是因为我们总是希望在进行输入的时候右部具有确定的信息(这里就是一个首终结字符)。然而,当右部打头的是非终结符的时候,我们就无从下手了(至少目前是),因为非终结符是不确定的(至少一下子无法知道),它最终能够推导成什么样子的串,似乎变数太多,要是我们事先有一个集合,已经存放好了产生式右部的串(无论它是ε,还是终结符,还是非终结符打头)的首非终结符,每次进行输入匹配的时候,看一下是不是在这里面,问题就迎刃而解了。于是,我们引入概念:串首终结符集。
串首终结符集
给定一个文法符号串α,α的串首终结符集FIRST(α)被定义为:可以从α推导出的所有串首终结符的集合。此外,如果α⇒∗ε,那么ε∈FIRST(α)。即:
对于∀α∈(VT,VN)+,FIRST(α)={a∣aβ,a∈VT,β∈(VT∪VN)∗}如果α⇒∗ε,那么ε∈FIRST(α)
至于这个串首终结符集如何生成,我们在后文再议。
有了串首终结符集的定义后,我们再次回头看一下产生式A→α的可选集SELECT(A→α)。我们先给出定义:
\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(α),即α无法通过任意步骤推导得到空串,即产生式A→α的左部A是无法推导得到空串,那么该产生式的可选集是FIRST(α)确实是合理的,试想,现在我有个输入字符c,因为我知道ε∈/FIRST(α),那么我检查字符c是不是在FIRST(α)中就可以做出判断报错还是继续,而无需担心因为左部A可以产生空串进而还要考虑A的后继符号集;
对于第二种情况,其实就是把空串的情况也考虑了:不仅仅要判断输入字符是不是在右部的串首终结符集中,还因为左部A能够推导出空串,而判断是不是在左部的后记符号集中。当然这里排除串首终结符中的空串的含义也是显而易见的,正式因为A→α⇒∗ε,我们才要判断是否在FOLLOW(A)中,这里自然要排除空串了。
LL(1)型文法
在上述理论基础上,我们现在将产生式的右部已经扩展到了任意形式了。于是,我们引入LL(1)型文法。它的定义如下:
文法G是LL(1)的,当且仅当G的任意两个具有相同左部的产生式A→α∣β满足如下的条件:
- 如果α和β均不能推导出ε,则FIRST(α)∩FIRST(β)=∅。
- α和β至多有一个能推导出ε。如果β⇒∗ε,则FIRST(α)∩FOLLOW(A)=∅;如果α⇒∗ε,则FIRST(β)∩FOLLOW(A)=∅。
对于情况1,对于α和β都无法推导出空集的时候,假设两者的串首终结符集的交集不为空,那么对于某个输入字符c,我到底该选择右部为α的产生式还是β的呢?很显然出现了二义性,所以我们有了交集为空集的条件;
对于情况2,首先解释为什么至多有一个能推到出ε。假设都能推导出空集,根据前面产生式的可选集对于右部推导有空集的情况,可选集会包含左部的后记符号集,在这里也就是说,SELECT(A→α)和SELECT(A→β)都有FOLLOW(A),当输入字符c属于FOLLOW(A)的时候,我们应该选择哪一个产生式呢?于是又出现了二义性。所以我们限定只能至多一个能够推导出ε。再进一步,当其中一个右部(这里以β为例)能够推导出ε,那么SELECT(A→β)={FIRST(β)−{ε}}∪FOLLOW(A),而α无法推导出ε,所以SELECT(A→α)=FIRST(α),为了保证不会出现二义性,我们需要SELECT(A→β)∩SELECT(A→α)=∅,这里FIRST(α)和FIRST(β)的交集不为空是一个隐藏的条件,因为是情况1的情形;于是,我们还需要保证FIRST(α)∩FOLLOW(A)=∅。