odin-blend2d

Odin bindings to Blend2D
Log | Files | Refs | README | LICENSE

commit 767a5ffa8778853bcadbc7617ea546ecc11264df
parent 52269afb1f5498cc58ac584a7dfd5819ad38f3b3
Author: Jack Mordaunt <jackmordaunt.dev@gmail.com>
Date:   Thu, 30 Oct 2025 22:13:45 -0300

Squashed 'odin-c-bindgen/' changes from 58199219..e3a75121

e3a75121 Reversed the order of bit_setify so that the key is the bit_set name and the value is the new name for a newly created enum type
bacf9b14 Update README.md
c343d2d2 remove_type_suffix config
5731ec8b Update README.md
66e7cffd Check for libclang version 16 or higher by asking the lib for a version string. I have checked that all the examples etc work on 16 or higher.

git-subtree-dir: odin-c-bindgen
git-subtree-split: e3a751218341e3e1acda40103539de092332546f

Diffstat:
MREADME.md | 15++++++++++-----
Mexamples/raylib/bindgen.sjson | 10+++++-----
Mexamples/raylib/raylib/raylib.odin | 16++++++++--------
Mexamples/ufbx/bindgen.sjson | 8+-------
Mexamples/ufbx/ufbx/ufbx.odin | 18+++++++++---------
Msrc/config.odin | 11+++++++----
Msrc/examples/tester/tester.h | 6++++++
Msrc/translate_collect.odin | 16++++++++++++++++
Msrc/translate_process.odin | 113++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------
9 files changed, 131 insertions(+), 82 deletions(-)

diff --git a/README.md b/README.md @@ -14,11 +14,11 @@ Features: ## Requirements - Odin -- libclang +- libclang version 16 or higher - On Windows: Download libclang 20.1.8 from here: https://github.com/llvm/llvm-project/releases/download/llvmorg-20.1.8/clang+llvm-20.1.8-x86_64-pc-windows-msvc.tar.xz -- Copy the following from that archive: - `lib/libclang.lib` into the generator's 'libclang' folder - `bin/libclang.dll` into the root of the generator (next to where the bindgen executable will end up). - - On Linux/mac, please install libclang. For example using `apt install libclang-dev` on Ubuntu/Debian/Mint. It doesn't have to be version 20, I've tried it with as low as version 18. + - On Linux/mac, please install libclang. For example using `apt install libclang-dev` on Ubuntu/Debian/Mint. Anything from clang version 16 and new should work. > [!NOTE] > libclang is used for analysing the C headers and deciding what Odin code to output. @@ -58,6 +58,9 @@ remove_macro_prefix = "" // Remove this prefix from function names (and add it as link_prefix) to the foreign group remove_function_prefix = "" +// Remove this suffix from type names (such as '_t' etc) +remove_type_suffix = "" + // Set to true translate type names to Ada_Case force_ada_case_types = false @@ -77,10 +80,12 @@ rename = { } // Turns an enum into a bit_set. Converts the values of the enum into appropriate values for a -// bit_set. Creates a bit_set type that uses the enum. Properly removes enum values with value 0. -// Translates the enum values using a log2 procedure. +// bit_set (translates the enum values using a log2 procedure). +// +// Note that the enum will be turned into a bit_set type. There will be a new type created that +// contains the actual enum, which the bit_set then references. bit_setify = { - // "Pre_Existing_Enum_Type" = "New_Bit_Set_Type" + // "Enum_To_Turn_Into_Bitset" = "New_Enum_Type_Name" } // Completely override the definition of a type. diff --git a/examples/raylib/bindgen.sjson b/examples/raylib/bindgen.sjson @@ -9,15 +9,15 @@ imports_file = "imports.odin" package_name = "raylib" // "Old_Name" = "New_Name" -// We rename ConfigFlags to ConfigFlag so we can introduce a bit_set with the name ConfigFlags. rename = { - "ConfigFlags" = "ConfigFlag" + "Gesture" = "Gestures" + "Gesture_Enum" = "Gesture" } -// "Pre_Existing_Enum_Type" = "New_Bit_Set_Type" +// "Enum_To_Turn_Into_Bitset" = "New_Enum_Type_Name" bit_setify = { - "Gesture" = "Gestures" - "ConfigFlags" = "ConfigFlags" + "Gesture" = "Gesture_Enum" + "ConfigFlags" = "ConfigFlag" } type_overrides = { diff --git a/examples/raylib/raylib/raylib.odin b/examples/raylib/raylib/raylib.odin @@ -422,12 +422,6 @@ AutomationEventList :: struct { events: [^]AutomationEvent, // Events entries } -//---------------------------------------------------------------------------------- -// Enumerators Definition -//---------------------------------------------------------------------------------- -// System/Window config flags -// NOTE: Every bit registers one state (use it with bit masks) -// By default all flags are set to 0 ConfigFlag :: enum i32 { VSYNC_HINT = 6, // Set to try enabling V-Sync on GPU FULLSCREEN_MODE = 1, // Set to run program in fullscreen @@ -447,6 +441,12 @@ ConfigFlag :: enum i32 { INTERLACED_HINT = 16, // Set to try enabling interlaced video format (for V3D) } +//---------------------------------------------------------------------------------- +// Enumerators Definition +//---------------------------------------------------------------------------------- +// System/Window config flags +// NOTE: Every bit registers one state (use it with bit masks) +// By default all flags are set to 0 ConfigFlags :: bit_set[ConfigFlag; i32] // Trace log level @@ -795,8 +795,6 @@ BlendMode :: enum i32 { CUSTOM_SEPARATE = 7, // Blend textures using custom rgb/alpha separate src/dst factors (use rlSetBlendFactorsSeparate()) } -// Gesture -// NOTE: Provided as bit-wise flags to enable only desired gestures Gesture :: enum i32 { TAP = 0, // Tap gesture DOUBLETAP = 1, // Double tap gesture @@ -810,6 +808,8 @@ Gesture :: enum i32 { PINCH_OUT = 9, // Pinch out gesture } +// Gesture +// NOTE: Provided as bit-wise flags to enable only desired gestures Gestures :: bit_set[Gesture; i32] // Camera system modes diff --git a/examples/ufbx/bindgen.sjson b/examples/ufbx/bindgen.sjson @@ -43,18 +43,12 @@ procedure_type_overrides = { "ufbx_generate_indices.streams" = "[^]" } -rename = { +bit_setify = { "ufbx_prop_flags" = "Prop_Flag" "ufbx_transform_flags" = "Transform_Flag" "ufbx_baked_key_flags" = "Baked_Key_Flag" } -bit_setify = { - "ufbx_prop_flags" = "Prop_Flags" - "ufbx_transform_flags" = "Transform_Flags" - "ufbx_baked_key_flags" = "Baked_Key_Flags" -} - remove_enum_members = [ "*FORCE_32BIT" ] diff --git a/examples/ufbx/ufbx/ufbx.odin b/examples/ufbx/ufbx/ufbx.odin @@ -205,7 +205,6 @@ Prop_Type :: enum i32 { PROP_TYPE_COUNT :: 16 -// Property flags: Advanced information about properties, not usually needed. Prop_Flag :: enum i32 { // Supports animation. // NOTE: ufbx ignores this and allows animations on non-animatable properties. @@ -265,6 +264,7 @@ Prop_Flag :: enum i32 { VALUE_BLOB = 26, } +// Property flags: Advanced information about properties, not usually needed. Prop_Flags :: bit_set[Prop_Flag; i32] // Single property with name/type/value. @@ -272,7 +272,7 @@ Prop :: struct { name: String, _internal_key: u32, type: Prop_Type, - flags: Prop_Flag, + flags: Prop_Flags, value_str: String, value_blob: Blob, value_int: i64, @@ -4390,9 +4390,9 @@ Baked_Key_Flag :: enum i32 { Baked_Key_Flags :: bit_set[Baked_Key_Flag; i32] Baked_Vec3 :: struct { - time: f64, // < Time of the keyframe, in seconds - value: Vec3, // < Value at `time`, can be linearly interpolated - flags: Baked_Key_Flag, // < Additional information about the keyframe + time: f64, // < Time of the keyframe, in seconds + value: Vec3, // < Value at `time`, can be linearly interpolated + flags: Baked_Key_Flags, // < Additional information about the keyframe } Baked_Vec3_List :: struct { @@ -4401,9 +4401,9 @@ Baked_Vec3_List :: struct { } Baked_Quat :: struct { - time: f64, // < Time of the keyframe, in seconds - value: Quat, // < Value at `time`, can be (spherically) linearly interpolated - flags: Baked_Key_Flag, // < Additional information about the keyframe + time: f64, // < Time of the keyframe, in seconds + value: Quat, // < Value at `time`, can be (spherically) linearly interpolated + flags: Baked_Key_Flags, // < Additional information about the keyframe } Baked_Quat_List :: struct { @@ -5121,7 +5121,6 @@ Panic :: struct { message: [128]i8, } -// Flags to control `ufbx_evaluate_transform_flags()`. Transform_Flag :: enum i32 { // Ignore parent scale helper. IGNORE_SCALE_HELPER = 0, @@ -5148,6 +5147,7 @@ Transform_Flag :: enum i32 { NO_EXTRAPOLATION = 7, } +// Flags to control `ufbx_evaluate_transform_flags()`. Transform_Flags :: bit_set[Transform_Flag; i32] // bindgen-enable diff --git a/src/config.odin b/src/config.odin @@ -19,6 +19,9 @@ Config :: struct { // Remove this prefix from function names (and add it as link_prefix) to the foreign group remove_function_prefix: string, + + // Remove this suffix from type names (structs, enum, etc) + remove_type_suffix: string, // Set to true translate type names to Ada_Case force_ada_case_types: bool, @@ -38,8 +41,10 @@ Config :: struct { rename: map[string]string, // Turns an enum into a bit_set. Converts the values of the enum into appropriate values for a - // bit_set. Creates a bit_set type that uses the enum. Properly removes enum values with value 0. - // Translates the enum values using a log2 procedure. + // bit_set (translates the enum values using a log2 procedure). + // + // Note that the enum will be turned into a bit_set type. There will be a new type created that + // contains the actual enum, which the bit_set then references. bit_setify: map[string]string, // Completely override the definition of a type. @@ -80,6 +85,4 @@ Config :: struct { // this path in search for included headers. clang_include_paths: []string, clang_defines: map[string]string, - - } diff --git a/src/examples/tester/tester.h b/src/examples/tester/tester.h @@ -44,6 +44,12 @@ struct Test2 { int y; }; +typedef enum { + Thing, + Something, + Else, +} A_Typedeffed_Enum; + enum Wa { One, Two, diff --git a/src/translate_collect.odin b/src/translate_collect.odin @@ -6,6 +6,7 @@ import clang "../libclang" import "core:slice" import "core:log" import "core:strings" +import "core:strconv" import "core:unicode" import "core:unicode/utf8" import "core:fmt" @@ -22,6 +23,21 @@ Translate_Collect_Result :: struct { // that is deferred to `translate_process`. @(private="package", require_results) translate_collect :: proc(filename: string, config: Config, types: Type_List, decls: Decl_List) -> (Translate_Collect_Result, bool) { + clang_version := string_from_clang_string(clang.getClangVersion()) + clang_version = strings.trim_prefix(clang_version, "clang version ") + clang_version_major_end := strings.index_rune(clang_version, '.') + + if clang_version_major_end == -1 { + log.panic("Failed checking libclang version") + } + + clang_major_version_str := clang_version[:clang_version_major_end] + + if clang_major_version, clang_major_version_ok := strconv.parse_int(clang_major_version_str); + clang_major_version_ok && clang_major_version < 16 { + log.panic("libclang version 16 or newer is required") + } + clang_args: [dynamic]cstring append(&clang_args, "-fparse-all-comments") diff --git a/src/translate_process.odin b/src/translate_process.odin @@ -164,18 +164,14 @@ translate_process :: proc(tcr: Translate_Collect_Result, config: Config, types: v.members = new_members[:] } - bit_set_name, bit_setify := config.bit_setify[d.name] + bit_set_enum_name, bit_setify := config.bit_setify[d.name] if bit_setify { clear(&bit_set_make_constant) - if bit_set_name == d.name && (d.name not_in config.rename) { - log.warnf("bit_set '%v' has same as enum '%v'. Suggestion: Add '\"%v\" = \"Some_New_Name\"' to 'rename' in bindgen.sjson", bit_set_name, d.name, d.name) - } - bs_idx := add_type(types, Type_Bit_Set { enum_type = d.def.(Type_Index), - enum_decl_name = Type_Name(d.name), + enum_decl_name = Type_Name(bit_set_enum_name), }) new_members: [dynamic]Type_Enum_Member @@ -191,7 +187,7 @@ translate_process :: proc(tcr: Translate_Collect_Result, config: Config, types: // it into a constant. bs_constant_idx := add_type(types, Type_Bit_Set_Constant { bit_set_type = bs_idx, - bit_set_type_name = Type_Name(bit_set_name), + bit_set_type_name = Type_Name(d.name), value = m.value, }) @@ -217,12 +213,15 @@ translate_process :: proc(tcr: Translate_Collect_Result, config: Config, types: v.members = new_members[:] - add_decl(decls, { - original_line = d.original_line + 1, - name = bit_set_name, - def = bs_idx, - explicitly_created = true, - }) + enum_decl := d + enum_decl.comment_before = "" + enum_decl.side_comment = "" + d.def = bs_idx + d.original_line += 1 + + enum_decl.name = bit_set_enum_name + + add_decl(decls, enum_decl) } case Type_Struct: @@ -253,6 +252,10 @@ translate_process :: proc(tcr: Translate_Collect_Result, config: Config, types: if default, has_default := config.procedure_parameter_defaults[key]; has_default { param.default = default } + + if override, has_override := config.procedure_type_overrides[key]; has_override { + override_procedure_parameter(&param, types, override) + } } } @@ -264,23 +267,7 @@ translate_process :: proc(tcr: Translate_Collect_Result, config: Config, types: for &p in v.parameters { param_key := fmt.tprintf("%s.%s", d.name, p.name) if override, has_override := config.procedure_type_overrides[param_key]; has_override { - if override == "[^]" { - if ptr_type, is_ptr_type := resolve_type_definition(types, p.type, Type_Pointer); is_ptr_type { - p.type = add_type(types, Type_Multipointer { - pointed_to_type = ptr_type.pointed_to_type, - }) - } - } else if override == "#by_ptr" { - if ptr_type, is_ptr_type := resolve_type_definition(types, p.type, Type_Pointer); is_ptr_type { - p.type = add_type(types, Type_Pointer_By_Ptr { - pointed_to_type = ptr_type.pointed_to_type, - }) - } - } else if override == "#any_int" { - p.any_int = true - } else { - p.type = Fixed_Value(override) - } + override_procedure_parameter(&p, types, override) } if default, has_default := config.procedure_parameter_defaults[param_key]; has_default { @@ -502,20 +489,8 @@ resolve_final_names :: proc(types: Type_List, decls: Decl_List, config: Config) } for &d in decls { - if d.explicitly_created { - continue - } - - _, is_proc := resolve_type_definition(types, d.def, Type_Procedure) - - if is_proc { - d.name = strings.trim_prefix(d.name, config.remove_function_prefix) - } else if d.from_macro { - d.name = strings.trim_prefix(d.name, config.remove_macro_prefix) - } else { - d.name = string(final_type_name(Type_Name(d.name), config)) - } - + d.name = final_decl_name(d, types, config) + switch &def in d.def { case Type_Name: d.def = final_type_name(def, config) case Macro_Name: d.def = final_macro_name(def, config) @@ -526,6 +501,26 @@ resolve_final_names :: proc(types: Type_List, decls: Decl_List, config: Config) } } +override_procedure_parameter :: proc(p: ^Type_Procedure_Parameter, types: Type_List, override: string) { + if override == "[^]" { + if ptr_type, is_ptr_type := resolve_type_definition(types, p.type, Type_Pointer); is_ptr_type { + p.type = add_type(types, Type_Multipointer { + pointed_to_type = ptr_type.pointed_to_type, + }) + } + } else if override == "#by_ptr" { + if ptr_type, is_ptr_type := resolve_type_definition(types, p.type, Type_Pointer); is_ptr_type { + p.type = add_type(types, Type_Pointer_By_Ptr { + pointed_to_type = ptr_type.pointed_to_type, + }) + } + } else if override == "#any_int" { + p.any_int = true + } else { + p.type = Fixed_Value(override) + } +} + is_number :: proc(b: byte) -> bool { return b >= '0' && b <= '9' } @@ -644,12 +639,42 @@ ensure_name_valid :: proc(s: string) -> string { return s } +final_decl_name :: proc(d: Decl, types: Type_List, config: Config) -> string { + if d.explicitly_created { + return d.name + } + + if new_name, rename := config.rename[string(d.name)]; rename { + return new_name + } + + _, is_proc := resolve_type_definition(types, d.def, Type_Procedure) + + if is_proc { + return strings.trim_prefix(d.name, config.remove_function_prefix) + } else if d.from_macro { + return strings.trim_prefix(d.name, config.remove_macro_prefix) + } else { + res := strings.trim_prefix(d.name, config.remove_type_prefix) + res = strings.trim_suffix(res, config.remove_type_suffix) + + if config.force_ada_case_types { + res = strings.to_ada_case(res) + } + + return res + } + + return d.name +} + final_type_name :: proc(name: Type_Name, config: Config) -> Type_Name { if new_name, rename := config.rename[string(name)]; rename { return Type_Name(new_name) } res := strings.trim_prefix(string(name), config.remove_type_prefix) + res = strings.trim_suffix(res, config.remove_type_suffix) if config.force_ada_case_types { res = strings.to_ada_case(res)