2011年9月29日木曜日

CLangでクラスの中身を調べる(3)

前回の続き。標準インクルードパスを設定する方法を探さねばならない。


クラス名的に最もクサイのはclang::HeaderSearchである。ということでHeaderSearchの.hから見てみると、SetSearchPaths()というAPIがある。
このAPIはclang::DirectoryLookupというクラスの配列を受け取る。これは単純にHeaderSearchのメンバ変数を書き換えるようだ。そちらのメンバ変数の説明によると

  • 最初にカレントフォルダを検索する。
  • 次に(当該メンバ変数)を順に検索する。
  • オプションで(当該メンバ変数)の検索開始インデックスを指定したり、カレントフォルダを検索しないようにできる。

という感じらしい。

DirectoryLookupはえーと・・・検索エントリで、単なるフォルダではなくフレームワーク?やヘッダマップ?も指定できるオブジェクトと説明があるがさっぱりわからない。DirectoryLookupは2種類のコンストラクタがあるが、そのうち片方ではclang::DirectoryEntryとclang::SrcMgr::CharacteristicKindを受け取る。DirectoryEntryは単にフォルダ名を格納するだけであるが、フォルダ名のセットAPIはない。代わりにFileManagerがfriend指定されているので、FileManagerから受け取るのだろう。CharacteristicKindは列挙でC_User, C_System, C_ExternCSystemが選べる。

FileManagerからDirectoryEntryを受け取る方法だが、単発のファイルパスの解決のときと同様、getDirectory()APIにファイルパス文字列を渡した結果として受け取れるようだ。

ってことで、以下のように実装してみる。
	llvm::StringRef stdHeaderPath1("C:\\Program Files (x86)\\Microsoft Visual Studio 8\\VC\\INCLUDE");
	llvm::StringRef stdHeaderPath2("C:\\Program Files (x86)\\Microsoft Visual Studio 8\\VC\\PlatformSDK\\include");
	std::vector<clang::directorylookup> stdHeaderPaths;
	{
		const clang::DirectoryEntry* p = fileMgr.getDirectory(stdHeaderPath1);
		clang::DirectoryLookup d(p, clang::SrcMgr::C_System, false, true);
		stdHeaderPaths.push_back(d);
	}
	{
		const clang::DirectoryEntry* p = fileMgr.getDirectory(stdHeaderPath2);
		clang::DirectoryLookup d(p, clang::SrcMgr::C_System, false, true);
		stdHeaderPaths.push_back(d);
	}
	headerSearch.SetSearchPaths(stdHeaderPaths, 0, false);

実行すると・・・まだダメ。headerSearchに追加したDirectoryLookupのLookupFile()は呼び出されているが、「フレームワーク」として登録されている場合、'/'文字が検索するファイルのパスに入ってないとダメらしい。フレームワークってboostとかclangとか、そういうのを指しているのか?

ためしにフーレムワーク指定をfalseにすると・・・今度は別の箇所でアサーションした。なるほど、標準ライブラリはフレームワークではないらしい。

ところで、ヘッダファイルが見つからないごときでアサーションが発生するのは勘弁してほしい気がする。次は先に進む前に、アサーションではなくエラーメッセージを表示させるようなことを考える。

CLangでクラスの中身を調べる(2)

CLangのプリプロセッサ実行の続き・・・の前に。CLangが提供する各クラスのドキュメントについて。
ググって出てくるドキュメントには要注意。バージョンアップでI/Fが変更されている箇所が非常に多いのだ。だから正確な情報を得るには、CLangのソースコードの.hを見たり、.hや.cppに記述されているdoxygenスタイルコメントを読む必要がある。


では続き。
ドキュメントによるとプリプロセッサの実行はclang::Preprocessor::Lex関数によるようだ。そしてLex関数の引数はclang::Tokenで戻り値はvoidなので、引数がoutなんだろうと推測される。

しかし、今まで対象のファイルやフォルダを指定していない。何も指定せずにLex関数をコールするとどうなるだろうか?
	clang::Token token;
	pp.Lex(token);
実行すると見事になにもエラーが発生しない。CLangは例外をthrowしないのか?

ということで対象ファイルを指定する方法を探す。前回ちょっと触ったクラスのうち、たぶんFileManagerあたりでファイルやフォルダを指定し、SourceManagerで読み込む、という感じではないかと推測して各クラスを調べる。
そしてFileManagerのヘッダを見ると、FileEntryとかDirectoryEntiryというのを返却するAPIがある。そしてSourceManagerのドキュメントを見ると、FileEntryを受け取るAPIとしてcreateFileID()やcreateMainFileID()がある。
createFileID()などはclang::FileIDを返す。そこでPreprocessorのヘッダを見ると、FileIDを引数に取るEnterSourceFile()や、引数はないけどFileIDとmain source fileに言及しているEnterMainSourceFile()がある。だいぶ見えてきた。

ということで、main source fileとmainじゃないsource fileの違いがよくわからないが、とりあえずmain source fileを処理しそうなコードを書いて実行してみる。
	llvm::StringRef cppPath("D:\\develop\\Clang\\temp.cpp");
	const clang::FileEntry* cppEntry = fileMgr.getFile(cppPath);
	clang::FileID fileId = srcMgr.createMainFileID(cppEntry);
	pp.EnterMainSourceFile();

	clang::Token token;
	pp.Lex(token);
ちなみにtemp.cppの中身はこんな感じ:
#include <cstdio>
int main(int, char**) { std::printf("Hello,world\n"); std::getchar(); return 0; }

そうすると、clang::Preprocessor::Lex()の実行でアサーションされてしまった。コールスタックを見るとclang::Preprocessor::HandleIncludeDirective()という関数にて「Search include directories.」とコメントされている箇所で、処理結果の診断がNGになっているようだ。なるほど。

CLangでクラスの中身を調べる(1)

C++において、ある特定の型で宣言されたメンバ変数を持つクラスとその変数名を列挙したい。
CLangを使ってそれを実現する(か挫折する)まで頑張る。

現在の環境:
・Visual Studio 2005 with SP1
・llvm 2.9
・CLang 2.9

まずCLangのドキュメントを読んでみたがよくわからない。さかんにASTという単語が出てくるが、これはたぶんAbstract Syntax Treeの略(=構文解析結果)だと思うので、そこまでたどり着ければ何か見えてくるだろう。

まずはプリプロセッサ。ドキュメントには「The Lexer and Preprocessor Library」という節があるので、ここを見ればいいはず。ちなみにLexer = Lexical Analyzerであるらしい。これによるとプリプロセッサのクラスはclang::Preprocessorというのがある。ドキュメントを読み進める前にこいつのオブジェクトを作ってみよう。
clang::Preprocessorはコンストラクタで多数のインスタンスが要求される。
  • clang::Diagnostic
  • clang::LangOptions
  • clang::TargetInfo
  • clang::SourceManager
  • clang::HeaderSearch
以下、1つずつ潰す。

■clang::Diagnostic
コンパイルエラーなどを通知する?
こいつはllvm::IntrusiveRefCntPtrを要求する。clang::DiagnosticIDsはデフォルト生成可能。


■clang::LangOptions
言語オプション?RTTIを有効にしたりする様子。
コンストラクタにて全オプションがデフォルト指定されているので、まずはデフォルトのまま利用する。

■clang::TargetInfo
ターゲットとは、たぶんバイナリの動作環境の事だろう。
こいつはコンストラクタは利用できず、staticなファクトリ関数を使う。clang::Diagnosticとclang::TargetOptionを要求する。
clang::TargetOptionは単なる構造体である。そのメンバにTripleというのがあって最初躓いたが、どうも以下の4つをまとめたものであるらしい:
  • CPUアーキテクチャ(alpha, ppc, x86, x86_64等)
  • ベンダ(UnknownとAppleとPCしかねぇー)
  • OS(Cygwin, Linux, Win32等)
  • 環境(オプション? GNU, MachO等)
そしてllvm::sysにgetHostTriple()というAPIが用意されている。今回は別にバイナリを作るわけではないで、ホスト環境をそのまま使うことにする。それ以外のオプションは設定しなくても大丈夫っぽい。Featuresがちょっと気になるが・・・。

■clang::SourceManager
ソースコードをファイルから読み込んでメモリ上に保持管理する役割を持つようだ。
こいつはclang::Diagnosticとclang::FileManagerを要求する。
clang::FileManagerはファイルやフォルダを検索する機能、またファイルをキャッシングする機能を提供する。clang::FileSystemOptionsを要求する。
clang::FileSystemOptionsは単なる構造体で、作業フォルダの指定しか属性がない。しかもデフォルトでは指定する必要がないようだ。

■clang::HeaderSearch
その名の通りヘッダ検索をサポートするようだ。
こいつもclang::FileManagerを要求する。

■clang::Preprocessor
こんな感じになった。
#include "llvm/Support/Host.h"
#include "clang/Basic/Diagnostic.h"
#include "clang/Basic/LangOptions.h"
#include "clang/Basic/TargetOptions.h"
#include "clang/Basic/TargetInfo.h"
#include "clang/Basic/FileManager.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Lex/HeaderSearch.h"
#include "clang/Lex/Preprocessor.h"

int main(int, char*[])
{
	llvm::IntrusiveRefCntPtr<clang::DiagnosticIDs> diagIds(new clang::DiagnosticIDs());
	clang::Diagnostic diag(diagIds);

	clang::LangOptions langOpts;

	clang::TargetOptions targetOpts;
	targetOpts.Triple = llvm::sys::getHostTriple();

	clang::TargetInfo* targetInfo
		= clang::TargetInfo::CreateTargetInfo(diag, targetOpts);

	clang::FileSystemOptions fileSysOpts;
	clang::FileManager fileMgr(fileSysOpts);
	clang::SourceManager srcMgr(diag, fileMgr);

	clang::HeaderSearch headerSearch(fileMgr);

	clang::Preprocessor pp(diag, langOpts, *targetInfo, srcMgr, headerSearch);

	getchar();
	return 0;
}

ビルドすると当然リンクエラーが出る。今はclang/Basicとclang/Lexに依存しているので、それぞれclangBasicプロジェクトとclangLexプロジェクトに依存するよう、プロジェクト依存関係を追加すればOK。
※LLVM.slnに自分のテストプロジェクトを追加している。
実行してエラーも何もでないことを確認した。

2011年9月27日火曜日

CLangをビルド

CLangをビルドしたので、ちょっとメモ。

基本は
に従って進める。

trunkだとビルドが通らなかったので、LLVMとCLang共にRELEASE 2.9を取得。
チェックアウト時にCLangのチェックアウトフォルダ名をうっかりcfeにしたらビルド対象に含まれなかった。clangに修正してやり直したらOK。

Cygwinでビルドしたときは、改行にCRが入っているとCygwinのシェルに怒られた。Unix系使ったことないから何故怒られるのかわからないので全部LFのみに置換。
llvm/config
llvm/autoconf/config.guess
llvm/autoconf/config.sub
llvm/autoconf/mkinstalldirs
llvm/autoconf/install-sh
llvm/projects/sample/configure

VS2010でビルドしたバイナリで.cppをコンパイルしたら
「clang: error: unable to execute command: program not executable」
「clang: error: linker command failed due to signal 1 (use -v to see invocation)」
と出力され、コンパイルできなかった。何故だろう・・・。