prosperon/source/engine/thirdparty/sokol/bindgen/gen_odin.py

501 lines
17 KiB
Python

#-------------------------------------------------------------------------------
# gen_odin.py
#
# Generate Odin bindings.
#-------------------------------------------------------------------------------
import gen_ir
import gen_util as util
import os, shutil, sys
bindings_root = 'sokol-odin'
c_root = f'{bindings_root}/c'
module_root = f'{bindings_root}/sokol'
module_names = {
'slog_': 'log',
'sg_': 'gfx',
'sapp_': 'app',
'stm_': 'time',
'saudio_': 'audio',
'sgl_': 'gl',
'sdtx_': 'debugtext',
'sshape_': 'shape',
'sglue_': 'glue',
}
system_libs = {
'sg_': {
'windows': {
'd3d11': "",
'gl': "",
},
'macos': {
'metal': '"system:Cocoa.framework","system:QuartzCore.framework","system:Metal.framework","system:MetalKit.framework"',
'gl': '"system:Cocoa.framework","system:QuartzCore.framework","system:OpenGL.framework"'
},
'linux': {
'gl': '"system:GL", "system:dl", "system:pthread"'
}
},
'sapp_': {
'windows': {
'd3d11': '',
'gl': '',
},
'macos': {
'metal': '"system:Cocoa.framework","system:QuartzCore.framework","system:Metal.framework","system:MetalKit.framework"',
'gl': '"system:Cocoa.framework","system:QuartzCore.framework","system:OpenGL.framework"',
},
'linux': {
'gl': '"system:X11", "system:Xi", "system:Xcursor", "system:GL", "system:dl", "system:pthread"'
}
},
'saudio_': {
'windows': {
'd3d11': '',
'gl': '',
},
'macos': {
'metal': '"system:AudioToolbox.framework"',
'gl': '"system:AudioToolbox.framework"',
},
'linux': {
'gl': '"system:asound", "system:dl", "system:pthread"',
}
}
}
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',
]
# NOTE: syntax for function results: "func_name.RESULT"
overrides = {
'context': 'ctx', # reserved keyword
'SGL_NO_ERROR': 'SGL_ERROR_NO_ERROR',
}
prim_types = {
'int': 'c.int',
'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': 'u64',
'intptr_t': 'i64',
'size_t': 'u64'
}
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 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_odin_module_path(c_prefix):
return f'{module_root}/{module_names[c_prefix]}'
def get_csource_path(c_prefix):
return f'{c_root}/{c_source_names[c_prefix]}'
def make_odin_module_directory(c_prefix):
path = get_odin_module_path(c_prefix)
if not os.path.isdir(path):
os.makedirs(path)
def as_prim_type(s):
return prim_types[s]
# prefix_bla_blub(_t) => (dep.)Bla_Blub
def as_struct_or_enum_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()
outp += '_'
outp = outp[:-1]
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():
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_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', 'odin_arg', 'struct_field']:
sys.exit(f"Error: map_type(): unknown sub_type '{sub_type}")
if type == "void":
return ""
elif is_prim_type(type):
if sub_type == 'odin_arg':
# for Odin args, maps C int (32-bit) to Odin int (pointer-sized),
# and the C bool type to Odin's bool type
if type == 'int' or type == 'uint32_t':
return 'int'
elif type == 'bool':
return 'bool'
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 "rawptr"
elif util.is_const_void_ptr(type):
return "rawptr"
elif util.is_string_ptr(type):
return "cstring"
elif is_const_struct_ptr(type):
# pass Odin struct args by value, not by pointer
if sub_type == 'odin_arg':
return f"{as_struct_or_enum_type(util.extract_ptr_type(type), prefix)}"
else:
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"[{array_sizes[0]}]{map_type(array_type, prefix, sub_type)}"
elif util.is_2d_array_type(type):
array_type = util.extract_array_type(type)
array_sizes = util.extract_array_sizes(type)
return f"[{array_sizes[0]}][{array_sizes[1]}]{map_type(array_type, prefix, sub_type)}"
elif util.is_func_ptr(type):
res_type = funcptr_result_c(type, prefix)
res_str = '' if res_type == '' else f' -> {res_type}'
return f'proc "c" ({funcptr_args_c(type, prefix)}){res_str}'
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'])
if is_const_struct_ptr(param_type):
s += f"#by_ptr {param_name}: {map_type(param_type, prefix, 'odin_arg')}"
elif is_int_type(param_type):
s += f"#any_int {param_name}: {map_type(param_type, prefix, 'c_arg')}"
else:
s += f"{param_name}: {map_type(param_type, prefix, 'c_arg')}"
return s
def funcptr_args_c(field_type, prefix):
tokens = field_type[field_type.index('(*)')+4:-1].split(',')
s = ''
arg_index = 0
for token in tokens:
arg_type = token.strip()
if s != '':
s += ', '
c_arg = map_type(arg_type, prefix, 'c_arg')
if c_arg == '':
return ''
else:
s += f'a{arg_index}: {c_arg}'
arg_index += 1
return s
def funcptr_result_c(field_type, prefix):
res_type = field_type[:field_type.index('(*)')].strip()
return map_type(res_type, prefix, 'c_arg')
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 get_system_libs(module, platform, backend):
if module in system_libs:
if platform in system_libs[module]:
if backend in system_libs[module][platform]:
libs = system_libs[module][platform][backend]
if libs != '':
return f", {libs}"
return ''
def gen_c_imports(inp, c_prefix, prefix):
clib_prefix = f'sokol_{inp["module"]}'
clib_import = f'{clib_prefix}_clib'
windows_d3d11_libs = get_system_libs(prefix, 'windows', 'd3d11')
windows_gl_libs = get_system_libs(prefix, 'windows', 'gl')
macos_metal_libs = get_system_libs(prefix, 'macos', 'metal')
macos_gl_libs = get_system_libs(prefix, 'macos', 'gl')
linux_gl_libs = get_system_libs(prefix, 'linux', 'gl')
l( 'import "core:c"')
l( 'when ODIN_OS == .Windows {')
l( ' when #config(SOKOL_USE_GL,false) {')
l(f' when ODIN_DEBUG == true {{ foreign import {clib_import} {{ "{clib_prefix}_windows_x64_gl_debug.lib"{windows_gl_libs} }} }}')
l(f' else {{ foreign import {clib_import} {{ "{clib_prefix}_windows_x64_gl_release.lib"{windows_gl_libs} }} }}')
l( ' } else {')
l(f' when ODIN_DEBUG == true {{ foreign import {clib_import} {{ "{clib_prefix}_windows_x64_d3d11_debug.lib"{windows_d3d11_libs} }} }}')
l(f' else {{ foreign import {clib_import} {{ "{clib_prefix}_windows_x64_d3d11_release.lib"{windows_d3d11_libs} }} }}')
l( ' }')
l( '} else when ODIN_OS == .Darwin {')
l( ' when #config(SOKOL_USE_GL,false) {')
l( ' when ODIN_ARCH == .arm64 {')
l(f' when ODIN_DEBUG == true {{ foreign import {clib_import} {{ "{clib_prefix}_macos_arm64_gl_debug.a"{macos_gl_libs} }} }}')
l(f' else {{ foreign import {clib_import} {{ "{clib_prefix}_macos_arm64_gl_release.a"{macos_gl_libs} }} }}')
l( ' } else {')
l(f' when ODIN_DEBUG == true {{ foreign import {clib_import} {{ "{clib_prefix}_macos_x64_gl_debug.a"{macos_gl_libs} }} }}')
l(f' else {{ foreign import {clib_import} {{ "{clib_prefix}_macos_x64_gl_release.a"{macos_gl_libs} }} }}')
l( ' }')
l( ' } else {')
l( ' when ODIN_ARCH == .arm64 {')
l(f' when ODIN_DEBUG == true {{ foreign import {clib_import} {{ "{clib_prefix}_macos_arm64_metal_debug.a"{macos_metal_libs} }} }}')
l(f' else {{ foreign import {clib_import} {{ "{clib_prefix}_macos_arm64_metal_release.a"{macos_metal_libs} }} }}')
l( ' } else {')
l(f' when ODIN_DEBUG == true {{ foreign import {clib_import} {{ "{clib_prefix}_macos_x64_metal_debug.a"{macos_metal_libs} }} }}')
l(f' else {{ foreign import {clib_import} {{ "{clib_prefix}_macos_x64_metal_release.a"{macos_metal_libs} }} }}')
l( ' }')
l( ' }')
l( '}')
l( 'else {')
l(f' when ODIN_DEBUG == true {{ foreign import {clib_import} {{ "{clib_prefix}_linux_x64_gl_debug.a"{linux_gl_libs} }} }}')
l(f' else {{ foreign import {clib_import} {{ "{clib_prefix}_linux_x64_gl_release.a"{linux_gl_libs} }} }}')
l( '}')
# Need to special case sapp_sg to avoid Odin's context keyword
if c_prefix == "sapp_sg":
l(f'@(default_calling_convention="c")')
else:
l(f'@(default_calling_convention="c", link_prefix="{c_prefix}")')
l(f"foreign {clib_import} {{")
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 = '' if res_type == '' else f'-> {res_type}'
# Need to special case sapp_sg to avoid Odin's context keyword
if c_prefix == "sapp_sg":
l(f' @(link_name="{decl["name"]}")')
l(f" {check_override(as_snake_case(decl['name'], c_prefix))} :: proc({args}) {res_str} ---")
else:
l(f" {as_snake_case(decl['name'], c_prefix)} :: proc({args}) {res_str} ---")
l('}')
def gen_consts(decl, prefix):
for item in decl['items']:
item_name = check_override(item['name'])
l(f"{as_snake_case(item_name, prefix)} :: {item['value']}")
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_name} :: struct {{')
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')
# any field name starting with _ is considered private
if field_name.startswith('_'):
l(f' _ : {field_type},')
else:
l(f' {field_name} : {field_type},')
l('}')
def gen_enum(decl, prefix):
enum_name = check_override(decl['name'])
l(f'{as_struct_or_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_imports(dep_prefixes):
for dep_prefix in dep_prefixes:
dep_module_name = module_names[dep_prefix]
l(f'import {dep_prefix[:-1]} "../{dep_module_name}"')
l('')
def gen_helpers(inp):
if inp['prefix'] == 'sdtx_':
l('import "core:fmt"')
l('import "core:strings"')
l('printf :: proc(s: string, args: ..any) {')
l(' fstr := fmt.tprintf(s, ..args)')
l(' putr(strings.unsafe_string_to_cstring(fstr), len(fstr))')
l('}')
def gen_module(inp, c_prefix, dep_prefixes):
pre_parse(inp)
l('// machine generated, do not edit')
l('')
l(f"package sokol_{inp['module']}")
gen_imports(dep_prefixes)
gen_helpers(inp)
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)
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 Odin bindings:')
if not os.path.isdir(c_root):
os.makedirs(c_root)
if not os.path.isdir(module_root):
os.makedirs(module_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_odin_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']}/{ir['module']}.odin", 'w', newline='\n') as f_outp:
f_outp.write(out_lines)