Pegjs: 重新设计错误报告

创建于 2013-08-14  ·  7评论  ·  资料来源: pegjs/pegjs

我发现 PEG.js 中当前的错误报告系统存在三个问题:

  1. 操作通过返回null来报告错误
Reporting using `null` is inflexible (it doesn't allow to carry any information about the kind of error) and makes it impossible to return `null` as a regular value from actions, which would be useful sometimes (for example [in a JSON parser](https://github.com/dmajda/pegjs/blob/791034fad92c9cd7a9d1c71187df03441bbfd521/examples/json.pegjs#L45)).

另见#17。

  1. 完全自定义消息无法产生错误
For example, imagine a HTTP 1.1 parser that wants to report an error about missing `Host` header in a HTTP request with a message like "A HTTP 1.1 request must contain a Host header". Currently the only way to do this is to throw an exception with desired message manually. This is cumbersome and it does not interact well with the backtracking behavior (throwing an exception halts the parsing completely, even when it is possible to backtrack and try another alternatives).

  1. $#$ SyntaxError $#$ 的expected属性很难机械处理
The `expected` property of `SyntaxError` is either an array of expectations (each describing one alternative which the parser expected at the position of error) or `null` (meaning that the end of input was expected).

每个期望都由一个字符串表示。 此字符串可以表示预期的文字(使用其 PEG.js 语法表示 - 带引号的字符串)、字符类(使用其 PEG.js 语法表示 - [...] )或任何字符(由字符串表示"any" )。 为了区分这些情况,必须手动解析字符串表示,这使得基于 PEG.js 的构建工具(例如自动完成器)变得不必要地困难。

我计划重新设计错误报告系统来解决这些问题。 在我描述我正在考虑的变化之前,需要稍微描述一下当前系统是如何工作的。

错误报告目前如何工作?

当 PEG.js 解析器尝试匹配字符串文字、字符类或.并失败时,它会产生 _match failure_。 它由一个 _position_ 和一个 _expectation_ (解析器试图匹配的描述)组成。

在产生匹配失败后,解析器回溯并可能尝试其他替代方案。 如果它们都没有成功并且没有更多尝试,解析器将抛出SyntaxError异常。 在设置其属性(如位置、期望、错误消息等)时,解析器只考虑最远的匹配失败(位置最大的那个)。 如果有更多这样的失败,他们的期望就会结合起来。

如果使用命名规则,情况会稍微复杂一些,但这与本次讨论无关。

提议的变更

我正在考虑错误报告系统中的以下更改:

  1. SyntaxError.expected中的期望不会由字符串表示,而是由对象表示。 对象将具有type属性(其值如"literal""class""any""eof" ),它将确定期望类型。 对于某些期望类型,其他属性将包含详细信息(例如,对于"literal"期望,其中将是包含期望字符串的value属性)。 这将简化预期的机械加工。

type属性的替代方法是使用类。 但我认为type属性对于用户来说会更容易处理。

  1. 将允许操作返回null 。 这将是一个常规值,不会表示错误。
  2. 操作将能够使用新的error函数触发匹配失败。 它将以错误消息作为其参数。 此函数触发的失败(称为 _custom match failures_)将由 _position_ 和 _error message_ 组成。 他们不会有任何期待。

error函数不会中断操作执行,它只会将其标记为失败并保存错误消息。 只有在动作执行完成后才会产生实际的失败。 如果error函数被多次调用,最后一次调用将获胜(将使用其错误消息)。

自定义匹配失败将像常规匹配失败一样处理,这意味着它们不会完全停止解析并让解析器回溯并可能尝试其他替代方案。 但是有一个区别——当最终抛出SyntaxError异常时,适用于常规匹配失败的期望组合规则将不适用于自定义匹配失败。 如果在最远位置的匹配失败集中至少有一个自定义匹配失败,它将完全覆盖常规匹配失败。 如果有更多的自定义失败,最后生成的将获胜。

基于自定义匹配失败的SyntaxError异常与基于常规失败的异常不同。 它的message属性将等于失败的错误消息,它的expected属性将是null

例子

```
开始 = 符号:[+-]? 数字:[0-9]+ {
var 结果 = parseInt((sign || "") + digits.join(""), 10);

 if (result % 2 == 0) {
   error("The number must be an odd integer.");
 }

 return result;

}
```

在输入2时,从上面的语法生成的解析器将生成一个SyntaxError ,其中message设置为"The number must be an odd integer."并且expected设置为null

  1. 操作还可以使用expected函数触发常规匹配失败。 它将一个期望值描述作为参数。 提供此功能主要是为了方便不需要生成完整的错误消息并自动生成表单“预期_X_但找到“2”的情况。 足够。

基于expected函数生成的匹配失败的SyntaxError异常将类似于基于常规失败的异常。

例子

```
开始 = 符号:[+-]? 数字:[0-9]+ {
var 结果 = parseInt((sign || "") + digits.join(""), 10);

 if (result % 2 == 0) {
   expected("odd integer");
 }

 return result;

}
```

在输入2时,从上面的语法生成的解析器将生成一个SyntaxError ,其中message设置为"Expected odd integer but "2" found."并且expected设置为[ { type: "user", description: "odd integer" } ]

下一步

我欢迎对提议的更改提出任何注释——请将它们添加为评论。 我计划很快开始实施该提案(或基于反馈的一些修改版本)。

feature

最有用的评论

那么,还没有“警告”功能吗?

所有7条评论

如果错误函数的多次调用导致多个错误会更好。 然后,您可以尽可能地继续解析。

string = '"' value:(!(eol / '"') .)+ '"' { return value; }
       / '"' value:(!(eol / '"') .)+     { error('unterminated string constant'); return value; }

我还建议添加对警告的支持。

如果错误函数的多次调用导致多个错误会更好。 然后,您可以尽可能地继续解析。

你能说出一些你需要这个的用例吗? 在我看来,您提供的示例也适用于我的建议。

我已经有了一些用例,但我不知道它们有多大的代表性,所以我想看看更多。

我还建议添加对警告的支持。

我也在考虑。

多个错误和警告的问题是它们需要一个不同于简单直观的界面“ parse成功返回一些值或错误时返回异常”。 解析器需要在解析不成功时报告多个错误,以及解析成功和不成功时的警告。

你会觉得哪种 API 最直观? 同样,我有一些想法,但我想看看其他人的想法。

你能说出一些你需要这个的用例吗? 在我看来,您提供的示例也适用于我的建议。

我的主要用例是非致命的解析错误。 非终止字符串、以数字开头的标识符、嵌套注释、缺少分号等。

一般情况下,最好让解析器尽可能地前进,以便尽可能多地向用户显示错误。 如果解析器甚至可以完成解析,并让后续步骤(例如编译)也报告错误,那就太好了。

现在已实施tools/impact脚本报告整个提交集的以下性能影响:

Speed impact
------------
Before:     1144.21 kB/s
After:      999.89 kB/s
Difference: -12.62%

Size impact
-----------
Before:     863523 b
After:      1019968 b
Difference: 18.11%

(Measured by /tools/impact with Node.js v0.6.18 on x86_64 GNU/Linux.)

我认为 12.62% 的速度惩罚和 18.11% 的大小惩罚对于解决这样一个长期存在的问题是很好的。

关闭。

@dmajda :这是个好消息! 我很高兴null不再表示失败。

那么,还没有“警告”功能吗?

警告功能主题跟踪在#325

此页面是否有帮助?
0 / 5 - 0 等级