说明:文章内容可能随时修改。
=======================================================
和First集合一样,Follow集合也是语法分析中要用到的一个重要的概念,不管是自顶向下的分析方法,还是自底向上的分析方法,都需要用到Follow集合。
一、Follow集合的定义
对于First集合而言,我们可以针对终结符、空记号、非终结符和文法符号串来计算,但是Follow集合则只考虑非终结符,换言之,终结符、空记号和文法符号串是没有所谓Follow集合一说的。对于非终结符A,我们通常用Follow(A)来表示它的Follow集合。
从集合元素上看,First集合可以包含终结符和空记号ε,而Follow集合则是由终结符和一个特殊符号“$”组成,它不能包含空记号,这是因为,Follow集合想要表示的,其实是当一个非终结符号通过规约出现时,接下来的输入可以是哪些终结符号,而实际上在分析过程中,我们是不可能从输入识别出所谓“空记号”的。在考虑First集合时引入空记号,只是为了说明某个符号(串)可以“消失”,而在实际的输入串中,并不真的存在“空记号”这样的东西。
接下来,我们来看Follow集合是怎么定义的,其实也说明了Follow集合的计算方法。
最开始的时候,所有符号的Follow集合我们都看成是空集,而对于起始符号S,我们需要单独考虑,那就是Follow(S)={$}。这是因为,当已经规约到只有起始符号时,唯一合法的情况就是输入串剩下的只有标志其结束的“$”符号,不再包含任何终结符,换言之,起始符号后面不能够再有任何输入。
然后我们开始逐个看文法的产生式。对于每一个产生式,只有当一个非终结符出现在其右部的时候,它才对计算这个非终结符的Follow集合有用。另外需要说明的是,Follow集合的计算时建立在First集合的基础上。
对于一个产生式B→αAγ,我们分两种情况来看:
(1) ε∈First(γ),那么Follow(A)一定包含First(γ),但是要注意的是,由于First()可能包含空记号,因此应该写成:Follow(A)=Follow(A)⋃(First(γ)-{ε});
(2) ε∉First(γ),那么这时候相当于是说,B可以直接用αA替换,这种替换没有任何限制,因为我们考虑的是上下文无关文法,由此,显而易见的一点就是,Follow(A)一定包含Follow(B)——反过来是不成立的,因为可能存在一个C→βA的产生式,那么此时出现在A后面的符号,能出现在C后面,却未必能出现在B后面。
从某种角度来看,这两条其实说的是一件事情:紧跟在A后面的终结符,是A后面可能出现的串的第一个终结符。
二、计算Follow集合的算法
计算一个非终结符,基本的思路是要不断地依次利用每一个产生式计算Follow集合,直到所有的Follow集合都不再改变。对所有Follow集合初始化之后,在每一遍计算过程中,对于每一个产生式,我们都需要按照定义中指出的两种情况,考虑出现在它右部的每一个终结符。
在有空产生式的情况下,算法可以得到简化,注意到没有空产生式就意味着任何First集合中都不可能有空记号,那么也就是说,对于一个产生式B→αAγ,只有上面的第1种情况会发生,那么最内存循环就不需要再进行空记号的判断了。将上面的算法中最后两行的那条if语句去掉,就得到了简化后的算法。
三、举例
四、小结
1. 只有非终结符才有Follow集合。
2. Follow集合只能包含终结符或者“$”符号。
3. Follow集合的计算是建立在First集合的基础上。
4. 一个终结符可以利用某个产生式来计算自己的Follow集合,当且仅当它出现在了这个产生式的右部。
♪ 参考资料
Louden, K. C.(1997). Compiler Construction: Principles and Practice: PWS Pub. Co.