Clangのpython bindingsを使う

研究全然進まないのに時間だけは過ぎていく. そんでずっと環境構築している気がする….

環境構築

clang ASTの情報が欲しかったので,python bindingsを使おうとしたらまたつまりました. 教えてもらったのをメモ!

homebrewでllvmインストール.

% brew install llvm --with-clang --with-python

この場合,brewから入れたpythonを使うので,pythonもhomebrewで入れる.

% brew install python

python bindingsを使うためには,LD_LIBRARY_PATHを設定する必要がある.

% export LD_LIBRARY_PATH=$(llvm-config --libdir):$LD_LIBRARY_PATH

(ちなみに,ソースコードからコンパイルした場合にはPYTHONPATHも設定しないといけない)

これで,無事使えるようになりました!

動かしたサンプルはこれ.

[main.py]

import sys

import clang.cindex
from clang.cindex import Index
from clang.cindex import Config

def print_node_tree(node):
    print "%s : %s" % (node.kind.name, node.displayname)
    for child in node.get_children():
        print_node_tree(child)

index = Index.create()
tu = index.parse("test.cpp")
print_node_tree(tu.cursor)

libclang の Python binding を使用する 〜導入編〜 - C++でゲームプログラミングこのサイトのを参考にしました.

Clang's AST

無事結果は出力されましたが,なにをやってるか全然わからない…

Clang ASTのこと知らないから当然わかるはずないよな,と思ってドキュメントを読みました.

Introduction to the Clang AST — Clang 3.5 documentation

clangのASTをダンプするには,-ast-dumpのオプションを使って

% clang -Xclang -ast-dump -fsyntax-only sample.c

とすればよい.

解析したコードと出力結果は以下のようになりました.

[sample.c]

int f()
{
        int a = 1;
        int b = 2;
        int c = 0;

        c = a + b;

        return c;
}

[出力結果]

TranslationUnitDecl 0x7fb55b8012c0 <<invalid sloc>> <invalid sloc>
|-TypedefDecl 0x7fb55b8017c0 <<invalid sloc>> <invalid sloc> implicit __int128_t '__int128'
|-TypedefDecl 0x7fb55b801820 <<invalid sloc>> <invalid sloc> implicit __uint128_t 'unsigned __int128'
|-TypedefDecl 0x7fb55b801b70 <<invalid sloc>> <invalid sloc> implicit __builtin_va_list '__va_list_tag [1]'
`-FunctionDecl 0x7fb55b801c10 <a.c:1:1, line:10:1> line:1:5 f 'int ()'
  `-CompoundStmt 0x7fb55b801fe8 <line:2:1, line:10:1>
    |-DeclStmt 0x7fb55b801d38 <line:3:2, col:11>
    | `-VarDecl 0x7fb55b801cc0 <col:2, col:10> col:6 used a 'int' cinit
    |   `-IntegerLiteral 0x7fb55b801d18 <col:10> 'int' 1
    |-DeclStmt 0x7fb55b801dd8 <line:4:2, col:11>
    | `-VarDecl 0x7fb55b801d60 <col:2, col:10> col:6 used b 'int' cinit
    |   `-IntegerLiteral 0x7fb55b801db8 <col:10> 'int' 2
    |-DeclStmt 0x7fb55b801e78 <line:5:2, col:11>
    | `-VarDecl 0x7fb55b801e00 <col:2, col:10> col:6 used c 'int' cinit
    |   `-IntegerLiteral 0x7fb55b801e58 <col:10> 'int' 0
    |-BinaryOperator 0x7fb55b801f60 <line:7:2, col:10> 'int' '='
    | |-DeclRefExpr 0x7fb55b801e90 <col:2> 'int' lvalue Var 0x7fb55b801e00 'c' 'int'
    | `-BinaryOperator 0x7fb55b801f38 <col:6, col:10> 'int' '+'
    |   |-ImplicitCastExpr 0x7fb55b801f08 <col:6> 'int' <LValueToRValue>
    |   | `-DeclRefExpr 0x7fb55b801eb8 <col:6> 'int' lvalue Var 0x7fb55b801cc0 'a' 'int'
    |   `-ImplicitCastExpr 0x7fb55b801f20 <col:10> 'int' <LValueToRValue>
    |     `-DeclRefExpr 0x7fb55b801ee0 <col:10> 'int' lvalue Var 0x7fb55b801d60 'b' 'int'
    `-ReturnStmt 0x7fb55b801fc8 <line:9:2, col:9>
      `-ImplicitCastExpr 0x7fb55b801fb0 <col:9> 'int' <LValueToRValue>
        `-DeclRefExpr 0x7fb55b801f88 <col:9> 'int' lvalue Var 0x7fb55b801e00 'c' 'int'
  • AST Context

translation unitの最上位の宣言は常にtranslation unit declatationで,ユーザが書いたコードの最初の宣言はfunction declaration.
そのbodyはcompound statementで,さらにその子ノードはdeclaration statement.
最後に,return statementがある.

※translation unitは,コンパイルにおける基本単位

  • AST Nodes

重要なASTのノードはType(型),Decl(宣言),DeclContext(宣言コンテキスト),Stmt(文).
ASTを探索するにはTranslationUnitDeclからスタートして,そこから再起的に到達しうるノードをたどる.
Clang's ASTの基本的な2つのノードはstatement(Stmt)とdeclatations(Decl).
Clang's ASTではexpressions(Expr)はstatementであることに注意.

なんとなくClang's ASTのことがわかったような気がする…!

python bindings

で,python bindingsってどうやって使うんだ!と思って調べると,python bindingsのソースコードを読めばいいですよって書いてありました.

これがある場所は,

/usr/local/Cellar/llvm/3.5.0_2/lib/python2.7/site-packages/clang/cindex.py

でも3500行ぐらいあるので,どっから読めばいいのかわからないし,ネットで親切な記事を探しました.そしたらドキュメントっぽいものが見つかりました.

Parsing C++ in Python with Clang

  • Creating the index and parsing the source
index = clang.cindex.Index.create()
tu = index.parse(<ファイル名>)

1行目のindexはtranslation unitを表す.
2行目のparceメソッドはファイルから1つのtranslation unitをパースする.

python bindingsはClang C APIのCXTranslationUnitオブジェクトをTranslationUnitとしている.

cursorはlibclangのキーになる概念で,これはパースされたtranslation unitのASTの中のノードを表す.
TranslationUnitl.cursorはtranslation unitの最上位のcursolを返す.これは,ASTの探索のスタート地点を提供する.

  • Working with cursors

python bindingsはlibclangのcursorをCursorオブジェクトとしている. Cursorはたくさんのアトリビュートを持っているが,その中で興味深いものは

- kind:cursorがさしているAStのノードの種類の一覧
- spelling:ノードのソースコードの名前
- location:ノートがパースされたソースコードの場所
- get_children:子ノード

get_childrenを使って次のようにして再起的に,与えられたノードのすべての子ノードを探索することができる.

for c in node.get_children():
    find_typerefs(c, typename)

         

とりあえず今まで調べたことのメモでした.

なんだか少しだけわかってきた気がする!!