likes
comments
collection
share

TypeScript 语法分析器如何进行错误恢复

作者站长头像
站长
· 阅读数 74

语法分析器在遇到一个错误动作时停止分析并报告失败,这种做法对程序员不是很友好,因为程序员希望分析器报告程序中所有错误,而不仅仅是第一个错误。

错误恢复有哪些

在《现代编译原理》一书中,将错误恢复机制分为以下两种:

局部错误恢复

局部错误恢复机制是通过调整分析栈和错误查出点的输入以允许分析能够继续进行来实现的。当分析器在表达式的中间遇到语法错误时,应该跳到下一个分号或右括号等(它们称为同步单词)。

全局错误恢复

全局错误恢复寻找的是可将源程序中的单词串变成语法上正确的单词串需要的最小插入和删除集合,即使这些插入和删除的地点不是语法分析器首先报告错误的地点。

TypeScript 的错误恢复

TypeScript 只做了局部错误恢复,因为全局错误恢复复杂且昂贵。

错误的数据结构

TypeScript 使用 DiagnosticWithDetachedLocation 接口来描述一个错误,这些错误被收集到 ParserparseDiagnostics 属性中,其类图如下:

«interface»
DiagnosticWithDetachedLocation
file: undefined
fileName: string
start: number
length: number
«interface»
Diagnostic
source?: string
relatedInformation?: DiagnosticRelatedInformation[]
«interface»
DiagnosticRelatedInformation
category: DiagnosticCategory
code: number
file: SourceFile | undefined
start: number | undefined
length: number | undefined
messageText: string | DiagnosticMessageChain
«enumeration»
DiagnosticCategory
Warning,
Error,
Suggestion,
Message

DiagnosticWithDetachedLocation 存储错误有关的所有信息,包含错误发生的位置、错误的级别、错误的描述信息等。

错误的管理

TypeScript 的所有错误都定义在 src/compiler/diagnosticMessages.json 文件中,例如:

{
    "'{0}' expected.": {
        "category": "Error",
        "code": 1005
    }
}

上面是一个 Error 级别的错误,错误代码是 1005,在 VSCode 中将被展示成下面这个样子:

TypeScript 语法分析器如何进行错误恢复

TypeScript 在构建时会对 src/compiler/diagnosticMessages.json 进行转换,生成 src/compiler/diagnosticInformationMap.generated.ts 文件,便于在开发中使用,上面所定义的错误会被转换为:

const Diagnostics = {
     _0_expected: diag(1005, DiagnosticCategory.Error, "_0_expected_1005", "'{0}' expected."),
}

实际例子

以下面代码为例,我们具体看一下 TypeScript 是如何进行错误恢复的:

catch (err) {
    console.log(err)

TypeScript 扫描到 catch 时会调用 parseTryStatement 方法尝试扫描 try 语句,其具体的流程如下:

  1. 调用 parseExpected(SyntaxKind.TryKeyword) 方法消费 try 关键字,但当前的单词是 catch 关键字,故创建一个信息为 Diagnostics._0_expectedDiagnosticWithDetachedLocation 对象添加到 parseDiagnostics 属性中。
  2. 调用 parseBlock 分析 try 代码块,但当前的单词仍是 catch 而非 {,故创建一个空代码块对象。
  3. 调用 parseCatchClause 分析 catch 代码块。
  4. 调用 parseExpected(SyntaxKind.CatchKeyword) 方法消费 catch 关键字,当前的单词是 catch 正确,词法分析器指向下一个单词。
  5. 调用 parseBlock 分析 catch 代码块。
  6. 调用 parseExpected(SyntaxKind.OpenBraceToken) 方法消费 { 单词,正确,词法分析器指向下一个单词。
  7. 调用 parseList 方法分析代码块中的代码,正确,词法分析器指向 EOF。
  8. 调用 parseExpectedMatchingBrackets 方法尝试消费 } 单词,当前的单词是 EOF,错误,故创建一个信息为 Diagnostics._0_expectedDiagnosticWithDetachedLocation 对象添加到 parseDiagnostics 属性中。
转载自:https://juejin.cn/post/7222075996064661541
评论
请登录