The Decaf Book
  • Introduction
  • 语言规范
  • Java 框架分阶段指导
    • 本章导引
    • PA1-A:语法分析器的自动构造
      • 阶段任务
      • 词法分析
      • 抽象语法树
      • 文法分析
    • PA1-B:基于 LL(1) 的语法分析器半自动构造
      • 阶段任务
      • LL(1) 文法分析
      • 错误恢复
    • PA2:语义分析
      • 阶段任务
      • 类型、符号和作用域
      • 访问者模式
      • 符号表构建
      • 类型检查
    • PA3:中间代码生成
      • 阶段任务
      • TAC 程序
      • 面向对象机制
      • 控制流的翻译
    • PA4:中间代码优化
      • 实验内容
      • 基本块
      • 数据流分析概述
      • 数据流优化概述
      • 公共表达式提取
      • 复写传播
      • 常量传播
      • 死代码消除
  • Scala 框架分阶段指导
    • 本章导引
    • PA1:语法分析器的自动构造
      • 阶段任务
      • 词法分析
      • 抽象语法树
      • 文法分析
      • 访问者模式
    • PA2:语义分析
      • 阶段任务
      • 类型、符号和作用域
      • 符号表构建
      • 类型检查
    • PA3:中间代码生成
      • 阶段任务
      • TAC 程序
      • 面向对象机制
      • 控制流的翻译
    • PA3-JVM:JVM 字节码生成
      • 阶段任务
      • JVM 字节码简介
      • 翻译过程
    • PA4:中间代码优化
      • 实验内容
      • 基本块
      • 数据流分析概述
      • 数据流优化概述
      • 公共表达式提取
      • 复写传播
      • 常量传播
      • 死代码消除
  • Rust 框架分阶段指导
    • 本章导引
    • PA1-A:语法分析器的自动构造
      • 实验内容
      • lalr1使用指导
        • 编写lexer
        • impl块的可选属性
        • 产生式和语法动作
        • 解决冲突
        • 一个完整的例子
      • 抽象语法树
      • 框架中部分实现的解释
      • 文件结构
    • PA1-B:基于 LL(1) 的语法分析器半自动构造
      • 实验内容
      • lalr1使用指导
      • 错误恢复
      • 文件结构
    • PA2:语义分析
      • 实验内容
      • 语义分析
      • 符号表
      • visitor模式
    • PA3:中间代码生成
      • 实验内容
      • 中间代码
      • 中间代码中的类型信息
      • 运行时存储布局
      • 面向对象机制
      • tacvm简述
    • PA4:中间代码优化
      • 基本块
      • 数据流分析概述
      • 数据流优化概述
      • 公共表达式提取
      • 复写传播
      • 常量传播
      • 死代码消除
    • PA5:寄存器分配
      • 实验内容
      • 图着色基本原理
      • 着色算法
      • 预着色节点
      • 干涉图节点合并
      • 调用约定
Powered by GitBook
On this page

Was this helpful?

  1. Java 框架分阶段指导
  2. PA1-B:基于 LL(1) 的语法分析器半自动构造

错误恢复

PreviousLL(1) 文法分析NextPA2:语义分析

Last updated 5 years ago

Was this helpful?

在课程讲义 Lecture04 中,我们介绍了应急恢复和短语层恢复的方法。这里,我们提出一种介于二者之间的错误恢复方法:

与应急恢复的方法类似,当分析非终结符AAA时,若当前输入符号a∉Begin(A)a \notin Begin(A)a∈/Begin(A),则先报错, 然后跳过输入符号串中的一些符号,直至遇到Begin(A)∪End(A)Begin(A) \cup End(A)Begin(A)∪End(A)中的符号:

  • 若遇到的是Begin(A)Begin(A)Begin(A)中的符号,可恢复分析AAA;

  • 若遇到的是End(A)End(A)End(A)中的符号,则AAA分析失败,返回 null,继续分析AAA后面的符号。

这个处理方法与应急恢复方法的不同之处在于:

  • 我们用集合Begin(A)={s∣M[A,s]非空}Begin(A)=\{s \mid M[A,s] 非空\}Begin(A)={s∣M[A,s]非空}(其中,MMM为预测分析表)来代替First(A)First(A)First(A)。由于First(A)⊆Begin(A)First(A) \subseteq Begin(A)First(A)⊆Begin(A),我们能少跳过一些符号。

  • 我们用集合End(A)=Follow(A)∪FEnd(A)=Follow(A) \cup FEnd(A)=Follow(A)∪F 来代替Follow(A)Follow(A)Follow(A)。其中,FFF集合(即 parseSymbol 函数的第二个参数 follow)包含了AAA各父节点的FollowFollowFollow集合。因此,我们既能少跳过一些符号,同时由于结束符 EOF 必然属于文法开始符号的FollowFollowFollow集合,本算法还无需额外考虑因读到 EOF 而陷入死循环的问题。这个处理方法借鉴了短语层恢复中EndSymEndSymEndSym的设计。

  • 另外,当匹配终结符失败时,只报错,但不消耗此匹配失败的终结符,而是将它保留在剩余输入串中。这部分的处理已经在 matchToken 函数中实现了。

一般来说,错误恢复是一个非常难的问题。上述方法显然也会有在一些 Decaf 程序上产生误报。

避免重复报错

由于实现的原因,本框架的 parseSymbol 函数有可能会多次连续调用 issue 方法报告同样的语法错误 (DecafError)。 为了避免输出重复的错误信息,在 LLParser 类中,我们重载了 issue 方法,在添加错误之前先检查它是不是刚添加过了:

@Override
public void issue(DecafError error) {
    if (!errors.isEmpty()) {
        var last = errors.get(errors.size() - 1);
        if (error.toString().equals(last.toString())) { // ignore
            return;
        }
    }

    super.issue(error);
}