Skip to content

Parser Backends

autopxd2 ships two parser backends: libclang (the default) and tree-sitter.

$ autopxd --list-backends
Available backends:
  libclang     Full C/C++ support via LLVM [available] (default)
  tree-sitter  Fast C/C++ parsing without system LLVM dependencies [available]

Default: libclang

Choosing a Backend

libclang tree-sitter
Install needs the system libclang shared library pip install "autopxd2[treesitter]", no system LLVM
Follows #include directives ✅ ❌ (parses the given file only)
#define macro extraction ✅ ❌
System include path detection ✅ ❌
C++ classes, templates, namespaces ✅ ✅
--std / --clang-arg ✅ ❌ (rejected with an error)

Use libclang unless you cannot install LLVM. Use tree-sitter for self-contained headers in environments (such as minimal CI or Docker containers) where a system libclang is unavailable.

libclang

Uses LLVM's clang library for parsing. Provides the same parser used by production compilers.

Features:

  • Full C++ support (classes, templates, namespaces)
  • Extracts macros as constants (integers, floats, strings, expressions)
  • Automatic system include path detection
  • Follows and recursively expands #include directives across translation units
  • Handles comments and preprocessor directives directly
  • Better error messages
  • Handles complex and umbrella headers reliably

Limitations:

  • Requires libclang shared library to be available on your system (use python -m headerkit.install_libclang or install LLVM manually)
  • Function-like macros are not extracted

Usage:

autopxd myheader.h

# For C++ headers
autopxd myheader.hpp

tree-sitter

Uses tree-sitter grammars for C and C++. It needs no system LLVM, so it installs and runs anywhere Python does using pre-built PyPI wheels.

Install:

pip install "autopxd2[treesitter]"

Usage:

autopxd --backend tree-sitter myheader.h

# C++ works too
autopxd --backend tree-sitter --cpp myclass.hpp

Limitations:

The tree-sitter backend is substantially more limited than libclang:

  • No #include recursion: It parses only the file given to it. Given #include "inner.h" or #include <header.h>, declarations from the included headers do not appear in the output; only the parsed file's own declarations do. If the header relies on types defined in included files, those type definitions will not be present.
  • No #define macro extraction: Constants and macro functions defined via #define are not extracted into the .pxd.
  • No system include paths: It does not query or search compiler system include directories.
  • Rejects compiler driver options: Options that configure the compiler frontend (--std and --clang-arg) are rejected with an explicit error:
$ autopxd --backend tree-sitter --std c11 myheader.h
Error: --std is only supported by the libclang backend, but the resolved backend is 'tree-sitter'.
Use --backend libclang, or drop the option.

When to use tree-sitter: - Single, self-contained C or C++ header files. - Headless CI pipelines, Docker containers, or minimal environments (e.g. Alpine/musllinux) where installing LLVM/Clang is impractical. - When you do not need macro constants or included header declarations in the generated .pxd.

System Include Paths

The libclang backend automatically detects system include directories by querying the system clang compiler. This means headers like <stddef.h> and <stdint.h> work without requiring manual -I flags.

# This works automatically - system includes are detected
autopxd myheader.h

To disable automatic include detection:

autopxd --no-default-includes myheader.h

You can still add additional include directories:

autopxd -I /my/project/include myheader.h

Macro Extraction

The libclang backend extracts #define macros as Cython constant declarations. The type is automatically detected from the macro value.

Integer Macros

#define SIZE 100
#define MASK 0xFF
#define MODE 0755
#define FLAGS 0b1010
#define BIG_NUM 100ULL

Generates int declarations:

    int SIZE
    int MASK
    int MODE
    int FLAGS
    int BIG_NUM

Supported formats: decimal, hex (0x), octal (0), binary (0b), with optional type suffixes (U, L, UL, LL, ULL).

Floating-Point Macros

#define PI 3.14159
#define EPSILON 1e-10
#define FACTOR 2.5f

Generates double declarations:

    double PI
    double EPSILON
    double FACTOR

String Macros

#define VERSION "1.0.0"
#define APP_NAME "myapp"

Generates const char* declarations:

    const char* VERSION
    const char* APP_NAME

Expression Macros

#define A 10
#define B 20
#define TOTAL (A + B)
#define FLAGS (0x01 | 0x02)
#define NEGATIVE -1

Expression macros that consist of numeric literals, operators, and other macro references are detected and declared with appropriate types:

    int A
    int B
    int TOTAL
    int FLAGS
    int NEGATIVE

Unsupported Macros (silently ignored)

  • Function-like macros: #define MAX(a, b) ((a) > (b) ? (a) : (b))
  • Empty macros: #define EMPTY
  • String concatenation: #define CONCAT "hello" "world"

C++ Template Support

The backends support C++ templates, including primary templates and full specializations.

Primary Templates

C++ class templates with type parameters are translated to Cython's template syntax:

template<typename T>
class Container {
public:
    T value;
    T get();
    void set(T v);
};

Generates:

cdef cppclass Container[T]:
    T value
    T get()
    void set(T v)

Multiple type parameters are supported:

template<typename K, typename V>
class Map {
public:
    V lookup(K key);
    void insert(K key, V value);
};

Generates:

cdef cppclass Map[K, V]:
    V lookup(K key)
    void insert(K key, V value)

Template Specializations

Full template specializations are emitted with mangled Python-safe names and the original C++ name as a string literal:

template<>
class Container<int> {
public:
    int special_value;
    int get_special();
};

Generates:

cdef cppclass Container_int "Container<int>":
    int special_value
    int get_special()

The mangling scheme converts special characters to valid Python identifiers: - < and > are removed, contents become underscore-separated - * becomes _ptr - & becomes _ref - :: becomes _

Examples: - Container<int> -> Container_int - Map<int, double> -> Map_int_double - Foo<int*> -> Foo_int_ptr

Using Docker for libclang

If you don't want to install libclang locally, use the Docker image:

docker run --rm -v $(pwd):/work -w /work ghcr.io/elijahr/python-autopxd2 autopxd myheader.h

See Docker Usage for details.

Programmatic Backend Selection

from headerkit.backends import get_backend, list_backends

# List registered backends
print(list_backends())  # ['libclang', 'tree-sitter']

# Get a specific backend
backend = get_backend("libclang")

# Parse a header
header = backend.parse(code, "myheader.h")