aboutsummaryrefslogtreecommitdiff
path: root/bindgen
diff options
context:
space:
mode:
authorAndre Weissflog <floooh@gmail.com>2025-02-10 12:38:42 +0100
committerAndre Weissflog <floooh@gmail.com>2025-02-10 12:38:42 +0100
commit4395d06f40729c3b2f6af5003d22d759584ef52e (patch)
tree70c61c88724346ac7ee373ae501dce5788069288 /bindgen
parent132dd7ca11ff6f8ab0cedb9a6a715deb7c98f821 (diff)
parent80a19191017aae0f0b7eb9872c6720386f66b980 (diff)
Merge branch 'c3-lang-support' of https://github.com/radekm/sokol into radekm-c3-lang-support
Diffstat (limited to 'bindgen')
-rw-r--r--bindgen/.gitignore1
-rw-r--r--bindgen/README.md1
-rw-r--r--bindgen/gen_all.py8
-rw-r--r--bindgen/gen_c3.py445
4 files changed, 454 insertions, 1 deletions
diff --git a/bindgen/.gitignore b/bindgen/.gitignore
index 926ae525..070ac1cf 100644
--- a/bindgen/.gitignore
+++ b/bindgen/.gitignore
@@ -8,3 +8,4 @@ sokol-odin/
sokol-rust/
sokol-d/
sokol-jai/
+sokol-c3/
diff --git a/bindgen/README.md b/bindgen/README.md
index a7315521..d6304b6b 100644
--- a/bindgen/README.md
+++ b/bindgen/README.md
@@ -28,6 +28,7 @@ To update the Zig bindings:
> git clone https://github.com/floooh/sokol-rust
> git clone https://github.com/floooh/sokol-d
> git clone https://github.com/colinbellino/sokol-jai
+> git clone https://github.com/floooh/sokol-c3
> python3 gen_all.py
```
diff --git a/bindgen/gen_all.py b/bindgen/gen_all.py
index 45a969b1..03a93756 100644
--- a/bindgen/gen_all.py
+++ b/bindgen/gen_all.py
@@ -1,4 +1,4 @@
-import os, gen_nim, gen_zig, gen_odin, gen_rust, gen_d, gen_jai
+import os, gen_nim, gen_zig, gen_odin, gen_rust, gen_d, gen_jai, gen_c3
tasks = [
[ '../sokol_log.h', 'slog_', [] ],
@@ -58,3 +58,9 @@ gen_rust.prepare()
for task in tasks:
[c_header_path, main_prefix, dep_prefixes] = task
gen_rust.gen(c_header_path, main_prefix, dep_prefixes)
+
+# C3
+gen_c3.prepare()
+for task in tasks:
+ [c_header_path, main_prefix, dep_prefixes] = task
+ gen_c3.gen(c_header_path, main_prefix, dep_prefixes)
diff --git a/bindgen/gen_c3.py b/bindgen/gen_c3.py
new file mode 100644
index 00000000..11718476
--- /dev/null
+++ b/bindgen/gen_c3.py
@@ -0,0 +1,445 @@
+#-------------------------------------------------------------------------------
+# gen_c3.py
+#
+# Generate C3 bindings.
+#-------------------------------------------------------------------------------
+import gen_ir
+import gen_util as util
+import os, shutil, sys
+
+bindings_root = 'sokol-c3'
+c_root = f'{bindings_root}/sokol.c3l/c'
+module_root = f'{bindings_root}/sokol.c3l'
+
+# TODO: Consider chaning module names to something shorter.
+# For example we could C prefixes, for example `sg` instead of current `gfx`.
+module_names = {
+ 'slog_': 'slog',
+ 'sg_': 'sg',
+ 'sapp_': 'sapp',
+ 'stm_': 'stm',
+ 'saudio_': 'saudio',
+ 'sgl_': 'sgl',
+ 'sdtx_': 'sdtx',
+ 'sshape_': 'sshape',
+ 'sglue_': 'sglue',
+}
+
+c_source_names = {
+ 'slog_': 'sokol_log.c',
+ 'sg_': 'sokol_gfx.c',
+ 'sapp_': 'sokol_app.c',
+ 'sapp_sg': 'sokol_glue.c',
+ 'stm_': 'sokol_time.c',
+ 'saudio_': 'sokol_audio.c',
+ 'sgl_': 'sokol_gl.c',
+ 'sdtx_': 'sokol_debugtext.c',
+ 'sshape_': 'sokol_shape.c',
+ 'sglue_': 'sokol_glue.c',
+}
+
+ignores = [
+ 'sdtx_printf',
+ 'sdtx_vprintf',
+ 'sg_install_trace_hooks',
+ 'sg_trace_hooks',
+]
+
+overrides = {
+ # `any` is treated specially in C3.
+ 'any': '_any',
+ # Constants must be uppercase - lowercase `x` is not allowed.
+ 'SG_PIXELFORMAT_ASTC_4x4_RGBA': 'SG_PIXELFORMAT_ASTC_4X4_RGBA',
+ 'SG_PIXELFORMAT_ASTC_4x4_SRGBA': 'SG_PIXELFORMAT_ASTC_4X4_SRGBA',
+}
+
+prim_types = {
+ 'int': 'CInt',
+ # TODO: Check whether we should translate to `CBool` instead?
+ 'bool': 'bool',
+ 'char': 'char',
+ 'int8_t': 'ichar',
+ 'uint8_t': 'char',
+ 'int16_t': 'short',
+ 'uint16_t': 'ushort',
+ 'int32_t': 'int',
+ 'uint32_t': 'uint',
+ 'int64_t': 'long',
+ 'uint64_t': 'ulong',
+ 'float': 'float',
+ 'double': 'double',
+ 'uintptr_t': 'uptr',
+ 'intptr_t': 'iptr',
+ 'size_t': 'usz'
+}
+
+prim_defaults = {
+ 'int': '0',
+ 'bool': 'false',
+ 'int8_t': '0',
+ 'uint8_t': '0',
+ 'int16_t': '0',
+ 'uint16_t': '0',
+ 'int32_t': '0',
+ 'uint32_t': '0',
+ 'int64_t': '0',
+ 'uint64_t': '0',
+ 'float': '0.0',
+ 'double': '0.0',
+ 'uintptr_t': '0',
+ 'intptr_t': '0',
+ 'size_t': '0'
+}
+
+special_constant_types = {
+ "SG_INVALID_ID": "uint",
+ "SAPP_MODIFIER_SHIFT": "uint",
+ "SAPP_MODIFIER_CTRL": "uint",
+ "SAPP_MODIFIER_ALT": "uint",
+ "SAPP_MODIFIER_SUPER": "uint",
+ "SAPP_MODIFIER_LMB": "uint",
+ "SAPP_MODIFIER_RMB": "uint",
+ "SAPP_MODIFIER_MMB": "uint",
+}
+
+# Aliases for function pointers.
+# Function pointers must be aliased - we can't use them directly inside structs,
+# instead we have to create an alias and use the alias in the struct.
+aliases = {
+ # C type -> (Alias name, Right hand side of alias).
+ "void (*)(void *)":
+ ("DataCb", "fn void(void*)"),
+ "void *(*)(size_t, void *)":
+ ("AllocCb", "fn void*(usz, void*)"),
+ "void (*)(void *, void *)":
+ ("FreeCb", "fn void*(usz, void*)"),
+ "void (*)(const char *, uint32_t, uint32_t, const char *, uint32_t, const char *, void *)":
+ ("LogCb", "fn void(ZString, uint, uint, ZString, uint, ZString, void*)"),
+ "void (*)(void)":
+ ("Cb", "fn void()"),
+ "void (*)(const sapp_event *)":
+ ("EventCb", "fn void(Event*)"),
+ "void (*)(const sapp_event *, void *)":
+ ("EventDataCb", "fn void(Event*, void*)"),
+ "void (*)(const sapp_html5_fetch_response *)":
+ ("ResponseCb", "fn void(Html5FetchResponse*)"),
+ "void (*)(float *, int, int)":
+ ("StreamCb", "fn void(float*, CInt, CInt)"),
+ "void (*)(float *, int, int, void *)":
+ ("StreamDataCb", "fn void(float*, CInt, CInt, void*)"),
+}
+
+struct_types = []
+enum_types = []
+enum_items = {}
+# Which alias were used in current module.
+# At the end of module we emit only used aliases.
+used_aliases = [] # We shouldn't use `set()` because the order differs among runs.
+out_lines = ''
+
+def reset_globals():
+ global struct_types
+ global enum_types
+ global enum_items
+ global used_aliases
+ global out_lines
+ struct_types = []
+ enum_types = []
+ enum_items = {}
+ used_aliases = []
+ out_lines = ''
+
+def l(s):
+ global out_lines
+ out_lines += s + '\n'
+
+def check_override(name, default=None):
+ if name in overrides:
+ return overrides[name]
+ elif default is None:
+ return name
+ else:
+ return default
+
+def check_ignore(name):
+ return name in ignores
+
+# PREFIX_BLA_BLUB to BLA_BLUB, prefix_bla_blub to bla_blub
+def as_snake_case(s, prefix):
+ outp = s
+ if outp.lower().startswith(prefix):
+ outp = outp[len(prefix):]
+ return outp
+
+def get_c3_module_path(c_prefix):
+ return f'{module_root}'
+
+def get_csource_path(c_prefix):
+ return f'{c_root}/{c_source_names[c_prefix]}'
+
+def make_c3_module_directory(c_prefix):
+ path = get_c3_module_path(c_prefix)
+ if not os.path.isdir(path):
+ os.makedirs(path)
+
+def as_prim_type(s):
+ return prim_types[s]
+
+def as_upper_snake_case(s, prefix):
+ outp = s.lower()
+ if outp.startswith(prefix):
+ outp = outp[len(prefix):]
+ return outp.upper()
+
+def as_module_name_for_enum_type(enum_name, prefix):
+ parts = enum_name.lower().split('_')
+ parent_module = module_names[prefix]
+ if parts[-1] == 't':
+ # Ignore '_t' suffix.
+ module = "_".join(parts[1:-1])
+ else:
+ module = "_".join(parts[1:])
+ return f"sokol::{parent_module}::{module}"
+
+def as_parent_module_name_for_enum_type(enum_name, prefix):
+ parent_module = module_names[prefix]
+ return f"sokol::{parent_module}"
+
+# prefix_bla_blub(_t) => (dep::)BlaBlub
+def as_struct_or_enum_type(s, prefix):
+ parts = s.lower().split('_')
+ outp = '' if s.startswith(prefix) else f'sokol::{module_names[parts[0]+"_"]}::'
+ for part in parts[1:]:
+ # ignore '_t' type postfix
+ if part != 't':
+ outp += part.capitalize()
+ return outp
+
+# PREFIX_ENUM_BLA_BLUB => BLA_BLUB, _PREFIX_ENUM_BLA_BLUB => BLA_BLUB
+def as_enum_item_name(s):
+ outp = s.lstrip('_')
+ parts = outp.split('_')[2:]
+ outp = '_'.join(parts)
+ if outp[0].isdigit():
+ if outp in ["2D", "3D"]:
+ outp = "TYPE_" + outp
+ else:
+ outp = 'NUM_' + outp
+ return outp
+
+def is_prim_type(s):
+ return s in prim_types
+
+def is_int_type(s):
+ return s == "int"
+
+def is_struct_type(s):
+ return s in struct_types
+
+def is_enum_type(s):
+ return s in enum_types
+
+def is_const_prim_ptr(s):
+ for prim_type in prim_types:
+ if s == f"const {prim_type} *":
+ return True
+ return False
+
+def is_prim_ptr(s):
+ for prim_type in prim_types:
+ if s == f"{prim_type} *":
+ return True
+ return False
+
+def is_const_struct_ptr(s):
+ for struct_type in struct_types:
+ if s == f"const {struct_type} *":
+ return True
+ return False
+
+def type_default_value(s):
+ return prim_defaults[s]
+
+def map_type(type, prefix, sub_type):
+ if sub_type not in ['c_arg', 'struct_field']:
+ sys.exit(f"Error: map_type(): unknown sub_type '{sub_type}")
+ if type == "void":
+ return ""
+ elif is_prim_type(type):
+ return as_prim_type(type)
+ elif is_struct_type(type):
+ return as_struct_or_enum_type(type, prefix)
+ elif is_enum_type(type):
+ return as_struct_or_enum_type(type, prefix)
+ elif util.is_void_ptr(type):
+ return "void*"
+ elif util.is_const_void_ptr(type):
+ return "void*"
+ elif util.is_string_ptr(type):
+ return "ZString"
+ elif is_const_struct_ptr(type):
+ return f"{as_struct_or_enum_type(util.extract_ptr_type(type), prefix)}*"
+ elif is_prim_ptr(type):
+ return f"{as_prim_type(util.extract_ptr_type(type))}*"
+ elif is_const_prim_ptr(type):
+ return f"{as_prim_type(util.extract_ptr_type(type))}*"
+ elif util.is_1d_array_type(type):
+ array_type = util.extract_array_type(type)
+ array_sizes = util.extract_array_sizes(type)
+ return f"{map_type(array_type, prefix, sub_type)}[{array_sizes[0]}]"
+ elif util.is_2d_array_type(type):
+ array_type = util.extract_array_type(type)
+ array_sizes = util.extract_array_sizes(type)
+ # TODO: Check if the dimensions are in correct order.
+ return f"{map_type(array_type, prefix, sub_type)}[{array_sizes[0]}][{array_sizes[1]}]"
+ elif util.is_func_ptr(type):
+ if type in aliases:
+ alias_name, _ = aliases[type]
+ if type not in used_aliases:
+ used_aliases.append(type)
+ return alias_name
+ else:
+ sys.exit(f"Error map_type(): missing alias for function pointer '{type}'")
+ else:
+ sys.exit(f"Error map_type(): unknown type '{type}'")
+
+def funcdecl_args_c(decl, prefix):
+ s = ''
+ func_name = decl['name']
+ for param_decl in decl['params']:
+ if s != '':
+ s += ', '
+ param_name = param_decl['name']
+ param_type = check_override(f'{func_name}.{param_name}', default=param_decl['type'])
+ s += f"{map_type(param_type, prefix, 'c_arg')} {param_name}"
+ return s
+
+def funcdecl_result_c(decl, prefix):
+ func_name = decl['name']
+ decl_type = decl['type']
+ res_c_type = decl_type[:decl_type.index('(')].strip()
+ return map_type(check_override(f'{func_name}.RESULT', default=res_c_type), prefix, 'c_arg')
+
+def gen_c_imports(inp, c_prefix, prefix):
+ prefix = inp['prefix']
+ for decl in inp['decls']:
+ if decl['kind'] == 'func' and not decl['is_dep'] and not check_ignore(decl['name']):
+ args = funcdecl_args_c(decl, prefix)
+ res_type = funcdecl_result_c(decl, prefix)
+ res_str = 'void' if res_type == '' else res_type
+ l(f'extern fn {res_str} {check_override(as_snake_case(decl["name"], c_prefix))}({args}) @extern("{decl["name"]}");')
+ l('')
+
+def gen_consts(decl, prefix):
+ for item in decl["items"]:
+ #
+ # TODO: What type should these constants have? Currently giving all `usz`
+ # unless specifically overridden by `special_constant_types`
+ #
+
+ item_name = check_override(item["name"])
+ tpe = "usz"
+ if item_name in special_constant_types:
+ tpe = special_constant_types[item_name]
+ l(f"const {tpe} {as_upper_snake_case(item_name, prefix)} = {item['value']};")
+ l('')
+
+def gen_struct(decl, prefix):
+ c_struct_name = check_override(decl['name'])
+ struct_name = as_struct_or_enum_type(c_struct_name, prefix)
+ l(f'struct {struct_name}')
+ l('{')
+ for field in decl['fields']:
+ field_name = check_override(field['name'])
+ field_type = map_type(check_override(f'{c_struct_name}.{field_name}', default=field['type']), prefix, 'struct_field')
+ l(f' {field_type} {field_name};')
+ l('}')
+ l('')
+
+def gen_enum(decl, prefix):
+ enum_name = check_override(decl['name'])
+ tpe = "int"
+ if any(as_enum_item_name(check_override(item['name'])) == 'FORCE_U32' for item in decl['items']):
+ tpe = "uint"
+ l(f'distinct {as_struct_or_enum_type(enum_name, prefix)} = {tpe};')
+ # Constants are in submodule.
+ l(f'module {as_module_name_for_enum_type(enum_name, prefix)};')
+ value = "-1"
+ for item in decl['items']:
+ item_name = as_enum_item_name(check_override(item['name']))
+ if item_name != 'FORCE_U32':
+ if 'value' in item:
+ value = item['value']
+ else:
+ value = str(int(value) + 1)
+ l(f"const {as_struct_or_enum_type(enum_name, prefix)} {item_name} = {value};")
+ l(f'module {as_parent_module_name_for_enum_type(enum_name, prefix)};')
+ # After reopening the original module all dependencies must be reimported.
+ l(f'import sokol;')
+ l('')
+
+def gen_imports(dep_prefixes):
+ l(f'import sokol;')
+ l('')
+
+def gen_function_pointer_aliases():
+ for type in used_aliases:
+ alias_name, right_hand_side = aliases[type]
+ l(f'def {alias_name} = {right_hand_side};')
+ l('')
+
+def gen_module(inp, c_prefix, dep_prefixes):
+ pre_parse(inp)
+ l('// machine generated, do not edit')
+ l('')
+ l(f"module sokol::{module_names[c_prefix]};")
+ gen_imports(dep_prefixes)
+ prefix = inp['prefix']
+ gen_c_imports(inp, c_prefix, prefix)
+ for decl in inp['decls']:
+ if not decl['is_dep']:
+ kind = decl['kind']
+ if kind == 'consts':
+ gen_consts(decl, prefix)
+ elif not check_ignore(decl['name']):
+ if kind == 'struct':
+ gen_struct(decl, prefix)
+ elif kind == 'enum':
+ gen_enum(decl, prefix)
+ gen_function_pointer_aliases()
+
+def pre_parse(inp):
+ global struct_types
+ global enum_types
+ for decl in inp['decls']:
+ kind = decl['kind']
+ if kind == 'struct':
+ struct_types.append(decl['name'])
+ elif kind == 'enum':
+ enum_name = decl['name']
+ enum_types.append(enum_name)
+ enum_items[enum_name] = []
+ for item in decl['items']:
+ enum_items[enum_name].append(as_enum_item_name(item['name']))
+
+def prepare():
+ print('=== Generating C3 bindings:')
+ if not os.path.isdir(module_root):
+ os.makedirs(module_root)
+ if not os.path.isdir(c_root):
+ os.makedirs(c_root)
+
+def gen(c_header_path, c_prefix, dep_c_prefixes):
+ if not c_prefix in module_names:
+ print(f' >> warning: skipping generation for {c_prefix} prefix...')
+ return
+ reset_globals()
+ make_c3_module_directory(c_prefix)
+ print(f' {c_header_path} => {module_names[c_prefix]}')
+ shutil.copyfile(c_header_path, f'{c_root}/{os.path.basename(c_header_path)}')
+ csource_path = get_csource_path(c_prefix)
+ module_name = module_names[c_prefix]
+ ir = gen_ir.gen(c_header_path, csource_path, module_name, c_prefix, dep_c_prefixes)
+ gen_module(ir, c_prefix, dep_c_prefixes)
+ with open(f"{module_root}/{ir['module']}.c3", 'w', newline='\n') as f_outp:
+ f_outp.write(out_lines)