TypeScript 语法分析器如何进行错误恢复
语法分析器在遇到一个错误动作时停止分析并报告失败,这种做法对程序员不是很友好,因为程序员希望分析器报告程序中所有错误,而不仅仅是第一个错误。
错误恢复有哪些
在《现代编译原理》一书中,将错误恢复机制分为以下两种:
局部错误恢复
局部错误恢复机制是通过调整分析栈和错误查出点的输入以允许分析能够继续进行来实现的。当分析器在表达式的中间遇到语法错误时,应该跳到下一个分号或右括号等(它们称为同步单词)。
全局错误恢复
全局错误恢复寻找的是可将源程序中的单词串变成语法上正确的单词串需要的最小插入和删除集合,即使这些插入和删除的地点不是语法分析器首先报告错误的地点。
TypeScript 的错误恢复
TypeScript 只做了局部错误恢复,因为全局错误恢复复杂且昂贵。
错误的数据结构
TypeScript 使用 DiagnosticWithDetachedLocation
接口来描述一个错误,这些错误被收集到 Parser
的 parseDiagnostics
属性中,其类图如下:
DiagnosticWithDetachedLocation
存储错误有关的所有信息,包含错误发生的位置、错误的级别、错误的描述信息等。
错误的管理
TypeScript 的所有错误都定义在 src/compiler/diagnosticMessages.json
文件中,例如:
{
"'{0}' expected.": {
"category": "Error",
"code": 1005
}
}
上面是一个 Error
级别的错误,错误代码是 1005,在 VSCode 中将被展示成下面这个样子:
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 语句,其具体的流程如下:
- 调用
parseExpected(SyntaxKind.TryKeyword)
方法消费try
关键字,但当前的单词是catch
关键字,故创建一个信息为Diagnostics._0_expected
的DiagnosticWithDetachedLocation
对象添加到parseDiagnostics
属性中。 - 调用
parseBlock
分析 try 代码块,但当前的单词仍是catch
而非{
,故创建一个空代码块对象。 - 调用
parseCatchClause
分析 catch 代码块。 - 调用
parseExpected(SyntaxKind.CatchKeyword)
方法消费catch
关键字,当前的单词是catch
正确,词法分析器指向下一个单词。 - 调用
parseBlock
分析 catch 代码块。 - 调用
parseExpected(SyntaxKind.OpenBraceToken)
方法消费{
单词,正确,词法分析器指向下一个单词。 - 调用
parseList
方法分析代码块中的代码,正确,词法分析器指向 EOF。 - 调用
parseExpectedMatchingBrackets
方法尝试消费}
单词,当前的单词是 EOF,错误,故创建一个信息为Diagnostics._0_expected
的DiagnosticWithDetachedLocation
对象添加到parseDiagnostics
属性中。
转载自:https://juejin.cn/post/7222075996064661541