C++ Advent Calendar 2013 後編 - 構文解析ツールとして Clang を使用してみる

この記事は C++ Advent Calendar 2013 の 19日目の後編の記事になります。


さて、Clang なのですが C++er では知らない人はいないと思います。
Clang とは LLVM をバックエンドとした C++コンパイラです。
最近では Clang が C++14 の機能を全て実装して話題になったかと思います。
今回はそんな Clang をコンパイラとしてではなくて構文解析ツールとしての一面を紹介してみたいと思います。

[注意]

今回使用した Clang のバージョンは 3.3 になります。
予定ではあと 4日ぐらいで 3.4 がリリースされるんですが…タイミングが悪いですね…。

[コード補完を行う]

Clang ではコンパイラのコマンドから直接コード補完の結果を出力する事ができます。
例えば、次のようなコードで補完候補を出力してみます。

[test.cpp]

struct X{
    void
    func(){}

    int
    func(int n){
        return n;
    }

    int value1;
    float value2;
};


int
main(){
    X x;
    x.            // ここでのコード補完結果を出力する
    return 0;
}

[出力]

$ clang++ -cc -fsyntax-only -code-completion-at=test.cpp:18:7 test.cpp
COMPLETION: func : [#void#]func()
COMPLETION: func : [#int#]func(<#int n#>)
COMPLETION: operator= : [#X &#]operator=(<#const X &#>)
COMPLETION: value1 : [#int#]value1
COMPLETION: value2 : [#float#]value2
COMPLETION: X : X::
COMPLETION: ~X : [#void#]~X()


これだけです。
-code-completion-at 引数にファイル名と補完を行いたい行と列の位置を渡せばその位置の補完結果が出力されます。
定義されていないデフォルトコンストラクタなども出力されていますね。
コンパイラのコマンドから実行しているだけなのでインクルードディレクトリを追加したければ -I をつければよいし、C++1y として実行したければ -std=c++1y を追加するだけでよいです。
例えば Vim でコード補完を行うプラグインを作成したい場合はこれを利用すれば割りと簡単につくる事ができます。
わたしの作成した marching.vim もこれを利用してコード補完を行っています。

[AST を出力する]

さて、コード補完の他に AST も出力する事が可能です。
先ほどの test.cpp の AST を出力してみます。

$ clang++ -cc1 -fsyntax-only -ast-dump test.cpp
TranslationUnitDecl 0x275e020 <<invalid sloc>>
|-TypedefDecl 0x275e320 <<invalid sloc>> __builtin_va_list 'char *'
|-CXXRecordDecl 0x275e350 <test.cpp:1:1, line:12:1> struct X
| |-CXXRecordDecl 0x275e420 <line:1:1, col:8> struct X
| |-CXXMethodDecl 0x275e4b0 <line:2:5, line:3:12> func 'void (void)'
| | `-CompoundStmt 0x275e708 <col:11, col:12>
| |-CXXMethodDecl 0x275e5d0 <line:5:5, line:8:5> func 'int (int)'
| | |-ParmVarDecl 0x275e540 <line:6:10, col:14> n 'int'
| | `-CompoundStmt 0x275e750 <col:16, line:8:5>
| |   `-ReturnStmt 0x275e740 <line:7:9, col:16>
| |     `-ImplicitCastExpr 0x275e730 <col:16> 'int' <LValueToRValue>
| |       `-DeclRefExpr 0x275e718 <col:16> 'int' lvalue ParmVar 0x275e540 'n' 'int'
| |-FieldDecl 0x275e660 <line:10:5, col:9> value1 'int'
| |-FieldDecl 0x275e6b0 <line:11:5, col:11> value2 'float'
| |-CXXConstructorDecl 0x275e8a0 <line:1:8> X 'void (void)' inline
| | `-CompoundStmt 0x275ea60 <col:8>
| `-CXXConstructorDecl 0x275e960 <col:8> X 'void (const struct X &)' inline
|   `-ParmVarDecl 0x275ea20 <col:8> 'const struct X &'
`-FunctionDecl 0x275e7b0 <line:15:1, line:20:1> main 'int (void)'
  `-CompoundStmt 0x275ebc0 <line:16:7, line:20:1>
    |-DeclStmt 0x275ea98 <line:17:5, col:8>
    | `-VarDecl 0x275e850 <col:5, col:7> x 'struct X'
    |   `-CXXConstructExpr 0x275ea70 <col:7> 'struct X' 'void (void)'
    |-BinaryOperator 0x275eaf8 <line:18:5, col:16> 'int' lvalue '='
    | |-MemberExpr 0x275eac0 <col:5, col:7> 'int' lvalue .value1 0x275e660
    | | `-DeclRefExpr 0x275eaa8 <col:5> 'struct X' lvalue Var 0x275e850 'x' 'struct X'
    | `-IntegerLiteral 0x275eae0 <col:16> 'int' 10
    `-ReturnStmt 0x275ebb0 <line:19:5, col:12>
      `-IntegerLiteral 0x275eb98 <col:12> 'int' 0


以前は XML 形式で出力する事もできたらしいですが、現在では廃止されてしまったらしいです。
残念。

[Python バインディングから Clang を利用する]

今まではコンパイラのコマンドから出力してみましたがもう少し具体的な事を行いたいですね。
Clang の構文解析器は C++ から直接利用する事も可能です。
しかし、例えばエディタのプラグインとして Clang の構文解析器を利用したい場合、C++ のコードを配布して各自でビルドしてもらうのはなかなかに敷居が高いです。
もう少しカジュアルに扱いたいですね。
そこで Clang が用意している Python バインディングを使用して Clang のランタイムライブラリ(libclang)を扱ってみたいと思います。
こちらは Python からランタイムライブラリを使用するので直接 C++ を扱うよりは敷居が低いです。
Python バインディングのコードは Clang のソースコードに含まれています。
Python から使用したい場合は予め PYTHONPATH に設定しておくとよいでしょう。

PYTHONPATH={Clang のパス}/bindings/python

[ソース]

# -*- coding: utf-8 -*
import clang.cindex

conf = clang.cindex.Config()

# ランタイムライブラリ名を出力
# このファイルが読み込まれる
print conf.get_filename()

[出力]

libclang.dll


ちなみに Python 以外にも Rubyバインディングも gems にあります。

[Python からコード補完を行う]

実際に使ってみます。
コード補完は次のように記述する事ができます。

[ソース]

# -*- coding: utf-8 -*
import clang.cindex

index = clang.cindex.Index.create()

# 使用するソースファイルはコマンドから使用したものと同じ
# コンパイルオプションはリストで1つずつ渡す
tu = index.parse("test.cpp", ["-std=c++1y"])
print "Translation unite:", tu.spelling

# コード補完を行う
completion = tu.codeComplete("test.cpp", 18, 7)

# 結果を出力
for result in completion.results:
	info = "> "
	for chunk in result.string:
		# 戻り値型であれば出力してみたり
		if chunk.isKindResultType():
			print "result_type :", chunk.spelling
			info += chunk.spelling + " "
		else:
			info += chunk.spelling
	print info

[出力]

Translation unite: test.cpp
result_type : X &
> X & operator=(const X &)
result_type : X &
> X & operator=(X &&)
result_type : void
> void ~X()
result_type : int
> int value1
result_type : float
> float value2
> X::
result_type : void
> void func()
result_type : int
> int func(int n)


こんな感じで出力することができます。
コマンドから出力する場合は結果をパースする必要がありますが、python から直接出力すれば好きなフォーマットで出力を行うことができそうですね。

[インクルードされているヘッダーファイルの一覧を出力する]

インクルードされているヘッダーファイルの一覧を出力する事もできます。

[ソース]

# -*- coding: utf-8 -*
import clang.cindex

index = clang.cindex.Index.create()

source = """\
#include <string>

int
main(){
	
}
"""

index = clang.cindex.Index.create()


# ファイルではなくてソースコードを直接渡す
tree = index.parse("INPUT.cpp", unsaved_files = [ ("INPUT.cpp", source) ])
for value in tree.get_includes():
	print value.source.name

[出力]

INPUT.cpp
c:/MinGW/lib/gcc/mingw32/4.6.2/include/c++\string
c:/MinGW/lib/gcc/mingw32/4.6.2/include/c++/mingw32\bits/c++config.h
c:/MinGW/lib/gcc/mingw32/4.6.2/include/c++/mingw32\bits/c++config.h
c:/MinGW/lib/gcc/mingw32/4.6.2/include/c++\string
c:/MinGW/lib/gcc/mingw32/4.6.2/include/c++\string
c:/MinGW/lib/gcc/mingw32/4.6.2/include/c++\bits/char_traits.h
c:/MinGW/lib/gcc/mingw32/4.6.2/include/c++\bits/stl_algobase.h
c:/MinGW/lib/gcc/mingw32/4.6.2/include/c++\bits/functexcept.h
c:/MinGW/lib/gcc/mingw32/4.6.2/include/c++\bits/stl_algobase.h
c:/MinGW/lib/gcc/mingw32/4.6.2/include/c++\bits/stl_algobase.h
c:/MinGW/lib/gcc/mingw32/4.6.2/include/c++\bits/stl_algobase.h
c:/MinGW/lib/gcc/mingw32/4.6.2/include/c++\bits/stl_algobase.h
c:/MinGW/lib/gcc/mingw32/4.6.2/include/c++\bits/stl_pair.h
c:/MinGW/lib/gcc/mingw32/4.6.2/include/c++\bits/move.h
c:/MinGW/lib/gcc/mingw32/4.6.2/include/c++\bits/stl_algobase.h
c:/MinGW/lib/gcc/mingw32/4.6.2/include/c++\bits/stl_algobase.h
c:/MinGW/lib/gcc/mingw32/4.6.2/include/c++\bits/stl_algobase.h
c:/MinGW/lib/gcc/mingw32/4.6.2/include/c++\bits/stl_algobase.h
c:/MinGW/lib/gcc/mingw32/4.6.2/include/c++\bits/char_traits.h
c:/MinGW/lib/gcc/mingw32/4.6.2/include/c++\bits/postypes.h
c:/MinGW/lib/gcc/mingw32/4.6.2/include/c++\cwchar
c:/mingw/include\wchar.h
c:/mingw/include\wchar.h
c:/mingw/include\wchar.h
c:/mingw/include\wchar.h
c:/MinGW/lib/gcc/mingw32/4.6.2/include/c++\bits/char_traits.h
c:/MinGW/lib/gcc/mingw32/4.6.2/include/c++\string
c:/MinGW/lib/gcc/mingw32/4.6.2/include/c++\bits/allocator.h
c:/MinGW/lib/gcc/mingw32/4.6.2/include/c++/mingw32\bits/c++allocator.h
c:/MinGW/lib/gcc/mingw32/4.6.2/include/c++\ext/new_allocator.h
c:/MinGW/lib/gcc/mingw32/4.6.2/include/c++\new
c:/MinGW/lib/gcc/mingw32/4.6.2/include/c++\string
c:/MinGW/lib/gcc/mingw32/4.6.2/include/c++\bits/localefwd.h
c:/MinGW/lib/gcc/mingw32/4.6.2/include/c++/mingw32\bits/c++locale.h
c:/MinGW/lib/gcc/mingw32/4.6.2/include/c++\clocale
c:/MinGW/lib/gcc/mingw32/4.6.2/include/c++\bits/localefwd.h
c:/MinGW/lib/gcc/mingw32/4.6.2/include/c++\bits/localefwd.h
c:/MinGW/lib/gcc/mingw32/4.6.2/include/c++\cctype
c:/MinGW/lib/gcc/mingw32/4.6.2/include/c++\string
c:/MinGW/lib/gcc/mingw32/4.6.2/include/c++\bits/ostream_insert.h
c:/MinGW/lib/gcc/mingw32/4.6.2/include/c++\string
c:/MinGW/lib/gcc/mingw32/4.6.2/include/c++\bits/stl_function.h
c:/MinGW/lib/gcc/mingw32/4.6.2/include/c++\string
c:/MinGW/lib/gcc/mingw32/4.6.2/include/c++\string
c:/MinGW/lib/gcc/mingw32/4.6.2/include/c++\bits/basic_string.h
c:/MinGW/lib/gcc/mingw32/4.6.2/include/c++\ext/atomicity.h
c:/MinGW/lib/gcc/mingw32/4.6.2/include/c++/mingw32\bits/gthr.h
c:/MinGW/lib/gcc/mingw32/4.6.2/include/c++/mingw32\bits/gthr-default.h
c:/MinGW/lib/gcc/mingw32/4.6.2/include/c++\ext/atomicity.h
c:/MinGW/lib/gcc/mingw32/4.6.2/include/c++\bits/basic_string.h
c:/MinGW/lib/gcc/mingw32/4.6.2/include/c++\string


これはコマンドから行うことはできませんね。
と、いうか gcc 4.6.2 とかいつの時代


と、いう感じで簡単にですが Clang の構文解析周りについて紹介してみました。
最近はコンパイラ以外でも Clang を利用したツール(clang-check や clang-format)なども開発されています。
こういうツール群はエディタとも親和性が高いのでどんどん活用していきたいですね。
ぶっちゃけわたしもまだあまり把握できていないのでこれからちょっとずつ使って行きたいと思います。


ちなみに今、この Python バインディングを利用した Vim プラグインを開発中です。


まだ開発中なので今後仕様が変わる可能性がありますが、気になる方は使ってみるとよいと思います。
ここで紹介した以外の機能も実装してあります。