GCCとClangパーサーは本当に手書きですか?


90

GCCとLLVM-Clangは、手書きの再帰下降パーサーを使用しておりマシンで生成されたBison-Flexベースのボトムアップ解析ではないようです。

ここの誰かがこれが事実であることを確認できますか?もしそうなら、なぜ主流のコンパイラフレームワークは手書きのパーサーを使用するのですか?

更新このトピックに関する興味深いブログはこちら


27
ほとんどすべての主流のコンパイラーは手書きのパーサーを使用しています。それの何が問題なのですか?
SK-logic

2
パフォーマンスが必要な場合は、手動で(半)実行する必要があります。
Gene Bushuyev 2011年

15
そして、パフォーマンスだけでなく、より良いエラーメッセージ、回復能力など
SK-logic

MS VisualStudioはどうですか?オープンソースではありませんが、MSの誰かが、自分たちも手書きの再帰下降パーサーを使用していることを確認できますか?
OrenIshShalom 2017年

3
@ GeneBushuyev、GCC wikiから:「...タイミングは1.5%のスピードアップを示しましたが、主な利点は将来の機能強化を促進することです...」このスピードアップはかなりわずかなようです...
OrenIshShalom 2017年

回答:


78

はい:

  • GCCはかつてyacc(bison)パーサーを使用していましたが、3.xシリーズのある時点で手書きの再帰下降パーサーに置き換えられました。http://gcc.gnu.org/wiki/New_C_Parserを参照してください。関連するパッチ提出へのリンク。

  • Clangは、手書きの再帰下降パーサーも使用します。http://clang.llvm.org/features.htmlの最後にある「C、Objective C、C ++、およびObjective C ++用の単一の統合パーサー」のセクションを参照してください


3
それは、ObjC、C、C ++にLL(k)文法があることを意味しますか?
Lindemann 2012年

47
いいえ:3つの中で最も単純なCでさえ、あいまいな文法を持っています。たとえばfoo * bar;、乗算式(結果は未使用)、またはbarポインタ型の変数の宣言として解析できますfoo。どちらが正しいかは、その時点でtypedefforfooがスコープ内にあるかどうかによって異なります。これは、任意の量の先読みで判断できるものではありません。しかし、それは、再帰下降パーサーがそれを処理するためにいくつかの醜い余分な機械を追加する必要があることを意味します。
Matthew Slattery 2012年

9
経験的証拠から、C ++ 11、C、Objective Cには、GLRパーサーが処理できる文脈自由文法があることが確認できます。
Ira Baxter

2
文脈依存性に関して、この回答はどちらも主張していません。これらの言語の構文解析はチューリング完全である可能性が高いということです。
ioannis Filippidis 2014年

106

Cは構文解析が難しく、C ++は本質的に不可能であるというフォーク定理があります。

それは真実ではありません。

本当のことは、CとC ++は、解析機構をハッキングしたり、シンボルテーブルデータを絡ませたりせずに、LALR(1)パーサーを使用して解析するのはかなり難しいということです。実際、GCCは、YACCとこのような追加のハッカーを使用して、それらを解析するために使用されていましたが、確かに醜いものでした。 現在、GCCは手書きのパーサーを使用していますが、それでもシンボルテーブルハッカーを使用しています。Clangの人々は、自動パーサジェネレータを使用しようとしたことはありません。AFAIK Clangパーサーは、常に手動でコード化された再帰下降です。

本当のことは、CとC ++は、GLRパーサーなどのより強力な自動生成パーサーを使用すると比較的簡単に解析でき、ハックは必要ないということです。エルザC ++パーサはその一例です。私たちのC ++フロントエンドは別のものです(すべての「コンパイラ」フロントエンドと同様に、GLRは非常に素晴らしい解析テクノロジです)。

私たちのC ++フロントエンドはGCCほど高速ではなく、確かにElsaよりも低速です。他にも差し迫った問題があるため、慎重に調整することにほとんど力を入れていません(それでも、数百万行のC ++コードで使用されています)。Elsaは、より一般的であるという理由だけで、GCCよりも遅い可能性があります。最近のプロセッサ速度を考えると、これらの違いは実際にはそれほど重要ではないかもしれません。

しかし、今日広く普及している「本物のコンパイラ」は、10年または20年以上前のコンパイラにルーツがあります。その後、非効率性がはるかに重要になり、GLRパーサーについて誰も聞いたことがなかったため、人々は自分たちが知っている方法で実行しました。Clangは確かに最近のものですが、フォーク定理は長い間その「説得力」を保持しています。

もうそのようにする必要はありません。コンパイラの保守性が向上し、GLRやその他のパーサーをフロントエンドとして非常に合理的に使用できます。

本当のこと、フレンドリーなネイバーフッドコンパイラの動作に一致する文法を取得するのは難しいということです。事実上すべてのC ++コンパイラは(ほとんど)元の標準を実装していますが、MSコンパイラのDLL仕様など、多くのダークコーナー拡張機能も備えている傾向があります。強力な解析エンジンを使用している場合は、取得に時間を費やすことができます。パーサージェネレーターの制限に一致するように文法を曲げようとするのではなく、現実に一致する最終的な文法。

2012年11月の編集:この回答を書いた後、ANSI、GNU、およびMSバリアント方言を含む完全なC ++ 11を処理するようにC ++フロントエンドを改善しました。余分なものがたくさんありましたが、解析エンジンを変更する必要はありません。文法規則を改訂しました。私たちはやった意味解析を変更する必要があります。C ++ 11は意味的に非常に複雑であり、この作業はパーサーを実行するための労力を圧倒します。

2015年2月の編集:...完全なC ++ 14を処理するようになりました。(コードの単純なビットのGLR解析、およびC ++の悪名高い「最も厄介な解析」については、c ++コードから人間が読めるAST取得するを参照してください)。

2017年4月の編集:(ドラフト)C ++ 17を処理するようになりました。


6
PostScript:ベンダーが実際に行っていることと文法を一致させるのが難しいのと同じように、C ++ 11マニュアルのさまざまなベンダーの解釈と一致するように名前と型の解決を取得するのはさらに困難です。別の方法で、それらを見つけることができれば。2013年8月の時点で、C ++ 11本体についてはそれを大幅に過ぎていますが、Cの形式でさらに大きな(そして経験から、より混乱する)標準を作成することにひどい思いをしているC ++委員会に少し絶望しています+ + 1年。
Ira Baxter

5
本当に知りたいのですが、そのfoo * bar;あいまいさをどのように処理しますか?
マーティン

14
@Martin:パーサーはそれを両方の方法で解析し、子が代替解析である特別な「あいまいノード」を含むツリーを生成します。子供たちは子供たちを最大限に共有するので、ツリーではなくDAGになります。 解析が完了した、DAG(「ツリーを歩いて何かをする」の仮称)に対して属性文法エバリュエーター(AGE)を実行し、宣言されたすべての識別子のタイプを計算します。...
Ira Baxter

12
...あいまいな子は、両方とも型の一貫性を持つことはできません。賢明に入力できないあいまいな子を発見したAGEは、単にそれを削除します。残っているのは、よくタイプされた子供たちです。したがって、「foobar」のどの解析を決定しました。正しい。このトリックは、C ++ 11の実際の方言用に構築した実際の文法に見られるあらゆる種類のクレイジーなあいまいさに対して機能し、*名前の構文解析とセマンティック分析を完全に分離します。このクリーンな分離により、エンジニアリング作業が大幅に削減されます(デバッグするためのもつれがなくなります)。詳細については、stackoverflow.com / a / 1004737/120163を参照してください。
Ira Baxter

3
@TimCas:実際、私はあなたと一緒に、正しく理解するのが非常に難しいほど複雑な言語構文(およびセマンティクス)を設計するという見かけの愚かさを理解しています(はい、C ++言語はここでひどく苦しんでいます)。言語設計委員会が構文を設計して、より単純な構文解析テクノロジーが機能するようにし、言語セマンティクスを明示的に定義して、いくつかのセマンティック分析ツールでチェックすることを望みます。悲しいかな、世界はそのようではないようです。ですから、私は、あなたが自分が構築しなければならないものをできる限り構築し、ぎこちなくても人生をやり遂げるという見方をしています。
Ira Baxter

31

Clangのパーサーは、他のいくつかのオープンソースおよび商用のCおよびC ++フロントエンドと同様に、手書きの再帰下降パーサーです。

Clangは、いくつかの理由で再帰下降パーサーを使用します。

  • パフォーマンス:手書きのパーサーを使用すると、高速パーサーを記述して、必要に応じてホットパスを最適化でき、常にそのパフォーマンスを制御できます。高速パーサーを使用することで、「実際の」パーサーが通常使用されない他の開発ツール(IDEでの構文の強調表示やコード補完など)でClangを使用できるようになりました。
  • 診断とエラー回復:手書きの再帰下降パーサーで完全に制御できるため、一般的な問題を検出し、優れた診断とエラー回復を提供する特別なケースを簡単に追加できます(例:http://clang.llvmを参照)。 .org / features.html#expressivediags)自動生成されたパーサーでは、ジェネレーターの機能に制限されます。
  • シンプルさ:再帰下降パーサーは、記述、理解、およびデバッグが簡単です。解析の専門家である必要も、パーサーを拡張/改善するための新しいツールを学ぶ必要もありません(これはオープンソースプロジェクトにとって特に重要です)が、それでも素晴らしい結果を得ることができます。

全体として、C ++コンパイラの場合、それはそれほど重要ではありません。C++の解析部分は重要ですが、それでも簡単な部分の1つであるため、単純に保つことにはメリットがあります。セマンティック分析---特に名前の検索、初期化、過負荷の解決、およびテンプレートのインスタンス化---は、解析よりも桁違いに複雑です。証明が必要な場合は、Clangの「Sema」コンポーネント(セマンティック分析用)とその「Parse」コンポーネント(解析用)のコードとコミットの分布を確認してください。


4
はい、セマンティック分析は非常に困難です。C ++ 11文法を構成する約4000行の文法規則と、上記の「セマンティック分析」ダブリストの約180,000行の属性文法コードと、さらに100,000行のサポートコードがあります。構文解析は実際には問題ではありませんが、間違った足で開始した場合は十分に困難です。
Ira Baxter

1
手書きのパーサーがエラーの報告/回復に必ずしも適しているかどうかはわかりません。人々は、実際に自動パーサジェネレータによって生成されたパーサを強化するよりも、そのようなパーサに多くのエネルギーを投入しているようです。このトピックについてはかなり良い研究があるようです。この特定の論文は本当に私の目を引きました:MG Burke、1983年、LRおよびLL構文エラーの診断と回復のための実用的な方法、博士論文、ニューヨーク大学コンピュータサイエンス学部、archive.org / details / practicalmethodf00burk
IraBaxterを

1
...この思考トレインを継続する:より良い診断のために特別なケースをチェックするために手作りのパーサーを変更/拡張/カスタマイズすることをいとわない場合は、機械的に生成されたパーサーのより良い診断に同等の投資をすることをいとわないはずです。手動解析用にエンコードできる特別な解析の場合は、機械解析用のチェックもコーディングできます((G)LRパーサーの場合、これは削減のセマンティックチェックとしてほぼ実行できます)。食欲をそそらないように思える範囲で、怠惰なだけですが、それは機械的に生成されたパーサーIMHOの告発ではありません。
Ira Baxter

8

gccのパーサーは手書きです。。clangについても同じだと思います。これはおそらくいくつかの理由によるものです。

  • パフォーマンス:特定のタスク用に手動で最適化したものは、ほとんどの場合、一般的なソリューションよりもパフォーマンスが向上します。抽象化は通常、パフォーマンスに影響を与えます
  • タイミング:少なくともGCCの場合、GCCは多くの無料の開発ツールよりも前から存在しています(1987年に発表されました)。当時、yaccなどの無料版はありませんでした。FSFの人々にとって優先事項だったと思います。

これはおそらく「ここで発明されていない」症候群の場合ではありませんが、「私たちが必要とするもののために特別に最適化されたものは何もなかったので、私たちは自分で書いた」という線に沿っています。


15
1987年にyaccの無料版はありませんか?70年代にyaccがUnixで最初に配信されたときは無料バージョンがあったと思います。そして、IIRC(他のポスターは同じようです)、GCCが使用YACCベースのパーサを持っています。変更の言い訳は、エラー報告を改善することだと聞きました。
Ira Baxter 2011年

7
手書きのパーサーから適切なエラーメッセージを生成する方が簡単な場合が多いことを付け加えたいと思います。
ディートリッヒエップ2011年

1
タイミングに関するあなたのポイントは不正確です。GCCには以前YACCベースのパーサーがありましたが、これは後で手書きの再帰下降パーサーに置き換えられました。
トミーアンデルセン

7

そこに奇妙な答えがあります!

C / C ++の文法は文脈自由ではありません。Foo *バーがあるため、状況依存です。あいまいさ。Fooが型であるかどうかを知るには、typedefのリストを作成する必要があります。

Ira Baxter:あなたのGLRのことには意味がありません。あいまいさを含む解析ツリーを構築する理由。構文解析とは、あいまいさを解決し、構文ツリーを構築することを意味します。2回目のパスでこれらのあいまいさを解決するので、これはそれほど醜いことではありません。私にとってそれははるかに醜いです...

YaccはLR(1)パーサジェネレーター(またはLALR(1))ですが、状況依存になるように簡単に変更できます。そして、それに醜いものは何もありません。Yacc / Bisonは、C言語の解析を支援するために作成されたため、おそらくCパーサーを生成するための最も醜いツールではありません...

GCC 3.xまでは、Cパーサーはyacc / bisonによって生成され、解析中にtypedefsテーブルが作成されていました。「inparse」typedefsテーブル構築により、C文法はローカルで文脈自由になり、さらに「ローカルでLR(1)」になります。

現在、Gcc 4.xでは、再帰下降パーサーです。これはGcc3.xとまったく同じパーサーであり、それでもLR(1)であり、同じ文法規則を持っています。違いは、yaccパーサーが手動で書き直され、shift / reduceが呼び出しスタックに非表示になり、gcc 3.xyaccのように「state454:if(nextsym == '(')gotostate398」がないことです。パーサーなので、パッチの適用、エラーの処理、より適切なメッセージの印刷、および解析中の次のコンパイル手順の実行が簡単になります。gccnoobの「読みやすい」コードがはるかに少なくなります。

なぜ彼らはyaccから再帰下降に切り替えたのですか?C ++を解析するためにyaccを回避することが非常に必要であり、GCCは多言語コンパイラー、つまりコンパイルできる異なる言語間で最大のコードを共有することを夢見ているためです。これが、C ++とCパーサーが同じように記述されている理由です。

C ++は、Cのように「ローカルに」LR(1)ではなく、LR(k)でもないため、Cよりも解析が困難です。func<4 > 2>4> 2でインスタンス化されたテンプレート関数はどれかを見てください。つまり、func<4 > 2> として読み取る必要がありますfunc<1>。これは間違いなくLR(1)ではありません。ここで、を考えてみましょうfunc<4 > 2 > 1 > 3 > 3 > 8 > 9 > 8 > 7 > 8>。これは、再帰下降が、さらにいくつかの関数呼び出しを犠牲にして、あいまいさを簡単に解決できる場所です(parse_template_parameterはあいまいなパーサー関数です。parse_template_parameter(17tokens)が失敗した場合は、parse_template_parameter(15tokens)、parse_template_parameter(13tokens)...まで再試行してください。できます)。

なぜyacc / bison再帰サブ文法に追加できないのかわかりません。おそらく、これがgcc / GNUパーサー開発の次のステップになるのでしょうか?


9
「私にとって、それははるかに醜いです」。私が言えることは、GLRと遅延のあいまいさの解決を使用した生産品質パーサーのエンジニアリングは、非常に小さなチームで実用的であるということです。私が見た他のすべての解決策は、LR、再帰下降で動作させるために必要なバックフリップとハックを介して公共の場で何年にもわたって歯を食いしばることを含みました。他の多くのクールな新しい構文解析テクノロジーを仮定することができますが、私が知る限り、それは現時点では歯を食いしばっているだけです。アイデアは安いです。実行は大切です。
Ira Baxter

@IraBaxter:ネズミ!citeseerx.ist.psu.edu/viewdoc/...
フィズ

@Fizz:複雑な科学プログラミング言語であるFortressの解析に関する興味深い論文。彼らはいくつかの注意点を述べました:a)古典的なパーサジェネレーター(LL(k)、LALR(1))は難しい文法を処理できませんb)彼らはGLRを試しました、スケールに問題がありましたが、開発者は経験が浅いので彼らはしませんでした完全[GLRのせいではありません]そしてc)彼らはバックトラッキング(トランザクション)Packratパーサーを使用し、より良いエラーメッセージを生成するための作業を含めてそれに多大な努力を払いました。「{| x || x←mySet、3 | x}」を解析する彼らの例に関しては、GLRはそれをうまく実行し、スペースを必要としないと私は信じています。
アイラバクスター

0

GCCとLLVM-Clangは、手書きの再帰下降パーサーを使用しており、マシンで生成されたBison-Flexベースのボトムアップ解析ではないようです。

特にバイソンは、いくつかのことを曖昧に解析し、後で2回目のパスを実行せずに、文法を処理できるとは思いません。

Haskell's Happyでは、C構文の特定の問題を解決できるモナド(つまり、状態に依存する)パーサーが許可されていることは知っていますが、ユーザー指定の状態モナドを許可するCパーサージェネレーターはありません。

理論的には、エラー回復は手書きのパーサーを支持するポイントですが、GCC / Clangでの私の経験では、エラーメッセージは特に良くありません。

パフォーマンスに関しては、主張のいくつかは根拠がないようです。パーサジェネレータを使用して大きなステートマシンを生成すると、何かが発生するはずO(n)です。解析が多くのツールのボトルネックになるとは思えません。


3
この質問にはすでに非常に質の高い回答がありますが、何を追加しようとしていますか?
tod 2018年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.