语法分析器从词法分析器获得 Token 序列
- 对于语法错误的程序
报告错误信息, - 对于语法正确的程序
生成语法分析树, 例如抽象语法树, Abstract Syntax Tree, AST( )
自顶向下指的是
描述编程语言的语法结构
为描述编程语言的语法结构
上下文无关文法( Context-Free Grammar, CFG)
上下文无关语言 $L(G)$ 是正则语言 $L(r)$ 的超集
正则语言不能用于描述配对或嵌套的结构
$T$ - 终结符集合
$N$ - 非终结符集合
$P$ - 产生式集合
$S$ - 开始符号
上下文无关文法的含义是
推导与规约
推导与规约是产生式和非终结符之间的转换过程
直接推导 把产生式看成重写规则
用符号 $\Rightarrow$ 表示直接推导
直接规约 把符号串中的某一部分用某一个产生式左部来代替
即如果 $\alpha A \beta \Rightarrow \alpha \gamma \beta$
最左推导 指的是每步总是代换最左边的非终结符
最左规约 是最右推导的反过程
句型、 句子、 语言
句型
句子
语言
编程语言文法
上下文无关文法也是编程语言文法的超集
如果文法的某些句子存在不止一棵分析树
产生式 $E \rightarrow E + E\ |\ E \times E\ |\ (E)\ |\ id$
对于句子 $id \times id + id$ 存在两棵不同的分析树
src:https://rainoftime.github.io/teaching/index.html
在编程语言文法的情形下
将文法进行分层
我们首先考虑符号的优先级
然后考虑符号的结合性
这里的改写方法是
优先级( 根据算符不同的优先级) 为每一个算符引入新的非终结符, 越接近开始符号的算符优先级越低, 结合性( 递归非终结符在终结符左边) 运算就左结合, 反之就右结合, 例如上面的 $+$ 与 $\times$,
递归下降分析( Recursive-Descent Parsing)
使用递归实现的穷举法
- 如果 $X_i$ 是一个非终结符
递归调用非终结符 $X_i$ 对应的处理过程, - 如果 $X_i$ 是一个终结符 $a$
匹配输入串中对应的终结符 $a$,
如果选择了不合适的产生式
例如
src:https://rainoftime.github.io/teaching/index.html
由于往往需要对多个非终结符进行推导展开
预测分析法( Predictive Parsing)
$LL(k)$ 文法
L = left-to-right
L = leftmost derivation
k = 向前看 $k$ 个 Token 来确定产生式
$LL(1)$ 文法
$LL(1)$ 文法
$\text{First}(\alpha) ::= \{a | \alpha \Rightarrow^* a\dots,a \in T\}$
$\text{Follow}(A) ::= \{a | S \Rightarrow^* \dots Aa\dots, a \in T\}$
$\text{Nullable}(A) ::= \text{whether A can derive } \varepsilon$
为确保产生式选择的唯一性
- $\text{First}(\alpha) \cap \text{First}(\beta) = \varnothing$
即( $\alpha$ 与 $\beta$ 不能推导出以同一个字符为首的串: ) - 若 $\beta \Rightarrow^* \varepsilon$
那么 $\alpha \nRightarrow^* \varepsilon$, 且 $\text{First}(\alpha) \cap \text{Follow}(A) = \varnothing$, 即( $\alpha$ 与 $\beta$ 不能同时推导出 $\varepsilon$: 在 $\beta$ 能推导出空串的情形下。 $\alpha$ 可能推导出的串的首部不应该出现在可能在推导过程中跟在 $A$ 右边的终结符号集中, )
$LL(1)$ 文法满足以下三个性质
- $LL(1)$ 文法是无二义的
- $LL(1)$ 文法是无左递归的
即( 不存在 $A \rightarrow^* A\alpha$: ) - $LL(1)$ 文法是无左公因子的
即( 不存在 $X \rightarrow \alpha\beta\ |\ \alpha\gamma$: )
消除左递归
对于产生式 $A \rightarrow^* A\alpha\ |\ \beta$
观察
消除左公因子
对于产生式 $X \rightarrow \alpha\beta\ |\ \alpha\gamma$
通过改写产生式来推迟决定
$LL(1)$ 的预测分析法( 递归下降实现)
- 计算所有非终结符的 $\text{First}()$
$\text{Follow}()$, $\text{Nullable}()$, - 构造预测分析表
- 根据预测分析表上的内容
即可通过向前看一个输入符号来唯一地确定选择哪个产生式,
计算所有非终结符的 $\text{Nullable}()$, $\text{First}()$, $\text{Follow}()$
$\text{Nullable}()$
根据 $\text{Nullable}()$ 的归纳定义 base case 以及 inductive caseFalse
src:https://rainoftime.github.io/teaching/index.html
$\text{First}()$
根据 $\text{First}()$ 的归纳定义 base case 以及 inductive case
src:https://rainoftime.github.io/teaching/index.html
$\text{Follow}()$
根据 $\text{Follow}()$ 的归纳定义 base case 以及 inductive case
src:https://rainoftime.github.io/teaching/index.html
构造预测分析表
以非终结符为行
for each production "A -> \alpha"
for each a \in first(\alpha)
add "A -> \alpha" to T[A, a]
if \varepsilon \in first(\alpha) then
for each b \in follow(A)
add "A -> \alpha" to T[n , b]
或者引入 $\text{Nullable}$ 的概念时
for each production "A -> \alpha"
for each a \in first(\alpha)
add "A -> \alpha" to T[A, a]
if \alpha is Nullable then
for each a \in follow(A)
add "A -> \alpha" to T[A, a]
注意 这里的 $\alpha$ 可能是由若干终结符与非终结符组合而成的右部
对于产生式 $E \rightarrow E_1\ E_2\ E_3\ \dots \ E_n$
令右部 $\text{First}(\text{RHS})$ 集初始为空
顺序检定终结符或非终结符 $E_i$
- 如果 $E_i$ 是终结符 $t$
: - 如果 $t \neq \varepsilon$
则将 $t$ 并入 $\text{First}(\text{RHS})$ 集, 然后终止检定, - 如果 $t = \varepsilon$
则将 $\varepsilon$ 并入 $\text{First}(\text{RHS})$ 集, 然后检定 $E_{i+1}$,
- 如果 $t \neq \varepsilon$
- 如果 $E_i$ 是非终结符
: - 如果 $\text{Nullable}(E_i) = 0$
则将 $\text{First}(E_i)$ 并入 $\text{First}(\text{RHS})$ 集, 然后终止检定, - 如果 $\text{Nullable}(E_i) = 1$
则将 $\text{First}(E_i)$ 并入 $\text{First}(\text{RHS})$ 集, 然后检定 $E_{i+1}$,
- 如果 $\text{Nullable}(E_i) = 0$
如图所示add "A -> \alpha" to T[n , b]
src: https://www.cs.york.ac.uk/fp/lsa/
可以证明
$LL(1)$ 语法分析器可视化
https://www.cs.princeton.edu/courses/archive/spring20/cos320/LL1/
错误恢复
如果语法分析器的接收的 token 流不是文法所能够接受的
我们可以通过删除
一个通过删除 token 实现的从错误中恢复的方法是