自上而下分析遇到的问题
- 左递归
- 回溯
消除左递归
直接左递归
P→Pα∣β
β 不以 P 开头,推导后可得
P &\Rightarrow& P\alpha|\beta \\
&\Rightarrow& P\alpha\alpha\\
&\Rightarrow& P\alpha\alpha...\alpha\\
&\Rightarrow& \beta\alpha\alpha...\alpha
也就是会进入不断地递归,但是这个文法结束的时候一定是开头为 β 的时候
1 2 3 4
| void ParseP() { ParseP(); ... }
|
左递归转右递归
既然能够确定 P 结束的时候一定是以 β 开头,不如直接将文法转化为:
P→βP′P′→αP′∣ε
于是递归就转换到了产生式右边的结尾。
在进行语法分析时,产生式将不断地将源程序读入与终结符 α 进行匹配,故在面对有限长度的程序输入时不会陷入死循环
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| void ParseP() { if (curToken == 'b') { nextToken(); ParseP2(); } ... } void ParseP2() { if (curToken == 'a') { nextToken(); ParseP2(); } ... }
|
左递归文法:
P→Pα1∣Pα2∣...∣Pαm∣β1∣β2∣...∣βn
转化为右递归:
P &\rightarrow& \beta_1P' | \beta_2P'|...|\beta_nP'\\
P' &\rightarrow& \alpha_1P' | \alpha_2P'|...|\alpha_mP'|\varepsilon
例题:
&& E \rightarrow E+T|T \\
转化后:&& \\
&&E \rightarrow TE' \\
&&E' \rightarrow +TE'
间接左递归
直接看产生式没有左递归,但是在推导过程中产生了左递归
文法消除左递归的条件:
- 不含以 ε 为右部的产生式
- 不含回路,,即不含 P+P
消除思路:
- 将文法 G 的所有非终结符按照任一种顺序排列 Pi
- 把 Pi 的规则改造成 Pi→a..∣Pi+1...∣Pi+2… ,即产生式右部若以非终结符开头,一定要是按照第 1 步的顺序 Pi 往后的非终结符,如果不是,则用其定义进行替换
- 化简,删除无用的产生式
由于排序不同,得到的产生式可能不一样,但是是等价的
消除回溯
A→B∣CB→abdC→abcd
A 可能的两个候选都是非终结符开头,若选择用 B 去匹配,若源程序为 “abcd”,则匹配到 ‘c’ 的时候发现不符合 B 的文法规则,于是需要回溯到 ‘a’,再使用 C 进行匹配。
解决思路
A→α1∣α2∣α3∣...∣αn
终结首符集 (FIRST 集合):将来若干步推导后在开头出现的终结符集合
FIRST(αi)={αi∣αi∗a...,a∈VT}
可能包含空字
FIRST(α) 也就是选择非终结符候选 α 后,经过若干步推导、可能存在于首部的非终结符的集合
要根据 FIRST(α) 确定选择哪个候选,前提条件是任意两个候选的 FIRST 集合都不相交。
提取公共左因子
引进非终结符和新的产生式
A \rightarrow \var\beta_1 | \var\beta_2|\var\beta_3...
可改写为
A \rightarrow \var A' \\
A' \rightarrow \beta_1|\beta_2|...|\beta_n
候选含有空字
A→α1∣α2∣α3∣...∣αn∣ε
若此时遇到了要匹配的字符 a,是否应该使用 A 的空字去匹配,应该取决于 A 之后是否有其他符号可以匹配 a,所以需要有 FOLLOW 集合(S 为开始符号)
FOLLOW(A)={a∣S∗...Aa...,a∈VT}
就是在某个句型里,可能跟在 A 后面的终结符
若存在 S∗...A 则规定 # ∈ FOLLOW(A)‘
LL(1) 文法
含义
L: 从左到右
L: 最左推导
1: 根据当前单词分析
规则
-
不含左递归
-
所有产生式的所有候选的 FIRST 集合不相交
-
对每个非终结符 A 如果候选的 FIRST 集合包含空字,则
FIRST(αi)∩FOLLOW(A)=ϕ
也就是当需要使用 ε 的时候,一定是不得已而为之(候选 FIRST 集合内没有当前这个输入字符),且之后有其它推导可以匹配当前的这个字符,如果之后也没有,则报错。
分析
A→α1∣α2∣α3∣...∣αn
-
若 a∈FIRST(αi),则指派 αi 执行任务
-
若 a∈/FIRST(αi):
- ε∈FIRST(αi) 且 a∈FOLLOW(A),则让 A 与 ε 匹配
- 否则出错
SELECT 集合
SELECT(A→α)={(FIRST(α)−{ε})∪FOLLOW(α)FIRST(α)α∗εα⇏∗ε
代表的是当遇到某个字符 a 的时候,如果 a∈SELECT(A→α) ,则应该使用 A 的候选 α 来匹配 a
FIRST 与 FOLLOW 的构造
FIRST(αi)={αi∣αi∗a...,a∈VT}
对每一个 X∈VT∪VN:
- 若 X 本身就是终结符,则 FIRST(X) = {X}
- 若 X→a...,则 a 加入 FIRST(X) 中,若推出空字也加入
- 若 X→Y... 是一个产生式,则 FIRST(Y) 的所有非空字(因为 Y 后可能还有其他符号)元素加入 FIRST(X)
- 若 X 可以推出很长的符号串,且前 j 个符号都是非终结符,且都能推出 ε ,则需要将第 j + 1个符号的 FIRST 集合加入 FIRST(X)
- 如果 X 可以推出的符号串中的所有符号都可以推出 ε,则 ε 应该加入 FIRST(X)
FOLLOW(A)={a∣S∗...Aa...,a∈VT}
对每个非终结符 A
- 对于文法的开始符号,将 # 加入 FOLLOW(S)
- 若 A→αBβ 是一个产生式,则把 FIRST(β)−{ε} 加入 FOLLOW(B)(β 的第一个字符就是 B 的后继字符)
- 若 A→αB 是一个产生式,或 A→αBβ 是一个产生式而 β∗ε,则把 FOLLOW(A) 加入 FOLLOW(B) 中(B 在 A 产生式的最右边,所以跟在 A 后面的元素也可能跟在 B 后面)
总结:
- 看有没有产生式是 一个非终结符跟在另一个非终结符后面的,有的话,就要考虑将后面的 FIRST 加入前面 FOLLOW
- 看有没有位于产生式末尾的非终结符,或者位于产生式末尾的非终结符可能推出 ε ,有的话,则考虑将左部的 FOLLOW 加入末尾的 FOLLOW
预测分析
- 总控程序
- 分析表:M[A,a] 矩阵,A∈Vn,a∈VT∪{#}
- 分析栈:
- 初始状态:#E
- 读入新的符号 a,查表 M[E, a],并将查询结果压入分析栈
构造预测分析表
- 构造 FIRST 集合和 FOLLOW 集合
- 构造分析表
A→α
应该放到分析表中的 A 行,放到 FIRST(α) 中所有元素的列中
A→ε
应该放到分析表中的 A 行,放到 FOLLOW(α) 中所有元素的列中
所有没有定义的格子上放上出错标志
若文法满足 LL(1),其上不会有多重定义入口(也就是表格内的一个单元格有两个值)