553 lines
20 KiB
Python
553 lines
20 KiB
Python
#-------------------------------------------------------------------------------
|
|
# Generate Zig bindings.
|
|
#
|
|
# Zig coding style:
|
|
# - types are PascalCase
|
|
# - functions are camelCase
|
|
# - otherwise snake_case
|
|
#-------------------------------------------------------------------------------
|
|
import gen_ir
|
|
import os, shutil, sys
|
|
|
|
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',
|
|
}
|
|
|
|
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',
|
|
}
|
|
|
|
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_index': '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',
|
|
}
|
|
|
|
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 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)
|
|
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("};")
|
|
|
|
def gen_consts(decl, prefix):
|
|
for item in decl['items']:
|
|
item_name = check_override(item['name'])
|
|
l(f"pub const {util.as_lower_snake_case(item_name, prefix)} = {item['value']};")
|
|
|
|
def gen_enum(decl, prefix):
|
|
enum_name = check_override(decl['name'])
|
|
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("};")
|
|
|
|
def gen_func_c(decl, prefix):
|
|
l(f"pub extern fn {decl['name']}({funcdecl_args_c(decl, prefix)}) {funcdecl_result_c(decl, prefix)};")
|
|
|
|
def gen_func_zig(decl, prefix):
|
|
c_func_name = decl['name']
|
|
zig_func_name = util.as_lower_camel_case(check_override(decl['name']), prefix)
|
|
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("}")
|
|
|
|
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_']:
|
|
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 compatible Writer')
|
|
l('pub const Writer = struct {')
|
|
l(' pub const Error = error{};')
|
|
l(' pub fn writeAll(self: Writer, bytes: []const u8) Error!void {')
|
|
l(' _ = self;')
|
|
l(' for (bytes) |byte| {')
|
|
l(' putc(byte);')
|
|
l(' }')
|
|
l(' }')
|
|
l(' pub fn writeByteNTimes(self: Writer, byte: u8, n: usize) Error!void {')
|
|
l(' _ = self;')
|
|
l(' var i: u64 = 0;')
|
|
l(' while (i < n) : (i += 1) {')
|
|
l(' putc(byte);')
|
|
l(' }')
|
|
l(' }')
|
|
l(' pub fn writeBytesNTimes(self: Writer, bytes: []const u8, n: usize) Error!void {')
|
|
l(' var i: usize = 0;')
|
|
l(' while (i < n) : (i += 1) {')
|
|
l(' try self.writeAll(bytes);')
|
|
l(' }')
|
|
l(' }')
|
|
l('};')
|
|
l('// std.fmt-style formatted print')
|
|
l('pub fn print(comptime fmt: anytype, args: anytype) void {')
|
|
l(' const writer: Writer = .{};')
|
|
l(' @import("std").fmt.format(writer, fmt, args) catch {};')
|
|
l('}')
|
|
l('')
|
|
|
|
def gen_module(inp, dep_prefixes):
|
|
l('// machine generated, do not edit')
|
|
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)
|
|
gen_func_zig(decl, prefix)
|
|
|
|
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):
|
|
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)
|
|
gen_module(ir, dep_c_prefixes)
|
|
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)
|