错误处理
在语法分析阶段,我们要检查程序中的语法是否正确,如果有错误,需要一些方法报错,以及越过错误点继续检查。
下面是几个错误处理方法
恐慌模式
最简单同时也是使用最多的方法。
当遇到一个错误时,我们跳过一些输入字符,直到遇到指定的字符(串)后从该处继续检查,这些指定的字符需要人为设置,通常称为同步字符(synchronizing token)。
错误产生式
在产生式集合中加入可以推导出含有错误的产生式,如果在程序推导过程中推导出了含有错误的产生式,就说明程序有语法错误,并且可以根据该含有错误的产生式报相应的信息。
局部和全局纠正
当错误产生时,在错误点修改(增删改)尽可能少的字符,以使程序正确,这个方法目前仅具理论价值。
自顶向下的语法分析
在自顶向下的分析中,我们要从开始符开始,如果通过推导可以得到输入串,那么说明输入串没有语法错误,并且在这个过程中我们也生成了语法分析树,如果不能推导出,则输入串有语法错误。
有两种方法:
- 第一种说白了,就是普通的深度搜索算法,其中会发生回溯,开销较大,不常用。
- 第二种是不会发生回溯的搜索算法,比第一种常用,结果是给出输入串的最左推导或发现输入中有错。
带回溯的深度搜索算法
对于当前的公式,从左到右对每个非终结符的可能推导一个一个尝试,直到找到结果(返回答案),或发现不匹配(回溯),如果所有可能都无法匹配,那么报错。
左递归
立即左递归
形如 A → A α A\rightarrow A\alpha A→Aα的产生式为立即左递归。
消除立即左递归
将 A → A α ∣ β A\rightarrow A\alpha |\beta A→Aα∣β替换为: A → β A ′ A\rightarrow \beta A^{'} A→βA′和 A ′ → α A ′ ∣ ϵ A^{'}\rightarrow \alpha A^{'} | \epsilon A′→αA′∣ϵ
消除一般的左递归
处理算法:
将非终结符排序为A1 A2 ... An
for (int i = 1; i <= n; i++) {
for (int j = 1; j < i; j++) {
将((Ai) -> (Aj)r) 替换为
(Ai) -> (c1)r | (c2)r | ... | (ck)r
}
消除Ai产生式间的立即左递归
}
不带回溯的预测算法
通过查看输入串中当前字符的下一个字符(look ahead),可以指导我们选择哪一个产生式进行推导,从而避免了开销较大的回溯过程。在这里需要使用LL(k)语法。
先来解释两个概念:
First(a)
我们需要知道从一个字符可以推导出的首个字符有哪些。
定义:从a可以推出的串的首字符的集合记为First(a)。
求First(a)的方法:
运用下面的3条规则直到没有可以被加入 F i r s t ( a ) First(a) First(a)的元素为止:
- 若 a a a为终结符,则 F i r s t ( a ) = a First(a) = {a} First(a)=a
- 若 a a a为非终结符,且 a → Y 1 Y 2 . . . Y n a\rightarrow Y_1Y_2...Y_n a→Y1Y2...Yn是一个产生式,对于任意 i ∈ [ 1 , n ] i \in [1,n] i∈[1,n],若有 Y 1 Y 2 . . . Y i − 1 ⇒ ∗ ϵ Y_1Y_2...Y_{i-1} \Rightarrow ^* \epsilon Y1Y2...Yi−1⇒∗ϵ,则 F i r s t ( Y i ) ⊆ F i r s t ( a ) First(Y_i) \subseteq First(a) First(Yi)⊆First(a)
- 如果有 a → ϵ a\rightarrow \epsilon a→ϵ,则将 ϵ \epsilon ϵ加入 F i r s t ( a ) First(a) First(a)。
我的理解:
- 对于规则1,终结符不能推出任何字符串,因此必然没有首字符集合。
- 对于规则2,若一个产生式右部的前半部分可以推成空,那么后半部分的首字符当然就是整个右部的首字符,即当作推出串的首字符。
Follow(a)
定义:紧跟在从a可以推出的串后面的第一个终结字符的集合即为Follow(a)。
方法:
- 将输入结束符$加入 F o l l o w ( S ) Follow(S) Follow(S),其中S为开始符号
- 若存在 A → α B β A\rightarrow \alpha B\beta A→αBβ,则 F i r s t ( β ) − ϵ ⊆ F o l l o w ( B ) First(\beta)-\epsilon \subseteq Follow(B) First(β)−ϵ⊆Follow(B)
- 若存在 A → α B A \rightarrow \alpha B A→αB,则 F o l l o w ( A ) ⊆ F o l l o w ( B ) Follow(A) \subseteq Follow(B) Follow(A)⊆Follow(B)
- 若存在 A → α B β A \rightarrow \alpha B \beta A→αBβ,且 ϵ ∈ β \epsilon \in \beta ϵ∈β,则 F o l l o w ( A ) ⊆ F o l l o w ( B ) Follow(A) \subseteq Follow(B) Follow(A)⊆Follow(B)
LL(k)语法
含义
第一个L表示从左到右遍历解析,第二个L表示产生最左推导,k表示向前看k个字符,通常是1。
判定
使用下面的规则判断文法G是否是LL(1)的:
当且仅当G中任意的两个产生式 A → α A\rightarrow \alpha A→α和 A → β A\rightarrow \beta A→β满足下面的条件:
- F i r s t ( α ) First(\alpha) First(α)和 F i r s t ( β ) First(\beta) First(β)不含相同终结符
- α \alpha α和 β \beta β中至多一个可以推出空串
- 若 β \beta β可以推出空串,则 α \alpha α不能推出以 F o l l o w ( A ) Follow(A) Follow(A)中某个终结符开头的串,同理对 α \alpha α。
我的理解:
- 这3条都是为了避免在推导中有多个选择的情况。
- 前两条说 F i r s t ( α ) First(\alpha) First(α)和 F i r s t ( β ) First(\beta) First(β)不相交,即在一个产生式右部的开头,一个终结符只能出现一次,换句话说,对于一个终结符,只能选择这一个产生式进行推导,也就避免了多个选择的情况。
- 第3条说,若 ϵ ∈ F i r s t ( β ) \epsilon \in First(\beta) ϵ∈First(β),则 F i r s t ( α ) First(\alpha) First(α)和 F o l l o w ( A ) Follow(A) Follow(A)不相交。
LL(1)的预测分析表构造
预测分析表是一个二维表格,其中某一项为M[A, a],表示当推导A时,若下一个字符为a,应该执行的产生式,即这个表告诉我们什么时候应该做什么事。
构造方法:
对于G中的每个产生式 A → α A \rightarrow \alpha A→α:
- 对于 F i r s t ( α ) First(\alpha) First(α)中的每个终结符a,将 A → α A \rightarrow \alpha A→α加入M[A, a]
- 若 ϵ ∈ F i r s t ( α ) \epsilon \in First(\alpha) ϵ∈First(α),对所有终结符 b ∈ F o l l o w ( A ) b \in Follow(A) b∈Follow(A),将 A → α A \rightarrow \alpha A→α加入M[A, b]
- 若 ϵ ∈ F i r s t ( α ) \epsilon \in First(\alpha) ϵ∈First(α),且$$ \in Follow(A) ,将 ,将 ,将A \rightarrow \alpha$加入M[A, $]