Skip to content

A C++ binding generator for LVGL. Written in Rust (oh the irony)

License

Notifications You must be signed in to change notification settings

dynamium/lv_cxx_bindgen

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

33 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

lv_cxx_bindgen

A C++ binding generator for LVGL. Written in Rust (oh the irony)

This is still work-in-progress, it doesn't even generate any sources for now, but it's in the works.

This generator uses the LVGL JSON API map, that will later be analyzed, and used as a basis for all autogenerated C++ code. Also, it supports different C++ target versions, ranging from C++11 to C++23.

Usage

lv_cxx_bindgen requires a config file, by default named lv_cxx_bindgen.toml and looked for in CWD, but can be specified in the -c argument.

TODO: Finish writing this section after CLI API is finished

Configuration

All configuration is done in the lv_cxx_bindgen.toml file. An example file looks like this:

[classes]
exclude = {
    groups = ["anim"],
    functions = ["obj_get_disp", "font_set_kerning"]
},
renames = [
    ["obj", "Object"]
],
inheritances = [
    ["img", "obj"],
    ["menu", "obj"]
]
namespaces = {
    exclude = ["obj"],
    renames = [
        ["anim", "animation"]
    ]
}
functions = {}

Input

  • cwd - current working directory.

Generation

  • target - target C++ version, by default C++23. All the differences between different targets and generated output:

Note: read the list from the start to your desired C++ target to fully understand what code will be generated

  • C++11
    • Functions that accept arrays in arguments have those arguments converted from pointers to std::vector or std::array, depending on configuration
    • Functions that accept function pointers in arguments have those arguments converted to std::function, but as an overload, so there are options for std::function, and normal function pointers
    • Functions that can error out have lvgl::expected as their result type.
    • Functions that can have null values use lvgl::optional.
  • C++14 doesn't have any changes, but it's still a good idea to set the target to your C++ version, so that in future updates it will not break
  • C++17
    • Instead of lvgl::optional, std::optional is used as an option type.
  • C++20
    • Now there is no header file in the output, only a .cppm file, which is a C++ module, that can be imported with import lvgl;. And yes, when you use import, you lose access to the C API, but it still can be included via its header file (that is not recommended tho, see FAQ).
  • C++23
    • Instead of lvgl::expected, std::expected is used as a result type.

TODO: document classes and namespaces when those will be implemented

  • class.exclude - function groups that are excluded from assigning to a class. For example, if you specify "obj" in the list, any function that starts with lv_obj_ will not get assigned to any class
  • class.renames -

Process

In short, the whole process can be simplified to 3 main steps:

  • Parsing
  • Conversion
  • Generation

The first step is the simplest one, it just parses a JSON API map file and extracts all relevant data.

The second step converts that map file to High-level Abstract Syntax Tree (HL-AST), which represents the entire C++ binding in a high-level format. That step is the most interesting one, because this one does the fun stuff - converting the C API into idiomatic C++ code.

And the (not-so) best for last, the third step consists of actually converting allat into actual C++ code. That shit is done manually (without any codegen library), for plain efficiency and also ease of understanding what is actually going on. Also, an additional clang-format run can be named a "three and a half" step, because it's part of codegen, but not part of the actual generator.

So, a full process can be described like this:

  • Extraction
    • Parsing of lvgl.json (a.k.a. LVGL API Map)
    • Function list extraction
    • Typedef/struct/enum/etc extraction and combination (combining anonymous struct with its associated typedef for example)
  • Conversion to C++ a.k.a. HL-AST (High-level Abstract Syntax Tree) generation
    • Function processing/generation
      • Argument conversion for more idiomatic C++
      • Argument filtering (removal of singular void args, like lv_init(void))
    • Struct processing/generation
      • Replace *char with std::string, function pointers with std::function, and other stuff when applicable (configurable via CLI)
    • Enum processing/generation
      • Generate as enum classes
    • Namespace generation
      • Group by function/struct/enum namespace (lv_)
    • Class generation
      • Group by function/struct/enum namespace (lv_)
      • Create constructors from lv__create functions
  • Generation
    • Conversion of HL-AST to LL-AST (Low-level Abstract Syntax Tree)
    • LL-AST to source code conversion
    • clang-format run over generated code (optional)

FAQ

Q: Are exceptions used in any shape or form?

A: No, never. That's the worst idea known to man, especially knowing that LVGL is supposed to run on embedded systems. Exceptions introduce a lot of overhead, and are generally a pain in the ass. As a replacement, std::optional, std::expected, and similar types are used, depending on context.

Q: Can I use the normal C API together with C++?

A: TL;DR: Don't. Please.

Full answer: Technically, yes, no one is stripping that right from you, but that is far from recommended, use the C API only when you REALLY know what you are doing. This is basically black&white, you should only use one API, no mixing of C and C++ APIs, no "grays. You're only making it harder for yourself by using both at the same time.

Technical

Templating

If you have looked at this repository longer that 99% of people that go here, you've probably noticed that res/src/lvgl.hpp has comments that look something like // MARKER: OBJ_CLASS_MEMBERS all over it. And that is just for simple templating built-in to lv_cxx_binding itself. All the templating engine does is it looks for // MARKER: inside the source code, and if found, gets the ID after the statement, and replaces the comment with what should be in that spot. No dark magic or anything, simple search-and-replace :)

Thanks

The implementation of lvgl::optional is basically martinmoene/optional-lite. Thanks for this great piece of software @martinmoene!

Same goes for lvgl::expected, it is from martinmoene/expected-lite. Fabulous piece of software.