cppast - C++ AST を解析して操作するためのライブラリ

昨年、C++ ドキュメント ジェネレーターである standardese を開始しました。正確なドキュメントを提供するために、C++ コードを解析する必要があります。そのときは、libclang を使用することにしました。

libclang は、clang の上に構築された C++ 抽象構文ツリー (AST) を公開する C API です。そして、clang は優れた準拠の C++ コンパイラであるため、機能する AST を読み取り、情報を提供するインターフェイスを期待していました。

私は間違っていました。なぜ、どのように解決したのかを以下に示します。

tl;dr:cppast.

libclang の問題

libclang は悪くない 合理的に使いやすいインターフェースを備えているため、すぐに使い始めることができます。また、clang に基づいているため、準拠する C++ コードを問題なく処理できます。さらに、GCC および MSVC コンパイラ拡張機能をサポートしており、十分に高速です。ドキュメント ジェネレーター。

ただし、その Web サイトが宣伝しているように、完全な AST は公開されていません。

「指定されたファイル内のすべての関数を出力する」などの基本的なタスクを実行する必要があるだけの場合は、うまく機能します。

その理由は簡単です:libclang 機能はオンデマンドで実装されます.あなたのプロジェクトのために XXX に関する詳細情報を取得する必要がありますか?それを自分で実装してください.したがって、他の人がすでに必要としているものにはうまく機能しますが、それ以外のものにはうまく機能しません.

後から考えると、API が安定していなくても、おそらく LibTooling を使用する必要がありました。私が聞いたところによると、スタンドアロン プロジェクトで使用するのは困難です。しかし、代わりに別の道を選びました:

libclang の制限を回避し始めました。

libclang の回避策

たとえば、libclang は、関数が 04 とマークされているかどうかを公開しません。 もしそうなら、13 式は、条件付きの場合です。ただし、関数のすべてのトークンを公開します。

「うーん、それは簡単です。関数トークンをループして、21 が見つかるかどうかを確認してください。 」.それが私がしたことです。

私が遭遇した最初の問題はマクロでした。互換性の理由から、時々 35 マクロの背後に隠されています.しかし、libclang トークンは前処理されていないため、それを行う必要がありました.

独自のプリプロセッサを実装する代わりに、適合する C++ プリプロセッサ実装として宣伝されている Boost.Wave を使用することにしました。それらを自動的に前処理するインターフェイスの背後にトークン化をラップしました。

これには 2 つの結果がありました:

<オール> <リ>

標準語のコンパイル時間は爆発的に増加しました:ほとんどのファイルがトークンの解析に必要なため、ほとんどのファイルは Boost.Spirit を含む Boost.Wave を必要とし、コンパイルに時間がかかりました。

<リ>

いくつかのボイラープレート関数を生成するマクロがある場合、このアプローチは機能しませんでした。

そのため、Boost.Wave を使用してファイル全体を前処理する必要がありました。これにより、必要なファイルが 1 つだけになったため、コンパイル時間が短縮されましたが、完全ではありませんでした。前処理する必要があるヘッダー ファイルを選択するシステムに頼る必要があります。しかし、もっと重要なこと:Boost.Wave は遅い 、だから私はあまり幸せではありませんでした。

独自のプリプロセッサを作成しようとして多くの時間を無駄にした後 (マクロ展開は驚くほどトリッキーです)、前処理に直接 clang を使用することにしました。 clang は、前処理された後にファイルを出力します。つまり、プロセス ライブラリを使用して clang を呼び出し、出力を解析しました。特に、51 また、すべてのインクルードを展開しますが、これは望ましくないため、元に戻す必要がありました。ライン マーカー出力のおかげで、これは難しくありませんでした。また、この機会を利用して、マクロを解析し、ディレクティブをインクルードしました。プリプロセッサはまだ最も遅いですが、一部、満足しています。

これで、エンティティのトークンを安全にスキャンして、必要な追加情報を取得できるようになりました。しかし、単純な「ループして、トークンが含まれているかどうかを確認する」ことから始まったものは、取得する必要があるため、多かれ少なかれスマートなヒューリスティックのボールにすぐに成長しました。ますます高度な情報 (64 のようなコンテキスト キーワード と 76 、私はあなたを見ています。最終結果は、私が投げたどのコードでも機能し、さまざまなエッジケースを思いつくことができましたが、実際のコードでは誰もそれらを使用していません™.

しかし、私の構文解析コードは複雑で保守が困難でした。libclang API のさまざまな矛盾を回避する必要があったため、役に立ちませんでした。このファイルを見てください!

また、構文解析コードが標準化コードと強く結合していたため、プロジェクト全体がごちゃごちゃになりました。私は当初、標準化を、必要に応じてドキュメントを生成するために使用できるライブラリとツールの両方として設計しました。しかし、現在の状態では、そうではありません。

そのため、別の解決策が必要でした。

libclang アウトソーシング

なぜこの話をするのですか?

C++ AST を取得する方法を見つけましたが、それは使用できず、自分で AST が必要な場合は、同じ回避策をすべて実行する必要があります。

だから私は唯一の合理的なことをしました:私は混乱を別のプロジェクトに抽出しました.

私には 2 つの目標がありました:

<オール> <リ>

AST で動作するクリーンな API を提供し、すべての解析コードを実装に隠します。これは、私の libclang 回避策で 1 つの場所を汚染するだけです。

<リ>

基礎となる解析の実装から独立してください。これにより、使用コードに影響を与えることなく、複数のバックエンドまたはバックエンドの切り替えが可能になります。

標準的な解析 API は、多かれ少なかれ libclang.Each 83 の薄いラッパーでした。 libclang 93 を保存しました それを使用するのは、私の API と libclang を組み合わせたものでした。目標を達成するために、エンティティ階層を libclang から完全に分離する必要がありました。そのためには、相互参照の解決やまったく新しい階層の作成などのインフラストラクチャをミラー化する必要がありました。型の場合:以前は libclang の 100 を単純に使用していました 、今私は 118 を持っています および派生クラス。

しかし、最終結果はそれだけの価値がありました。C++ AST のクリーンでモダンな表現が得られました。API で type_safe を使用しているため、表現力が向上し、libclang から何も公開されていません。

解析インターフェイスは可能な限りシンプルです - 1 つのメンバー関数を呼び出すだけです - そしてすべての厄介な詳細は物理的に隠されています. 現在、変数テンプレートを除いて、基本的にヘッダーファイルに入れることができるすべてのものを解析できます. これは libclang の制限です.つまり、文字列に連結されたトークンのみを取得し、それ以上の情報を取得することはできません。

また、完全な 123 もあります 階層ですが、137 階層には現在、2 つのクラス (リテラルと非公開) しかありません。関数本体、つまりステートメントや属性も解析しません。ただし、これらの機能は必要に応じて追加されます (必要な場合は、私に叫んでください)。

そこには、標準化の他の部分も実装しました。さまざまな形式のドキュメント コメントのサポートと、同じスマート コメント マッチング システム、および一致しないコメントを反復処理する方法が特徴です。また、AST エンティティのカスタマイズ可能なコード生成もサポートしています。概要を生成するために使用できます。

現在、すべての AST エンティティは不変ですが、これを変更して、新しいエンティティの合成と既存のエンティティの変更の両方ができるようにします。これにより、標準化のコードも大幅に簡素化されます。

おそらく、clang の AST マッチャーのような、より高レベルの訪問インターフェイスも追加する予定です。

誇らしげに紹介できるようになりました:

cppast - libclang 回避策のコレクション

現在、これは最初のプロトタイプにすぎず、実際にはまだ標準に統合していません。これにはおそらく API にいくつかの変更が必要になるため、現時点ではすべてが不安定です。しかし、チェックアウトすることをお勧めします。簡単なコマンドが特徴です。 ASTを「きれいに」出力するラインツールなので、独自のコードを処理できるかどうかを確認してください.

おまけとして、libclang の制限事項とバグの完全なリストができたので、時間があれば実際にそれらを修正し、いくつかの回避策を削除できます。そのため、古い LLVM バージョンをサポートする予定はありません。現時点では、clang 4.0 で使用することをお勧めしますが、3.9.1 も同様に機能します (140 を除く)。 そして、4.1 がリリースされ次第、3.9.1 のサポートを終了します。

AST (リフレクション ライブラリ、ドキュメント ジェネレーター、コード ジェネレーター) を必要とするプロジェクトを作成している場合は、cppast の使用を検討してください。