概要
Pythonのlibclangを使用してC++のヘッダーファイルから関数宣言の情報を抽出する方法の例です。
具体例のリポジトリは以下で公開しています。
https://github.com/ishioka0222/libclang_example
この記事には、以下のようなヘッダーファイル(説明のため、わざと意味のないincludeをしています)をパースし、
header.h
#include <iostream>
int my_add(int a, int b); // addの宣言と同じ行にあるコメント
int my_sub(int a, int b); // subの宣言と同じ行にあるコメント
以下のようなJSONファイルを出力するサンプルコードを載せています。
[
{
"name": "my_add",
"return_type": "int",
"params": [
{
"name": "a",
"type": "int"
},
{
"name": "b",
"type": "int"
}
],
"filename": "header.h",
"line_number": 3,
"comment": "// addの宣言と同じ行にあるコメント"
},
{
"name": "my_sub",
"return_type": "int",
"params": [
{
"name": "a",
"type": "int"
},
{
"name": "b",
"type": "int"
}
],
"filename": "header.h",
"line_number": 5,
"comment": "// subの宣言と同じ行にあるコメント"
}
]
関数宣言の情報を抽出する
まずはlibclangをインストールします。
pip install libclang
main.py
という名前で以下のようなファイルを作成します。
main.py
import argparse
import json
import os
from clang.cindex import CursorKind, Index, TokenKind
def main():
# コマンドライン引数をパースする。
parser = argparse.ArgumentParser()
parser.add_argument("filename", help="Input file")
args = parser.parse_args()
# 入力ファイル名を取得する。
input_filename = os.path.basename(args.filename)
# libclang の Index オブジェクトを作成する。
index = Index.create()
# 入力ファイルをパースする。
# -x c++ オプションを指定することで、C++ のソースコードとしてパースする。
translation_unit = index.parse(args.filename, args=["-x", "c++"])
# コメントの行番号からコメント文字列へのディクショナリを作成する。
comments = {}
for token in translation_unit.cursor.get_tokens():
if token.kind == TokenKind.COMMENT:
line_number = token.location.line
comment = token.spelling
comments[line_number] = comment
# 関数宣言の情報からなるリストを作成する。
functions = []
for child in translation_unit.cursor.get_children():
# 関数宣言以外は無視する。
if child.kind != CursorKind.FUNCTION_DECL:
continue
# ファイル名がない場合は無視する。
if child.location.file is None:
continue
# ファイル名を取得する。
filepath = child.location.file.name
filename = os.path.basename(filepath)
# 関数宣言があるファイルが入力ファイルに一致しない場合は無視する。
# これがないと #include で読み込んだヘッダファイルの関数宣言も出力されてしまう。
if filename != input_filename:
continue
# 各種情報を取得する。
function_name = child.spelling
return_type = child.result_type.spelling
line_number = child.location.line
comment = comments.get(line_number, "")
params = []
for param in child.get_arguments():
param_name = param.spelling
param_type = param.type.spelling
params.append({
"name": param_name,
"type": param_type
})
functions.append({
"name": function_name,
"return_type": return_type,
"params": params,
"filename": filename,
"line_number": line_number,
"comment": comment
})
# JSON 形式で出力する。
print(json.dumps(functions, indent=4, ensure_ascii=False))
if __name__ == "__main__":
main()
main.py
の引数としてC++のヘッダーファイルを渡すことで、最初の例のような出力が得られます。
$ python main.py header.h
[
{
"name": "my_add",
"return_type": "int",
"params": [
{
"name": "a",
"type": "int"
},
{
"name": "b",
"type": "int"
}
],
"filename": "header.h",
"line_number": 3,
"comment": "// addの宣言と同じ行にあるコメント"
},
{
"name": "my_sub",
"return_type": "int",
"params": [
{
"name": "a",
"type": "int"
},
{
"name": "b",
"type": "int"
}
],
"filename": "header.h",
"line_number": 5,
"comment": "// subの宣言と同じ行にあるコメント"
}
]
備考
関数宣言程度の情報であれば上の例のようにデフォルトで取得できますが、プリプロセッサなどの詳細な情報はデフォルトでは取得できないようです。
こういった詳細な情報も取得したい場合は、parse
を呼び出すときにPARSE_DETAILED_PROCESSING_RECORD
というオプションを指定する必要があります。
from clang.cindex import CursorKind, Index, TokenKind, TranslationUnit
# (略)
translation_unit = index.parse(
args.filename, options=TranslationUnit.PARSE_DETAILED_PROCESSING_RECORD)