libclangのPython bindingsで構文解析する

,

なんだか難しいやつという印象があったが使えば使えたので簡単にメモしておく。 githubに上げて動態保存したいところだが、用途が死んでしまったのでblogに。

導入

Ubuntuなら

$ apt install python-clang-4.0 libclang-4.0-dev

あるいはpip https://pypi.python.org/pypi/clang

$ pip2 instal clang

Python 3.xのは入るが動かなかった。

資料

コードは全てclang/cindex.pyの中。これを読んでいい感じにすればよい。 手元では特に /usr/lib/python2.7/dist-packages/clang/cindex.py に位置していた。

公式のdocumentはこれ: http://releases.llvm.org/4.0.0/tools/clang/docs/index.html

その他の資料としては:

概要

登場するclassについて

  • Index
    • なんか top-level なやつ、libclang-x.y.so を保持するぐらいの役割
  • TranslationUnit
    • 構文解析結果の木をまとめたもの
    • index.parse(...)により作られる
  • Cursor
    • 木の頂点を指す
    • tu.cursorで得られる
    • cursor.get_children()で子を取得し再帰する
    • cursor.kindCursorKind.FUNCTION_DECLだとかそういう情報が取れる

具体例

まずIndexの作成。これはすぐ。

# Python Version: 2.x
from clang.cindex import Index
index = Index.create()

次にTranslationUnitを作る。コードを持ってきて解析させる。コンパイル時オプションを渡したり出力される警告やエラーを受け取ったりもできる。

path = 'foo.cpp'
code = '''\
#include <iostream>
using namespace std;
int main() {
    cout << hello + world << endl;
    return 0;
}
'''
tu = index.parse(path, unsaved_files=[ (path, code) ], args=[ '-std=c++14' ])

そしてCursorで木を舐める。 普通にやるだけ。 例えば以下のようにすると、iostreamをincludeすることで定義される構造体/classが列挙される。

from clang.cindex import CursorKind
namespace = []
def visit(cursor):
    global namespace
    if cursor.kind == CursorKind.TRANSLATION_UNIT:
        for child in cursor.get_children():
            visit(child)
    elif cursor.kind == CursorKind.NAMESPACE:
        namespace += [ cursor.spelling ]
        for child in cursor.get_children():
            visit(child)
        namespace.pop()
    elif cursor.kind in ( CursorKind.STRUCT_DECL, CursorKind.CLASS_DECL, CursorKind.CLASS_TEMPLATE, ):
        if cursor.spelling.strip() and not cursor.spelling.startswith('_'):
            print '::'.join(namespace + [ cursor.spelling ])  #=> std::allocator std::uses_allocator std::char_traits std::__cxx11::basic_string ...
    else:
        pass
visit(tu.cursor)