跳转至

核心问题

  • 经过词法分析处理后,源程序变成:
  • 单词流的形式
  • 需要通过语法分析,判断单词组成的句子是否合法
  • 本质上:
  • 相当于 上下文无关文法 的匹配
  • 对于给定的上下文无关文法,某个由合法单词组成的句子,是否符合句法
  • 如果符合,则给出语法分析树或最左推导

常见报错

常见语法报错示意

自顶向下分析

基本概念

  • 同上下文无关语言的自顶向下分析过程
  • 由开始符号推导到句子

带回溯的自顶向下分析

  • 采用最左推导:每次替换最左边的非终结符
  • 非确定性:每次选择哪一个产生式来进行替换是非确定的
  • 带回溯的分析:类似一个深度优先搜索
  • 改进:确定的自顶向下分析
  • 主要是要确定每一次推导选择 哪个产生式
  • 向前查看:如果当前读入的单词不能决定非终结符应该选择哪个产生式,则再考虑后面的几个单词(注意:这里是纯粹考虑过程,实际匹配的只有当前的字符)
  • 左递归:
  • 形式:A → A x x x x
  • 推导出以自己开头的表达式
  • 不利于预测分析

自顶向下预测分析

  • 要求文法不含左递归:
  • 直接左递归:A → A x x x
  • 间接左递归:A → B x x x,B → A x x x
  • 需要通过文法变换消除左递归
  • 要求文法不含左公因子
  • LL(k):LL 表示自顶向下、最左推导;k 表示最多向前查看 k 个单词就可以决定产生式

LL(1) 分析

  • 重要的两个集合:First 集合Follow 集合
  • 作用:基于下一个输入的符号,判断采用哪个产生式

First 集合

  • 首先考虑对于任意一个句子的 First 集合:
  • 如果一个句型可以推导出另一个以某终结符开头的句型,则该终结符属于 First
  • 如果 A → α | β,且 First(α) 与 First(β) 没有交集,则向后看一个单词,就知道选哪个产生式

First 集合示例

  • 对于一个文法的 First 集合的计算:
  • 需要计算的元素为:所有终结符、非终结符、ε、产生式右部的后缀
  • 如果有个非终结符可以推导到 ε,将 ε 加入其 First
  • 对于一个多个符号构成的串:
    • 考察其所有前缀,找到第一个不属于 First(Yᵢ) 的 ε 的 Yᵢ
    • 则前面的都可以导出 ε,因此首元素可以是所有 First(Yⱼ) 中的任意元素
    • 如果不存在这样的 Yᵢ,则即是所有非终结符 First 集合的并集
  • 对于产生式,左部并上右部的 First

First 集合计算

Follow 集合

  • 为什么要用 Follow 集合?
  • First(A) 可能包含 ε:仅看 First 不足以区分「用哪条产生式」与「A 是否应推导为空」——当当前输入符号不在各候选首符集合里时,仍可能靠 A ⇒* ε 再依赖 Follow(A) 做决策
  • Follow 集合的含义:
  • Follow(A) 代表紧跟着 A、可能出现的终结符集合
  • 可以理解为:如果此时 A 推导完了,则其后可能跟的字符有哪些
  • 参见 Follow 集合的简明理解
  • 使用 Follow 集合:
  • 首先是看 First(A),如果 a 属于 First(A),则选用产生式
  • 如果不属于,则看 Follow(A),如果属于 Follow(A),则跳过 A(选 ε)
  • 否则分析出错

  • 计算 Follow 集合:

Follow 集合计算规则

  • 理解:
  • 第一条规则:如果 B 推导完了,后面可能跟着的终结符就是 First(β)
  • 第二条规则:如果 B 后面可以啥都不跟,那么 B 和 A 可以推导为同一个结尾,则 A 推导完成后可能接着的终结符,也可以当作 B 推导完成后可能接着的终结符

预测集合

预测集合

  • 这就是在读到一个字符时,判断选用哪个产生式的依据
  • LL(1) 文法中,任意两个产生式的预测集合没有交集,因此可以据此选用
  • 这也是判断 LL(1) 文法的依据:

LL(1) 判定

LL(1) 分析

基本思想

  • 每一个非终结符对应一个分析子程序
  • 子程序的处理:根据预测集合来选择产生式,或者是否抛弃
  • 递归下降
  • 栈顶每读到一个终结符,则匹配同时弹栈
  • 栈顶是非终结符,调用子程序
  • 借助预测分析表和下推栈

  • 分析过程:

LL(1) 分析过程

  • 预测分析表:

预测分析表 1

预测分析表 2

消除左递归

  • 某些文法可以消除左递归,从而实现文法的化简
  • 消除直接左递归
  • 把产生式分为两部分:用了左递归的和没用的
  • 由于左递归最终肯定用且仅用了一次非递归的产生式,因此可以把非递归的提前

消除直接左递归

  • 消除间接左递归

消除间接左递归

  • 举例:

消除左递归举例

提取左公因子

提取左公因子

LL(1) 文法的生成

  • 许多文法在消除左递归和提取左公因子后,可以变成 LL(1) 文法
  • 但不一定都可以变成 LL(1) 文法

LL(1) 文法的错误处理

  • 可能情况:
  • 栈顶的终结符和当前输入符号不匹配
  • 分析表的当前表项为空
  • 处理方法:
  • 简单的应急恢复:跳过输入串中的一些符号,直到遇到同步符号(可以将 Follow(A) 中符号作为同步符号)
  • 短语层恢复:

短语层恢复 1

短语层恢复 2

LL(k) 文法的结论

LL(k) 结论


相关笔记