编译原理 笔记 1

自顶向下语法分析

自顶向下的核心思想是从文法的开始符号出发,每一步推导得到一个句型,最终产生一个句子即为期待的终结符串

带回溯的自顶向下分析

由于每一步推导的终结符串和使用的文法都是不确定的,因此复杂度很高,只能进行不断回溯,因此我们进行改进:每步推导总是用最左边的非终结符,产生最左推导,如果想进一步确认使用的文法,则需改进为:

确定的自顶向下分析

在从左向右扫描的过程中,向前查看常数个的单词,以确定每一步使用的文法

例如对于文法GG

SABAaAϵBbbB\begin{align*} S &\to AB \\ A &\to aA | \epsilon \\ B &\to b | bB \end{align*}

则在自顶向下分析anbm(n0,m>0)a^{n}b^{m}(n\geq 0, m > 0)时,只需要向前查看2个单词,即可预测每步所应该使用的文法

如果想要实现这种分析,需要保证文法不含左递归与左公因子,否则:

考虑文法GG

SSab\begin{align*} S &\to Sa | b \end{align*}

则在分析banba^{n}时需要向前看的单词数为n+2n + 2

考虑文法GG

SaAbaAcAaaA\begin{align*} S &\to aAb | aAc \\ A &\to a | aA \end{align*}

则在分析an(b+c)a^{n}(b + c)时需要向前看的单词数为n+2n + 2

都不为常数,无法确定地分析

LL(1) 分析

最常用的预测分析方法,要求文法是LL(1)文法

  • 向右扫描单词
  • 每步产生最推导
  • 向前看个单词

重要的集合

First集合

First集合定义为:

G=(VT,VN,P,S)G = (V_{T}, V_{N}, P, S)是上下文无关文法,则对α(VTVN)\alpha \in (V_{T}\cup V_{N})^{*}

First(α)={aαaβ,aVT,β(VTVN) or a=β=ϵ}\mathrm{First}(\alpha) = \{a\,|\, \alpha \Rightarrow^{*} a\beta,\, a\in V_{T}, \beta\in(V_{T}\cup V_{N})^{*}\text{ or } a = \beta = \epsilon\}

即任意句型或句子的First是指这个句型或句子能推导出的串中首个单词的集合

First(α)\mathrm{First}(\alpha)计算过程为:

  • 先置所有First(α)=\mathrm{First}(\alpha) = \varnothing
  • αVT{ϵ}\alpha \in V_{T}\cup \{\epsilon\},则First(α)={α}\mathrm{First}(\alpha) = \{\alpha\}
  • α=X1X2Xk(VTVN)\alpha = X_{1}X_{2}\dots X_{k} \in (V_{T} \cup V_{N})^{*},则先置First(α)=First(X1)\mathrm{First}(\alpha) = \mathrm{First}(X_{1})
    遍历XiX_{i},若X1XiϵX_{1}\dots X_{i} \Rightarrow^{*} \epsilon
    First(α)=First(Xi+1)\mathrm{First}(\alpha) \cup= \mathrm{First}(X_{i + 1}),其中Xk+1=ϵX_{k + 1} = \epsilon
  • αVN\alpha \in V_{N},且αX1X2Xk\alpha \to X_{1}X_{2}\dots X_{k}
    First(α)=First(X1X2Xk)\mathrm{First}(\alpha) \cup= \mathrm{First}(X_{1}X_{2}\dots X_{k})

Follow集合

Follow集合定义为:

G=(VT,VN,P,S)G = (V_{T}, V_{N}, P, S)是上下文无关文法,则对AVNA\in V_{N}

Follow(A)={aS#αAβ#,aFirst(β#),α,β(VTVN)}\mathrm{Follow}(A) = \{a\,|\, S\# \Rightarrow^{*} \alpha A\beta\#,\, a\in \mathrm{First}(\beta\#), \alpha, \beta\in(V_{T}\cup V_{N})^{*}\}

也即Follow集合被定义为可能在某些句型中紧跟在AA右边的终结符集合

Follow(A)\mathrm{Follow}(A)的计算方法为:

  • Follow(S)={#}\mathrm{Follow}(S) = \{\#\},其他均为\varnothing
  • 循环直到所有集合不变:
    • 对于AαBβA\to \alpha B \betaFollow(B)=First(β){ϵ}\mathrm{Follow}(B) \cup= \mathrm{First}(\beta) - \{\epsilon\}
    • ϵFirst(β)\epsilon \in \mathrm{First}(\beta),则Follow(B)=Follow(A)\mathrm{Follow}(B) \cup= \mathrm{Follow}(A)

预测集合

预测集合的定义为:

G=(VT,VN,P,S)G = (V_{T}, V_{N}, P, S)是上下文无关文法,则对AαPA\to \alpha \in P

PS(Aα)={First(α)ϵFirst(α)(First(α){ϵ})Follow(A)ϵFirst(α)\mathrm{PS}(A\to \alpha) = \begin{cases} \mathrm{First}(\alpha) & \epsilon \notin \mathrm{First}(\alpha) \\ (\mathrm{First}(\alpha) - \{\epsilon\}) \cup \mathrm{Follow}(A) & \epsilon \in \mathrm{First}(\alpha)\end{cases}

预测集合给出了读入了什么字符的时候需要采用产生式AαA\to \alpha

LL(1)文法

文法GG是LL(1)文法当且仅当对于GG的每个非终结符AA的任何两个不同产生式AαβA\to \alpha | \beta,满足:

PS(Aα)PS(Aβ)=\mathrm{PS}(A\to\alpha) \cap \mathrm{PS}(A\to\beta) = \varnothing

递归下降LL(1)分析程序

每个非终结符对应一个子程序,每个子程序的行为根据语法描述来明确:

  • 根据当前非终结符的PS集合与下一个输入符号选择产生式
  • 如果产生式右端遇到非终结符,则调用相应的子程序
  • 如果产生式右端遇到终结符,判断当前读入的单词是否与该终结符相匹配,匹配则继续读取,反之报错
递归下降分析
递归下降分析

实际应用中,可以将产生式的右端添加新运算,使之更加简洁,例如将SXSSϵS\to XS\quad S \to \epsilon替换为S{X}S\to \{X\}等,具体来说:

  • {X}=X\{X\} = X^{*}
  • [X]=Xϵ[X] = X \,|\, \epsilon
  • (X)(X)代表XX优先

表驱动LL(1)分析程序

由PS集合形成一个预测分析表,即根据非终结符和下一个单词决定产生式的表,并利用该表和一个下推栈实现:

  • #\#SS依次入栈
  • 若栈顶为终结符,则判断读入的单词和终结符是否匹配,匹配则出栈并继续读取,反之报错
  • 若栈顶为非终结符,则查表找到产生式,若为None则报错,反之非终结符出栈,产生式右端从右至左依次入栈,无需继续读入
  • 直到栈顶和下一位输入都是#\#

可以证明,预测分析表的每一项都只包含一个产生式,当且仅当文法是LL(1)的

文法变换

主要包含消除左递归与提取左公因子两种,通常用于将一些文法转换成LL(1)文法

消除左递归

消除直接左递归

PPαβP \to P\alpha\,|\,\betaαϵ\alpha\neq\epsilonβ\beta首字符不是PP,则消除方法为引入新终结符QQ使得:

PβQQαQϵ\begin{align*} P &\to \beta Q \\ Q &\to \alpha Q \,|\, \epsilon \end{align*}

对于一般形式PPα1Pαmβ1βnP \to P\alpha_{1}\,|\,\dots\,|\,P\alpha_{m}\,|\,\beta_{1}\,|\,\dots\,|\,\beta_{n},则:

Pβ1QβmQQα1QαnQϵ\begin{align*} P &\to \beta_{1}Q\,|\,\dots\,|\, \beta_{m}Q \\ Q &\to \alpha_{1}Q \,|\,\dots\,|\,\alpha_{n}Q\,|\, \epsilon \end{align*}

消除一般左递归

对于无环无ϵ\epsilon产生式的文法,消除一般左递归的方法为:

  • 排列非终结符A1,A2,,AnA_{1}, A_{2}, \dots, A_{n}
  • for i in 1..n:
    • for j in 1..(i-1):
      • 对于形如AiAjrA_{i} \to A_{j}r的规则
      • 其中AjA_{j}的全部产生式为Ajα1α2αkA_{j} \to \alpha_{1}\,|\,\alpha_{2}\,|\,\dots\,|\,\alpha_{k}
      • AiA_{i}产生式替换为Aiα1rα2rαkrA_{i} \to \alpha_{1}r\,|\,\alpha_{2}r\,|\,\dots\,|\,\alpha_{k}r
    • 再消除AiA_{i}的直接左递归
  • 化简文法

提取左公因子

对于形如PαβαγP\to \alpha\beta \,|\, \alpha\gamma的产生式,增加新终结符使得:

PαQQβγ\begin{align*} P &\to \alpha Q \\ Q &\to \beta \,|\, \gamma \end{align*}

一般化为Pαβ1αβmγ1γnP\to \alpha\beta_{1}\,|\,\dots\,|\,\alpha\beta_{m}\,|\,\gamma_{1}\,|\,\dots\,|\,\gamma_{n}

PαQγ1γnQβ1βm\begin{align*} P &\to \alpha Q\,|\,\gamma_{1}\,|\,\dots\,|\,\gamma_{n} \\ Q &\to \beta_{1} \,|\,\dots\,|\, \beta_{m} \end{align*}

预测分析中的出错处理

处理原则:

  • 尽可能准确地给出错误位置与属性
  • 尽可能校正

表驱动LL(1)分析中的错误处理

对于栈顶终结符与输入不匹配,直接弹出终结符
对于栈顶非终结符与输入符号在表中找不到产生式,我们采用恐慌模式,即跳过一些符号以找到同步符号

同步符号集合的构建为:

  • Follow(A)\mathrm{Follow}(A)中的所有符号都是AA的同步符号
  • First(B)\mathrm{First}(B)中的符号加入AA同步符号,代表AA遇到错误的时候可以从BB开始继续分析

递归下降分析的错误处理

当递归进入某个语法单位的时候,检查当前符号是否属于该单位的开始符号,离开该语法单位的时候检查符号是否属于该单位的结束符号

若不属于则不断滤去直到到达补救集合(即开始符号与结束符号的并集)中的符号并重新判断

递归下降分析错误处理
递归下降分析错误处理

LL(k)的结论

LL(k)文法的定义是LL(1)的推广,有关的结论有:

  • 给定k>0k>0,一个CFG是否为LL(k)是可判定的
  • 给定CFG,是否存在kk使得该文法是LL(k)是不可判定的
  • 给定CFG,是否存在与之等价的LL(k)是不可判定的
  • 两个LL(k)是否相等是可判定的
  • LL(k)无二义
  • LL(k)中不存在左递归
  • 给定k>0k>0,不含ϵ\epsilon产生式的LL(k)的集合真包含于不含ϵ\epsilon产生式的LL(k+1)的集合