| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562 |
- #-------------------------------------------------------------------------------
- # Read output of gen_json.py and generate Zig language bindings.
- #
- # Nim coding style:
- # - types and constants are PascalCase
- # - functions, parameters, and fields are camelCase
- #-------------------------------------------------------------------------------
- import gen_ir
- import json, re, os, shutil
- module_names = {
- 'sg_': 'gfx',
- 'sapp_': 'app',
- 'stm_': 'time',
- 'saudio_': 'audio',
- 'sgl_': 'gl',
- 'sdtx_': 'debugtext',
- 'sshape_': 'shape',
- }
- c_source_paths = {
- 'sg_': 'sokol-nim/src/sokol/c/sokol_gfx.c',
- 'sapp_': 'sokol-nim/src/sokol/c/sokol_app.c',
- 'stm_': 'sokol-nim/src/sokol/c/sokol_time.c',
- 'saudio_': 'sokol-nim/src/sokol/c/sokol_audio.c',
- 'sgl_': 'sokol-nim/src/sokol/c/sokol_gl.c',
- 'sdtx_': 'sokol-nim/src/sokol/c/sokol_debugtext.c',
- 'sshape_': 'sokol-nim/src/sokol/c/sokol_shape.c',
- }
- func_name_ignores = [
- 'sdtx_printf',
- 'sdtx_vprintf',
- ]
- func_name_overrides = {
- 'sgl_error': 'sgl_get_error', # 'error' is reserved in Zig
- 'sgl_deg': 'sgl_as_degrees',
- 'sgl_rad': 'sgl_as_radians',
- }
- struct_field_type_overrides = {
- 'sg_context_desc.color_format': 'int',
- 'sg_context_desc.depth_format': 'int',
- }
- prim_types = {
- 'int': 'int32',
- 'bool': 'bool',
- 'char': 'char',
- 'int8_t': 'int8',
- 'uint8_t': 'uint8',
- 'int16_t': 'int16',
- 'uint16_t': 'uint16',
- 'int32_t': 'int32',
- 'uint32_t': 'uint32',
- 'int64_t': 'int64',
- 'uint64_t': 'uint64',
- 'float': 'float32',
- 'double': 'float64',
- 'uintptr_t': 'uint',
- 'intptr_t': 'int',
- 'size_t': 'int',
- }
- 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 = ''
- re_1d_array = re.compile("^(?:const )?\w*\s\*?\[\d*\]$")
- re_2d_array = re.compile("^(?:const )?\w*\s\*?\[\d*\]\[\d*\]$")
- def l(s):
- global out_lines
- out_lines += s + '\n'
- def as_nim_prim_type(s):
- return prim_types[s]
- # prefix_bla_blub(_t) => (dep.)BlaBlub
- def as_nim_struct_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
- # prefix_bla_blub(_t) => (dep.)BlaBlub
- def as_nim_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
- # prefix_bla_blub(_t) => (dep.)BlaBlub
- def as_nim_const_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_struct_field_type_override(struct_name, field_name, orig_type):
- s = f"{struct_name}.{field_name}"
- if s in struct_field_type_overrides:
- return struct_field_type_overrides[s]
- else:
- return orig_type
- def check_func_name_ignore(func_name):
- return func_name in func_name_ignores
- def check_func_name_override(func_name):
- if func_name in func_name_overrides:
- return func_name_overrides[func_name]
- else:
- return func_name
- def trim_prefix(s, prefix):
- outp = s;
- if outp.lower().startswith(prefix.lower()):
- outp = outp[len(prefix):]
- return outp
- # PREFIX_BLA_BLUB to bla_blub
- def as_snake_case(s, prefix = ""):
- return trim_prefix(s, prefix).lower()
- # prefix_bla_blub => blaBlub
- def as_camel_case(s, prefix = ""):
- parts = trim_prefix(s, prefix).lower().split('_')
- outp = parts[0]
- for part in parts[1:]:
- outp += part.capitalize()
- return outp
- # prefix_bla_blub => BlaBlub
- def as_pascal_case(s, prefix):
- parts = trim_prefix(s, prefix).lower().split('_')
- outp = ""
- for part in parts:
- outp += part.capitalize()
- return outp
- # PREFIX_ENUM_BLA => Bla, _PREFIX_ENUM_BLA => Bla
- def as_enum_item_name(s):
- outp = s
- if outp.startswith('_'):
- outp = outp[1:]
- parts = outp.lower().split('_')[2:]
- outp = ""
- for part in parts:
- outp += part.capitalize()
- if outp[0].isdigit():
- outp = 'N' + 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_string_ptr(s):
- return s == "const char *"
- def is_const_void_ptr(s):
- return s == "const void *"
- def is_void_ptr(s):
- return s == "void *"
- 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 is_func_ptr(s):
- return '(*)' in s
- def is_1d_array_type(s):
- return re_1d_array.match(s)
- def is_2d_array_type(s):
- return re_2d_array.match(s)
- def type_default_value(s):
- return prim_defaults[s]
- def extract_array_type(s):
- return s[:s.index('[')].strip()
- def extract_array_nums(s):
- return s[s.index('['):].replace('[', ' ').replace(']', ' ').split()
- def extract_ptr_type(s):
- tokens = s.split()
- if tokens[0] == 'const':
- return tokens[1]
- else:
- return tokens[0]
- def as_extern_c_arg_type(arg_type, prefix):
- if arg_type == "void":
- return "void"
- elif is_prim_type(arg_type):
- return as_nim_prim_type(arg_type)
- elif is_struct_type(arg_type):
- return as_nim_struct_type(arg_type, prefix)
- elif is_enum_type(arg_type):
- return as_nim_enum_type(arg_type, prefix)
- elif is_void_ptr(arg_type):
- return "pointer"
- elif is_const_void_ptr(arg_type):
- return "pointer"
- elif is_string_ptr(arg_type):
- return "cstring"
- elif is_const_struct_ptr(arg_type):
- return f"ptr {as_nim_struct_type(extract_ptr_type(arg_type), prefix)}"
- elif is_prim_ptr(arg_type):
- return f"[*c] {as_nim_prim_type(extract_ptr_type(arg_type))}"
- elif is_const_prim_ptr(arg_type):
- return f"ptr {as_nim_prim_type(extract_ptr_type(arg_type))}"
- else:
- return '??? (as_extern_c_arg_type)'
- def as_nim_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_nim_prim_type(arg_type)
- elif is_struct_type(arg_type):
- return pre + as_nim_struct_type(arg_type, prefix)
- elif is_enum_type(arg_type):
- return pre + as_nim_enum_type(arg_type, prefix)
- elif is_void_ptr(arg_type):
- return pre + "pointer"
- elif is_const_void_ptr(arg_type):
- return pre + "pointer"
- elif is_string_ptr(arg_type):
- return pre + "cstring"
- elif is_const_struct_ptr(arg_type):
- return pre + f"ptr {as_nim_struct_type(extract_ptr_type(arg_type), prefix)}"
- elif is_prim_ptr(arg_type):
- return pre + f"ptr {as_nim_prim_type(extract_ptr_type(arg_type))}"
- elif is_const_prim_ptr(arg_type):
- return pre + f"ptr {as_nim_prim_type(extract_ptr_type(arg_type))}"
- else:
- return arg_prefix + "??? (as_nim_arg_type)"
- # 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 = ""
- n = 0
- for token in tokens:
- n += 1
- arg_type = token.strip()
- if s != "":
- s += ", "
- c_arg = f"a{n}:" + as_extern_c_arg_type(arg_type, prefix)
- if (c_arg == "void"):
- return ""
- else:
- s += c_arg
- if s == "a1:void":
- s = ""
- return s
- # get C-style result of a function pointer as string
- def funcptr_res_c(field_type):
- res_type = field_type[:field_type.index('(*)')].strip()
- if res_type == 'void':
- return ''
- elif is_const_void_ptr(res_type):
- return ':pointer'
- else:
- return '???'
- def funcdecl_args_c(decl, prefix):
- s = ""
- for param_decl in decl['params']:
- if s != "":
- s += ", "
- arg_type = param_decl['type']
- s += as_extern_c_arg_type(arg_type, prefix)
- return s
- def funcdecl_args_nim(decl, prefix):
- s = ""
- for param_decl in decl['params']:
- if s != "":
- s += ", "
- arg_name = param_decl['name']
- arg_type = param_decl['type']
- s += f"{as_nim_arg_type(f'{arg_name}:', arg_type, prefix)}"
- return s
- def funcdecl_res_c(decl, prefix):
- decl_type = decl['type']
- res_type = decl_type[:decl_type.index('(')].strip()
- return as_extern_c_arg_type(res_type, prefix)
- def funcdecl_res_nim(decl, prefix):
- decl_type = decl['type']
- res_type = decl_type[:decl_type.index('(')].strip()
- nim_res_type = as_nim_arg_type(None, res_type, prefix)
- if nim_res_type == "":
- nim_res_type = "void"
- return nim_res_type
- def gen_struct(decl, prefix, use_raw_name=False):
- struct_name = decl['name']
- nim_type = struct_name if use_raw_name else as_nim_struct_type(struct_name, prefix)
- l(f"type {nim_type}* = object")
- isPublic = True
- for field in decl['fields']:
- field_name = field['name']
- if field_name == "__pad":
- # FIXME: these should be guarded by SOKOL_ZIG_BINDINGS, but aren't?
- continue
- isPublic = not field_name.startswith("_")
- field_name = as_camel_case(field_name, "_")
- if field_name == "ptr":
- field_name = "source"
- if field_name == "ref":
- field_name = "`ref`"
- if field_name == "type":
- field_name = "`type`"
- if isPublic:
- field_name += "*"
- field_type = field['type']
- field_type = check_struct_field_type_override(struct_name, field_name, field_type)
- if is_prim_type(field_type):
- l(f" {field_name}:{as_nim_prim_type(field_type)}")
- elif is_struct_type(field_type):
- l(f" {field_name}:{as_nim_struct_type(field_type, prefix)}")
- elif is_enum_type(field_type):
- l(f" {field_name}:{as_nim_enum_type(field_type, prefix)}")
- elif is_string_ptr(field_type):
- l(f" {field_name}:cstring")
- elif is_const_void_ptr(field_type):
- l(f" {field_name}:pointer")
- elif is_void_ptr(field_type):
- l(f" {field_name}:pointer")
- elif is_const_prim_ptr(field_type):
- l(f" {field_name}:ptr {as_nim_prim_type(extract_ptr_type(field_type))}")
- elif is_func_ptr(field_type):
- l(f" {field_name}:proc({funcptr_args_c(field_type, prefix)}){funcptr_res_c(field_type)} {{.cdecl.}}")
- elif is_1d_array_type(field_type):
- array_type = extract_array_type(field_type)
- array_nums = extract_array_nums(field_type)
- if is_prim_type(array_type) or is_struct_type(array_type):
- if is_prim_type(array_type):
- nim_type = as_nim_prim_type(array_type)
- elif is_struct_type(array_type):
- nim_type = as_nim_struct_type(array_type, prefix)
- elif is_enum_type(array_type):
- nim_type = as_nim_enum_type(array_type, prefix)
- else:
- nim_type = '??? (array type)'
- t0 = f"array[{array_nums[0]}, {nim_type}]"
- t0_slice = f"[]const {nim_type}"
- t1 = f"[_]{nim_type}"
- l(f" {field_name}:{t0}")
- elif is_const_void_ptr(array_type):
- l(f" {field_name}:array[{array_nums[0]}, pointer]")
- else:
- l(f"// FIXME: ??? array {field_name}:{field_type} => {array_type} [{array_nums[0]}]")
- elif is_2d_array_type(field_type):
- array_type = extract_array_type(field_type)
- array_nums = extract_array_nums(field_type)
- if is_prim_type(array_type):
- nim_type = as_nim_prim_type(array_type)
- def_val = type_default_value(array_type)
- elif is_struct_type(array_type):
- nim_type = as_nim_struct_type(array_type, prefix)
- def_val = ".{ }"
- else:
- nim_type = "???"
- def_val = "???"
- t0 = f"array[{array_nums[0]}, array[{array_nums[1]}, {nim_type}]]"
- l(f" {field_name}:{t0}")
- else:
- l(f"// FIXME: {field_name}:{field_type};")
- l("")
- def gen_consts(decl, prefix):
- l("const")
- for item in decl['items']:
- l(f" {trim_prefix(item['name'], prefix)}* = {item['value']}")
- l("")
- def gen_enum(decl, prefix):
- item_names_by_value = {}
- value = -1
- hasForceU32 = False
- hasExplicitValues = False
- for item in decl['items']:
- itemName = item['name']
- if itemName.endswith("_FORCE_U32"):
- hasForceU32 = True
- elif itemName.endswith("_NUM"):
- continue
- else:
- if 'value' in item:
- hasExplicitValues = True
- value = int(item['value'])
- else:
- value += 1
- item_names_by_value[value] = as_enum_item_name(item['name']);
- if hasForceU32:
- l(f"type {as_nim_enum_type(decl['name'], prefix)}* {{.pure, size:4.}} = enum")
- else:
- l(f"type {as_nim_enum_type(decl['name'], prefix)}* {{.pure.}} = enum")
- if hasExplicitValues:
- # Nim requires explicit enum values to be declared in ascending order
- for value in sorted(item_names_by_value):
- name = item_names_by_value[value]
- l(f" {name} = {value},")
- else:
- for name in item_names_by_value.values():
- l(f" {name},")
- l("")
- def gen_func_nim(decl, prefix):
- c_func_name = decl['name']
- nim_func_name = as_camel_case(decl['name'], prefix)
- nim_res_type = funcdecl_res_nim(decl, prefix)
- l(f"proc {nim_func_name}*({funcdecl_args_nim(decl, prefix)}):{funcdecl_res_nim(decl, prefix)} {{.cdecl, importc:\"{decl['name']}\".}}")
- 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):
- for dep_prefix in dep_prefixes:
- dep_module_name = module_names[dep_prefix]
- l(f'import {dep_module_name}')
- l('')
- def gen_module(inp, dep_prefixes):
- l('## machine generated, do not edit')
- l('')
- gen_imports(inp, dep_prefixes)
- 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 kind == 'enum':
- gen_enum(decl, prefix)
- elif kind == 'struct':
- gen_struct(decl, prefix)
- elif kind == 'func':
- if not check_func_name_ignore(decl['name']):
- gen_func_nim(decl, prefix)
- def prepare():
- print('Generating nim bindings:')
- if not os.path.isdir('sokol-nim/src/sokol'):
- os.makedirs('sokol-nim/src/sokol')
- if not os.path.isdir('sokol-nim/src/sokol/c'):
- os.makedirs('sokol-nim/src/sokol/c')
- def gen(c_header_path, c_prefix, dep_c_prefixes):
- global out_lines
- 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-nim/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-nim/src/sokol/{ir['module']}.nim"
- ## some changes for readability
- out_lines = out_lines.replace("PixelformatInfo", "PixelFormatInfo")
- out_lines = out_lines.replace(" Dontcare,", " DontCare,")
- out_lines = out_lines.replace(" Vertexbuffer,", " VertexBuffer,")
- out_lines = out_lines.replace(" Indexbuffer,", " IndexBuffer,")
- out_lines = out_lines.replace(" N2d,", " Plane,")
- out_lines = out_lines.replace(" N3d,", " Volume,")
- out_lines = out_lines.replace(" Vs,", " Vertex,")
- out_lines = out_lines.replace(" Fs,", " Fragment,")
- ## include extensions in generated code
- l("# Nim-specific API extensions")
- l(f"include nim/{ir['module']}")
- with open(output_path, 'w', newline='\n') as f_outp:
- f_outp.write(out_lines)
|