语法分析

  • 文法
    • 定义
      • 文法 G=(VN,VT,S,P)G = (V_N, V_T, S, P),其中:
        • VNV_N:非空有限非终结符号集合。
        • VTV_T:非空有限终结符号集合。
        • SS:开始符号。
        • PP:有限产生式集合。
      • VNVT=VV_N \cup V_T = VVNVT=V_N \cap V_T = \varnothing
      • 语言 L(G)L(G):从 GGSS 开始,不断用产生式替换非终结符号,得到的只有终结符号的串集合。
    • 推导和规约
      • 直接推导:当且仅当 AγA \to \gamma 时,V=αAβW=αγβV = \alpha A \beta \to W = \alpha \gamma \betaVV 直接推导出 WWWW 直接规约到 VV
      • 多步推导:VV 经过 α0α1,α1α2,,αn1αn\alpha_0 \Rightarrow \alpha_1, \alpha_1 \Rightarrow \alpha_2, \cdots, \alpha_{n - 1} \Rightarrow \alpha_nnn 个推导得到 WW,则 VWV \xRightarrow{*} W。如果 V=WV = WVWV \xRightarrow{*} W,则 VWV \xRightarrow{*} W
      • 最左/右推导:从 VV 推导到 WW,总是替换最左/右边的非终结符号。
      • 规范推导:最右推导又称规范推导,逆序为规范规约。
    • 句型与句子
      • 句型:Sα,α(VNVT),αS \xRightarrow{*} \alpha, \alpha \in (V_N \cup V_T)^*, \alphaG(S)G(S) 的句型。
      • 句子:Sα,αVT,αS \xRightarrow{*} \alpha, \alpha \in V_T^*, \alphaG(S)G(S) 的句子。
      • 规范句型/句子:仅用规范推导得到的句型/句子。
    • 递归
      • 直接递归:GG 存在产生式 AαAβA \to \alpha A \beta 时,GG 为直接递归文法。
      • 间接递归:GG 可以有 AγA \to \gamma 和推导 γ+αAβ\gamma \xRightarrow{+} \alpha A \beta 时,GG 为间接递归文法。
      • 递归文法:直接与间接递归文法统称递归文法。
      • 左递归:AγA \to \gammaγ+Aβ\gamma \xRightarrow{+} A \beta
      • 右递归:AγA \to \gammaγ+αA\gamma \xRightarrow{+} \alpha A
    • 表示
      • BNF
        • 产生式格式:ABcA \to B \mid c
      • EBNF
        • 与 BNF 类似,并包括扩展语法:
          • {A}nm\{ A \}_n^mnnmmAA
          • [A][A]0011AA
          • A(BC)A(B|C):可以使用分配律。
      • 语法图
        • 椭圆:非终结符。
        • 矩形:终结符。
        • 箭头:匹配字符串的路径。
    • 二义性
      • 定义:存在一个字符串,一个文法可以推导得到两个不同的语法树,则文法是二义的。
      • 常见原因:
        • 运算符优先级不明确。
        • 运算符结合性不明确。
  • 自上而下分析
    • 文法变换
      • 直接左递归消除
        • 对于 AAαβA \to A \alpha | \beta,则变换为 AβAA \to \beta A'AαAεA' \to \alpha A' | \varepsilon
        • 对于 AAα1Aαnβ1βmA \to A \alpha_1 | \cdots | A \alpha_n | \beta_1 | \cdots | \beta_m,则变换为 Aβ1AβmAA \to \beta_1 A' | \cdots | \beta_m A' Aα1Aα2AεA' \to \alpha_1 A' | \cdots | \alpha_2 A' | \varepsilon
      • 间接左递归消除
        • 按顺序枚举所有非终结符 A1,,AnA_1, \dots, A_n,对于 AiA_i
          • 对于 Aj (j<i)A_j\ (j < i),如果 AiAjαA_i \to A_j \alpha,则带入 AjA_j 的产生式到 AiA_i 中。
          • 消除带入后的 AiA_i 产生式的直接左递归。
      • 回溯消除
        • FIRST
          • FIRST(α)={aαaβ,aVT}\mathrm{FIRST}(\alpha) = \{ a \mid \alpha \Rightarrow a \beta, a \in V_T \}。即 α\alpha 推导的所有句子的开头。
            • 如果 aεa \Rightarrow \varepsilon,则 εFIRST(α)\varepsilon \in \mathrm{FIRST}(\alpha),反之也成立。
          • 文法不带回溯的条件:对于 GG 中任意非终结符 Aα1αnA \to \alpha_1 | \cdots | \alpha_nFIRST(α1),,FIRST(αn)\mathrm{FIRST}(\alpha1), \dots, \mathrm{FIRST}(\alpha_n) 两两无交集。
          • 文法不带回溯的候选式选择:对于 Aα1αnA \to \alpha_1 | \cdots | \alpha_n 和当前符号 aa,选择满足 aFIRST(αi)a \in \mathrm{FIRST}(\alpha_i)αi\alpha_i
          • FIRST\mathrm{FIRST} 的构造是自下而上的。
        • 消除方法
          • 如果 FIRST(αi)\mathrm{FIRST}(\alpha_i)FIRST(αj)\mathrm{FIRST}(\alpha_j) 有交集,则交集部分就是左公因子。
          • 将两个候选式分别拆分出交集部分 β\beta 和剩余部分 αi,αj\alpha_i', \alpha_j'Aβ(αiαj)A \to \beta (\alpha_i' | \alpha_j') | \cdots
    • 递归下降
      • 按照文法编写函数,每个非终结符对应一个函数,对候选式的推导就是调用函数。
    • LL(1)
      • 算法过程
        • LL(1) 算法包括:
          • 总控程序:算法本身。
          • 分析栈:存放待分析单词。
          • 分析表 M(A,a)M(A, a)AA 为非终结符,aa 为单词,M(A,a)M(A, a) 表示识别 aa 需要的产生式 AαA \to \alpha。如果是空则表示出错。
        • 初始时,分析栈从底部存放结束标志和 SS
        • 设栈顶为 XX,当前单词为 aa,循环:
          • XX 是非终结符:
            • M(X,a)M(X, a) 非空,则根据 M(X,a)M(X, a) 选取产生式,把 XX 出栈,把 M(X,a)M(X, a) 倒序入栈。
            • M(X,a)M(X, a) 为空,出错。
          • XX 是终结符:
            • X=aX = a 为结束标志,则完全识别成功。
            • X=aX = a 且不为结束标志,则识别 aa 成功,出栈 XX,读取下一个输入。
            • XaX \ne a,则出错。
      • FOLLOW
        • FOLLOW(A)={aAaL(G(S)),aVT}\mathrm{FOLLOW}(A) = \{ a \mid \dots A a \dots \in L(G(S)), a \in V_T \},即语言中在 AA 后的所有可能的非终结符 aa
        • 计算方法:
          • FOLLOW(S)=#\mathrm{FOLLOW}(S) = \#
          • 对于 AαBβA \to \alpha B \betaFIRST(β)FOLLOW(B)\mathrm{FIRST}(\beta) \subseteq \mathrm{FOLLOW}(B)
          • 对于 AαBA \to \alpha BAαBβA \to \alpha B \betaεFIRST(β)\varepsilon \in \mathrm{FIRST}(\beta),则 FOLLOW(A)FOLLOW(B)\mathrm{FOLLOW}(A) \subseteq \mathrm{FOLLOW}(B)
          • 自上而下计算。
      • 分析表构造
        • 对于 Aα1αnA \to \alpha_1 | \cdots | \alpha_n
          • aFIRST(αi)a \in \mathrm{FIRST}(\alpha_i),则 M(A,a)=AαiM(A, a) = A \to \alpha_i
          • εFIRST(αi)\varepsilon \in \mathrm{FIRST}(\alpha_i)aFOLLOW(A)a \in \mathrm{FOLLOW}(A),则 M(A,a)=AαiM(A, a) = A \to \alpha_i
  • 自下而上分析
    • 基本概念
      • 短语:若 SαAδS \xRightarrow{*} \alpha A \deltaA+βA \xRightarrow{+} \beta,则 β\beta 是句型 αβδ\alpha \beta \delta 相对于 AA 的短语。
        • 从语法树上理解,即选取非终结符节点 AA,则其子树所有叶子节点按顺序排列是短语。
        • 一般省略句型,使用整个输入串作为分析的句型。
      • 直接短语:AβA \to \beta,即在短语基础上,只需要一步推导。
        • 从语法树上理解,即 AA 的子节点都是叶子节点。
      • 句柄:最左边的直接短语。
      • 活前缀:规范句型的前缀,且此前缀的末端不超过句柄的末端。
      • 可行前缀:最长的活前缀,后缀就是句柄。
    • LR 分析
      • LR 分析类方法使用 LR 分析器,分析器算法都相同,区别在于分析表的构造方法。
      • LR 分析器结构:
        • 输入串
        • 分析栈:元素为符号和状态的二元组。
        • 分析表:
          • 每一行对应一个状态,每一列对应一个符号。终结符的列是 ACTION 表,非终结符的列是 GOTO 表。
          • ACTION(Qi,xj)\mathrm{ACTION}(Q_i, x_j) 的取值:
            • SQkS_{Q_k}:移进操作,输入串指针向前移动一位,转换到状态 QkQ_k
            • rkr_{k}:规约操作,选取第 kk 个产生式 Axj1xjA \to \dots x_{j - 1} x_j,弹出所有匹配的符号和状态,插入产生式左部和 GOTO(Ql,A)\mathrm{GOTO}(Q_l, A),其中 QlQ_l 是弹出后的栈顶。
              • 规约操作不会消费输入符号,不同解析器只会基于输入符号决策。
            • 接受:分析成功。
            • 错误:遇到无法识别的符号。
          • GOTO(Qi,Aj)\mathrm{GOTO}(Q_i, A_j) 的取值:
            • QkQ_k:规约操作中要转移的下一个状态。
            • 空:QiQ_i 状态下不可能规约 AjA_j,分析表构造正确时不可能到达这里。
      • 分析的基本方法:不断规约句柄,无法规约时则移进,最终规约到开始符号。
    • LR(0)
      • LR(0) 项目
        • 把所有产生式的候选式插入圆点,则为 LR(0) 项目。
          • 对于开始符号 SS,额外增加 SSS' \to \bullet SSSS' \to S \bullet
          • 对于 AεA \to \varepsilon,改为 AA \to \bullet
          • 对于 AabA \to a b,改为 AabA \to \bullet a bAabA \to a \bullet bAabA \to a b \bullet
          • 对于 ABCA \to B | C,则分别改 ABA \to BACA \to C
        • \bullet 用于表示这个候选式中,\bullet 前面的部分已经在分析栈中,后面的部分还没有。
        • 项目的分类:
          • 接受项目:SSS' \to S \bullet,表示整体分析成功。
          • 规约项目:AαA \to \alpha \bullet,其中 αS\alpha \ne S
            • 分析栈中是可归前缀,后缀就是句柄 α\alpha,表示可以规约。
          • 移进项目:AαaβA \to \alpha \bullet a \beta,其中 aVTa \in V_T
            • 分析栈中是活前缀,后缀不是句柄,可以继续读 aa,表示可以移进。
          • 待约项目:AαBβA \to \alpha \bullet B \beta,其中 BVNB \in V_N
            • 需要先规约后面的输入到 BB,才能获得可归前缀 αBβ\alpha B \beta
      • 构造 LR(0) 状态
        • 方法 1
          • 找出所有的项目后,给每个项目分配编号。
          • 构造 NFA,NFA 识别所有的活前缀:
            • 起始状态为 SSS' \to \bullet S,表示没有扫描和匹配任意的符号。
            • 接受状态为所有的规约项目对应的状态。
            • 如果存在 AiαXβA_i \to \alpha \bullet X \betaAjαXβA_j \to \alpha X \bullet \beta,则连边 Ajδ(Ai,X)A_j \in \delta(A_i, X)
              • 这表示扫描符号 XX 可以转移状态。
            • 如果存在 AiαXβA_i \to \alpha \bullet X \betaXδX \to \bullet \delta,则连边 Xδ(Ai,ε)X \in \delta(A_i, \varepsilon)
              • 这表示不消费符号而先进入规约 XX 的状态。
          • 使用子集法确定化 NFA:
            • 得到的 DFA 的每个节点则为 LR(0) 项目的集合,称为项目集。
            • DFA 的节点集合称为项目集规范族。
        • 方法 2
          • 定义项目集闭包 closure(I)\operatorname{closure}(I)
            • Iclosure(I)I \subseteq \operatorname{closure}(I)
            • 如果存在 AiαXβclosure(I)A_i \to \alpha \bullet X \beta \in \operatorname{closure}(I)XδX \to \bullet \delta,则 Xδclosure(I)X \to \bullet \delta \in \operatorname{closure}(I)
              • 这与方法一 Xδ(Ai,ε)X \in \delta(A_i, \varepsilon) 含义类似。
            • 项目集闭包也是项目集,II 中的状态可以不需要消费符号而转移到 closure(I)\operatorname{closure}(I),类似于单向的“等价”。
          • 定义 GO(I,X)\operatorname{GO}(I, X) 为从 II 中的项目消费 XX 可以到达的其他项目组成的项目集。
            • GO(I,X)=closure(I)\operatorname{GO}(I, X) = \operatorname{closure}(I')
            • 如果存在 AiαXβIA_i \to \alpha \bullet X \beta \in IAjαXβA_j \to \alpha X \bullet \beta,则 AjαXβIA_j \to \alpha X \bullet \beta \in I'
              • 类似方法 1 中的 Ajδ(Ai,X)A_j \in \delta(A_i, X)
            • II 看作 DFA 的状态,则 GO(I,X)\operatorname{GO}(I, X) 就是 DFA 的状态转移函数。
          • 构造整个项目集规范族:
            • I={SS}I = \{S' \to \bullet S\} 开始,计算 closure(I)\operatorname{closure}(I)
            • 对于所有的 XX,计算 GO(I,X)\operatorname{GO}(I, X)
            • 重复上一步,用上一步得出的 II 继续计算。
      • 构造 LR(0) 分析表
        • 对于当前状态/项目集 IiI_i
          • 如果项目集为一个接受项目,则填写 ACTION(i,#)=accept\operatorname{ACTION}(i, \#) = \text{accept}
          • 如果项目集为一个规约项目,则填写整行 ACTION(i,)=rt\operatorname{ACTION}(i, *) = r_t,其中 tt 为对应的候选式编号。
          • 复制 GO\operatorname{GO}ACTION\operatorname{ACTION}GOTO\operatorname{GOTO}
            • 对于终结符 aa 的状态转移 GO(Ii,a)=Ij\operatorname{GO}(I_i, a) = I_j,填写 ACTION(i,a)=Sj\operatorname{ACTION}(i, a) = S_j
            • 对于非终结符 XX 的状态转移 GO(Ii,X)=Ij\operatorname{GO}(I_i, X) = I_j,填写 GOTO(i,X)=j\operatorname{GOTO}(i, X) = j
        • LR(0) 中,规约项目、接受项目是互斥性的,可能存在以下冲突:
          • 移进-规约冲突
          • 规约-规约冲突
          • 接受-规约冲突
        • 如果存在冲突,则无法按照上面的过程构造分析表,文法也就不是 LR(0) 文法。
    • SLR(1)
      • 与 LR(0) 大致类似,但是在构造分析表时,对于规约项目的处理:
        • 设规约项目为 AαIiA \to \alpha \bullet \in I_i,仅对所有的 cFOLLOW(A)c \in \operatorname{FOLLOW}(A) 填写 ACTION(i,c)=rt\operatorname{ACTION}(i, c) = r_t
        • 项目集中可以存在多个规约项目,只要各自对应的 FOLLOW()\operatorname{FOLLOW}(*) 不交。
      • 项目冲突类别与 LR(0) 相同,冲突条件为 FOLLOW()\operatorname{FOLLOW}(*) 相交。
    • LR(1)
      • LR(1) 项目
        • LR(1) 项目由 LR(0) 项目和一个搜索符组成,记作 [Aα,a][A \to \alpha, a]
          • a{#}VTa \in \{ \# \} \cup V_T
          • 对于规约项目,只能在当前读入符号是 aa 时才可以规约。
        • 有效项目:若 [Aαβ,a][A \to \alpha \bullet \beta, a] 对活前缀 γ\gamma 有效,当且仅当:
          • 存在规范推导 SδAωδαβωS \xRightarrow{*} \delta A \omega \xRightarrow{*} \delta \alpha \beta \omega,其中 γ=δα\gamma = \delta \alpha
          • aFIRST(ω)a \in \operatorname{FIRST}(\omega)ω=ε\omega = \varepsilona=#a = \#
        • LR(1) 项目包括了搜索符,在规约时更加精确,可以完全避免规约冲突。
      • 构造 LR(1) 状态机
        • 定义项目集闭包 closure(I)\operatorname{closure}(I) 为:
          • Iclosure(I)I \subseteq \operatorname{closure}(I)
          • 如果存在 [AiαXβ,a]closure(I)[A_i \to \alpha \bullet X \beta, a] \in \operatorname{closure}(I)[Xδ,b][X \to \bullet \delta, b]bFIRST(Xβ)b \in \operatorname{FIRST}(X \beta),则 [Xδ,b]closure(I)[X \to \bullet \delta, b] \in \operatorname{closure}(I)
            • AiαXβA_i \to \alpha \bullet X \beta 依赖 XδX \to \bullet \delta 的规约,所以后者的搜索符需要在 XβX \beta 的最前面。
        • 定义 GO(I,X)=closure(I)\operatorname{GO}(I, X) = \operatorname{closure}(I')
          • 如果存在 [AiαXβ,a]I[A_i \to \alpha \bullet X \beta, a] \in I[AjαXβ,a][A_j \to \alpha X \bullet \beta, a],则 [AjαXβ,a]I[A_j \to \alpha X \bullet \beta, a] \in I'
            • GO\operatorname{GO} 不涉及规约,所以搜索符不变。
        • 构造 LR(1) 状态机的方法与 LR(0) 一样,区别只有 closure(I)\operatorname{closure}(I)GO(I,X)\operatorname{GO}(I, X)
      • 构造 LR(1) 分析表
        • 考虑每个项目集 IiI_i 中的每一个项目:
          • 如果是接受项目,同 LR(0)。
          • 如果是规约项目 [Ajα,a][A_j \to \alpha \bullet, a],填写 ACTION(Ii,a)=rt\operatorname{ACTION}(I_i, a) = r_t
            • 仅根据搜索符 aa 精确填写第 aa 列。
          • 同 LR(0) 复制 GO\operatorname{GO}ACTION\operatorname{ACTION}GOTO\operatorname{GOTO}
        • LR(1) 不存在冲突。
    • LALR(1)
      • LR(1) 中的两个项目集如果核心项目相同,则为同心项目集。
      • LALR(1) 合并所有的同心项目集,搜索符取所有的并集。
      • LALR(1) 仅可能存在规约-规约冲突,其他特性继承 LR(1)。