Skip to content

Some ideas =) #1

@GavinRay97

Description

@GavinRay97

Hello, thank you for the collaboration invite =D

I have been investigating + experimenting with converting C++ headers to Dlang extern (C++) bindings and also more recently to Nim.

Maybe I can share some thoughts/findings with you 🙂


libclang vs LibTooling

Probably not so important for just one library, but I have been doing reading on which approach is best for translating headers to other languages + bindgen/codegen.

There are some screenshots of experiences from the authors of c2rust and dpp:

🌟 Click for relevant parts of conversation 👇

image

(I am not certain, but I think the newer libclang-cpp shared lib that contains all of the clang-based libraries also contains the LibTooling stuff and would be much smaller.)

# For MinGW we only enable shared library if LLVM_LINK_LLVM_DYLIB=ON.
# Without that option resulting library is too close to 2^16 DLL exports limit.
if(UNIX OR (MINGW AND LLVM_LINK_LLVM_DYLIB))
  add_clang_subdirectory(clang-shlib)
endif()

It's possible to use cppyy for LibTooling access, if you build LLVM on Linux/MinGW with BUILD_SHARED_LIBS set to ON (I think it's also possible with LLVM_BUILD_DYLIB)

Doing this gives you:

image


API/codegen approach

I found two interesting approaches others have taken for the actual codegen that I have been experimenting with. Maybe you have some ideas?

The first

Create decorators in Python which hold rules, the argument is a clang::QualType or clang::Type. Nodes are evaluated against rules, the one matching is chosen for codegen.

Each class is a "frontend" driver/generator for a language to create from C++ headers. It's very clever! Currently he has:

  • C
  • Rust
  • Lua bindings (C)

Here is the repo where I found this. In his approach, he has used PyBind for binding the Clang/LibTooling C++ API to Python, which could also be used instead of cppyy I suppose (not sure why you'd do that)

class NimCodegen():
  # t = clang::Type or clang::QualType
  @rule(lambda t: t.is_pointer() or t.is_reference() and \
                  t.pointee().is_record_indirection())
  def input(cls, t, args):
      return f"{{interm}} = &{c_util.struct_cast(t, '{inp}')};"

  @rule(lambda t: t.is_pointer() or t.is_reference())
  def input(cls, t, args):
      raise ValueError("unsupported input pointer/reference type {}".format(t))

The second

Use template languages like Jinja/Moustache to create declarative template files for codegen. This allows easy visualization of what the output will be, and for people to tweak it or pass a custom template file easily.

I saw this approach here:

Generate arbitrary files from C/C++ header files using CppHeaderParser to read the input files and Jinja2 templates to generate the outputs.

This grew out of a desire to generate pybind11 wrapping code from C++ header files. pybind11gen was created, but then I wanted to generate more things...

{#  Silly example that shows how to convert a header file to YAML #}
---
{% for header in headers %}
{% for fn in header.functions %}
{{ fn.name }}:
  returns: {{ fn.returns }}
  params:
  {% for param in fn.parameters %}
  - { name: {{ param.name }}, type: "{{ param.type }}" }
  {% endfor %}

{% endfor %}
{% endfor %}

I imagine that if clang nodes were passed into here, and a template language was used which allowed to evaluate expressions like t.is_pointer() then it could actually be pretty okay.

The approach with this library for evaluating expressions is by using a custom "hooks" file for defining pre-processing logic.

It's very basic though and does not use clang, but a custom Python preprocessor.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions