iOS App 開発担当の松本です。
Sleipnir for Mac をお試しいただいた皆様、ありがとうございます。まだの方は是非!
さて、先日の記事にもあるように、Apple は LLVM を基盤とした開発環境を着々と整備してきています。今日は LLVM 関連ツールの中でも特に注目されている Clang で少し遊んでみたいと思います。
Clang は LLVM をバックエンドとした C/C++/Objective-C のコンパイラです。GCC に比べてコンパイルが速かったり、生成されたバイナリがより最適化されてたりと、色々と優れているのですが、中でも面白いのがコンパイル途中で生成される構文木に、アクセスする為のインターフェースが用意されている点です。Xcode ではお馴染みの「コード補完」や「リファクタリング」などの機能が、このインターフェースを利用して実装されていて、方法次第でかなり強力なツールを作れる事がわかります。今回はこのインターフェースを叩いて構文解析を少しだけ行います。
早速試してみます。まず llvm および clang をインストールしましょう。llvm と clang は別々にチェックアウトしますが、ビルドは一括でできます。また上記のインターフェースには Python 用のバインディングが用意されているので、合わせてパスの通ったディレクトリにコピーしておきましょう。
$ svn co http://llvm.org/svn/llvm-project/llvm/trunk llvm $ cd llvm/tools $ svn co http://llvm.org/svn/llvm-project/cfe/trunk clang $ cd .. $ ./configure $ make && make install $ cp -R tools/clang/bindings/python/clang /path/to/python_modules
ここでは以下の Objective-C のコードの構文解析を行ってみる事にします。簡単のため本来必要な #import 文は省いています。
/* test.m */ // 登録されているすべてのクラスを表示します void showClassList() { int numClasses = objc_getClassList(NULL, 0); if (numClasses == 0) return; Class *classes = malloc(sizeof(Class) * numClasses); numClasses = objc_getClassList(classes, numClasses); NSLog(@"%d", numClasses); for (int i = 0; i < numClasses; i++) { Class *c = (Class *)classes[i]; NSLog(@"%@", c); } free(classes); } int main() { showClassList(); return 0; }
まずは構文木をすべて表示してみましょう。以下のコードを実行します。
#!/usr/bin/env python # -*- coding: utf-8 -*- # python dump_tree.py test.m import sys import clang.cindex def visit_node(node, indent=0): print '%s%s : %s' % (' ' * indent, node.kind.name, node.spelling) for c in node.get_children(): visit_node(c, indent=indent+1) index = clang.cindex.Index.create() tree = index.parse(sys.argv[1]) visit_node(tree.cursor)
長くなるので省略しますが、上記コードを実行すると以下のように構文木が出力されます。簡単ですね。
TRANSLATION_UNIT : None TYPEDEF_DECL : __int128_t TYPEDEF_DECL : __uint128_t TYPEDEF_DECL : SEL TYPE_REF : None TYPEDEF_DECL : id TYPE_REF : None TYPEDEF_DECL : Class TYPE_REF : None STRUCT_DECL : __va_list_tag FIELD_DECL : gp_offset FIELD_DECL : fp_offset FIELD_DECL : overflow_arg_area FIELD_DECL : reg_save_area ...
次に変数が宣言されている部分を抜き出してみましょう。先ほどの Python スクリプトを少し書き換えます。
#!/usr/bin/env python # -*- coding: utf-8 -*- import sys import clang.cindex def visit_node(node, indent=0): if node.kind.name == 'VAR_DECL': print '%s (line:%s column:%s)' % (node.spelling, node.location.line, node.location.column) for c in node.get_children(): visit_node(c) index = clang.cindex.Index.create() tree = index.parse(sys.argv[1]) visit_node(tree.cursor)
これを実行すると以下のような結果が得られます。変数宣言部分のみ抜き出せていますね。
numClasses (line:4 column:9) classes (line:7 column:12) i (line:11 column:14) c (line:12 column:16)
このように Clang は単純にコンパイラとしてだけではなく、強力な構文解析機としても利用できます。C++ や Objective-C の構文解析器を flex や bison で組む事を考えると、とてつもなく楽ができますね。現状では doxygen で出力した api リファレンス程度しかドキュメントがありませんが、どんなことが出来るのかざっと見ておくのは悪くないでしょう。
C++0x に未対応等、現時点では GCC を完全に置き換えるまでは行かないものの、速いペースで開発が進む Clang の動向をこれからも見守って行きたいと思います。