web-dev-qa-db-ja.com

パーサーコンビネーターを使用する場合パーサージェネレーターを使用するタイミング

最近、パーサーの世界を深く掘り下げて、独自のプログラミング言語を作成したいと考えています。

ただし、パーサーの作成には、パーサージェネレーターとパーサーコンビネーターという2つの異なるアプローチが存在することがわかりました。

興味深いことに、どのアプローチでより良いアプローチを説明できるリソースを見つけることができませんでした。むしろ、私が対象について問い合わせた多くのリソース(および人)は他のアプローチを知りませんでした。それらのアプローチをtheアプローチし、他の人にはまったく触れない:

  • 有名なドラゴンの本 は、字句解析/スキャンに入り、(f)Lexについて言及していますが、パーサーコンビネーターについてはまったく触れていません。
  • 言語実装パターン Javaで構築されたANTLRパーサージェネレーターに大きく依存し、パーサーコンビネーターについてはまったく触れていません。
  • Parsecの概要 HaskellのパーサーコンビネーターであるParsecのチュートリアルでは、パーサージェネレーターについてはまったく触れられていません。
  • Boost :: spirit 、最もよく知られているC++パーサーコンビネーターは、パーサージェネレーターについてまったく触れていません。
  • すばらしい説明ブログの投稿 パーサーコンビネーターを発明した可能性があります は、パーサージェネレーターについてはまったく触れていません。

簡単な概要:

パーサージェネレーター

パーサージェネレーターは [〜#〜] dsl [〜#〜]Extended Backus-Naur form の一部の方言で記述されたファイルを受け取り、それをソースに変換しますその後、(コンパイル時に)このDSLで記述されている入力言語のパーサーになることができるコード。

つまり、コンパイルプロセスは2つの別々のステップで行われます。興味深いことに、パーサージェネレーター自体もコンパイラーです(そして、それらの多くは確かに self-hosting です)。

パーサーコンビネーター

Parser Combinatorはparsersと呼ばれる単純な関数を記述します。これらはすべて入力をパラメーターとして受け取り、一致する場合はこの入力の最初の文字を取り出します。パーサーがこの入力から何かを解析できなかった場合、タプル(result, rest_of_input)を返します。ここで、resultは空の場合があります(例:nilまたはNothing)。例としては、digitパーサーがあります。もちろん、他のパーサーはパーサーを最初の引数(最後の引数はまだ入力文字列のままです)として結合することができます。 many1は、他のパーサーと可能な限り何度も一致させようとします(ただし、少なくとも1回は失敗するか、それ自体が失敗します)。

もちろん、digitmany1を組み合わせて(合成して)、新しいパーサーを作成することもできます(たとえば、integer)。

また、より高いレベルのchoiceパーサーを記述して、パーサーのリストを取得し、それぞれを順番に試すことができます。

このようにして、非常に複雑なレクサー/パーサーを構築できます。演算子のオーバーロードをサポートする言語では、ターゲット言語で直接記述されているにもかかわらず、これもEBNFに非常によく似ています(必要なターゲット言語のすべての機能を使用できます)。

単純な違い

言語:

  • パーサージェネレーターは、EBNF風のDSLと、これらのステートメントが一致したときに生成されるコードの組み合わせで記述されます。
  • パーサーコンビネーターはターゲット言語で直接書かれています。

語彙/構文解析:

  • パーサージェネレーターは、「レクサー」(文字列をタグ付けするトークンに分割して、処理する値の種類を示す)と「パーサー」(レクサーからトークンの出力リストを取得する)との間に非常に明確な違いがあります。それらを組み合わせて、抽象構文ツリーを形成しようとします。
  • パーサーコンビネーターはこの区別をしていません/必要としません。通常、単純なパーサーは「レクサー」の処理を実行し、より高レベルのパーサーはこれらの単純なパーサーを呼び出して、作成するASTノードの種類を決定します。

質問

ただし、これらの違いが与えられたとしても(そして、この違いのリストはおそらく完全とはほど遠い!)、whenについて知識のある選択をしてどれを使用するかを決めることはできません。私はこれらの違いの影響/結果が何であるかを理解できません。

問題のプロパティは、問題がパーサージェネレーターを使用してより適切に解決されることを示しますか?とParser Combinatorを使用して問題をより適切に解決できることを示す問題のプロパティは何ですか?

77
Qqwy

構文エラーがないことが保証されている入力の場合、または構文の正しさの全体的な合格/不合格が問題ない場合、特に関数型プログラミング言語では、パーサーコンビネーターの操作がはるかに簡単です。これらは、パズルのプログラミング、データファイルの読み取りなどの状況です。

パーサージェネレーターの複雑さを追加する機能は、エラーメッセージです。ユーザーに行と列を示すエラーメッセージが必要であり、うまくいけば人間にも理解できるはずです。これを適切に行うには多くのコードが必要であり、antlrのようなより優れたパーサージェネレーターがそれを支援します。

ただし、自動生成ができるのはこれまでのところだけであり、ほとんどの商用で長寿命のオープンソースコンパイラーは、手動でパーサーを作成することになります。私はあなたがこれを行うのに慣れていると思うなら、あなたはこの質問をしなかっただろうと思うので、私はパーサジェネレータを使うことを勧めます。

8
Karl Bielefeldt

ANTLRパーサージェネレーターのメンテナーの1人であるSam Harwell氏 最近書いた

[コンビネーター]が私のニーズを満たしていないことがわかりました。

  1. ANTLRは、あいまいさなどを管理するためのツールを提供します。開発中、あいまいな解析結果を表示できるツールがあるため、文法のあいまいさを排除できます。実行時にIDEでの不完全な入力から生じるあいまいさを利用して、コード補完などの機能でより正確な結果を生成できます。
  2. 実際には、パーサーのコンビネーターは私のパフォーマンス目標を満たすのに適していないことがわかりました。これの一部は戻る
  3. 解析結果をアウトライン、コード補完、スマートインデントなどの機能に使用すると、文法の微妙な変更が結果の精度に影響を与えるのは簡単です。 ANTLRは、タイプがコンパイルされる場合でも、これらの不一致をコンパイルエラーに変換できるツールを提供します。 IDEを形成するすべての追加コードが最初から新しい機能の完全なエクスペリエンスを提供することを知っているため、文法に影響を与える新しい言語機能のプロトタイプを自信を持って作成できます。ANTLR4の私のフォーク(C#ターゲットのベースとなっている)は、この機能を提供しようとしても、私が知っている唯一のツールです。

基本的に、パーサーのコンビネーターは遊ぶのに最適なおもちゃですが、真剣な仕事をするために切り抜かれているわけではありません。

5
Mason Wheeler

Karlが言及しているように、パーサージェネレーターはより適切なエラー報告を行う傾向があります。加えて:

  • 生成されたコードは構文に特化でき、先読み用のジャンプテーブルを生成できるため、より高速になる傾向があります。
  • あいまいな構文の特定、左再帰の削除、エラーブランチの入力など、より優れたツールを使用する傾向があります。
  • 彼らは再帰的な定義をよりよく処理する傾向があります。
  • ジェネレーターが長く使用されており、ボイラープレートの多くを実行するため、より堅牢になる傾向があります。

一方、コンビネータには独自の利点があります。

  • これらはコード内にあるため、実行時に構文が異なる場合は、より簡単に変更できます。
  • それらは結び付きやすく、実際に消費する傾向があります(パーサージェネレーターの出力は非常に一般的で使いにくい傾向があります)。
  • それらはコード内にあるため、文法が期待どおりに機能しない場合はデバッグが少し簡単になる傾向があります。
  • 他のコードと同様に機能するため、学習曲線が浅くなる傾向があります。パーサージェネレーターは、物事を機能させることを学ぶために独自の癖を持っている傾向があります。
4
Telastyn