123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565 |
- #-------------------------------------------------------------------------------
- # Generate Zig bindings.
- #
- # Zig coding style:
- # - types are PascalCase
- # - functions are camelCase
- # - otherwise snake_case
- #-------------------------------------------------------------------------------
- import gen_ir
- import os, shutil, sys
- import textwrap
- import gen_util as util
- module_names = {
- 'slog_': 'log',
- 'sg_': 'gfx',
- 'sapp_': 'app',
- 'stm_': 'time',
- 'saudio_': 'audio',
- 'sgl_': 'gl',
- 'sdtx_': 'debugtext',
- 'sshape_': 'shape',
- 'sglue_': 'glue',
- 'sfetch_': 'fetch',
- 'simgui_': 'imgui',
- }
- c_source_paths = {
- 'slog_': 'sokol-zig/src/sokol/c/sokol_log.c',
- 'sg_': 'sokol-zig/src/sokol/c/sokol_gfx.c',
- 'sapp_': 'sokol-zig/src/sokol/c/sokol_app.c',
- 'stm_': 'sokol-zig/src/sokol/c/sokol_time.c',
- 'saudio_': 'sokol-zig/src/sokol/c/sokol_audio.c',
- 'sgl_': 'sokol-zig/src/sokol/c/sokol_gl.c',
- 'sdtx_': 'sokol-zig/src/sokol/c/sokol_debugtext.c',
- 'sshape_': 'sokol-zig/src/sokol/c/sokol_shape.c',
- 'sglue_': 'sokol-zig/src/sokol/c/sokol_glue.c',
- 'sfetch_': 'sokol-zig/src/sokol/c/sokol_fetch.c',
- 'simgui_': 'sokol-zig/src/sokol/c/sokol_imgui.c',
- }
- ignores = [
- 'sdtx_printf',
- 'sdtx_vprintf',
- 'sg_install_trace_hooks',
- 'sg_trace_hooks',
- ]
- # functions that need to be exposed as 'raw' C callbacks without a Zig wrapper function
- c_callbacks = [
- 'slog_func'
- ]
- # NOTE: syntax for function results: "func_name.RESULT"
- overrides = {
- 'sgl_error': 'sgl_get_error', # 'error' is reserved in Zig
- 'sgl_deg': 'sgl_as_degrees',
- 'sgl_rad': 'sgl_as_radians',
- 'sg_apply_uniforms.ub_slot': 'uint32_t',
- 'sg_draw.base_element': 'uint32_t',
- 'sg_draw.num_elements': 'uint32_t',
- 'sg_draw.num_instances': 'uint32_t',
- 'sshape_element_range_t.base_element': 'uint32_t',
- 'sshape_element_range_t.num_elements': 'uint32_t',
- 'sdtx_font.font_index': 'uint32_t',
- 'SGL_NO_ERROR': 'SGL_ERROR_NO_ERROR',
- 'sfetch_continue': 'continue_fetching', # 'continue' is reserved in Zig
- 'sfetch_desc': 'sfetch_get_desc' # 'desc' shadowed by earlier definition
- }
- prim_types = {
- 'int': 'i32',
- 'bool': 'bool',
- 'char': 'u8',
- 'int8_t': 'i8',
- 'uint8_t': 'u8',
- 'int16_t': 'i16',
- 'uint16_t': 'u16',
- 'int32_t': 'i32',
- 'uint32_t': 'u32',
- 'int64_t': 'i64',
- 'uint64_t': 'u64',
- 'float': 'f32',
- 'double': 'f64',
- 'uintptr_t': 'usize',
- 'intptr_t': 'isize',
- 'size_t': 'usize'
- }
- 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'
- }
- struct_types = []
- enum_types = []
- enum_items = {}
- out_lines = ''
- def reset_globals():
- global struct_types
- global enum_types
- global enum_items
- global out_lines
- struct_types = []
- enum_types = []
- enum_items = {}
- out_lines = ''
- def l(s):
- global out_lines
- out_lines += s + '\n'
- def c(s, indent="", comment="///"):
- if not s:
- return
- prefix = f"{indent}{comment}"
- for line in textwrap.dedent(s).splitlines():
- l(f"{prefix} {line}" if line else prefix )
- def as_zig_prim_type(s):
- return prim_types[s]
- # prefix_bla_blub(_t) => (dep.)BlaBlub
- def as_zig_struct_type(s, prefix):
- parts = s.lower().split('_')
- outp = '' if s.startswith(prefix) else f'{parts[0]}.'
- for part in parts[1:]:
- # ignore '_t' type postfix
- if (part != 't'):
- outp += part.capitalize()
- return outp
- # prefix_bla_blub(_t) => (dep.)BlaBlub
- def as_zig_enum_type(s, prefix):
- parts = s.lower().split('_')
- outp = '' if s.startswith(prefix) else f'{parts[0]}.'
- for part in parts[1:]:
- if (part != 't'):
- outp += part.capitalize()
- return outp
- 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_ENUM_BLA => Bla, _PREFIX_ENUM_BLA => Bla
- def as_enum_item_name(s):
- outp = s.lstrip('_')
- parts = outp.split('_')[2:]
- outp = '_'.join(parts)
- if outp[0].isdigit():
- outp = '_' + outp
- return outp
- def enum_default_item(enum_name):
- return enum_items[enum_name][0]
- def is_prim_type(s):
- return s in prim_types
- 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 as_c_arg_type(arg_type, prefix):
- if arg_type == "void":
- return "void"
- elif is_prim_type(arg_type):
- return as_zig_prim_type(arg_type)
- elif is_struct_type(arg_type):
- return as_zig_struct_type(arg_type, prefix)
- elif is_enum_type(arg_type):
- return as_zig_enum_type(arg_type, prefix)
- elif util.is_void_ptr(arg_type):
- return "?*anyopaque"
- elif util.is_const_void_ptr(arg_type):
- return "?*const anyopaque"
- elif util.is_string_ptr(arg_type):
- return "[*c]const u8"
- elif is_const_struct_ptr(arg_type):
- return f"[*c]const {as_zig_struct_type(util.extract_ptr_type(arg_type), prefix)}"
- elif is_prim_ptr(arg_type):
- return f"[*c]{as_zig_prim_type(util.extract_ptr_type(arg_type))}"
- elif is_const_prim_ptr(arg_type):
- return f"[*c]const {as_zig_prim_type(util.extract_ptr_type(arg_type))}"
- else:
- sys.exit(f"Error as_c_arg_type(): {arg_type}")
- def as_zig_arg_type(arg_prefix, arg_type, prefix):
- # NOTE: if arg_prefix is None, the result is used as return value
- pre = "" if arg_prefix is None else arg_prefix
- if arg_type == "void":
- if arg_prefix is None:
- return "void"
- else:
- return ""
- elif is_prim_type(arg_type):
- return pre + as_zig_prim_type(arg_type)
- elif is_struct_type(arg_type):
- return pre + as_zig_struct_type(arg_type, prefix)
- elif is_enum_type(arg_type):
- return pre + as_zig_enum_type(arg_type, prefix)
- elif util.is_void_ptr(arg_type):
- return pre + "?*anyopaque"
- elif util.is_const_void_ptr(arg_type):
- return pre + "?*const anyopaque"
- elif util.is_string_ptr(arg_type):
- return pre + "[:0]const u8"
- elif is_const_struct_ptr(arg_type):
- # not a bug, pass const structs by value
- return pre + f"{as_zig_struct_type(util.extract_ptr_type(arg_type), prefix)}"
- elif is_prim_ptr(arg_type):
- return pre + f"*{as_zig_prim_type(util.extract_ptr_type(arg_type))}"
- elif is_const_prim_ptr(arg_type):
- return pre + f"*const {as_zig_prim_type(util.extract_ptr_type(arg_type))}"
- else:
- sys.exit(f"ERROR as_zig_arg_type(): {arg_type}")
- def is_zig_string(zig_type):
- return zig_type == "[:0]const u8"
- # get C-style arguments of a function pointer as string
- def funcptr_args_c(field_type, prefix):
- tokens = field_type[field_type.index('(*)')+4:-1].split(',')
- s = ""
- for token in tokens:
- arg_type = token.strip()
- if s != "":
- s += ", "
- c_arg = as_c_arg_type(arg_type, prefix)
- if c_arg == "void":
- return ""
- else:
- s += c_arg
- return s
- # get C-style result of a function pointer as string
- def funcptr_result_c(field_type):
- res_type = field_type[:field_type.index('(*)')].strip()
- if res_type == 'void':
- return 'void'
- elif is_prim_type(res_type):
- return as_zig_prim_type(res_type)
- elif util.is_const_void_ptr(res_type):
- return '?*const anyopaque'
- elif util.is_void_ptr(res_type):
- return '?*anyopaque'
- else:
- sys.exit(f"ERROR funcptr_result_c(): {field_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 += as_c_arg_type(param_type, prefix)
- return s
- def funcdecl_args_zig(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"{as_zig_arg_type(f'{param_name}: ', param_type, prefix)}"
- return s
- def funcdecl_result_c(decl, prefix):
- func_name = decl['name']
- decl_type = decl['type']
- result_type = check_override(f'{func_name}.RESULT', default=decl_type[:decl_type.index('(')].strip())
- return as_c_arg_type(result_type, prefix)
- def funcdecl_result_zig(decl, prefix):
- func_name = decl['name']
- decl_type = decl['type']
- result_type = check_override(f'{func_name}.RESULT', default=decl_type[:decl_type.index('(')].strip())
- zig_res_type = as_zig_arg_type(None, result_type, prefix)
- return zig_res_type
- def gen_struct(decl, prefix):
- struct_name = check_override(decl['name'])
- zig_type = as_zig_struct_type(struct_name, prefix)
- c(decl.get('comment'))
- l(f"pub const {zig_type} = extern struct {{")
- for field in decl['fields']:
- field_name = check_override(field['name'])
- field_type = check_override(f'{struct_name}.{field_name}', default=field['type'])
- if is_prim_type(field_type):
- l(f" {field_name}: {as_zig_prim_type(field_type)} = {type_default_value(field_type)},")
- elif is_struct_type(field_type):
- l(f" {field_name}: {as_zig_struct_type(field_type, prefix)} = .{{}},")
- elif is_enum_type(field_type):
- l(f" {field_name}: {as_zig_enum_type(field_type, prefix)} = .{enum_default_item(field_type)},")
- elif util.is_string_ptr(field_type):
- l(f" {field_name}: [*c]const u8 = null,")
- elif util.is_const_void_ptr(field_type):
- l(f" {field_name}: ?*const anyopaque = null,")
- elif util.is_void_ptr(field_type):
- l(f" {field_name}: ?*anyopaque = null,")
- elif is_const_prim_ptr(field_type):
- l(f" {field_name}: ?[*]const {as_zig_prim_type(util.extract_ptr_type(field_type))} = null,")
- elif util.is_func_ptr(field_type):
- l(f" {field_name}: ?*const fn ({funcptr_args_c(field_type, prefix)}) callconv(.c) {funcptr_result_c(field_type)} = null,")
- elif util.is_1d_array_type(field_type):
- array_type = util.extract_array_type(field_type)
- array_sizes = util.extract_array_sizes(field_type)
- if is_prim_type(array_type) or is_struct_type(array_type):
- if is_prim_type(array_type):
- zig_type = as_zig_prim_type(array_type)
- def_val = type_default_value(array_type)
- elif is_struct_type(array_type):
- zig_type = as_zig_struct_type(array_type, prefix)
- def_val = '.{}'
- elif is_enum_type(array_type):
- zig_type = as_zig_enum_type(array_type, prefix)
- def_val = '.{}'
- else:
- sys.exit(f"ERROR gen_struct is_1d_array_type: {array_type}")
- t0 = f"[{array_sizes[0]}]{zig_type}"
- t1 = f"[_]{zig_type}"
- l(f" {field_name}: {t0} = {t1}{{{def_val}}} ** {array_sizes[0]},")
- elif util.is_const_void_ptr(array_type):
- l(f" {field_name}: [{array_sizes[0]}]?*const anyopaque = [_]?*const anyopaque{{null}} ** {array_sizes[0]},")
- else:
- sys.exit(f"ERROR gen_struct: array {field_name}: {field_type} => {array_type} [{array_sizes[0]}]")
- elif util.is_2d_array_type(field_type):
- array_type = util.extract_array_type(field_type)
- array_sizes = util.extract_array_sizes(field_type)
- if is_prim_type(array_type):
- zig_type = as_zig_prim_type(array_type)
- def_val = type_default_value(array_type)
- elif is_struct_type(array_type):
- zig_type = as_zig_struct_type(array_type, prefix)
- def_val = ".{}"
- else:
- sys.exit(f"ERROR gen_struct is_2d_array_type: {array_type}")
- t0 = f"[{array_sizes[0]}][{array_sizes[1]}]{zig_type}"
- l(f" {field_name}: {t0} = [_][{array_sizes[1]}]{zig_type}{{[_]{zig_type}{{{def_val}}} ** {array_sizes[1]}}} ** {array_sizes[0]},")
- else:
- sys.exit(f"ERROR gen_struct: {field_name}: {field_type};")
- l("};")
- l("")
- def gen_consts(decl, prefix):
- c(decl.get('comment'))
- for item in decl['items']:
- item_name = check_override(item['name'])
- c(item.get('comment'))
- l(f"pub const {util.as_lower_snake_case(item_name, prefix)} = {item['value']};")
- l("")
- def gen_enum(decl, prefix):
- enum_name = check_override(decl['name'])
- c(decl.get('comment'))
- l(f"pub const {as_zig_enum_type(enum_name, prefix)} = enum(i32) {{")
- for item in decl['items']:
- item_name = as_enum_item_name(check_override(item['name']))
- if item_name != "FORCE_U32":
- if 'value' in item:
- l(f" {item_name} = {item['value']},")
- else:
- l(f" {item_name},")
- l("};")
- l("")
- def gen_func_c(decl, prefix):
- c(decl.get('comment'))
- l(f"extern fn {decl['name']}({funcdecl_args_c(decl, prefix)}) {funcdecl_result_c(decl, prefix)};")
- l('')
- def gen_func_zig(decl, prefix, tiger_style=False):
- c_func_name = decl['name']
- if not tiger_style:
- zig_func_name = util.as_lower_camel_case(check_override(decl['name']), prefix)
- else:
- zig_func_name = util.as_lower_snake_case(check_override(decl['name']), prefix)
- c(decl.get('comment'))
- if c_func_name in c_callbacks:
- # a simple forwarded C callback function
- l(f"pub const {zig_func_name} = {c_func_name};")
- else:
- zig_res_type = funcdecl_result_zig(decl, prefix)
- l(f"pub fn {zig_func_name}({funcdecl_args_zig(decl, prefix)}) {zig_res_type} {{")
- if is_zig_string(zig_res_type):
- # special case: convert C string to Zig string slice
- s = f" return cStrToZig({c_func_name}("
- elif zig_res_type != 'void':
- s = f" return {c_func_name}("
- else:
- s = f" {c_func_name}("
- for i, param_decl in enumerate(decl['params']):
- if i > 0:
- s += ", "
- arg_name = param_decl['name']
- arg_type = param_decl['type']
- if is_const_struct_ptr(arg_type):
- s += f"&{arg_name}"
- elif util.is_string_ptr(arg_type):
- s += f"@ptrCast({arg_name})"
- else:
- s += arg_name
- if is_zig_string(zig_res_type):
- s += ")"
- s += ");"
- l(s)
- l("}")
- l("")
- 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 gen_imports(inp, dep_prefixes):
- l('const builtin = @import("builtin");')
- for dep_prefix in dep_prefixes:
- dep_module_name = module_names[dep_prefix]
- l(f'const {dep_prefix[:-1]} = @import("{dep_module_name}.zig");')
- l('')
- def gen_helpers(inp):
- l('// helper function to convert a C string to a Zig string slice')
- l('fn cStrToZig(c_str: [*c]const u8) [:0]const u8 {')
- l(' return @import("std").mem.span(c_str);')
- l('}')
- if inp['prefix'] in ['sg_', 'sdtx_', 'sshape_', 'sfetch_']:
- l('// helper function to convert "anything" to a Range struct')
- l('pub fn asRange(val: anytype) Range {')
- l(' const type_info = @typeInfo(@TypeOf(val));')
- l(' switch (type_info) {')
- l(' .pointer => {')
- l(' switch (type_info.pointer.size) {')
- l(' .one => return .{ .ptr = val, .size = @sizeOf(type_info.pointer.child) },')
- l(' .slice => return .{ .ptr = val.ptr, .size = @sizeOf(type_info.pointer.child) * val.len },')
- l(' else => @compileError("FIXME: Pointer type!"),')
- l(' }')
- l(' },')
- l(' .@"struct", .array => {')
- l(' @compileError("Structs and arrays must be passed as pointers to asRange");')
- l(' },')
- l(' else => {')
- l(' @compileError("Cannot convert to range!");')
- l(' },')
- l(' }')
- l('}')
- l('')
- if inp['prefix'] == 'sdtx_':
- l('// std.fmt-style formatted print')
- l('pub fn print(comptime fmt: anytype, args: anytype) void {')
- l(' const cbuf = getClearedFmtBuffer();')
- l(' const p: [*]u8 = @constCast(@ptrCast(cbuf.ptr));')
- l(' const buf = p[0..cbuf.size];')
- l(' const out = @import("std").fmt.bufPrint(buf, fmt, args) catch "";')
- l(' for (out) |c| putc(c);')
- l('}')
- l('')
- def gen_module(inp, dep_prefixes, opt={}):
- l('// machine generated, do not edit')
- if inp.get('comment'):
- l('')
- c(inp['comment'], comment="//")
- l('')
- gen_imports(inp, dep_prefixes)
- gen_helpers(inp)
- pre_parse(inp)
- prefix = inp['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)
- elif kind == 'func':
- gen_func_c(decl, prefix)
- tiger_style = opt.get('tiger-style', False)
- gen_func_zig(decl, prefix, tiger_style=tiger_style)
- def prepare():
- print('=== Generating Zig bindings:')
- if not os.path.isdir('sokol-zig/src/sokol'):
- os.makedirs('sokol-zig/src/sokol')
- if not os.path.isdir('sokol-zig/src/sokol/c'):
- os.makedirs('sokol-zig/src/sokol/c')
- def gen(c_header_path, c_prefix, dep_c_prefixes, opt={}):
- if not c_prefix in module_names:
- print(f' >> warning: skipping generation for {c_prefix} prefix...')
- return
- module_name = module_names[c_prefix]
- c_source_path = c_source_paths[c_prefix]
- print(f' {c_header_path} => {module_name}')
- reset_globals()
- shutil.copyfile(c_header_path, f'sokol-zig/src/sokol/c/{os.path.basename(c_header_path)}')
- ir = gen_ir.gen(c_header_path, c_source_path, module_name, c_prefix, dep_c_prefixes, with_comments=True)
- gen_module(ir, dep_c_prefixes, opt)
- output_path = f"sokol-zig/src/sokol/{ir['module']}.zig"
- with open(output_path, 'w', newline='\n') as f_outp:
- f_outp.write(out_lines)
|