2
0
Эх сурвалжийг харах

Merge pull request #699 from floooh/odin-bindings

Add Odin language bindings generation.
Andre Weissflog 3 жил өмнө
parent
commit
9e45f2432a

+ 92 - 0
.github/workflows/gen_bindings.yml

@@ -50,6 +50,10 @@ jobs:
         with:
           repository: floooh/sokol-nim
           path: bindgen/sokol-nim
+      - uses: actions/checkout@v2
+        with:
+          repository: floooh/sokol-odin
+          path: bindgen/sokol-odin
       - name: generate
         run: |
           cd bindgen
@@ -66,6 +70,13 @@ jobs:
           name: ignore-me-nim
           path: bindgen/sokol-nim/src/sokol
           retention-days: 1
+      - name: upload-odin-artifact
+        uses: actions/upload-artifact@v2
+        with:
+          name: ignore-me-odin
+          path: |
+            bindgen/sokol-odin/sokol
+            bindgen/sokol-odin/c
 
   test-zig:
     needs: gen-bindings
@@ -118,6 +129,67 @@ jobs:
           nimble install glm -Y
           nimble build_all
 
+  test-odin:
+    needs: gen-bindings
+    strategy:
+      matrix:
+        os: [ubuntu-latest, macos-latest, windows-latest]
+    runs-on: ${{matrix.os}}
+    steps:
+      - uses: actions/checkout@v2
+        with:
+          repository: floooh/sokol-odin
+      - uses: actions/download-artifact@v2
+        with:
+          name: ignore-me-odin
+      # NOTE: see https://github.com/floooh/sokol-odin/blob/main/.github/workflows/main.yml
+      - uses: ilammy/msvc-dev-cmd@v1
+      - if: runner.os == 'Linux'
+        name: prepare-linux
+        run: |
+          sudo apt-get update
+          sudo apt-get install libglu1-mesa-dev mesa-common-dev xorg-dev libasound-dev
+          curl -L https://github.com/odin-lang/Odin/releases/download/dev-2022-08/odin-ubuntu-amd64-dev-2022-08.zip --output odin.zip
+          unzip odin.zip
+          chmod a+x odin
+          ./build_clibs_linux.sh
+      - if: runner.os == 'macOS'
+        name: prepare-macos
+        run: |
+          brew install llvm@11
+          curl -L https://github.com/odin-lang/Odin/releases/download/dev-2022-08/odin-macos-amd64-dev-2022-08.zip --output odin.zip
+          unzip odin.zip
+          chmod a+x odin
+          ./build_clibs_macos.sh
+      - if: runner.os == 'Windows'
+        name: prepare-windows
+        shell: cmd
+        run: |
+          curl -L https://github.com/odin-lang/Odin/releases/download/dev-2022-08/odin-windows-amd64-dev-2022-08.zip --output odin.zip
+          unzip odin.zip
+          build_clibs_windows.cmd
+      - name: build
+        run: |
+          ./odin build examples/clear -debug
+          ./odin build examples/triangle -debug
+          ./odin build examples/quad -debug
+          ./odin build examples/bufferoffsets -debug
+          ./odin build examples/cube -debug
+          ./odin build examples/noninterleaved -debug
+          ./odin build examples/texcube -debug
+          ./odin build examples/shapes -debug
+          ./odin build examples/offscreen -debug
+          ./odin build examples/instancing -debug
+          ./odin build examples/mrt -debug
+          ./odin build examples/blend -debug
+          ./odin build examples/debugtext -debug
+          ./odin build examples/debugtext-print -debug
+          ./odin build examples/debugtext-userfont -debug
+          ./odin build examples/saudio -debug
+          ./odin build examples/sgl -debug
+          ./odin build examples/sgl-points -debug
+          ./odin build examples/sgl-context -debug
+
   # only deploy the bindings for commits on the main branch
   deploy-zig:
     needs: test-zig
@@ -160,3 +232,23 @@ jobs:
           git add -A
           git diff-index --quiet HEAD || git commit -m "updated (https://github.com/floooh/sokol/commit/${{ github.sha }})"
           git push
+
+  deploy-odin:
+    needs: test-odin
+    if: github.ref == 'refs/heads/master'
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v2
+        with:
+          repository: floooh/sokol-odin
+          ssh-key: ${{ secrets.GHACTIONS_ODIN_PUSH }}
+      - uses: actions/download-artifact@v2
+        with:
+          name: ignore-me-odin
+      - name: "commit and push"
+        run: |
+          git config user.email "none"
+          git config user.name "GH Action"
+          git add -A
+          git diff-index --quiet HEAD || git commit -m "updated (https://github.com/floooh/sokol/commit/${{ github.sha }})"
+          git push

+ 1 - 0
bindgen/.gitignore

@@ -4,3 +4,4 @@
 __pycache__/
 sokol-nim/
 sokol-zig/
+sokol-odin/

+ 9 - 1
bindgen/gen_all.py

@@ -1,4 +1,4 @@
-import os, gen_nim, gen_zig
+import os, gen_nim, gen_zig, gen_odin
 
 tasks = [
     [ '../sokol_gfx.h',            'sg_',       [] ],
@@ -11,6 +11,14 @@ tasks = [
     [ '../util/sokol_shape.h',     'sshape_',   ['sg_'] ],
 ]
 
+# Odin
+gen_odin.prepare()
+for task in tasks:
+    c_header_path = task[0]
+    main_prefix = task[1]
+    dep_prefixes = task[2]
+    gen_odin.gen(c_header_path, main_prefix, dep_prefixes)
+
 # Nim
 gen_nim.prepare()
 for task in tasks:

+ 1 - 1
bindgen/gen_ir.py

@@ -79,7 +79,7 @@ def parse_func(decl):
     if 'inner' in decl:
         for param in decl['inner']:
             if param['kind'] != 'ParmVarDecl':
-                print(f"warning: ignoring func {decl['name']} (unsupported parameter type)")
+                print(f"  >> warning: ignoring func {decl['name']} (unsupported parameter type)")
                 return None
             outp_param = {}
             outp_param['name'] = param['name']

+ 9 - 6
bindgen/gen_nim.py

@@ -39,6 +39,9 @@ overrides = {
     'sgl_error':                    'sgl_get_error',
     'sgl_deg':                      'sgl_as_degrees',
     'sgl_rad':                      'sgl_as_radians',
+    'sg_context_desc.color_format': 'int',
+    'sg_context_desc.depth_format': 'int',
+    'SGL_NO_ERROR':                 'SGL_ERROR_NO_ERROR',
     'SG_BUFFERTYPE_VERTEXBUFFER':   'SG_BUFFERTYPE_VERTEX_BUFFER',
     'SG_BUFFERTYPE_INDEXBUFFER':    'SG_BUFFERTYPE_INDEX_BUFFER',
     'SG_ACTION_DONTCARE':           'SG_ACTION_DONT_CARE',
@@ -148,6 +151,9 @@ xor
 yield
 """.split() + common_prim_types
 
+re_1d_array = re.compile("^(?:const )?\w*\s\*?\[\d*\]$")
+re_2d_array = re.compile("^(?:const )?\w*\s\*?\[\d*\]\[\d*\]$")
+
 struct_types = []
 enum_types = []
 out_lines = ''
@@ -160,9 +166,6 @@ def reset_globals():
     enum_types = []
     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'
@@ -505,7 +508,7 @@ 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('')
+    l('')
 
 def gen_extra(inp):
     if inp['prefix'] in ['sg_']:
@@ -609,7 +612,7 @@ def gen_module(inp, dep_prefixes):
     gen_extra(inp)
 
 def prepare():
-    print('Generating nim bindings:')
+    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'):
@@ -617,7 +620,7 @@ def prepare():
 
 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...')
+        print(f'  >> warning: skipping generation for {c_prefix} prefix...')
         return
     global out_lines
     module_name = module_names[c_prefix]

+ 579 - 0
bindgen/gen_odin.py

@@ -0,0 +1,579 @@
+#-------------------------------------------------------------------------------
+#   gen_odin.py
+#
+#   Generate Odin bindings.
+#-------------------------------------------------------------------------------
+import gen_ir
+import re, os, shutil, sys
+
+bindings_root = 'sokol-odin'
+c_root = f'{bindings_root}/c'
+module_root = f'{bindings_root}/sokol'
+
+module_names = {
+    'sg_':      'gfx',
+    'sapp_':    'app',
+    'sapp_sg':  'glue',
+    'stm_':     'time',
+    'saudio_':  'audio',
+    'sgl_':     'gl',
+    'sdtx_':    'debugtext',
+    'sshape_':  'shape',
+}
+
+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 = {
+    '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',
+}
+
+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
+    'sapp_sgcontext':                       'sapp_sgctx',
+    'sg_context_desc.color_format':         'int',
+    'sg_context_desc.depth_format':         'int',
+    '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':    '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'
+}
+
+re_1d_array = re.compile("^(?:const )?\w*\s\*?\[\d*\]$")
+re_2d_array = re.compile("^(?:const )?\w*\s\*?\[\d*\]\[\d*\]$")
+
+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_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) is not None
+
+def is_2d_array_type(s):
+    return re_2d_array.match(s) is not None
+
+def type_default_value(s):
+    return prim_defaults[s]
+
+def extract_array_type(s):
+    return s[:s.index('[')].strip()
+
+def extract_array_sizes(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 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 is_void_ptr(type):
+        return "rawptr"
+    elif is_const_void_ptr(type):
+        return "rawptr"
+    elif 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(extract_ptr_type(type), prefix)}"
+        else:
+            return f"^{as_struct_or_enum_type(extract_ptr_type(type), prefix)}"
+    elif is_prim_ptr(type):
+        return f"^{as_prim_type(extract_ptr_type(type))}"
+    elif is_const_prim_ptr(type):
+        return f"^{as_prim_type(extract_ptr_type(type))}"
+    elif is_1d_array_type(type):
+        array_type = extract_array_type(type)
+        array_sizes = extract_array_sizes(type)
+        return f"[{array_sizes[0]}]{map_type(array_type, prefix, sub_type)}"
+    elif is_2d_array_type(type):
+        array_type = extract_array_type(type)
+        array_sizes = extract_array_sizes(type)
+        return f"[{array_sizes[0]}][{array_sizes[1]}]{map_type(array_type, prefix, sub_type)}"
+    elif 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'])
+        s += f"{param_name}: {map_type(param_type, prefix, 'c_arg')}"
+    return s
+
+def funcdecl_args_odin(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"{param_name}: {map_type(param_type, prefix, 'odin_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 funcdecl_result_odin(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, 'odin_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, 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( '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( '}')
+    l( '@(default_calling_convention="c")')
+    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}'
+            l(f"    {decl['name']} :: 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_func(decl, prefix):
+    c_func_name = decl['name']
+    args = funcdecl_args_odin(decl, prefix)
+    res_type = funcdecl_result_odin(decl, prefix)
+    res_str = '' if res_type == '' else f'-> {res_type}'
+    if res_type != funcdecl_result_c(decl, prefix):
+        # cast needed for return type
+        res_cast = f'cast({res_type})'
+    else:
+        res_cast = ''
+    l(f"{as_snake_case(check_override(decl['name']), prefix)} :: proc({args}) {res_str} {{")
+
+    # workaround for 'cannot take the pointer address of 'x' which is a procedure parameter
+    for param_decl in decl['params']:
+        arg_name = param_decl['name']
+        arg_type = check_override(f'{c_func_name}.{arg_name}', default=param_decl['type'])
+        if is_const_struct_ptr(arg_type):
+            l(f'    _{arg_name} := {arg_name}')
+    s = '    '
+    if res_type == '':
+        # void result
+        s += f"{c_func_name}("
+    else:
+        s += f"return {res_cast}{c_func_name}("
+    for i, param_decl in enumerate(decl['params']):
+        if i > 0:
+            s += ', '
+        arg_name = param_decl['name']
+        arg_type = check_override(f'{c_func_name}.{arg_name}', default=param_decl['type'])
+        if is_const_struct_ptr(arg_type):
+            s += f"&_{arg_name}"
+        else:
+            odin_arg_type = map_type(arg_type, prefix, 'odin_arg')
+            c_arg_type = map_type(arg_type, prefix, 'c_arg')
+            if odin_arg_type != c_arg_type:
+                cast = f'cast({c_arg_type})'
+            else:
+                cast = ''
+            s += f'{cast}{arg_name}'
+    s += ')'
+    l(s)
+    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, 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, 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(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, dep_c_prefixes)
+    with open(f"{module_root}/{ir['module']}/{ir['module']}.odin", 'w', newline='\n') as f_outp:
+        f_outp.write(out_lines)
+
+
+

+ 13 - 12
bindgen/gen_zig.py

@@ -50,6 +50,7 @@ overrides = {
     '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 = {
@@ -89,6 +90,9 @@ prim_defaults = {
     'size_t':       '0'
 }
 
+re_1d_array = re.compile("^(?:const )?\w*\s\*?\[\d*\]$")
+re_2d_array = re.compile("^(?:const )?\w*\s\*?\[\d*\]\[\d*\]$")
+
 struct_types = []
 enum_types = []
 enum_items = {}
@@ -104,9 +108,6 @@ def reset_globals():
     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'
@@ -235,7 +236,7 @@ def extract_ptr_type(s):
     else:
         return tokens[0]
 
-def as_extern_c_arg_type(arg_type, prefix):
+def as_c_arg_type(arg_type, prefix):
     if arg_type == "void":
         return "void"
     elif is_prim_type(arg_type):
@@ -257,7 +258,7 @@ def as_extern_c_arg_type(arg_type, prefix):
     elif is_const_prim_ptr(arg_type):
         return f"[*c]const {as_zig_prim_type(extract_ptr_type(arg_type))}"
     else:
-        sys.exit(f"Error as_extern_c_arg_type(): {arg_type}")
+        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
@@ -300,8 +301,8 @@ def funcptr_args_c(field_type, prefix):
         arg_type = token.strip()
         if s != "":
             s += ", "
-        c_arg = as_extern_c_arg_type(arg_type, prefix)
-        if (c_arg == "void"):
+        c_arg = as_c_arg_type(arg_type, prefix)
+        if c_arg == "void":
             return ""
         else:
             s += c_arg
@@ -327,7 +328,7 @@ def funcdecl_args_c(decl, prefix):
             s += ", "
         param_name = param_decl['name']
         param_type = check_override(f'{func_name}.{param_name}', default=param_decl['type'])
-        s += as_extern_c_arg_type(param_type, prefix)
+        s += as_c_arg_type(param_type, prefix)
     return s
 
 def funcdecl_args_zig(decl, prefix):
@@ -345,7 +346,7 @@ 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_extern_c_arg_type(result_type, prefix)
+    return as_c_arg_type(result_type, prefix)
 
 def funcdecl_result_zig(decl, prefix):
     func_name = decl['name']
@@ -483,7 +484,7 @@ def gen_imports(inp, dep_prefixes):
     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('')
+    l('')
 
 def gen_helpers(inp):
     l('// helper function to convert a C string to a Zig string slice')
@@ -558,7 +559,7 @@ def gen_module(inp, dep_prefixes):
                     gen_func_zig(decl, prefix)
 
 def prepare():
-    print('Generating zig bindings:')
+    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'):
@@ -566,7 +567,7 @@ def prepare():
 
 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...')
+        print(f' >> warning: skipping generation for {c_prefix} prefix...')
         return
     module_name = module_names[c_prefix]
     c_source_path = c_source_paths[c_prefix]