C/C++ のソースをパースして TranslaionUnit を得る

path が parse のエントリポイントとなる。

from typing import NamedTuple, List, Optional
from clang import cindex


class Unsaved(NamedTuple):
    name: str
    content: str


def get_tu(entrypoint: str,
           *,
           include_dirs: List[str] = None,
           flags: List[str] = None,
           unsaved: Optional[List[Unsaved]] = None) -> cindex.TranslationUnit:
    arguments = [
        "-x",
        "c++",
        "-target",
        "x86_64-windows-msvc",
        "-fms-compatibility-version=18",
        "-fdeclspec",
        "-fms-compatibility",
        "-std=c++17",
    ]
    if include_dirs:
        arguments.extend(f'-I{i}' for i in include_dirs)
    if flags:
        arguments.extend(flags)

    # path of libclang.dll
    cindex.Config.library_path = 'C:\\Program Files\\LLVM\\bin'

    index = cindex.Index.create()

    tu = index.parse(entrypoint, arguments, unsaved,
                     cindex.TranslationUnit.PARSE_DETAILED_PROCESSING_RECORD |
                     cindex.TranslationUnit.PARSE_SKIP_FUNCTION_BODIES)

    return tu

unsaved file はメモリ上のファイルに仮の名前を与えてパースする仕組み。 エディタで未保存のファイルを対象にする他に、 複数のヘッダーを一度にパースしたい場合に まとめて include する一時ファイルをメモリ上で済ます用途がある。

// unsaved content
#include "a.h"
#include "b.h"
#include "c.h"

compiler 引数

arguments の与え方で vc の cl.exe のようにふるまわせることができる。

  • -D による define

  • -I による include パス

  • c++17 などの対応レベル

などをよく使う。

flags

  • cindex.TranslationUnit.PARSE_SKIP_FUNCTION_BODIES

など。関数の中身をスキップする。 言語バインディングの生成をする場合には関数のシグニチャのみが必要。

マクロの制御などもあり、指定しないと出現しない cursor がある。