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 の動向をこれからも見守って行きたいと思います。









