aboutsummaryrefslogtreecommitdiff
path: root/bindgen
diff options
context:
space:
mode:
authorGarett Bass <garettbass@me.com>2021-02-25 19:29:59 -0800
committerGarett Bass <garettbass@me.com>2021-02-25 19:29:59 -0800
commit609900f2f0c183f9b27790ec1d2a74d3484d9d91 (patch)
treeb41139fb7658ef98154151a02256eaab92e6fd8c /bindgen
parentd84f79130dc6d13262f429e98fe9dc60b5320665 (diff)
added nim support to bindgen
Diffstat (limited to 'bindgen')
-rw-r--r--bindgen/.gitignore2
-rw-r--r--bindgen/README.md1
-rw-r--r--bindgen/gen_all.py10
-rw-r--r--bindgen/gen_nim.py565
4 files changed, 577 insertions, 1 deletions
diff --git a/bindgen/.gitignore b/bindgen/.gitignore
index 1b8d7732..9dcbf6fe 100644
--- a/bindgen/.gitignore
+++ b/bindgen/.gitignore
@@ -1,4 +1,6 @@
*.json
+*.nim
*.zig
__pycache__/
+sokol-nim/
sokol-zig/
diff --git a/bindgen/README.md b/bindgen/README.md
index faf6699c..ff80e4f9 100644
--- a/bindgen/README.md
+++ b/bindgen/README.md
@@ -7,6 +7,7 @@ To update the Zig bindings:
```
> cd sokol/bindgen
> git clone https://github.com/floooh/sokol-zig
+> git clone https://github.com/floooh/sokol-nim
> python3 gen_all.py
```
diff --git a/bindgen/gen_all.py b/bindgen/gen_all.py
index 11b6240e..dc409d2b 100644
--- a/bindgen/gen_all.py
+++ b/bindgen/gen_all.py
@@ -1,4 +1,4 @@
-import os, gen_zig
+import os, gen_nim, gen_zig
tasks = [
[ '../sokol_gfx.h', 'sg_', [] ],
@@ -10,6 +10,14 @@ tasks = [
[ '../util/sokol_shape.h', 'sshape_', ['sg_'] ],
]
+# Nim
+gen_nim.prepare()
+for task in tasks:
+ c_header_path = task[0]
+ main_prefix = task[1]
+ dep_prefixes = task[2]
+ gen_nim.gen(c_header_path, main_prefix, dep_prefixes)
+
# Zig
gen_zig.prepare()
for task in tasks:
diff --git a/bindgen/gen_nim.py b/bindgen/gen_nim.py
new file mode 100644
index 00000000..68bc8166
--- /dev/null
+++ b/bindgen/gen_nim.py
@@ -0,0 +1,565 @@
+#-------------------------------------------------------------------------------
+# 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-zig/src/sokol/c/sokol_app_gfx.c',
+ 'sapp_': 'sokol-zig/src/sokol/c/sokol_app_gfx.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',
+}
+
+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 "ptr uint8"
+ 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']:
+ if item['name'].endswith("_FORCE_U32"):
+ hasForceU32 = True
+ 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 ext/{ir['module']}")
+
+ ## copy extensions into generated code
+ # ext_path = f"sokol-nim/src/sokol/ext/{ir['module']}.nim"
+ # if os.path.isfile(ext_path):
+ # with open(ext_path, 'r') as f_ext:
+ # out_lines += f_ext.read()
+
+ with open(output_path, 'w', newline='\n') as f_outp:
+ f_outp.write(out_lines)