GCCとLLVM-Clangは、手書きの再帰下降パーサーを使用しており、マシンで生成されたBison-Flexベースのボトムアップ解析ではないようです。
ここの誰かがこれが事実であることを確認できますか?もしそうなら、なぜ主流のコンパイラフレームワークは手書きのパーサーを使用するのですか?
GCCとLLVM-Clangは、手書きの再帰下降パーサーを使用しており、マシンで生成されたBison-Flexベースのボトムアップ解析ではないようです。
ここの誰かがこれが事実であることを確認できますか?もしそうなら、なぜ主流のコンパイラフレームワークは手書きのパーサーを使用するのですか?
回答:
はい:
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 ++用の単一の統合パーサー」のセクションを参照してください。
foo * bar;
、乗算式(結果は未使用)、またはbar
ポインタ型の変数の宣言として解析できますfoo
。どちらが正しいかは、その時点でtypedef
forfoo
がスコープ内にあるかどうかによって異なります。これは、任意の量の先読みで判断できるものではありません。しかし、それは、再帰下降パーサーがそれを処理するためにいくつかの醜い余分な機械を追加する必要があることを意味します。
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を処理するようになりました。
foo * bar;
あいまいさをどのように処理しますか?
Clangのパーサーは、他のいくつかのオープンソースおよび商用のCおよびC ++フロントエンドと同様に、手書きの再帰下降パーサーです。
Clangは、いくつかの理由で再帰下降パーサーを使用します。
全体として、C ++コンパイラの場合、それはそれほど重要ではありません。C++の解析部分は重要ですが、それでも簡単な部分の1つであるため、単純に保つことにはメリットがあります。セマンティック分析---特に名前の検索、初期化、過負荷の解決、およびテンプレートのインスタンス化---は、解析よりも桁違いに複雑です。証明が必要な場合は、Clangの「Sema」コンポーネント(セマンティック分析用)とその「Parse」コンポーネント(解析用)のコードとコミットの分布を確認してください。
gccのパーサーは手書きです。。clangについても同じだと思います。これはおそらくいくつかの理由によるものです。
これはおそらく「ここで発明されていない」症候群の場合ではありませんが、「私たちが必要とするもののために特別に最適化されたものは何もなかったので、私たちは自分で書いた」という線に沿っています。
そこに奇妙な答えがあります!
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パーサー開発の次のステップになるのでしょうか?
GCCとLLVM-Clangは、手書きの再帰下降パーサーを使用しており、マシンで生成されたBison-Flexベースのボトムアップ解析ではないようです。
特にバイソンは、いくつかのことを曖昧に解析し、後で2回目のパスを実行せずに、文法を処理できるとは思いません。
Haskell's Happyでは、C構文の特定の問題を解決できるモナド(つまり、状態に依存する)パーサーが許可されていることは知っていますが、ユーザー指定の状態モナドを許可するCパーサージェネレーターはありません。
理論的には、エラー回復は手書きのパーサーを支持するポイントですが、GCC / Clangでの私の経験では、エラーメッセージは特に良くありません。
パフォーマンスに関しては、主張のいくつかは根拠がないようです。パーサジェネレータを使用して大きなステートマシンを生成すると、何かが発生するはずO(n)
です。解析が多くのツールのボトルネックになるとは思えません。