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.
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
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 = {}
cwd
- current working directory.
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
orstd::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 forstd::function
, and normal function pointers - Functions that can error out have
lvgl::expected
as their result type. - Functions that can have
null
values uselvgl::optional
.
- Functions that accept arrays in arguments have those arguments converted from pointers
to
- 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.
- Instead of
- C++20
- Now there is no header file in the output, only a
.cppm
file, which is a C++ module, that can be imported withimport 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).
- Now there is no header file in the output, only a
- C++23
- Instead of
lvgl::expected
,std::expected
is used as a result type.
- Instead of
TODO: document
classes
andnamespaces
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 withlv_obj_
will not get assigned to any classclass.renames
-
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)
- Parsing of
- 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
- Function processing/generation
- 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)
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.
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.
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 :)
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.