FIRST集合、FOLLOW集合和预测分析表
由于课本的描述过于形式化,理解十分困难,故撰写本文。
本文为笔者多方参考后撰写的,力求简单易懂、步骤清晰。为了容易理解,语言可能比较啰嗦。
FIRST集合的求解
参考:【编译原理】大白话讲解 First 集和 Follow 集的构造算法 - bw98 - 博客园 (cnblogs.com)
假设我们要求非终结符A的FIRST集合。分以下几种情况:
- 最简单的情况,A->b,b是终结符。那么FIRST(A)={b}。即终结符的FIRST集合是它本身。
- 稍复杂一些的情况,A->b|c|...|z,其中b到z都是终结符,那么FIRST(A)={b, c, ..., z}。如果这里面有ε,那么把ε也加入FIRST(A)。
- 更复杂的情况,A->B|c|...|z,其中B是非终结符,c到z是终结符(也可以没有),那么FIRST(A)=FIRST(B)\{ε}∪{c, ..., z}。即所有终结符(如果有)都加入,同时要把FIRST(B)中除去ε(如果有)的部分合并进来。
- 最复杂的情况,A->B|C|...|Z,其中B到Z都是非终结符。这时要尤其留意ε。如果其中某个非终结符前面所有的非终结符的FIRST集合都包含ε,那么这时将 该非终结符 的FIRST集合加入FIRST(A)(记得去除集合中的ε);如果A的所有候选非终结符都能推出ε,那么把ε也加入FIRST(A)。
虎书的描述
给定一个由终结符和非终结符组成的字符串γ,FIRST(γ)是从γ可以推导出的任意字符串中的开头终结符组成的集合。例如,令γ=T*F。任何可从γ推导出的由终结符组成的字符串都必定以id
、num
或(
开始。因此有
FIRST(T*F) = {id, num, (}
如果两个不同的产生式 X->γ1 和 X->γ2 具有相同的左部符号(X),并且它们的右部有重叠的FIRST集合,则这个文法不能用预测分析法来分析。因为如果存在某个非终结符I,它既在FIRST(γ1)中,又在FIRST(γ2)中,则当输入单词为I时,递归下降分析器中与X对应的函数将不知道该怎么做。
FIRST集合的计算似乎很简单。若γ=XYZ,则好像只要忽略Y和Z,只需计算FIRST(X)就可以了。但是考虑下面的文法就可以看出情况并不如此。
Z -> d
Z -> XYZ
T -> ε
Y -> c
X -> Y
X -> a
因为Y可能产生空串,所以X也可能产生空串,于是我们发现FIRST(XYZ)一定包含FIRST(Z)。因此,在计算FIRST集合时,我们必须跟踪能产生空串的符号,同时我们还必须跟踪有可能跟随在其后的其他符号。
FOLLOW集合的求解
B站的 BV1Yr4y1m7PX 里面给出了一种容易理解的方法。设要求解FOLLOW集合的对象是非终结符A:
- 先看开始符,如果是文法的开始符,则在FOLLOW集合中额外加入#。
- 在文法的所有推导式的右部寻找A,找到后看紧跟在A后面的字符,设为B。B可以是非终结符,也可以是终结符。如果B是终结符,则直接将B加入FOLLOW(A)(其实是将FIRST(B)加入FOLLOW(A),但是由于B是终结符,所以FIRST(B)就是B本身);如果B是非终结符,则将FIRST(B)中除去ε(如果有)的部分加入FOLLOW(A)。如果B只能推导出ε,或A后面根本没有字符,转3。
- 接2,如果B只能推导出ε,或A后面根本没有字符,则看当前推导式的左部,显然左部是一个非终结符。将这个非终结符的FOLLOW集合加入FOLLOW(A)。
- 还有一种情况,即2中的B能够推导出ε,但也能推导出其他非空符(即包含ε在内的多个候选)。这时要分开来看,结合2和3。因为B能推导出ε,所以应用规则3,将当前推导式的左部的非终结符的FOLLOW集合加入FOLLOW(A);因为B还能推导出其他符号,所以应用规则2,将FIRST(B)中除去ε(如果有)的部分加入FOLLOW(A)。这时FOLLOW(A)将是两部分。
求解FOLLOW集合时常常遇到FOLLOW(A)中包含FOLLOW(B),而FOLLOW(B)中也包含FOLLOW(A)的情况。根据笔者的经验,这时可以将两个包含同时删去,并使较短的FOLLOW集合和较长的FOLLOW集合中已有的部分保持一致。(未严格证明,慎用)
预测分析表
有关预测分析表的求法,上面的blog在最后提到了,很容易理解。
首先构造出预测分析表的第一行与第一列,第一行为文法出现的所有终结符以及‘#’(注意:没有 ε ,因为 Follow 集不含 ε),第一列为文法出现的所有非终结符。
然后对文法 G 的每个产生式 A -> ab 都执行如下操作:
【1】对于每个属于 First(ab) 的终结符 m ,都把 A -> ab 添加到预测表中的 [A, m] 中去
【2】如果 ε 也属于 First(ab),那么对于任何属于 Follow(A) 的字符 x,都把 A -> ε 加入到 [A, x] 中去
简单写一下笔者的理解,再作一点补充:
首先写出预测分析表的第一行和第一列。第一行是文法的所有终结符外加#,第一列是文法的所有非终结符。
然后逐行地看,对于每行,在第一列都有一个非终结符,不妨设为A。
找出文法中对应该非终结符的产生式(文法保证这样的产生式只有一条),不妨设为A->α
这里的α不是单个字符,而是一个字符序列(在最简单的情况下,α就是一个单独的非终结符或终结符),并且可能有多个候选,用竖线|分隔开。这些候选中可能还包含空字ε。
比较简单的情况是α是单个非终结符(无其他候选,即没有竖线)或多个终结符。
- 如果是单个非终结符,看FIRST(A),其中当然包含若干终结符。在表中找出这些终结符对应的列,在本行的格子中填入推导A->α(α只有一种情况,因此填的内容是一样的)
- 如果是多个终结符(没有非终结符),还是看FIRST(A),同样在表中找出对应终结符的列,在本行的格子中填入推导,但此时填入的内容是不一样的!表头对应的哪个终结符,格子里填的就是推出这个终结符的推导。比如A->a|b|c,则a所在的列就填入A->a,b所在的列就填入A->b,以此类推。注意,在这一条的情况下,FIRST集合中的元素直接来自产生式的右部,所以能够找到一一对应关系。
稍复杂的情况是多个非终结符候选,或者一个非终结符和若干终结符的候选。
此时就要看FIRST集合的求解过程了。注意FIRST(A)里面只有终结符,要找到终结符对应的推导,就要看这个终结符是来自哪个非终结符的FIRST集合。因为这种情况下FIRST集合的求解过程是递归的。找到终结符对应的非终结符(可以设为B)推导,将A->B写入对应终结符的格子里。
一句话,A有多个非终结符候选,终结符a来自哪个非终结符候选的FIRST集合,就把 A->对应非终结符 这个推导写入a对应的格子里。
另外,如果A的候选里有ε,那么要看FOLLOW(A),找到里面的终结符对应的格子,将A->ε填入格子里。
这样逐行地应用上面的规则,就求出了预测分析表。表中会有一些空位,这是正常的。空位表示当前行最左边的非终结符在遇到当前列最上边的终结符时,将无法正常推导,产生一个错误。
如果预测分析表的每个格子里的产生式都只有一个候选,那么这个表对应的文法是LL(1)的。
例题
-
求下面的文法G(E)的非终结符的FIRST集合和FOLLOW集合:
E -> TE' E' -> +TE'|ε T -> FT' T' -> *FT'|ε F -> (E)|i
FIRST(E) = {(, i}
FIRST(E') = {+, ε}
FIRST(T) = {(, i}
FIRST(T') = {*, ε}
FIRST(F) = {(, i}
FOLLOW(E) = {), #}
FOLLOW(E') = {), #}
FOLLOW(T) = {+, ), #}
FOLLOW(T') = {+, ), #}
FOLLOW(F) =
-
求下面的文法G(S)的非终结符的FIRST集合和FOLLOW集合:
S -> a|^|(T) T -> ST' T' -> ,ST'|ε
FIRST(S) = {a, ^, (}
FIRST(T) = {a, ^, (}
FIRST(T') = {,, ε} (注意有两个逗号,第一个是元素,第二个是集合的分隔符)
FOLLOW(S) = {), ,, #} (同上)
FOLLOW(T) = {)}
FOLLOW(T') =
-
求下面的文法G(E)的非终结符的FIRST集合和FOLLOW集合:
E -> TE' E' -> +E|ε T -> FT' T' -> T|ε F -> PF' F' -> *F|ε P -> (E)|a|b|^
FIRST(E) = {(, a, b, ^}
FIRST(E') = {+, ε}
FIRST(T) = {(, a, b, ^}
FIRST(T') = {(, a, b, ^, ε}
FIRST(F) = {(, a, b, ^}
FIRST(F') = {*, ε}
FIRST(P) = {(, a, b, ^}
FOLLOW(E) = {), #}
FOLLOW(E') = {), #}
FOLLOW(T) = {+, ), #}
FOLLOW(T') = {+, ), #}
FOLLOW(F) = {(, a, b, +, ), #}
FOLLOW(F') = {(, a, b, +, ), #}
FOLLOW(P) =