Pegjs: エラーレポートの再設計

作成日 2013年08月14日  ·  7コメント  ·  ソース: pegjs/pegjs

PEG.jsの現在のエラー報告システムに3つの問題があります。

  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. $ SyntaxErrorexpectedプロパティは、機械的に処理するのが困難です
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パーサーが文字列リテラル、文字クラス、または.と一致しようとして失敗すると、_matchfailure_が生成されます。 これは、_position_と_expectation_(パーサーが一致させようとしたものの説明)で構成されます。

一致の失敗を生成した後、パーサーはバックトラックし、場合によっては別の方法を試します。 それらのいずれも成功せず、これ以上試すことがない場合、パーサーはSyntaxError例外をスローします。 そのプロパティ(位置、期待値、エラーメッセージなど)を設定するとき、パーサーは最も遠い一致の失敗(最大の位置を持つもの)のみを考慮します。 そのような失敗がもっとある場合、それらの期待は組み合わされます。

名前付きルールを使用する場合、状況は少し複雑になりますが、これはこの説明には関係ありません。

提案された変更

エラー報告システムの次の変更について考えています。

  1. SyntaxError.expectedの期待値は文字列ではなく、オブジェクトで表されます。 オブジェクトには、期待タイプを決定するtypeプロパティ( "literal""class""any""eof"などの値)があります。 一部の期待値タイプでは、他のプロパティが詳細を制限します(たとえば、 "literal"期待値の場合、期待される文字列を含むvalueプロパティになります)。 これにより、期待値の機械的処理が簡素化されます。

typeプロパティの代わりに、クラスを使用することもできます。 しかし、 typeプロパティは、ユーザーにとって扱いやすいと思います。

  1. アクションはnullを返すことができます。 これは通常の値であり、エラーを意味するものではありません。
  2. アクションは、新しいerror関数を使用して一致の失敗をトリガーできるようになります。 パラメータとしてエラーメッセージを受け取ります。 この関数によってトリガーされる失敗(_カスタム一致失敗_と呼ばれる)は、_位置_と_エラーメッセージ_で構成されます。 彼らには何の期待もありません。

error関数はアクションの実行を中断せず、失敗としてマークを付けてエラーメッセージを保存します。 実際の失敗は、アクションの実行が終了した後にのみ発生します。 error関数が複数回呼び出された場合、最後の呼び出しが優先されます(そのエラーメッセージが使用されます)。

カスタム一致の失敗は、通常の一致の失敗と同じように処理されます。つまり、解析が完全に停止せず、パーサーがバックトラックして、別の方法を試す可能性があります。 ただし、1つの違いがあります。最終的にSyntaxError例外をスローする場合、通常の一致の失敗に適用される期待値の組み合わせルールは、カスタムのルールには適用されません。 最も遠い位置での一致失敗のセットに少なくとも1つのカスタムが存在する場合、それは単に通常のものを完全にオーバーライドします。 カスタム障害がさらにある場合は、最後に作成されたものが優先されます。

カスタム一致の失敗に基づくSyntaxErrorの例外は、通常の失敗に基づく例外とは異なります。 messageプロパティは失敗のエラーメッセージと等しくなり、そのexpectedプロパティはnullになります。

`` `
start = sign:[+-]? 数字:[0-9] + {
var result = parseInt((sign || "")+ Digits.join( "")、10);

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

 return result;

}
`` `

入力2で、上記の文法から生成されたパーサーは、 message"The number must be an odd integer."に設定され、 expectedが$に設定されたnull SyntaxErrorを生成します。 null

  1. アクションは、 expected関数を使用して、通常の一致の失敗をトリガーすることもできます。 期待値の説明をパラメータとして取ります。 この機能は、主に、完全なエラーメッセージを生成する必要がなく、「Expected _X_ but "2" found」から自動的に生成される状況での利便性として提供されます。 十分です。

expected関数によって生成された一致の失敗に基づくSyntaxErrorの例外は、通常の失敗に基づく例外と同様になります。

`` `
start = sign:[+-]? 数字:[0-9] + {
var result = parseInt((sign || "")+ Digits.join( "")、10);

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

 return result;

}
`` `

入力2で、上記の文法から生成されたパーサーは、 message"Expected odd integer but "2" found."に設定され、 expectedが$に設定された[ { type: "user", description: "odd integer" } ] SyntaxErrorを生成します。 [ { 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 評価