nodejs & coffee-script をインストールしてみた
ふとnodenode(http://nodejs.org/)を使いたくなってインストールしてみました。以前インストールしたときは、結構面倒くさかったのですが最新(0.6.7)ではインストーラが存在していて楽チンになってました。
Windowsでもすぐ使えるようにPATHの設定もしてくれるようですね。あとCoffeeScript(http://coffeescript.org/)もWindowsで使えるようになっているようです。
npm install coffee-script
でインストールできました。
ちなみに、npmはユーザのホームディレクトリ(C:¥Users¥ユーザ名とか)にnode_modulesというディレクトリをつくってその下にインストールしていくみたいです。coffeeとcakeはそこの下にインストールされるので適宜PATHを通すと良いみたいです。
CoffeeScriptで小物を作っておけば
- Windows/Mac/Linuxで動作できるスクリプトとして使える
- ブラウザ上でも動く
という感じに便利なので覚えてみようかなと思います。
特にブラウザで動くというのは他の言語には類を見ない便利な点なので、Web上で使いそうなものは全部CoffeeScriptで書いておくくらいでも良いかもしれませんね。
新年あけましておめでとうございます
今年もよろしくお願いします。
年が開けてから大分立っていますが....。去年は色々大変な事がありましたが、今年は良いことが沢山ある年になると良いですね。
今年の目標は「東方吸闘紀の完成」にしようかと思います。去年は会社に休日を振り回された所為で、開発時間などを十分に確保できなかった結果完成には至りませんでした。今年はそういうことがないと考えているので、がんばって開発していきたいですね。
後はCommonLispの開発ノウハウの整理というのも進めて行かないといけないですね。
clangで相互参照の情報を取得する!
前回からだいぶ間が開いてますが、やっと解析の方法をまとめることができそうなのでまとめます。目標は「clangのIndexerを使ってコードの相互参照の情報を取得する」です。
- Indexerを作る
- IndexerにTranslationUnitを複数個渡していく
- IndexerからAnalyzerを作成
- Analyzerで参照の情報を取得
結論から言うと、前回予想したこの手順で問題ありませんでした。
手順の上で大事なところはTranslationUnitを作成する方法です。他の手順はインスタンスを作成すれば良いので、変数宣言さえ出来れば問題ないです。
TranslationUnitの準備
まず、clang::idx::TranslationUnit を作成したいです。しかし、このクラスは仮想クラスです、なので継承してインスタンス化が可能なクラスを定義する必要があります。
#pragma once #include "llvm/ADT/IntrusiveRefCntPtr.h" #include "llvm/Support/CommandLine.h" #include "clang/Frontend/ASTUnit.h" #include "clang/Frontend/CompilerInstance.h" #include "clang/Index/Indexer.h" #include "clang/Index/Program.h" #include "clang/Index/TranslationUnit.h" #include "clang/Index/DeclReferenceMap.h" #include "clang/Index/SelectorMap.h" #include "clang/Index/Handlers.h" #include "clang/Index/Analyzer.h" #include <clang/AST/ASTConsumer.h> #include <clang/AST/Decl.h> #include <clang/AST/DeclTemplate.h> #include <clang/Parse/ParseAST.h> #include <clang/Basic/TargetInfo.h> class ASTUnitTU : public clang::idx::TranslationUnit { clang::ASTUnit *AST; clang::idx::DeclReferenceMap DeclRefMap; clang::idx::SelectorMap SelMap; public: ASTUnitTU(clang::ASTUnit *ast) : AST(ast),DeclRefMap(ast->getASTContext()),SelMap(ast->getASTContext()) { } virtual clang::ASTContext &getASTContext() { return AST->getASTContext(); } virtual clang::Preprocessor &getPreprocessor() { return AST->getPreprocessor(); } virtual clang::DiagnosticsEngine &getDiagnostic() { return AST->getDiagnostics(); } virtual clang::DeclReferenceMap &getDeclReferenceMap() { return DeclRefMap; } virtual clang::SelectorMap &getSelectorMap() { return SelMap; } };
こんな感じに定義しておけば良いです。このクラスはclangのIndexerを利用しているサンプルから持って来ました。
ASTUnitをソースから生成
ASTUnitの定義でTranslationUnitの準備は出来ました。あとはclangの機能で作成していくだけです。
まず、clangのコンパイラを使うためにclang::CompilerInstanceを生成します
clang::CompilerInstance compiler;
このクラスに、clangを利用してコンパイルするためのすべての機能が含まれています。
次に、コンパイルエラーが発生した場合などに対処するためのDiagnosticEngineを生成します。今回の解析では、コンパイルエラーは無視して欲しいのでclang::IgnoringDiagConsumerを生成します。また、setUpDiagnosticOptsにてDiagnosticEngineのオプションを設定しています。基本的にデフォルトの設定にしてあります。変更したいときはヘッダなどに定義が書いてあるので、参考にすると良いと思います。...が解説も何も書いて無いものもあるのでデフォルトで使っていれば良いと思います。
setUpDiagnosticOptsの定義。
// setup DiagnosticOptions void setUpDiagnosticOpts(clang::DiagnosticOptions& diagOpts) { diagOpts.ErrorLimit = 60000; diagOpts.IgnoreWarnings = 1; diagOpts.TabStop = clang::DiagnosticOptions::DefaultTabStop; diagOpts.MessageLength = 0; diagOpts.NoRewriteMacros = 0; diagOpts.Pedantic = 0; diagOpts.PedanticErrors = 0; diagOpts.ShowCarets = 1; diagOpts.ShowColors = 0; diagOpts.ShowOverloads = clang::DiagnosticsEngine::Ovl_All; diagOpts.ShowColumn = 1; diagOpts.ShowFixits = 1; diagOpts.ShowLocation = 1; diagOpts.ShowOptionNames = 0; diagOpts.ShowCategories = 0; diagOpts.ShowSourceRanges = 0; diagOpts.ShowParseableFixits = 0; diagOpts.VerifyDiagnostics = 0; diagOpts.TemplateBacktraceLimit = clang::DiagnosticOptions::DefaultTemplateBacktraceLimit; diagOpts.MacroBacktraceLimit = clang::DiagnosticOptions::DefaultMacroBacktraceLimit; }
実際にDiagnosticEngineを作成するコード。
// Setup Diagnostic Options clang::DiagnosticOptions diagOpts; setUpDiagnosticOpts(diagOpts); // Setup DiagnosticEngine auto ignoreConsumer = new clang::IgnoringDiagConsumer(); // 入力されているファイルの数+1。今回は引数はファイル1つなので2を指定。 const int InputFileNum = 2; // コンパイラに与える引数を設定 const char* compilerArgments[] = {"analyzer",targetSource.c_str()}; auto diagnosticEngine = compiler.createDiagnostics(diagOpts,InputFileNum,compilerArgments,ignoreConsumer); // コンパイラにDiagnosticEngineを設定 compiler.setDiagnostics(diagnosticEngine.getPtr());
次に、実際にコンパイルを行なってくれるInvocationを生成します。これはCompilerInstanceから生成できます。
なお、setUpLangOpts はコンパイラの設定を指定するために定義した関数です。
ここも、デフォルトの値を設定しています。変更したいときはヘッダなどに定義が書いてあるので、参考にすると良いと思います。...が解説も何も書いて無いもの(以下略
setUpLangOptsの定義です。
void setUpLangOpts(clang::LangOptions& langOpts) { langOpts.NoInline = 0; //langOpts.BCPLComment = true; langOpts.Bool = true; //langOpts.MicrosoftExt = true; //langOpts.MicrosoftMode = true; langOpts.CPlusPlus = true; langOpts.CPlusPlus0x = true; langOpts.Exceptions = true; langOpts.CXXExceptions = true; langOpts.MSBitfields = true; langOpts.NeXTRuntime = false; langOpts.NoBuiltin = true; langOpts.CatchUndefined = true; langOpts.EmitAllDecls = true; //langOpts.MSCVersion = _MSC_VER; }
実際にInvocationを作成するコード。
/*============================ Invocationを作成 =====================*/ // CompilerInstanceからInvocationを作成 auto& invocation = compiler.getInvocation(); // setup language options clang::LangOptions langOpts; setUpLangOpts(langOpts); invocation.setLangDefaults(langOpts,clang::IK_C); auto& diag = compiler.getDiagnostics(); auto depOpts = invocation.getDependencyOutputOpts(); depOpts.UsePhonyTargets = 1; clang::CompilerInvocation::CreateFromArgs(invocation, compilerArgments + 1, compilerArgments + 2, diag);
次に、以下の設定を行う必要があります。
- コンパイルのターゲットを設定
- ファイル、ソースなどの行、列数といった情報を管理するためにマネージャ2つを作成
- インクルードファイルの追加
- ASTの情報を格納するASTContextを作成
そろそろ設定ばっかりで嫌になりそうな感じですがもう少しでASTが生成できる準備が整います。
/* インクルードパスを追加する関数 */ void addIncludeFiles(clang::CompilerInstance& compiler,std::vector<std::string> lines) { std::vector<std::string>::iterator itr; for(itr=lines.begin();itr!=lines.end();++itr) { compiler.getHeaderSearchOpts().AddPath(itr->c_str(), clang::frontend::Quoted, true, false, false); compiler.getHeaderSearchOpts().AddPath(itr->c_str(), clang::frontend::Angled, true, false, false); std::cout << "Include:" << itr->c_str() << std::endl; } }
// コンパイルのターゲットを設定 compiler.setTarget(clang::TargetInfo::CreateTargetInfo(diag,compiler.getTargetOpts())); // ファイル、ソースなどの行、列数といった情報を管理するためにマネージャ2つを作成 compiler.createFileManager(); compiler.createSourceManager(compiler.getFileManager()); // インクルードファイルを設定 addIncludeFiles(compiler,includes); // プリプロセッサの作成 compiler.createPreprocessor(); // ASTの情報を格納するASTContextを作成 compiler.createASTContext(); // 意味解析した結果を格納するSemaを作成 compiler.createSema(clang::TU_Complete, NULL);
さて、ハンドラの登録とParseASTを実行して解析しましょう。解析が完了したらinvocationからASTUnitを作成してくれるclang::ASTUnit::LoadFromCompilerInvocation関数を呼べば目標の「TranslationUnitを作成する」を達成できます。やったー。
// ASTをParseする時に呼んで欲しいイベントハンドラを登録 compiler.setASTConsumer(new MyASTConsumer()); // To Parse C Source file from ARG auto& inputs = compiler.getFrontendOpts().Inputs; if (inputs.size() > 0) { compiler.InitializeSourceManager(inputs[0].second); clang::ParseAST( compiler.getPreprocessor(), &compiler.getASTConsumer(), compiler.getASTContext() ); } // ASTUnitを作成 auto astUnit = clang::ASTUnit::LoadFromCompilerInvocation(&invocation, llvm::IntrusiveRefCntPtr<clang::DiagnosticsEngine>(&diag));
Indexerを使ってみる
今まで手順でファイルからASTUnitが作成できました。上の手順を各ファイルごとに実行し、ASTUnitの配列などを作成しておきましょう。そしてそれを使ってIndexerを使ってみましょう。
std::vector<clang::ASTUnit*> astList; std::vector<std::string>::iterator itr; for(itr=targetSources.begin();itr!=targetSources.end();++itr) { // 上記の手順でASTUnitを作成する関数 llvm::OwningPtr<clang::ASTUnit> ast(generateASTUnitFromSource(itr->c_str(),includeSources)); astList.push_back(ast.take()); } // ProgramとIndexerを作成する clang::idx::Program Prog; clang::idx::Indexer Idxer(Prog); // IndexerにIndexしてもらう。 for (unsigned i = 0, e = astList.size(); i != e; ++i) { ASTUnitTU *TU = new ASTUnitTU(astList[i]); Idxer.IndexAST(TU); }
これで、相互参照の情報が完成しました。この情報を使って参照情報を取得してみましょう。
相互参照情報の取得
情報の取得にはAnalyzerを使います。
clang::idx::Analyzer* analyzer = new clang::idx::Analyzer(Idxer.getProgram(),Idxer);
このAnalyzerを使えば、このような形で宣言と参照している箇所などの情報を取得できます。
analyzer->FindDeclarations(pDecl,*handler); analyzer->FindReferences(pDecl,*handler);
第一引数には,調べたい対象の宣言を与えます。
第二引数のhandlerにはハンドラーとなるTULocationHandlerクラスの派生クラスを登録します。
class Handler : public TULocationHandler { public: Handler(){}; private: void Handle(TULocation TULoc) { // TULocには場所の情報が格納されている // FindDeclarationsだと宣言箇所 // FindReferencesだと呼び出し箇所 }; }; auto handler = new Handler(); analyzer->FindDeclarations(pDecl,*handler); analyzer->FindReferences(pDecl,*handler);
なお、宣言の取得の方法としては
- 文字列からEntityを作成し、そこからDeclを取得
- ParseASTの時のハンドラで記憶しておく
の二種類が考えられます。
文字列から取得する場合は以下のようにします。
ParseASTの際のハンドラは適当に皆さんの好きなように実装するのが良いでしょう。
for (unsigned i = 0, e = astList.size(); i != e; ++i) { clang::idx::Entity Ent = clang::idx::Entity::get("func", Idxer.getProgram()); auto decl = Ent.getDecl(astList[i]->getASTContext()); if (decl) { Handler* handler = new Handler(); analyzer->FindReferences(decl,*handler); } }
それではみなさんも楽しいclangライフを。
clangで相互参照の情報解析
前回の記事の続きです。ソースコードから以下の情報を取りたいです。
- 定義されている関数、変数の詳細
- 関数、変数の参照関係
前回の内容では、「定義されている関数、変数の詳細」の情報は取得できました。ですが、「関数、変数の参照関係」を取る方法がわかりませんでした。
相互参照情報の取得
ソースを色々調べてみると、clangのサンプルの中のwpaではIndexerにTranslationUnitを渡してコールグラフの生成を行うコードがありました。コールグラフには参照情報が必須です。つまりIndexerを使えば参照情報の取得ができそうです。
Indexerの定義されているファイル付近(同じディレクトリ)で定義されているクラスもきっと有益なクラスに違いないと考え色々調べてみました。
- clang/Index/
具体的には上記のディレクトリを眺めてみます。ざっくり眺めていると、以下のクラスが重要そうです。
- clang::idx::Analyzer
- clang::idx::Indexer
- clang::idx::Program
このあたりのクラスで、プログラム中に現れている概念(clangではEntityと呼ぶ)の関連を調査できそうです。
- clang::idx::Analyzer::findReferences
とかがかなりそれっぽいです。
clang::idx::Analyzerをつくろう
とりあえず、clang::idx::Analyzerをnewするところから始めたいので、コンストラクタを眺めてみます。
clang::idx::Analyzer::Analyzer ( Program & prog,IndexProvider & idxer);
clang::idx::Programとclang::idx::IndexProviderが引数みたいですね。Analyzerを作成する前に、これらを先にnewする必要がありますね。
Programクラス
Programのコンストラクタには引数がないので、何もしなくても作成できそうです。ついでにコメントに
\brief Top level object that owns and maintains information
that is common across translation units.
と書いてありますね。
IndexProvider
IndexProviderはvirtualなクラスなので、これをimplemantしているIndexerというクラスがすでにclangに用意されています。
なので、Indexerのコンストラクタを眺めてみます。…特に指定されてません。デフォルトでよさそうです。
Indexerのメンバには、Programもあります。なので、Indexerをひとつ作ってしまえば、Analyzerを作成できそうですね。また、Indexerのメソッドで興味深いものとしては
/// \brief Find all Entities and map them to the given translation unit. void IndexAST(TranslationUnit *TU);
このあたりでしょうか。
解析の流れ
ここまでの情報から、恐らくclangのAnalyzerの使い方としては以下を想定しているように見えます。
- Indexerを作る
- IndexerにTranslationUnitを複数個渡していく
- IndexerからAnalyzerを作成
- Analyzerで参照の情報を取得
Indexerを作るのは難しくないことがわかっているので、TranslationUnitを自由に作れるようにできれば、Analyzerを使えるようになるのではないかと予想できます。
Let's ソースコード静的解析!
ふとソースコード静的解析で遊んでみたくなりました。そこで、最近活発なclangでC++のソースコード静的解析プログラムを書こうと思います。
大分昔clangで遊んだことがあって構文木をXMLで出力させるのは標準機能であったなぁと思い出していたら、どうやら最新のclangにその機能(-ast-print-xml)はなくなっている模様です。勉強がてら真面目にclangの機能を利用してみようかと思います。
参考にしたリンク
まずはGoogleさんにちょっと聞いてみると
http://s8823.blogspot.com/2011/10/clangrelease30-rc1.html
http://amnoid.de/tmp/clangtut/tut.html
この辺りが参考になりそうです。
関数と変数のリスト
関数と変数のリストを出力させてみました。
ソースはあまりにも長かったのでgithubで...。
https://github.com/lambdasakura/sourcecodeanalyze/
これで、
- ソース中にある変数、関数のリスト
は取得できそうです。
今直面している課題
残るは、関数、変数の参照関係の情報なのですが、さっぱり資料がなくて死にたくなって来ました。
clang::idx::Indexerがそれに相当すると思うのですが、うまく動いてくれないです。Cursorを使う方法もあるようですが、これは機能が若干少ないとソースに書いてあったので遠慮したいところです。
実際、ParseASTを使って取得できる情報とCursorから取得出来る情報に違いがある模様です。
ある変数宣言を見つけた場合、
- その変数が、staticな変数かそうでないか
- externが付いているかどうか
これらの情報はParseASTを使った解析ではわかったのですが、Cursorを使う方法ではそれは出来なそうです。
ぐぬぬ。
コルーチンをCommon Lispで簡単に定義
また更新滞ってしまいました...。なかなか長続きしませんね。
久しぶりに日記のネタが浮かんだので、メモがてら書きます。
ゲームを作っているとコルーチンが欲しくなることが多々あります。例えば、敵の行動が「飛行状態から通常攻撃をして着地から必殺技を出す」という連携攻撃を考えると、普通に状態遷移でやると、
- 飛行状態
- 通常攻撃
- 着地
- 必殺技
こんな状態を考える必要があるのではないでしょうか?そしてStateパターンなどを使って実装している場合には、このすべての状態に対してクラス定義をしたり、関数定義をしたりする必要があります。
コルーチンを使うとこういう時に便利です。コルーチンは、「関数を途中まで実行して、次に同じ関数を呼んだ時には前回の続きから実行する」というものです。pythonのジェネレータとかで同様のことができますね。
で、common lispでそれを実現するのはcl-contでできるのですが、cl-contが表に出て欲しくないのと
手軽に定義したかったのでマクロを作りました。
こんなのです。
(defmacro def-coroutine (name args &body body) "generate croutine from body form" `(defun ,name ,args (cl-cont:with-call/cc (macrolet ((yield () (let ((cc (gensym))) `(cl-cont:let/cc ,cc ,cc)))) ,@body))))
使い方は簡単で
(def-coroutine test-func (input1 input2) (print input1) (yield) (print input2) (yield))
こんな風にコルーチンが定義できます。定義したコルーチンは
CL-USER> (setf hoge (test-func "1" "2")) "1" #<COMPILED-LEXICAL-CLOSURE (:INTERNAL TEST-FUNC) #xCAD1AFE> CL-USER> (funcall hoge) "2" #<Compiled-function VALUES #x4071136>
こんな感じに使えます。