web-dev-qa-db-ja.com

パーサジェネレータの選択

OK、私はこの質問がかなり意見に基づいているように聞こえるかもしれないことを理解しています、しかし私はいくつかの特定の選択基準を持っているので、それはSOにぴったりだと思います。だから、ここに私は...

私は過去にコンパイラー/インタープリターの構築にかなり多く取り組んできました(主に趣味として)そして何らかの理由で私はLex/Yacc(またはFlex/Bison、彼らが今それらをどのように呼ぶかについてかなり混乱しています)に固執しました... 笑)。

しかし、現在、別の愛好家の通訳プロジェクトで遊んでいることに気付いたので、Lex/Yaccの嫌いなところを避けるために、何か別のことを試してみるべきだと思いました。

つまり:

  • より良いC++対応(C対応より)
  • 優れたドキュメント(できれば、いくつかの既存の文法がすでに実装されている+それらをコンパイル/使用する方法の説明-かなり明白に聞こえますね?)
  • LALR、LL(*)、再帰下降、私は本当に気にしない注:に関する入力どのタイプを好み、どのタイプの実装が素晴らしいのか、私は彼らが何を指しているのかは知っていても、正直に言うと、彼らの長所と短所を本当に理解したことはありません)
  • レクサー部分とパーサー文法を1つのファイルに結合するまったく悪いことではありません。なぜそれを2つに分割しなければならないのか、実際にはわかりませんでした。
  • 最後になりましたが、私はいつも...問題を抱えていました。つまり、少なくともLex/Yaccに関する限り、エラーメッセージの解析は多かれ少なかれ不可解です(Syntax Error... Yuhuu!)そして、問題の診断に役立つことはめったにありません。 (まあ、あなたが通訳を開発した人でない限り...笑)。それで、エラー報告に関してLex/Yaccより良いものはありますか?

OK、それがあまり冗長ではなかったといいのですが。ぜひ聞きたいです! :-)

17
Dr.Kameleon

私は1969年からパーサジェネレータとパーサを構築してきました。

再帰下降、YACC、JavaCCが典型的な答えです。

これらはおじいちゃんのパーサジェネレータであり、受け入れる文法に制限があります。常に(特にStack Overflowで)、一部の貧しい人々は「このシフト/削減を解決するにはどうすればよいですか」(YACCのようなLRパーサージェネレーターの場合)または「左再帰を排除するにはどうすればよいですか」(再帰下降またはLLパーサージェネレーターのように) JavaCC)。さらに悪いことに、ほとんどの複雑な言語で発生するように、構文が実際にあいまいな文法を処理できません。

[〜#〜] glr [〜#〜] (およびGLL)パーサーを使用すると、文脈自由文法を記述して、面倒なことなく解析できます。これは実際の生産性の向上です。代償があります。あいまいな解析になってしまう可能性がありますが、それを処理する方法はいくつかあります。 (これを参照してください YACCもJavaCCも単独で処理できないC++解析の問題の説明 )。

Bison(広く利用可能)には GLRオプション ;があります。これを使って!最近の多言語プログラム操作ツールはすべてGLLまたはGLRを使用しているようです。私たちのDMSソフトウェアリエンジニアリングツールキットはGLRを使用し、C++(MSの完全なC++ 14およびGNUバリアント!)、Java、COBOL、およびその他の複雑な言語のラフト)を解析します。GLRはStrategoはGLRを使用しています。RascalMPLはGLLを使用していると思います。ScottMcPeakのElkhoundGLRパーサージェネレーターはC++ベースであり、C++コードを生成します(OPはC++ベースの回答を求めました) )。

最近の話題はPEGとANTLR4です。これらはLLまたはLRパーサーよりも優れていますが、それでも文法を形成しようとする際に1つの悲しみを与えます。 (PEGでは、優先順位のあるあいまいなルールを処理するために、そのような順序が見つかると仮定して、プロダクションを注文する必要があります。ANTLR4では、あいまいさを解決するために先読みを指定する必要があります。無限の先読みを処理する方法はわかりません)。 AFAIK、これらのテクノロジーのいずれかを使用して実用的なC++パーサーを構築した人は誰もいないため、評判に応えていません。

GLRとGLLの方がはるかに良い答えだと思います。

27
Ira Baxter

最後の質問に少し編集して答えます。

少なくともLex/Yaccに関する限り、エラーメッセージの解析は多かれ少なかれ不可解であり(構文エラー... Yuhuu!)、問題の診断に役立つことはめったにありません。 (まあ、あなたが通訳を開発した人でない限り...笑)。だから、そこにあります より良いものより良い使用方法エラー報告に関するLex/Yacc?

さて、合理的に完全な 手動 オンライン(そして、bisonのインストール方法によっては、実行可能ファイルとともにインストールされる可能性が高い)を備えた最新バージョンのbisonを使用することから始めます。特に、次の宣言から始めます。

%define parse.error verbose
%define parse.lac full

これにより、少なくとも、不可解な「構文エラー」エラーが「予期される」トークンタイプのリストに置き換えられます。

次に、トークンタイプに意味のある名前が付いていることを確認します。これは、トークンタイプがエラーメッセージの一部としてユーザーに表示されるためです。 IDENTIFIERを端末として使用することに慣れている場合は、おそらく問題ありませんが、「ExpectedTOK_YY_ID」というメッセージは少しオタクです。 type宣言で、端末の読み取り可能を宣言できます。

%type TOK_YY_ID "identifier"

それはあなたをここまで連れて行くだけです。多くの場合、構文エラーを理解するには、何が「予期された」かを知るだけで十分ですが、より明確にすることが役立つ場合もあります。このような場合、実際にerrorルールを定義すると便利です。これらを正しくすることは科学というより芸術ですが、それはエラー報告/回復へのすべてのアプローチに当てはまります。重要なのは、誤った構文がどのように見えるかについてできるだけ具体的にすることであり、必要以上に具体的にしないことです。

エラーレポートへの興味深いアプローチの1つは、現在のパーサー状態と先読みトークン(どちらもエラーレポートの時点で表示されます)を使用して、カスタムエラーメッセージが存在する場合はそれを検索することです。このアプローチは長い間コンパイラの民間伝承の一部であったと思います、そして私はそれについてのいくつかの記事を何十年にもわたって見たと確信しています。これは Russ Cox による比較的最近の記事です。

9
rici

興味深い質問-あなたの実際の質問に対する素晴らしい答えがあるかどうかはわかりませんが、私の「コメント」はコメントするには少し長すぎました...

私はPascalコンパイラで作業しており、レクサー、トークナイザー、パーサー(LLVMのコードジェネレーターに入るAST)の生成を含む)を約11​​00行で記述しています。私は自分自身、C++コードのかなり「いい」と言うかもしれません-すべて手作業です。それは良いエラーメッセージを生成するのにはるかに友好的であり、それは役に立ちます。いくつかのビットが欠けています、そして私はまだ私の前にたくさんの仕事が残っていますコンパイラは完全ですが、かなり複雑なコードをコンパイルできます。

確かに、Lex/YaccやFlex/Bisonを実際に使用したことはありません。私は時々それを見ました、しかし私はこれらのツールを使うのが難しいと思います、そしてあなたは生成されたコードを取りそしてそれを修正することになるか(自動生成されたコードでは悪い考え)、または悪いエラー処理でそして上にコードをデバッグするのは難しいですその。しかし、その後、セミコロンの「食べ方」が早すぎるために発生したエラーを見つけるために約2時間費やし、その結果、パーサーがトークンフローで失われました...

2
Mats Petersson