mirror of
https://github.com/Ed94/Odin.git
synced 2026-07-25 17:03:48 +00:00
In vendor/vulkan, vendor constants ending with EXT/AMD/NV/NVX/GOOGLE/KHX (all except KHR) are now present. VK_SHADER_UNUSED_KHR (as well as others previously absent) was improperly translated from (~0U) to 0. Explicit checks for "(~0U)", "(~0UL)" and "(~0ULL)" have been added to produce ~u32(0) and ~u64(0). Hardcoded constants in the bindings generator ending with EXT have been removed as they are now properly caught by the regex.
1049 lines
37 KiB
Python
1049 lines
37 KiB
Python
import re
|
||
import urllib.request as req
|
||
from tokenize import tokenize
|
||
from io import BytesIO
|
||
import string
|
||
import os.path
|
||
import math
|
||
|
||
file_and_urls = [
|
||
("vk_platform.h", 'https://raw.githubusercontent.com/KhronosGroup/Vulkan-Headers/main/include/vulkan/vk_platform.h', True),
|
||
("vulkan_core.h", 'https://raw.githubusercontent.com/KhronosGroup/Vulkan-Headers/main/include/vulkan/vulkan_core.h', False),
|
||
("vk_layer.h", 'https://raw.githubusercontent.com/KhronosGroup/Vulkan-Headers/main/include/vulkan/vk_layer.h', True),
|
||
("vk_icd.h", 'https://raw.githubusercontent.com/KhronosGroup/Vulkan-Headers/main/include/vulkan/vk_icd.h', True),
|
||
("vulkan_win32.h", 'https://raw.githubusercontent.com/KhronosGroup/Vulkan-Headers/main/include/vulkan/vulkan_win32.h', False),
|
||
("vulkan_metal.h", 'https://raw.githubusercontent.com/KhronosGroup/Vulkan-Headers/main/include/vulkan/vulkan_metal.h', False),
|
||
("vulkan_macos.h", 'https://raw.githubusercontent.com/KhronosGroup/Vulkan-Headers/main/include/vulkan/vulkan_macos.h', False),
|
||
("vulkan_ios.h", 'https://raw.githubusercontent.com/KhronosGroup/Vulkan-Headers/main/include/vulkan/vulkan_ios.h', False),
|
||
("vulkan_wayland.h", 'https://raw.githubusercontent.com/KhronosGroup/Vulkan-Headers/main/include/vulkan/vulkan_wayland.h', False),
|
||
("vulkan_xlib.h", 'https://raw.githubusercontent.com/KhronosGroup/Vulkan-Headers/main/include/vulkan/vulkan_xlib.h', False),
|
||
("vulkan_xcb.h", 'https://raw.githubusercontent.com/KhronosGroup/Vulkan-Headers/main/include/vulkan/vulkan_xcb.h', False),
|
||
("vulkan_beta.h", 'https://raw.githubusercontent.com/KhronosGroup/Vulkan-Headers/main/include/vulkan/vulkan_beta.h', False),
|
||
# Vulkan Video
|
||
("vulkan_video_codec_av1std.h", 'https://raw.githubusercontent.com/KhronosGroup/Vulkan-Headers/main/include/vk_video/vulkan_video_codec_av1std.h', False),
|
||
("vulkan_video_codec_av1std_decode.h", 'https://raw.githubusercontent.com/KhronosGroup/Vulkan-Headers/main/include/vk_video/vulkan_video_codec_av1std_decode.h', False),
|
||
("vulkan_video_codec_av1std_encode.h", 'https://raw.githubusercontent.com/KhronosGroup/Vulkan-Headers/main/include/vk_video/vulkan_video_codec_av1std_encode.h', False),
|
||
("vulkan_video_codec_h264std.h", 'https://raw.githubusercontent.com/KhronosGroup/Vulkan-Headers/main/include/vk_video/vulkan_video_codec_h264std.h', False),
|
||
("vulkan_video_codec_h264std_decode.h", 'https://raw.githubusercontent.com/KhronosGroup/Vulkan-Headers/main/include/vk_video/vulkan_video_codec_h264std_decode.h', False),
|
||
("vulkan_video_codec_h264std_encode.h", 'https://raw.githubusercontent.com/KhronosGroup/Vulkan-Headers/main/include/vk_video/vulkan_video_codec_h264std_encode.h', False),
|
||
("vulkan_video_codec_h265std.h", 'https://raw.githubusercontent.com/KhronosGroup/Vulkan-Headers/main/include/vk_video/vulkan_video_codec_h265std.h', False),
|
||
("vulkan_video_codec_h265std_decode.h", 'https://raw.githubusercontent.com/KhronosGroup/Vulkan-Headers/main/include/vk_video/vulkan_video_codec_h265std_decode.h', False),
|
||
("vulkan_video_codec_h265std_encode.h", 'https://raw.githubusercontent.com/KhronosGroup/Vulkan-Headers/main/include/vk_video/vulkan_video_codec_h265std_encode.h', False),
|
||
]
|
||
|
||
for file, url, _ in file_and_urls:
|
||
if not os.path.isfile(file):
|
||
with open(file, 'w', encoding='utf-8') as f:
|
||
f.write(req.urlopen(url).read().decode('utf-8'))
|
||
|
||
src = ""
|
||
for file, _, skip in file_and_urls:
|
||
if skip: continue
|
||
with open(file, 'r', encoding='utf-8') as f:
|
||
src += f.read()
|
||
|
||
|
||
def no_vk(t):
|
||
t = t.replace('PFN_vk_icd', 'Procicd')
|
||
t = t.replace('PFN_vk', 'Proc')
|
||
t = t.replace('PFN_', 'Proc')
|
||
t = t.replace('PFN_', 'Proc')
|
||
|
||
t = re.sub('(?:Vk|VK_)?(\\w+)', '\\1', t)
|
||
|
||
# Vulkan Video
|
||
t = re.sub('(?:Std|STD_|VK_STD)?(\\w+)', '\\1', t)
|
||
return t
|
||
|
||
OPAQUE_STRUCTS = """
|
||
wl_surface :: struct {} // Opaque struct defined by Wayland
|
||
wl_display :: struct {} // Opaque struct defined by Wayland
|
||
xcb_connection_t :: struct {} // Opaque struct defined by xcb
|
||
IOSurfaceRef :: struct {} // Opaque struct defined by Apple’s CoreGraphics framework
|
||
"""
|
||
|
||
def convert_type(t, prev_name, curr_name):
|
||
table = {
|
||
"Bool32": 'b32',
|
||
"float": 'f32',
|
||
"double": 'f64',
|
||
"uint32_t": 'u32',
|
||
"uint64_t": 'u64',
|
||
"size_t": 'int',
|
||
'int16_t': 'i16',
|
||
'int32_t': 'i32',
|
||
'int64_t': 'i64',
|
||
'int': 'c.int',
|
||
'uint8_t': 'u8',
|
||
'int8_t': 'i8',
|
||
"uint16_t": 'u16',
|
||
"char": "byte",
|
||
"void": "void",
|
||
"void*": "rawptr",
|
||
"void *": "rawptr",
|
||
"char*": 'cstring',
|
||
"const uint32_t* const*": "^[^]u32",
|
||
"const void*": 'rawptr',
|
||
"const char*": 'cstring',
|
||
"const char* const*": '[^]cstring',
|
||
"const ObjectTableEntryNVX* const*": "^^ObjectTableEntryNVX",
|
||
"const void* const *": "[^]rawptr",
|
||
"const AccelerationStructureGeometryKHR* const*": "^[^]AccelerationStructureGeometryKHR",
|
||
"const AccelerationStructureBuildRangeInfoKHR* const*": "^[^]AccelerationStructureBuildRangeInfoKHR",
|
||
"const MicromapUsageEXT* const*": "^[^]MicromapUsageEXT",
|
||
"struct BaseOutStructure": "BaseOutStructure",
|
||
"struct BaseInStructure": "BaseInStructure",
|
||
"struct wl_display": "wl_display",
|
||
"struct wl_surface": "wl_surface",
|
||
"Display": "XlibDisplay",
|
||
"Window": "XlibWindow",
|
||
"VisualID": "XlibVisualID",
|
||
'v': '',
|
||
}
|
||
|
||
if t in table.keys():
|
||
return table[t]
|
||
|
||
if t == "":
|
||
return t
|
||
|
||
if t.startswith("const"):
|
||
t = convert_type(t[6:], prev_name, curr_name)
|
||
|
||
elif t.endswith("*"):
|
||
pointer = "^"
|
||
ttype = t[:len(t)-1]
|
||
elem = convert_type(ttype, prev_name, curr_name)
|
||
|
||
if curr_name.endswith("s") or curr_name.endswith("Table"):
|
||
if prev_name.endswith("Count") or prev_name.endswith("Counts"):
|
||
pointer = "[^]"
|
||
elif curr_name.startswith("pp"):
|
||
if elem.startswith("[^]"):
|
||
pass
|
||
else:
|
||
pointer = "[^]"
|
||
elif curr_name.startswith("p"):
|
||
pointer = "[^]"
|
||
|
||
if curr_name and elem.endswith("Flags"):
|
||
pointer = "[^]"
|
||
|
||
return "{}{}".format(pointer, elem)
|
||
elif t[0].isupper():
|
||
return t
|
||
|
||
return t
|
||
|
||
def parse_array(n, t):
|
||
name, length = n.split('[', 1)
|
||
length = no_vk(length[:-1])
|
||
type_ = "[{}]{}".format(length, do_type(t))
|
||
return name, type_
|
||
|
||
def remove_prefix(text, prefix):
|
||
if text.startswith(prefix):
|
||
return text[len(prefix):]
|
||
return text
|
||
def remove_suffix(text, suffix):
|
||
if text.endswith(suffix):
|
||
return text[:-len(suffix)]
|
||
return text
|
||
|
||
|
||
def to_snake_case(name):
|
||
s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name)
|
||
return re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower()
|
||
|
||
ext_suffixes = ["KHR", "EXT", "AMD", "NV", "NVX", "GOOGLE", "KHX"]
|
||
ext_suffixes_title = [ext.title() for ext in ext_suffixes]
|
||
|
||
|
||
def fix_arg(arg):
|
||
name = arg
|
||
|
||
# Remove useless pointer identifier in field name
|
||
for p in ('s_', 'p_', 'pp_', 'pfn_'):
|
||
if name.startswith(p):
|
||
name = name[len(p)::]
|
||
name = name.replace("__", "_")
|
||
|
||
return name
|
||
|
||
|
||
def fix_ext_suffix(name):
|
||
for ext in ext_suffixes_title:
|
||
if name.endswith(ext):
|
||
start = name[:-len(ext)]
|
||
end = name[-len(ext):].upper()
|
||
return start+end
|
||
return name
|
||
|
||
def to_int(x):
|
||
if x.startswith('0x'):
|
||
return int(x, 16)
|
||
return int(x)
|
||
|
||
def is_int(x):
|
||
try:
|
||
int(x)
|
||
return True
|
||
except ValueError:
|
||
return False
|
||
|
||
def fix_enum_arg(name, is_flag_bit=False):
|
||
# name = name.title()
|
||
name = fix_ext_suffix(name)
|
||
if len(name) > 0 and name[0].isdigit() and not name.startswith("0x") and not is_int(name):
|
||
if name[1] == "D":
|
||
name = name[1] + name[0] + (name[2:] if len(name) > 2 else "")
|
||
else:
|
||
name = "_"+name
|
||
if is_flag_bit:
|
||
name = name.replace("_BIT", "")
|
||
return name
|
||
|
||
def do_type(t, prev_name="", name=""):
|
||
return convert_type(no_vk(t), prev_name, name).replace("FlagBits", "Flags")
|
||
|
||
def parse_handles_def(f):
|
||
f.write("// Handles types\n")
|
||
handles = [h for h in re.findall(r"VK_DEFINE_HANDLE\(Vk(\w+)\)", src, re.S)]
|
||
|
||
max_len = max(len(h) for h in handles)
|
||
for h in handles:
|
||
f.write("{} :: distinct Handle\n".format(h.ljust(max_len)))
|
||
|
||
handles_non_dispatchable = [h for h in re.findall(r"VK_DEFINE_NON_DISPATCHABLE_HANDLE\(Vk(\w+)\)", src, re.S)]
|
||
max_len = max(len(h) for h in handles_non_dispatchable)
|
||
for h in handles_non_dispatchable:
|
||
f.write("{} :: distinct NonDispatchableHandle\n".format(h.ljust(max_len)))
|
||
|
||
|
||
flags_defs = set()
|
||
|
||
def parse_flags_def(f):
|
||
names = [n for n in re.findall(r"typedef VkFlags Vk(\w+?);", src)]
|
||
|
||
global flags_defs
|
||
flags_defs = set(names)
|
||
|
||
|
||
class FlagError(ValueError):
|
||
pass
|
||
class IgnoreFlagError(ValueError):
|
||
pass
|
||
|
||
def fix_enum_name(name, prefix, suffix, is_flag_bit):
|
||
name = remove_prefix(name, prefix)
|
||
if suffix:
|
||
name = remove_suffix(name, suffix)
|
||
if name.startswith("0x"):
|
||
if is_flag_bit:
|
||
i = int(name, 16)
|
||
if i == 0:
|
||
raise IgnoreFlagError(i)
|
||
v = int(math.log2(i))
|
||
if 2**v != i:
|
||
raise FlagError(i)
|
||
return str(v)
|
||
return name
|
||
elif is_flag_bit:
|
||
ignore = False
|
||
try:
|
||
if int(name) == 0:
|
||
ignore = True
|
||
except:
|
||
pass
|
||
if ignore:
|
||
raise IgnoreFlagError()
|
||
|
||
return fix_enum_arg(name, is_flag_bit)
|
||
|
||
|
||
def fix_enum_value(value, prefix, suffix, is_flag_bit):
|
||
v = no_vk(value)
|
||
g = tokenize(BytesIO(v.encode('utf-8')).readline)
|
||
tokens = [val for _, val, _, _, _ in g]
|
||
assert len(tokens) > 2
|
||
token = ''.join([t for t in tokens[1:-1] if t])
|
||
token = fix_enum_name(token, prefix, suffix, is_flag_bit)
|
||
return token
|
||
|
||
def parse_constants(f):
|
||
f.write("// General Constants\n")
|
||
all_data = re.findall(r"#define VK_(\w+)\s*(.*?)U?\n", src, re.S)
|
||
allowed_names = (
|
||
"HEADER_VERSION",
|
||
"MAX_DRIVER_NAME_SIZE",
|
||
"MAX_DRIVER_INFO_SIZE",
|
||
)
|
||
allowed_data = [nv for nv in all_data if nv[0] in allowed_names]
|
||
max_len = max(len(name) for name, value in allowed_data)
|
||
for name, value in allowed_data:
|
||
f.write("{}{} :: {}\n".format(name, "".rjust(max_len-len(name)), value))
|
||
|
||
f.write("\n// Vulkan Video Constants\n")
|
||
vulkan_video_data = re.findall(r"#define STD_(\w+)\s*(.*?)U?\n", src, re.S)
|
||
max_len = max(len(name) for name, value in vulkan_video_data)
|
||
for name, value in vulkan_video_data:
|
||
f.write("{}{} :: {}\n".format(name, "".rjust(max_len-len(name)), value))
|
||
|
||
f.write("\n// Vulkan Video Codec Constants\n")
|
||
vulkan_video_codec_allowed_suffixes = (
|
||
"_EXTENSION_NAME",
|
||
)
|
||
vulkan_video_codec_data = re.findall(r"#define VK_STD_(\w+)\s*(.*?)U?\n", src, re.S)
|
||
vulkan_video_codec_allowed_data = [nv for nv in vulkan_video_codec_data if nv[0].endswith(vulkan_video_codec_allowed_suffixes)]
|
||
max_len = max(len(name) for name, value in vulkan_video_codec_allowed_data)
|
||
for name, value in vulkan_video_codec_allowed_data:
|
||
f.write("{}{} :: {}\n".format(name, "".rjust(max_len-len(name)), value))
|
||
|
||
f.write("\n// Vendor Constants\n")
|
||
fixes = '|'.join(ext_suffixes)
|
||
inner = r"((?:(?:" + fixes + r")\w+)|(?:\w+(?:" + fixes + r")\b))"
|
||
pattern = r"#define\s+VK_" + inner + r"\s*(.*?)\n"
|
||
data = re.findall(pattern, src, re.S)
|
||
|
||
number_suffix_re = re.compile(r"(\d+)[UuLlFf]")
|
||
|
||
max_len = max(len(name) for name, value in data)
|
||
for name, value in data:
|
||
value = remove_prefix(value, 'VK_')
|
||
v = number_suffix_re.findall(value)
|
||
if value == "(~0U)" or value == "(~0UL)":
|
||
value = "~u32(0)"
|
||
elif value == "(~0ULL)":
|
||
value = "~u64(0)"
|
||
elif v:
|
||
value = v[0]
|
||
f.write("{}{} :: {}\n".format(name, "".rjust(max_len-len(name)), value))
|
||
f.write("\n")
|
||
|
||
|
||
def parse_enums(f):
|
||
f.write("import \"core:c\"\n\n")
|
||
f.write("// Enums\n")
|
||
|
||
data = re.findall(r"typedef enum (\w+) {(.+?)} \w+;", src, re.S)
|
||
data = [(no_vk(n), f) for n, f in data]
|
||
|
||
data.sort(key=lambda x: x[0])
|
||
|
||
generated_flags = set()
|
||
|
||
for name, fields in data:
|
||
enum_name = name
|
||
|
||
is_flag_bit = False
|
||
if "FlagBits" in enum_name:
|
||
is_flag_bit = True
|
||
flags_name = enum_name.replace("FlagBits", "Flags")
|
||
enum_name = enum_name.replace("FlagBits", "Flag")
|
||
generated_flags.add(flags_name)
|
||
f.write("{} :: distinct bit_set[{}; Flags]\n".format(flags_name, enum_name))
|
||
|
||
if is_flag_bit:
|
||
f.write("{} :: enum Flags {{\n".format(name.replace("FlagBits", "Flag")))
|
||
else:
|
||
f.write("{} :: enum c.int {{\n".format(name))
|
||
|
||
prefix = to_snake_case(name).upper()
|
||
suffix = None
|
||
for ext in ext_suffixes:
|
||
prefix_new = remove_suffix(prefix, "_"+ext)
|
||
assert suffix is None
|
||
if prefix_new != prefix:
|
||
suffix = "_"+ext
|
||
prefix = prefix_new
|
||
break
|
||
|
||
|
||
prefix = prefix.replace("_FLAG_BITS", "")
|
||
prefix += "_"
|
||
|
||
ff = []
|
||
|
||
names_and_values = re.findall(r"VK_(\w+?) = (.*?)(?:,|})", fields, re.S)
|
||
|
||
groups = []
|
||
flags = {}
|
||
|
||
for name, value in names_and_values:
|
||
n = fix_enum_name(name, prefix, suffix, is_flag_bit)
|
||
try:
|
||
v = fix_enum_value(value, prefix, suffix, is_flag_bit)
|
||
except FlagError as e:
|
||
v = int(str(e))
|
||
groups.append((n, v))
|
||
continue
|
||
except IgnoreFlagError as e:
|
||
groups.append((n, 0))
|
||
continue
|
||
|
||
if n == v:
|
||
continue
|
||
try:
|
||
flags[int(v)] = n
|
||
except ValueError as e:
|
||
pass
|
||
|
||
if v == "NONE":
|
||
continue
|
||
|
||
ff.append((n, v))
|
||
|
||
max_flag_value = max([int(v) for n, v in ff if is_int(v)] + [0])
|
||
max_group_value = max([int(v) for n, v in groups if is_int(v)] + [0])
|
||
if max_flag_value < max_group_value:
|
||
if (1<<max_flag_value)+1 < max_group_value:
|
||
ff.append(('_MAX', 31))
|
||
flags[31] = '_MAX'
|
||
pass
|
||
|
||
max_len = max([len(n) for n, v in ff] + [0])
|
||
|
||
flag_names = set([n for n, v in ff])
|
||
|
||
for n, v in ff:
|
||
if is_flag_bit and not is_int(v) and v not in flag_names:
|
||
print("Ignoring", n, "=", v)
|
||
continue
|
||
f.write("\t{} = {},".format(n.ljust(max_len), v))
|
||
if n == "_MAX":
|
||
f.write(" // Needed for the *_ALL bit set")
|
||
f.write("\n")
|
||
|
||
|
||
|
||
f.write("}\n\n")
|
||
|
||
for n, v in groups:
|
||
used_flags = []
|
||
for i in range(0, 32):
|
||
if 1<<i & v != 0:
|
||
if i in flags:
|
||
used_flags.append('.'+flags[i])
|
||
else:
|
||
used_flags.append('{}({})'.format(enum_name, i))
|
||
# Make sure the 's' is after Flags and not the extension name.
|
||
ext_suffix = ''
|
||
for suffix in ext_suffixes:
|
||
if not enum_name.endswith(suffix):
|
||
continue
|
||
|
||
ext_suffix = suffix
|
||
enum_name = remove_suffix(enum_name, ext_suffix)
|
||
break
|
||
s = "{enum_name}s{ext_suffix}_{n} :: {enum_name}s{ext_suffix}{{".format(enum_name=enum_name, ext_suffix=ext_suffix, n=n)
|
||
s += ', '.join(used_flags)
|
||
s += "}\n"
|
||
f.write(s)
|
||
|
||
if len(groups) > 0:
|
||
f.write("\n\n")
|
||
|
||
|
||
unused_flags = [flag for flag in flags_defs if flag not in generated_flags]
|
||
unused_flags.sort()
|
||
|
||
max_len = max(len(flag) for flag in unused_flags)
|
||
for flag in unused_flags:
|
||
flag_name = flag.replace("Flags", "Flag")
|
||
f.write("{} :: distinct bit_set[{}; Flags]\n".format(flag.ljust(max_len), flag_name))
|
||
f.write("{} :: enum u32 {{}}\n".format(flag_name.ljust(max_len)))
|
||
|
||
def parse_fake_enums(f):
|
||
data = re.findall(r"static const Vk(\w+FlagBits2) VK_(\w+?) = (\w+);", src, re.S)
|
||
|
||
data.sort(key=lambda x: x[0])
|
||
|
||
fake_enums = {}
|
||
|
||
for type_name, name, value in data:
|
||
if type_name in fake_enums:
|
||
fake_enums[type_name].append((name,value))
|
||
else:
|
||
fake_enums[type_name] = [(name, value)]
|
||
|
||
for name in fake_enums.keys():
|
||
flags_name = name.replace("FlagBits", "Flags")
|
||
enum_name = name.replace("FlagBits", "Flag")
|
||
f.write("{} :: distinct bit_set[{}; Flags64]\n".format(flags_name, enum_name))
|
||
f.write("{} :: enum Flags64 {{\n".format(name.replace("FlagBits", "Flag")))
|
||
|
||
prefix = to_snake_case(name).upper()
|
||
suffix = None
|
||
for ext in ext_suffixes:
|
||
prefix_new = remove_suffix(prefix, "_"+ext)
|
||
assert suffix is None
|
||
if prefix_new != prefix:
|
||
suffix = "_"+ext
|
||
prefix = prefix_new
|
||
break
|
||
|
||
|
||
prefix = prefix.replace("_FLAG_BITS2", "_2")
|
||
prefix += "_"
|
||
|
||
ff = []
|
||
|
||
groups = []
|
||
flags = {}
|
||
|
||
names_and_values = fake_enums[name]
|
||
|
||
for name, value in names_and_values:
|
||
value = value.replace("ULL", "")
|
||
n = fix_enum_name(name, prefix, suffix, True)
|
||
try:
|
||
v = fix_enum_value(value, prefix, suffix, True)
|
||
except FlagError as e:
|
||
v = int(str(e))
|
||
groups.append((n, v))
|
||
continue
|
||
except IgnoreFlagError as e:
|
||
groups.append((n, 0))
|
||
continue
|
||
|
||
if n == v:
|
||
continue
|
||
try:
|
||
flags[int(v)] = n
|
||
except ValueError as e:
|
||
pass
|
||
|
||
if v == "NONE":
|
||
continue
|
||
|
||
ff.append((n, v))
|
||
|
||
max_flag_value = max([int(v) for n, v in ff if is_int(v)] + [0])
|
||
max_group_value = max([int(v) for n, v in groups if is_int(v)] + [0])
|
||
if max_flag_value < max_group_value:
|
||
if (1<<max_flag_value)+1 < max_group_value:
|
||
ff.append(('_MAX', 31))
|
||
flags[31] = '_MAX'
|
||
pass
|
||
|
||
max_len = max([len(n) for n, v in ff] + [0])
|
||
|
||
flag_names = set([n for n, v in ff])
|
||
|
||
for n, v in ff:
|
||
if not is_int(v) and v not in flag_names:
|
||
print("Ignoring", n, "=", v)
|
||
continue
|
||
f.write("\t{} = {},".format(n.ljust(max_len), v))
|
||
if n == "_MAX":
|
||
f.write(" // Needed for the *_ALL bit set")
|
||
f.write("\n")
|
||
|
||
f.write("}\n\n")
|
||
|
||
class BitfieldError(ValueError):
|
||
pass
|
||
|
||
def bitfield_type_to_size(type_):
|
||
if type_ == 'u8':
|
||
return 8
|
||
if type_ == 'u16':
|
||
return 16
|
||
if type_ == 'u32':
|
||
return 32
|
||
if type_ == 'u64':
|
||
return 64
|
||
if 'Flags' in type_:
|
||
return 32
|
||
else:
|
||
raise BitfieldError(f"Invalid type for bitfield: {type_}")
|
||
|
||
def bitfield_size_to_type(size):
|
||
if size == 8:
|
||
return 'u8'
|
||
if size == 16:
|
||
return 'u16'
|
||
if size == 32:
|
||
return 'u32'
|
||
if size == 64:
|
||
return 'u64'
|
||
else:
|
||
raise BitfieldError(f"Invalid size for bitfield: {size}")
|
||
|
||
|
||
class Bitfield:
|
||
class Field:
|
||
def __init__(self, name, type_, bitsize):
|
||
self.name = name
|
||
self.type = type_
|
||
self.bitsize = bitsize
|
||
|
||
def __init__(self, type_):
|
||
self.bitsize = bitfield_type_to_size(type_)
|
||
self.type = bitfield_size_to_type(self.bitsize)
|
||
self.fields_bitsize = 0
|
||
self.fields = []
|
||
|
||
def add_field(self, name, type_, bitsize):
|
||
self.fields.append(Bitfield.Field(name, type_, bitsize))
|
||
self.fields_bitsize += bitsize
|
||
|
||
def write(self, f, name=None, indent=0, justify=True):
|
||
max_name = 1 if not justify else max([len(f.name) for f in self.fields], default=0)
|
||
max_type = 1 if not justify else max([len(f.type) for f in self.fields], default=0)
|
||
is_bit_set = all([f.bitsize == 1 or f.name == "reserved" for f in self.fields])
|
||
if is_bit_set and name is None:
|
||
raise BitfieldError(f"bit_set can not be anonymous")
|
||
|
||
if is_bit_set:
|
||
if not name.endswith("Flags"):
|
||
raise BitfieldError(f"bit_set name should end with 'Flags': {name}")
|
||
enum_name = re.sub('Flags$', 'Flag', name)
|
||
f.write("{}{} :: distinct bit_set[{}; {}]\n".format('\t' * indent, name, enum_name, self.type))
|
||
f.write("{}{} :: enum {} {{\n".format('\t' * indent, enum_name, self.type))
|
||
for field in self.fields:
|
||
if field.name != "reserved":
|
||
f.write("{}{},\n".format('\t' * (indent + 1), field.name))
|
||
f.write(('\t' * indent) + "}\n")
|
||
|
||
else:
|
||
f.write("{}{} bit_field {} {{\n".format('\t' * indent, name + ' ::' if name else 'using _:', self.type))
|
||
for field in self.fields:
|
||
type_ = field.type.replace("Flags", "Flag")
|
||
f.write("{}{} {} | {},\n".format(
|
||
'\t' * (indent + 1),
|
||
(field.name + ":").ljust(max_name + 1),
|
||
type_.ljust(max_type),
|
||
field.bitsize))
|
||
f.write(('\t' * indent) + "}" + ("," if name is None else "") + "\n")
|
||
|
||
def parse_structs(f):
|
||
data = re.findall(r"typedef (struct|union) Vk(\w+?) {(.+?)} \w+?;", src, re.S)
|
||
data += re.findall(r"typedef (struct|union) Std(\w+?) {(.+?)} \w+?;", src, re.S)
|
||
|
||
for _type, struct_name, fields in data:
|
||
fields = re.findall(r"\s+(.+?)[\s:]+([_a-zA-Z0-9[\]]+);", fields)
|
||
|
||
prev_name = ""
|
||
ffields = []
|
||
bitfield = None
|
||
for type_, fname in fields:
|
||
# If the field name only has a number in it, then it is a C bit field.
|
||
# We will collect all the bit fields and then create either a bit_field or a bit_set.
|
||
if is_int(fname):
|
||
bf_field = type_.split(' ')
|
||
# Get rid of empty spaces
|
||
bf_field = list(filter(bool, bf_field))
|
||
# [type, fieldname]
|
||
assert len(bf_field) == 2, "Failed to parse the bit field!"
|
||
field_type = do_type(bf_field[0])
|
||
bitsize = int(fname)
|
||
|
||
# Close the set because the field size is greater than the bitfield type
|
||
if bitfield and (bitfield.fields_bitsize + bitsize) > bitfield_type_to_size(field_type):
|
||
ffields.append(tuple([None, bitfield]))
|
||
bitfield = None
|
||
|
||
# Raise an error if the field type size is greater than the bitfield type size
|
||
if bitfield is not None and bitfield_type_to_size(bitfield.type) < bitfield_type_to_size(field_type):
|
||
raise BitfieldError(f"field will not fit in the bitfield: {bitfield.type} < {field_type}")
|
||
|
||
# Create a new bitfield if we don't have one
|
||
if not bitfield:
|
||
bitfield = Bitfield(field_type)
|
||
|
||
# Add the field to the bitfield
|
||
bitfield.add_field(bf_field[1], field_type, bitsize)
|
||
continue
|
||
|
||
# Close the bitfield because this is not a field
|
||
elif bitfield:
|
||
ffields.append(tuple([None, bitfield]))
|
||
bitfield = None
|
||
|
||
if '[' in fname:
|
||
fname, type_ = parse_array(fname, type_)
|
||
n = fix_arg(fname)
|
||
if "Flag_Bits" in type_:
|
||
# comment = " // only single bit set"
|
||
raise BitfieldError("only single bit set")
|
||
t = do_type(type_, prev_name, fname)
|
||
if n == "matrix":
|
||
n = "mat"
|
||
|
||
ffields.append(tuple([n, t]))
|
||
prev_name = fname
|
||
|
||
# Close the bitfield because we have no more fields
|
||
if bitfield:
|
||
ffields.append(tuple([None, bitfield]))
|
||
|
||
# Write the struct as a bitfield if it only has bit fields
|
||
if len(ffields) == 1 and ffields[0][0] is None:
|
||
ffields[0][1].write(f, struct_name, 0, True)
|
||
f.write("\n")
|
||
|
||
# Write as a normal struct (or union) if it has other fields
|
||
# and inject anonymous bitfields into the struct if there are any
|
||
else:
|
||
has_anon_bitfield = any(name is None for name, _ in ffields)
|
||
max_len = max([0 if n is None else len(n) for n, _ in ffields], default=0)
|
||
f.write("{} :: struct ".format(struct_name))
|
||
if _type == "union":
|
||
f.write("#raw_union ")
|
||
f.write("{\n")
|
||
for name, type_ in ffields:
|
||
if name is None:
|
||
# Inject an anonymous bitfield into the struct
|
||
type_.write(f, None, indent=1, justify=True)
|
||
else:
|
||
f.write("\t{} {},\n".format((name + ":").ljust(max_len + 1), type_))
|
||
f.write("}\n\n")
|
||
|
||
f.write("// Opaque structs\n")
|
||
f.write(OPAQUE_STRUCTS)
|
||
|
||
f.write("// Aliases\n")
|
||
data = re.findall(r"typedef Vk(\w+?) Vk(\w+?);", src, re.S)
|
||
aliases = []
|
||
for _type, name in data:
|
||
if _type == "Flags":
|
||
continue
|
||
name = name.replace("FlagBits", "Flag")
|
||
_type = _type.replace("FlagBits", "Flag")
|
||
|
||
if name.endswith("Flag2") or name.endswith("Flags2"):
|
||
continue
|
||
|
||
aliases.append((name, _type))
|
||
|
||
max_len = max([len(n) for n, _ in aliases] + [0])
|
||
for n, t in aliases:
|
||
k = max_len
|
||
f.write("{} :: {}\n".format(n.ljust(k), t))
|
||
|
||
|
||
|
||
procedure_map = {}
|
||
|
||
def parse_procedures(f):
|
||
data = re.findall(r"typedef (\w+\*?) \(\w+ \*(\w+)\)\((.+?)\);", src, re.S)
|
||
|
||
group_ff = {"Loader":[], "Misc":[], "Instance":[], "Device":[]}
|
||
|
||
for rt, name, fields in data:
|
||
proc_name = no_vk(name)
|
||
pf = []
|
||
prev_name = ""
|
||
for type_, fname, array_len in re.findall(r"(?:\s*|)(.+?)\s*(\w+)(?:\[(\d+)\])?(?:,|$)", fields):
|
||
curr_name = fix_arg(fname)
|
||
ty = do_type(type_, prev_name, curr_name)
|
||
if array_len != "":
|
||
ty = f"^[{array_len}]{ty}"
|
||
pf.append((ty, curr_name))
|
||
prev_name = curr_name
|
||
|
||
data_fields = ', '.join(["{}: {}".format(n, t) for t, n in pf if t != ""])
|
||
|
||
ts = "proc \"c\" ({})".format(data_fields)
|
||
rt_str = do_type(rt)
|
||
if rt_str != "void":
|
||
ts += " -> {}".format(rt_str)
|
||
|
||
procedure_map[proc_name] = ts
|
||
|
||
fields_types_name = [do_type(t) for t in re.findall(r"(?:\s*|)(.+?)\s*\w+(?:,|$)", fields)]
|
||
table_name = fields_types_name[0]
|
||
nn = (proc_name, ts)
|
||
if table_name in ('Device', 'Queue', 'CommandBuffer') and proc_name != 'GetDeviceProcAddr':
|
||
group_ff["Device"].append(nn)
|
||
elif table_name in ('Instance', 'PhysicalDevice') or proc_name == 'GetDeviceProcAddr':
|
||
group_ff["Instance"].append(nn)
|
||
elif table_name in ('rawptr', '', 'DebugReportFlagsEXT') or proc_name == 'GetInstanceProcAddr':
|
||
group_ff["Misc"].append(nn)
|
||
else:
|
||
group_ff["Loader"].append(nn)
|
||
|
||
|
||
f.write("import \"core:c\"\n\n")
|
||
for group_name, ff in group_ff.items():
|
||
ff.sort()
|
||
f.write("// {} Procedure Types\n".format(group_name))
|
||
max_len = max(len(n) for n, t in ff)
|
||
for n, t in ff:
|
||
f.write("{} :: #type {}\n".format(n.ljust(max_len), t.replace('"c"', '"system"')))
|
||
f.write("\n")
|
||
|
||
def group_functions(f):
|
||
data = re.findall(r"typedef (\w+\*?) \(\w+ \*(\w+)\)\((.+?)\);", src, re.S)
|
||
group_map = {"Loader":[], "Instance":[], "Device":[]}
|
||
|
||
for rt, vkname, fields in data:
|
||
fields_types_name = [do_type(t) for t in re.findall(r"(?:\s*|)(.+?)\s*\w+(?:,|$)", fields)]
|
||
table_name = fields_types_name[0]
|
||
name = no_vk(vkname)
|
||
|
||
nn = (fix_arg(name), fix_ext_suffix(name))
|
||
|
||
if table_name in ('Device', 'Queue', 'CommandBuffer') and name != 'GetDeviceProcAddr':
|
||
group_map["Device"].append(nn)
|
||
elif table_name in ('Instance', 'PhysicalDevice') and name != 'ProcGetInstanceProcAddr' or name == 'GetDeviceProcAddr':
|
||
group_map["Instance"].append(nn)
|
||
elif table_name in ('rawptr', '', 'DebugReportFlagsEXT') or name == 'GetInstanceProcAddr':
|
||
# Skip the allocation function and the dll entry point
|
||
pass
|
||
else:
|
||
group_map["Loader"].append(nn)
|
||
for _, group in group_map.items():
|
||
group.sort()
|
||
|
||
for group_name, group_lines in group_map.items():
|
||
f.write("// {} Procedures\n".format(group_name))
|
||
max_len = max(len(name) for name, _ in group_lines)
|
||
for name, vk_name in group_lines:
|
||
type_str = procedure_map[vk_name]
|
||
f.write('{}: {}\n'.format(remove_prefix(name, "Proc"), name.rjust(max_len)))
|
||
f.write("\n")
|
||
|
||
f.write("load_proc_addresses_custom :: proc(set_proc_address: SetProcAddressType) {\n")
|
||
for group_name, group_lines in group_map.items():
|
||
f.write("\t// {} Procedures\n".format(group_name))
|
||
max_len = max(len(name) for name, _ in group_lines)
|
||
for name, vk_name in group_lines:
|
||
k = max_len - len(name)
|
||
f.write('\tset_proc_address(&{}, {}"vk{}")\n'.format(
|
||
remove_prefix(name, 'Proc'),
|
||
"".ljust(k),
|
||
remove_prefix(vk_name, 'Proc'),
|
||
))
|
||
f.write("\n")
|
||
f.write("}\n\n")
|
||
|
||
f.write("// Device Procedure VTable\n")
|
||
f.write("Device_VTable :: struct {\n")
|
||
max_len = max(len(name) for name, _ in group_map["Device"])
|
||
for name, vk_name in group_map["Device"]:
|
||
f.write('\t{}: {},\n'.format(remove_prefix(name, "Proc"), name.rjust(max_len)))
|
||
f.write("}\n\n")
|
||
|
||
f.write("load_proc_addresses_device_vtable :: proc(device: Device, vtable: ^Device_VTable) {\n")
|
||
for name, vk_name in group_map["Device"]:
|
||
k = max_len - len(name)
|
||
f.write('\tvtable.{}{} = auto_cast GetDeviceProcAddr(device, "vk{}")\n'.format(
|
||
remove_prefix(name, 'Proc'),
|
||
"".ljust(k),
|
||
remove_prefix(vk_name, 'Proc'),
|
||
))
|
||
f.write("}\n\n")
|
||
|
||
f.write("load_proc_addresses_device :: proc(device: Device) {\n")
|
||
max_len = max(len(name) for name, _ in group_map["Device"])
|
||
for name, vk_name in group_map["Device"]:
|
||
k = max_len - len(name)
|
||
f.write('\t{}{} = auto_cast GetDeviceProcAddr(device, "vk{}")\n'.format(
|
||
remove_prefix(name, 'Proc'),
|
||
"".ljust(k),
|
||
remove_prefix(vk_name, 'Proc'),
|
||
))
|
||
f.write("}\n\n")
|
||
|
||
f.write("load_proc_addresses_instance :: proc(instance: Instance) {\n")
|
||
max_len = max(len(name) for name, _ in group_map["Instance"])
|
||
for name, vk_name in group_map["Instance"]:
|
||
k = max_len - len(name)
|
||
f.write('\t{}{} = auto_cast GetInstanceProcAddr(instance, "vk{}")\n'.format(
|
||
remove_prefix(name, 'Proc'),
|
||
"".ljust(k),
|
||
remove_prefix(vk_name, 'Proc'),
|
||
))
|
||
f.write("\n\t// Device Procedures (may call into dispatch)\n")
|
||
max_len = max(len(name) for name, _ in group_map["Device"])
|
||
for name, vk_name in group_map["Device"]:
|
||
k = max_len - len(name)
|
||
f.write('\t{}{} = auto_cast GetInstanceProcAddr(instance, "vk{}")\n'.format(
|
||
remove_prefix(name, 'Proc'),
|
||
"".ljust(k),
|
||
remove_prefix(vk_name, 'Proc'),
|
||
))
|
||
f.write("}\n\n")
|
||
|
||
f.write("load_proc_addresses_global :: proc(vk_get_instance_proc_addr: rawptr) {\n")
|
||
f.write("\tGetInstanceProcAddr = auto_cast vk_get_instance_proc_addr\n\n")
|
||
max_len = max(len(name) for name, _ in group_map["Loader"])
|
||
for name, vk_name in group_map["Loader"]:
|
||
k = max_len - len(name)
|
||
f.write('\t{}{} = auto_cast GetInstanceProcAddr(nil, "vk{}")\n'.format(
|
||
remove_prefix(name, 'Proc'),
|
||
"".ljust(k),
|
||
remove_prefix(vk_name, 'Proc'),
|
||
))
|
||
f.write("}\n\n")
|
||
|
||
f.write("""
|
||
load_proc_addresses :: proc{
|
||
\tload_proc_addresses_global,
|
||
\tload_proc_addresses_instance,
|
||
\tload_proc_addresses_device,
|
||
\tload_proc_addresses_device_vtable,
|
||
\tload_proc_addresses_custom,
|
||
}\n
|
||
"""[1::])
|
||
|
||
|
||
|
||
BASE = """
|
||
//
|
||
// Vulkan wrapper generated from "https://raw.githubusercontent.com/KhronosGroup/Vulkan-Headers/master/include/vulkan/vulkan_core.h"
|
||
//
|
||
package vulkan
|
||
"""[1::]
|
||
|
||
|
||
with open("../core.odin", 'w', encoding='utf-8') as f:
|
||
f.write(BASE)
|
||
f.write("""
|
||
// Core API
|
||
API_VERSION_1_0 :: (1<<22) | (0<<12) | (0)
|
||
API_VERSION_1_1 :: (1<<22) | (1<<12) | (0)
|
||
API_VERSION_1_2 :: (1<<22) | (2<<12) | (0)
|
||
API_VERSION_1_3 :: (1<<22) | (3<<12) | (0)
|
||
API_VERSION_1_4 :: (1<<22) | (4<<12) | (0)
|
||
|
||
MAKE_VERSION :: proc "contextless" (major, minor, patch: u32) -> u32 {
|
||
\treturn (major<<22) | (minor<<12) | (patch)
|
||
}
|
||
|
||
// Base types
|
||
Flags :: distinct u32
|
||
Flags64 :: distinct u64
|
||
DeviceSize :: distinct u64
|
||
DeviceAddress :: distinct u64
|
||
SampleMask :: distinct u32
|
||
|
||
Handle :: distinct rawptr
|
||
NonDispatchableHandle :: distinct u64
|
||
|
||
SetProcAddressType :: #type proc(p: rawptr, name: cstring)
|
||
|
||
|
||
RemoteAddressNV :: distinct rawptr // Declared inline before MemoryGetRemoteAddressInfoNV
|
||
|
||
// Base constants
|
||
LOD_CLAMP_NONE :: 1000.0
|
||
REMAINING_MIP_LEVELS :: ~u32(0)
|
||
REMAINING_ARRAY_LAYERS :: ~u32(0)
|
||
WHOLE_SIZE :: ~u64(0)
|
||
ATTACHMENT_UNUSED :: ~u32(0)
|
||
TRUE :: 1
|
||
FALSE :: 0
|
||
QUEUE_FAMILY_IGNORED :: ~u32(0)
|
||
SUBPASS_EXTERNAL :: ~u32(0)
|
||
MAX_PHYSICAL_DEVICE_NAME_SIZE :: 256
|
||
UUID_SIZE :: 16
|
||
MAX_MEMORY_TYPES :: 32
|
||
MAX_MEMORY_HEAPS :: 16
|
||
MAX_EXTENSION_NAME_SIZE :: 256
|
||
MAX_DESCRIPTION_SIZE :: 256
|
||
MAX_DEVICE_GROUP_SIZE :: 32
|
||
LUID_SIZE_KHX :: 8
|
||
LUID_SIZE :: 8
|
||
MAX_QUEUE_FAMILY_EXTERNAL :: ~u32(1)
|
||
MAX_GLOBAL_PRIORITY_SIZE :: 16
|
||
QUEUE_FAMILY_EXTERNAL :: MAX_QUEUE_FAMILY_EXTERNAL
|
||
|
||
// Vulkan Video API Constants
|
||
VULKAN_VIDEO_CODEC_AV1_DECODE_API_VERSION_1_0_0 :: (1<<22) | (0<<12) | (0)
|
||
VULKAN_VIDEO_CODEC_AV1_ENCODE_API_VERSION_1_0_0 :: (1<<22) | (0<<12) | (0)
|
||
VULKAN_VIDEO_CODEC_H264_ENCODE_API_VERSION_1_0_0 :: (1<<22) | (0<<12) | (0)
|
||
VULKAN_VIDEO_CODEC_H264_DECODE_API_VERSION_1_0_0 :: (1<<22) | (0<<12) | (0)
|
||
VULKAN_VIDEO_CODEC_H265_DECODE_API_VERSION_1_0_0 :: (1<<22) | (0<<12) | (0)
|
||
VULKAN_VIDEO_CODEC_H265_ENCODE_API_VERSION_1_0_0 :: (1<<22) | (0<<12) | (0)
|
||
|
||
VULKAN_VIDEO_CODEC_AV1_DECODE_SPEC_VERSION :: VULKAN_VIDEO_CODEC_AV1_DECODE_API_VERSION_1_0_0
|
||
VULKAN_VIDEO_CODEC_AV1_ENCODE_SPEC_VERSION :: VULKAN_VIDEO_CODEC_AV1_ENCODE_API_VERSION_1_0_0
|
||
VULKAN_VIDEO_CODEC_H264_ENCODE_SPEC_VERSION :: VULKAN_VIDEO_CODEC_H264_ENCODE_API_VERSION_1_0_0
|
||
VULKAN_VIDEO_CODEC_H264_DECODE_SPEC_VERSION :: VULKAN_VIDEO_CODEC_H264_DECODE_API_VERSION_1_0_0
|
||
VULKAN_VIDEO_CODEC_H265_DECODE_SPEC_VERSION :: VULKAN_VIDEO_CODEC_H265_DECODE_API_VERSION_1_0_0
|
||
VULKAN_VIDEO_CODEC_H265_ENCODE_SPEC_VERSION :: VULKAN_VIDEO_CODEC_H265_ENCODE_API_VERSION_1_0_0
|
||
|
||
MAKE_VIDEO_STD_VERSION :: MAKE_VERSION
|
||
|
||
"""[1::])
|
||
parse_constants(f)
|
||
parse_handles_def(f)
|
||
f.write("\n\n")
|
||
parse_flags_def(f)
|
||
with open("../enums.odin", 'w', encoding='utf-8') as f:
|
||
f.write(BASE)
|
||
f.write("\n")
|
||
parse_enums(f)
|
||
parse_fake_enums(f)
|
||
f.write("\n\n")
|
||
with open("../structs.odin", 'w', encoding='utf-8') as f:
|
||
f.write(BASE)
|
||
f.write("""
|
||
import "core:c"
|
||
|
||
import win32 "core:sys/windows"
|
||
_ :: win32
|
||
|
||
import "vendor:x11/xlib"
|
||
_ :: xlib
|
||
|
||
when ODIN_OS == .Windows {
|
||
\tHINSTANCE :: win32.HINSTANCE
|
||
\tHWND :: win32.HWND
|
||
\tHMONITOR :: win32.HMONITOR
|
||
\tHANDLE :: win32.HANDLE
|
||
\tLPCWSTR :: win32.LPCWSTR
|
||
\tSECURITY_ATTRIBUTES :: win32.SECURITY_ATTRIBUTES
|
||
\tDWORD :: win32.DWORD
|
||
\tLONG :: win32.LONG
|
||
\tLUID :: win32.LUID
|
||
} else {
|
||
\tHINSTANCE :: distinct rawptr
|
||
\tHWND :: distinct rawptr
|
||
\tHMONITOR :: distinct rawptr
|
||
\tHANDLE :: distinct rawptr
|
||
\tLPCWSTR :: ^u16
|
||
\tSECURITY_ATTRIBUTES :: struct {}
|
||
\tDWORD :: u32
|
||
\tLONG :: c.long
|
||
\tLUID :: struct {
|
||
\t\tLowPart: DWORD,
|
||
\t\tHighPart: LONG,
|
||
\t}
|
||
}
|
||
|
||
when xlib.IS_SUPPORTED {
|
||
\tXlibDisplay :: xlib.Display
|
||
\tXlibWindow :: xlib.Window
|
||
\tXlibVisualID :: xlib.VisualID
|
||
} else {
|
||
\tXlibDisplay :: struct {} // Opaque struct defined by Xlib
|
||
\tXlibWindow :: c.ulong
|
||
\tXlibVisualID :: c.ulong
|
||
}
|
||
|
||
xcb_visualid_t :: u32
|
||
xcb_window_t :: u32
|
||
CAMetalLayer :: struct {}
|
||
|
||
MTLBuffer_id :: rawptr
|
||
MTLTexture_id :: rawptr
|
||
MTLSharedEvent_id :: rawptr
|
||
MTLDevice_id :: rawptr
|
||
MTLCommandQueue_id :: rawptr
|
||
|
||
/********************************/
|
||
""")
|
||
f.write("\n")
|
||
parse_structs(f)
|
||
f.write("\n\n")
|
||
with open("../procedures.odin", 'w', encoding='utf-8') as f:
|
||
f.write(BASE)
|
||
f.write("\n")
|
||
parse_procedures(f)
|
||
f.write("\n")
|
||
group_functions(f)
|