编译原理基础:FOLLOW 集合与 LL(1) 文法条件
嗨!学编译原理是不是觉得脑子有点绕?上次我们聊了 FIRST 集合和提取公共左因子,今天我们来认识两个新朋友:FOLLOW 集合和 LL(1) 文法条件。别怕,我会用简单的话讲明白它们是什么,以及它们在编译器里怎么帮忙。
FOLLOW 集合:后面跟啥?
感性理解
想象你在读代码,编译器已经认出了一个符号(比如变量名),接下来它得猜猜后面会跟什么------是加号?分号?还是别的啥?FOLLOW 集合就像是编译器的"后视镜",告诉它某个符号后面可能会出现的东西。有了这个,编译器就不会在解析时迷路。
形式化表达
在文法里,FOLLOW 集合是针对某个非终结符(non-terminal),计算出它在推导过程中后面可能紧跟着的终结符(terminal)的集合。
定义一下:
- 对于非终结符 A,FOLLOW(A) 是所有在推导中可能出现在 A 后面的终结符的集合。
- 如果 A 是句子的结尾(比如整个程序的末尾),那 FOLLOW(A) 里还会包括特殊符号 $(表示输入结束)。
举个例子:
假设有文法:
bash
S → E ;
E → id | ( E )
- FOLLOW(E) 是啥?E 出现在 S → E ; 里,后面跟着 ";",还出现在 ( E ) 里,后面跟着 ")",所以 FOLLOW(E) = { ;, ) }。
- FOLLOW(S) 呢?S 是开始符号,后面啥也没有,所以 FOLLOW(S) = { $ }。
FOLLOW 集合的作用是啥?它帮编译器在解析时知道"接下来该期待啥",尤其在预测式分析里特别有用。
LL(1) 文法条件:让解析不纠结
感性理解
你有没有遇到过那种选择题,两个选项开头长得一模一样,搞得你完全不知道选哪个?编译器也讨厌这种"纠结"的情况。LL(1) 文法就像是给编译器定了个规矩:每次看一个符号,就能立刻决定走哪条路,不用犹豫。简单说,它让解析变得"干脆利落"。
形式化表达
LL(1) 文法是一种上下文无关文法,满足以下条件:
- L:从左到右扫描输入。
- L:从左推导(leftmost derivation)。
- 1:只看一个符号(lookahead 一个 token)就能决定用哪个产生式。
具体条件是:对于一个非终结符 A 的所有产生式 A → α | β:
- FIRST(α) 和 FIRST(β) 不能有交集(开头不能撞车)。
- 如果 α 或 β 能推导出空串 ε,那么 FIRST(β) 或 FIRST(α) 不能和 FOLLOW(A) 有交集(空串的情况不能和后面冲突)。
举个例子:
文法:
css
S → a S | b
- FIRST(a S) = { a },FIRST(b) = { b },没交集,满足条件。
但如果改成:
css
S → a S | a T
- FIRST(a S) = { a },FIRST(a T) = { a },有交集,就不是 LL(1) 的了。
这俩咋配合?
- FOLLOW 集合:帮 LL(1) 分析器判断某个非终结符推导完后,后面会跟啥,尤其在处理空串时特别关键。
- LL(1) 条件:用 FIRST 和 FOLLOW 集合来检查文法是不是"干脆"的,能不能一步到位解析。
比如预测分析表(parsing table)就是靠 FIRST 和 FOLLOW 集合填出来的。编译器看到输入符号,查表就能知道用哪个产生式,效率超高!
小结
FOLLOW 集合像是编译器的"后视镜",告诉它后面可能跟啥;LL(1) 文法条件则是"交通规则",保证解析时不撞车、不纠结。理解了这两个,你就掌握了预测式分析的核心啦!下次可以试着自己写个小文法,算算 FIRST 和 FOLLOW,看看能不能满足 LL(1),动手玩玩就更明白了。加油哦!