Pārlūkot izejas kodu

added nim support to bindgen

Garett Bass 4 gadi atpakaļ
vecāks
revīzija
609900f2f0
4 mainītis faili ar 577 papildinājumiem un 1 dzēšanām
  1. 2 0
      bindgen/.gitignore
  2. 1 0
      bindgen/README.md
  3. 9 1
      bindgen/gen_all.py
  4. 565 0
      bindgen/gen_nim.py

+ 2 - 0
bindgen/.gitignore

@@ -1,4 +1,6 @@
 *.json
+*.nim
 *.zig
 __pycache__/
+sokol-nim/
 sokol-zig/

+ 1 - 0
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
 ```
 

+ 9 - 1
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:

+ 565 - 0
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)