#!/usr/bin/env python3 import sys import logging from typing import List, Dict, Set, TextIO import pprint import xml.etree.ElementTree ############################################################################### def rename(name: str): return name ############################################################################### class Registry: types: Dict[str, object] = {} extensions: Dict = {} features: Dict = {} applied: Set = set() def __init__(self): self.types['API Constants'] = Unscoped('API Constants') def _serialise(self, name: str, queued: Set[str]): if name in queued: return [] result = [] obj = self.types[name] for d in obj.depends: if d == name: continue result += self._serialise(d, queued) assert name not in queued queued.add(name) result += [obj] return result def serialise(self, platform: Set[str]): required = [] for (_, f) in self.features.items(): required += f.apply(self) required.append(f.name) for e in self.extensions: required += self.extensions[e].apply(self, platform) queued = set() result = [] for r in required: result += self._serialise(r, queued) return result ############################################################################### class Type(object): """ The base class for all object defined in the Vulkan API. This includes (but is not limited to) types, like structures; and values, like constants. """ def __init__(self, name: str, depends: List[str] = None): assert name self.name = name self.depends = depends or [] assert isinstance(self.depends, list) for i in self.depends: assert isinstance(i, str) def depends(self): return self.depends def declare(self): return "" def define(self, reg: Registry): return "" ############################################################################### class AliasType(Type): """ A type that is an alias for another type. May be serialised using an appropriate host language facility (eg, a typedef) """ def __init__(self, name: str, target: str, depends: List[str] = None): depends = depends or [] super().__init__(name, depends=depends+[target]) self.target = target def declare(self): return f"using {rename(self.name)} = {rename(self.target)};" # ----------------------------------------------------------------------------- class AliasValue(Type): """ A value that is an alias for another value. May be serialised using an appropriate host language facility. """ def __init__(self, name: str, target: str): super().__init__(name, depends=[target]) self.target = target self.value = target def declare(self): return "constexpr auto %(name)s = %(target)s;" % { "name": rename(self.name), "target": rename(self.target), } # ----------------------------------------------------------------------------- class Placeholder(Type): def __init__(self, name: str): super().__init__(name) # ----------------------------------------------------------------------------- class Unscoped(Type): def __init__(self, name: str): super().__init__(name) self.values = [] def declare(self): return "\n".join(t.declare() for t in self.values) def define(self, reg): return "\n".join(t.define(reg.types) for t in self.values) ############################################################################### class Include(Type): def __init__(self, node): assert node.tag == 'type' assert node.attrib['category'] == 'include' super().__init__(node.attrib['name']) self.directive = node.text def declare(self): return self.directive or "#include <%s>" % self.name # ----------------------------------------------------------------------------- class Define(Type): def __init__(self, node): assert node.tag == 'type' assert node.attrib['category'] == 'define' name = node.attrib.get('name') or node.find('name').text super().__init__(name) self.directive = "".join(node.itertext()) def declare(self): return self.directive ############################################################################### class Bitmask(Type): def __init__(self, node): assert node.tag == 'type' assert node.attrib['category'] == 'bitmask' n = node.find('name').text t = node.find('type').text super().__init__(n, depends=[t]) self.type = t self.requires = node.attrib.get('requires') if self.requires: self.depends.append(self.requires) def declare(self): return "using %(name)s = %(type)s;" % { "name": self.name, "type": self.type } def define(self, reg: Registry): return self.declare() ############################################################################### class Handle(Type): parents: List[str] type: str def __init__(self, node): assert node.tag == 'type' assert node.attrib['category'] == 'handle' n = node.find('name').text t = node.find('type').text assert t super().__init__(n, depends=[t]) self.type = t parents = node.attrib.get('parent', None) self.parents = parents.split(',') if parents else [] def declare(self): return "struct %(name)s_t; using %(name)s = %(name)s_t*;" % { "name": self.name, "type": self.type } def has_parent(self, name: str, reg: Registry) -> bool: """ Recursively check if this type is derived from a given parent type. """ assert name assert reg if self.name == name: return True if not self.parents: return False if name in self.parents: return True for p in self.parents: if reg.types[p].has_parent(name, reg): return True return False ############################################################################### class Enum(Type): def __init__(self, node): assert node.tag == 'type' assert node.attrib['category'] == 'enum' name = node.attrib['name'] super().__init__(name, depends=["VkEnum"]) self.values = {} def __setitem__(self, key: str, value): assert isinstance(value, Constant) or isinstance(value, AliasValue) self.values[key] = value def declare(self): return "" def define(self, reg: Registry): values = ("%(name)s = %(value)s" % {"name": k, "value": v.value} for (k, v) in self.values.items()) return "enum %(name)s : int32_t { %(values)s };" % { "name": self.name, "values": ", ".join(values) } ############################################################################### class BaseType(AliasType): """ Represents fundamental types that aliases of system provided types and used extensively by the base API. eg, VkBool32 """ def __init__(self, node): assert node.tag == 'type' assert node.attrib['category'] == 'basetype' super().__init__( node.find('name').text, node.find('type').text ) ############################################################################### class FuncPointer(Type): def __init__(self, node): assert node.tag == 'type' assert node.attrib['category'] == 'funcpointer' name = node.find('name').text self.params = list(map(lambda x: x.text, node.findall('./type'))) self.text = "".join(node.itertext()) super().__init__(name, depends=['VkBool32']+self.params) def declare(self): return self.text ############################################################################### class POD(Type): def __init__(self, node): assert node.tag == 'type' assert node.attrib['category'] in ['struct', 'union'] super().__init__(node.attrib['name']) self._node = node self._category = node.attrib['category'] # sometimes there are enums hiding in the member fields being used as array sizes self.depends += list(e.text for e in node.findall('.//enum')) self.depends += list(t.text for t in node.findall('.//type')) self._members = [] for member in node.findall('./member'): t = member.find('type').text n = member.find('name').text comment = member.find('comment') if comment is not None: member.remove(comment) code = " ".join(member.itertext()) self._members.append({'code': code, 'type': t, 'name': n}) def declare(self): return "%(category)s %(name)s;" % { 'category': self._category, 'name': rename(self.name) } def define(self, reg: Registry): return "%(category)s %(name)s {\n%(members)s\n};" % { 'category': self._category, 'name': rename(self.name), 'members': "\n".join(m['code'] + ';' for m in self._members) } # ----------------------------------------------------------------------------- class Struct(POD): def __init__(self, node): super().__init__(node) # ----------------------------------------------------------------------------- class Union(POD): def __init__(self, node): super().__init__(node) ############################################################################### class Constant(Type): def __init__(self, node, **kwargs): assert node.tag == 'enum' name = node.attrib['name'] super().__init__(name) if 'offset' in node.attrib: assert 'extends' in node.attrib number = int(kwargs['extnumber']) offset = int(node.attrib['offset']) self.value = 1000000000 + 1000 * number + offset if 'dir' in node.attrib: self.value *= -1 elif 'value' in node.attrib: self.value = node.attrib['value'] elif 'bitpos' in node.attrib: self.value = "1 << %s" % node.attrib['bitpos'] else: raise RuntimeError("Unknown constant value type") def declare(self): return "constexpr auto %(name)s = %(value)s;" % { "name": self.name, "value": self.value } ############################################################################### class Command(Type): class Param(Type): name: str """An string that can be used to refer to the parameter variable""" type: str """The name of this parameter's dependant type""" param: str """ The components of this type for a C definition (ie, includes pointer, const, other decorations, _and_ the variable name (which must be the same as self.name for a useful system). """ def __init__(self, name:str, type:str, param:str, depends:List[str]): super().__init__( name=name, depends=depends, ) self.type = type self.param = param def __repr__(self) -> str: return f"{{ name: '{self.name}', type: '{self.type}', param: '{self.param}' }}" def is_pointer(self): return '*' in self.param def __init__(self, name: str, result: str, params: List[Param], depends: List[str] = None): super().__init__(name) self.result = result self.params = params self.depends += depends or [] def __repr__(self) -> str: return f"{{ name: '{self.name}', result: '{self.result}', param: {self.params} }}" def declare(self): return 'extern "C" %(result)s %(name)s [[gnu::visibility("default")]] (%(params)s) noexcept;' % { 'name': rename(self.name), 'result': self.result, 'params': ", ".join(p.param for p in self.params) } def is_instance(self, reg: Registry): assert reg if not self.params: return False first_name = self.params[0].type first_obj = reg.types[first_name] if not isinstance(first_obj, Handle): return True instance = first_obj.name == 'VkInstance' physical = first_obj.name == 'VkPhysicalDevice' return instance or physical def is_device(self, reg: Registry): if not self.params: return False first_name = self.params[0].type first_obj = reg.types[first_name] if not isinstance(first_obj, Handle): return False for i in ['VkDevice', 'VkQueue', 'VkCommandBuffer']: if first_obj.has_parent(i, reg): return True return False ############################################################################### class Require(object): def __init__(self, values, depends: List[str]): self.values = values or [] self.depends = depends or [] def apply(self, reg: Registry, extnumber=None): required = [] required += self.depends for value in self.values: name = value.attrib['name'] if len(value.attrib) == 1: assert 'name' in value.attrib required.append(name) continue if 'extends' not in value.attrib: obj = Constant(value) owner = reg.types['API Constants'] owner.values.append(obj) continue owner = reg.types[value.attrib['extends']] if 'alias' in value.attrib: owner[name] = AliasValue(name, value.attrib['alias']) required.append(owner.name) elif value.tag == 'enum': owner[name] = Constant(value, extnumber=extnumber or int(value.attrib.get('extnumber', '0'))) required.append(owner.name) elif value.tag == 'command': required.append(name) else: raise RuntimeError("Unknown type") return required # ----------------------------------------------------------------------------- class Feature(Type): def __init__(self, name: str, requires: List[Require] = []): super().__init__(name) self.requires = requires def define(self, reg: Registry): return "#define %s" % self.name def apply(self, reg: Registry): logging.info("Applying feature:", self.name, file=sys.stderr) result = [] for r in self.requires: result += r.apply(reg) return result # ----------------------------------------------------------------------------- class Extension(Type): def __init__(self, root): assert root.tag == 'extension' name = root.attrib['name'] super().__init__(name) if 'requires' in root.attrib: self.depends += root.attrib['requires'].split(',') self.number = int(root.attrib['number']) self.platform = root.attrib.get('platform') self.requires = [] for node in root: if node.tag == 'require': self.requires.append(parse_require(node)) else: raise RuntimeError("Unknown extension node") def apply(self, reg: Registry, platform: Set[str]): if self.name in reg.applied: return [] reg.applied.add(self.name) if self.platform and self.platform not in platform: return [] required = [] for dep in self.depends: required = reg.extensions[dep].apply(reg, platform) logging.info("Applying extension:", self.name, file=sys.stderr) for node in self.requires: required += node.apply(reg, extnumber=self.number) return required ############################################################################### def ignore_node(types: Dict[str, Type], root): types, root pass # ----------------------------------------------------------------------------- parse_comment = ignore_node parse_vendorids = ignore_node parse_platforms = ignore_node parse_tags = ignore_node # ----------------------------------------------------------------------------- def parse_types(reg: Registry, root): assert root.tag == 'types' for t in root.findall('type'): name = t.attrib.get ('name') or t.find('name').text assert name not in reg.types if 'alias' in t.attrib: name = t.attrib['name'] target = t.attrib['alias'] reg.types[name] = AliasType(name, target) continue category = t.attrib.get ('category') # if we don't have a category we should have a bare type that has a # dependency on something like a header. # # eg, 'Display' depends on 'X11/Xlib.h' if not category: reg.types[name] = Placeholder(name) else: # Whitelist the known types so we don't accidentally instantiate # something whacky supported_categories = { 'include': Include, 'define': Define, 'bitmask': Bitmask, 'basetype': BaseType, 'handle': Handle, 'enum': Enum, 'funcpointer': FuncPointer, 'struct': Struct, 'union': Union, } concrete = supported_categories.get(category, None) if concrete: reg.types[name] = concrete(t) else: raise RuntimeError('unhandled type') if 'requires' in t.attrib: reg.types[name].depends.append(t.attrib['requires']) # ----------------------------------------------------------------------------- def parse_enums(reg: Registry, root): assert root.tag == 'enums' ownername = root.attrib['name'] owner = reg.types[ownername] if ownername != 'API Constants' else reg.types for node in root.findall('./enum'): valuename = node.attrib.get('name') assert 'requires' not in node.attrib if 'alias' in node.attrib: owner[valuename] = AliasValue(valuename, node.attrib['alias']) else: owner[valuename] = Constant(node) #------------------------------------------------------------------------------ def parse_param(root) -> Command.Param: assert root.tag == 'param' param = "" for i in root.iter(): param += i.text or "" param += i.tail or "" # normalise whitespace param = " ".join(param.split()) name = root.find('name').text type = root.find('type').text depends = [root.find('type').text] return Command.Param( name=name, type=type, param=param, depends=depends, ) # ----------------------------------------------------------------------------- def parse_commands(reg: Registry, root): assert root.tag == 'commands' for node in root.findall('./command'): name = node.attrib.get('name') or node.find('./proto/name').text assert name not in reg.types if 'alias' in node.attrib: reg.types[name] = AliasValue(name, node.attrib['alias']) continue proto = node.find('proto') name = proto.find('name').text result = proto.find('type').text params = [parse_param(p) for p in node.findall('./param')] depends = [result] for p in params: depends += p.depends reg.types[name] = Command(name, result, params, depends) # ----------------------------------------------------------------------------- def parse_require(root) -> Require: assert root.tag == 'require' values = [] depends = [] for node in root: if node.tag == 'enum': values.append(node) elif node.tag in ['command', 'type']: depends.append(node.attrib['name']) elif node.tag in ['comment']: pass else: raise RuntimeError("Unknown requires node") return Require(values=values, depends=depends) ##----------------------------------------------------------------------------- def parse_feature(reg: Registry, root): assert root.tag == 'feature' name = root.attrib['name'] assert name not in reg.features requires = [] for node in root: if 'require' == node.tag: requires.append(parse_require(node)) else: raise RuntimeError("Unhandled feature node") reg.features[name] = Feature(name, requires) reg.types[name] = reg.features[name] # ----------------------------------------------------------------------------- def parse_extensions(reg: Registry, root): assert root.tag == 'extensions' for node in root.findall('./extension'): name = node.attrib['name'] assert name not in reg.extensions reg.extensions[name] = Extension(node) ############################################################################### def write_header(dst: TextIO, q: List[Type], reg: Registry): dst.write("#pragma once\n") # Write the declarations and definitions for all types. for obj in q: dst.write(obj.declare()) dst.write('\n') dst.write(obj.define(reg)) dst.write('\n') # Define the default case for device and instance type traits. dst.write(""" #include /// A type trait that tests if a Vulkan type is an instance type template struct is_instance: public std::false_type {}; /// A type trait that tests if a Vulkan type is a device type template struct is_device: public std::false_type {}; template constexpr auto is_instance_v = is_instance::value; template constexpr auto is_device_v = is_device::value; """) # Specialise traits for device and instance types. for obj in q: if not isinstance(obj, Handle): continue device_value = "true_type" if obj.has_parent("VkDevice", reg) else "false_type" instance_value = "true_type" if obj.has_parent("VkInstance", reg) else "false_type" dst.write(f""" template <> struct is_instance<{obj.name}>: public std::{instance_value} {{ }}; template <> struct is_device<{obj.name}>: public std::{device_value} {{ }}; """) # ----------------------------------------------------------------------------- def write_load(dst: TextIO, q: List[Type], reg: Registry): commands = [i for i in q if isinstance(i, Command)] collections = { 'instance': Command.is_instance, 'device': Command.is_device, } dst.write(f""" #pragma once #include #include namespace cruft::vk::load {{ class vendor; #define MAP_COMMANDS(FUNC) MAP0(FUNC,{",".join(i.name for i in commands)}) """) for name, test in collections.items(): curr = [i for i in commands if test(i, reg)] next = [i for i in commands if not test(i, reg)] commands = next dst.write(f""" #define MAP_{name.upper()}_COMMANDS(FUNC) \ MAP0(FUNC,{",".join([i.name for i in curr])}) struct {name}_table{{ """) # Generate the vtable entries for instance methods dst.writelines(( f"{obj.result} (*{obj.name}) ({','.join(p.param for p in obj.params)}) = nullptr;" for obj in curr )) dst.write(""" }; """) dst.write(""" struct vendor_table; extern cruft::vk::load::vendor_table const *v_table [[gnu::visibility("default")]]; } """) # ----------------------------------------------------------------------------- def write_dispatch(dst: TextIO, q: List[Type], reg: Registry): dst.write(""" #include #include #include #include #include #include #pragma GCC diagnostic ignored "-Wunused-parameter" template struct indirect { HandleT handle; TableT table; }; """) implementations = { 'vkCreateInstance': """ auto res = std::make_unique> (); auto ptr = (decltype(vkCreateInstance)*) (cruft::vk::load::v_table->vk_icdGetInstanceProcAddr (nullptr, "vkCreateInstance")); auto err = (*ptr) (pCreateInfo, pAllocator, &res->handle); if (err != VK_SUCCESS) return err; #define GET(NAME) res->table.NAME = reinterpret_cast (cruft::vk::load::v_table->vk_icdGetInstanceProcAddr (res->handle, #NAME)); MAP_INSTANCE_COMMANDS(GET) #undef GET #define GET(NAME) if (!res->table.NAME) res->table.NAME = reinterpret_cast (res->table.vkGetInstanceProcAddr (res->handle, #NAME)); MAP_INSTANCE_COMMANDS(GET) #undef GET *pInstance = (VkInstance)res.release (); return err; """, 'vkGetInstanceProcAddr': """ #define ATTEMPT(NAME) if (!strcmp (#NAME, pName)) return reinterpret_cast (&NAME); MAP_INSTANCE_COMMANDS(ATTEMPT) #undef ATTEMPT return nullptr; """, 'vkCreateDevice': """ (void)physicalDevice; (void)pCreateInfo; (void)pAllocator; (void)pDevice; unimplemented (); """, 'vkEnumeratePhysicalDevices': """ auto const entry = reinterpret_cast< indirect const* > (instance); if (!pPhysicalDeviceCount || !pPhysicalDevices) { return (entry->table.vkEnumeratePhysicalDevices)( entry->handle, pPhysicalDeviceCount, pPhysicalDevices ); } std::vector res (*pPhysicalDeviceCount); auto err = (entry->table.vkEnumeratePhysicalDevices)( entry->handle, pPhysicalDeviceCount, res.data () ); res.resize (*pPhysicalDeviceCount); if (err != VK_SUCCESS) return err; for (uint32_t i = 0; i < *pPhysicalDeviceCount; ++i) { auto wrapped = std::make_unique> (); wrapped->handle = res[i]; #define GET(NAME) wrapped->table.NAME = wrapped->table.NAME ?: reinterpret_cast (entry->table.vkGetInstanceProcAddr (entry->handle, #NAME)); MAP_INSTANCE_COMMANDS(GET) #undef GET #define GET(NAME) wrapped->table.NAME = wrapped->table.NAME ?: reinterpret_cast (cruft::vk::load::v_table->vk_icdGetInstanceProcAddr (entry->handle, #NAME)); MAP_INSTANCE_COMMANDS(GET) #undef GET #define GET(NAME) wrapped->table.NAME = wrapped->table.NAME ?: reinterpret_cast (cruft::vk::load::v_table->vk_icdGetPhysicalDeviceProcAddr (entry->handle, #NAME)); MAP_INSTANCE_COMMANDS(GET) #undef GET std::clog << "physical_device[" << i << "]; wrapped: " << wrapped.get() << ", native: " << (void*)wrapped->handle << '\\n'; pPhysicalDevices[i] = (VkPhysicalDevice)wrapped.release (); } return err; """ } for obj in (i for i in q if isinstance(i, Command)): first_arg = reg.types[obj.params[0].type] if obj.is_instance(reg): table = 'instance' elif obj.is_device(reg): table = 'device' else: raise Exception(f"Unhandled command type for {obj}") if obj.params: last_param = obj.params[-1] last_obj = reg.types[last_param.type] is_creating = isinstance(last_obj, Handle) and last_param.is_pointer() else: is_creating = False if obj.is_instance(reg) and obj.params[0].type not in ['VkInstance', 'VkPhysicalDevice']: forwarding = f""" auto ptr= cruft::vk::load::v_table->vk_icdGetInstanceProcAddr (nullptr, "{obj.name}"); return ((decltype({obj.name})*) (ptr)) ({', '.join (i.name for i in obj.params)}); """ else: forwarding = f""" if constexpr (std::is_same_v<{obj.params[0].type}, VkInstance> || std::is_same_v<{obj.params[0].type}, VkPhysicalDevice>) {{ if ({obj.params[0].name} == VK_NULL_HANDLE) {{ auto ptr= cruft::vk::load::v_table->vk_icdGetInstanceProcAddr (nullptr, "{obj.name}"); return ((decltype({obj.name})*) (ptr)) ({', '.join (i.name for i in obj.params)}); }} }} auto const entry = reinterpret_cast< indirect<{obj.params[0].type},cruft::vk::load::{table}_table> const* > ({obj.params[0].name}); return (entry->table.{obj.name})( {", ".join(['entry->handle'] + [p.name for p in obj.params[1:]])} ); """ dst.write(f""" extern "C" {obj.result} {rename(obj.name)} ({", ".join(p.param for p in obj.params)}) noexcept {{ std::clog << "{obj.name}" << "\\n"; {implementations.get(obj.name, forwarding)} }} """) ############################################################################### import argparse # ----------------------------------------------------------------------------- def main(): logging.getLogger().setLevel(logging.WARNING) parser = argparse.ArgumentParser(description='Transform XML API specification into C++ headers') parser.add_argument('--src', type=str, help='the path to the XML file to transform') parser.add_argument('--dst', type=str, help='the output path for the result') parser.add_argument('--load', type=str, help='the output path for the loading routines') parser.add_argument('--dispatch', type=str, help="the output path for function dispatch") parser.add_argument( '--platform', type=str, action='append', help='a platform to generate output for. may be specific multiple times"' ) args = parser.parse_args() # Get a copy of the specification XML with open(args.src, 'r') as src: tree = xml.etree.ElementTree.parse(src) root = tree.getroot() # Find a parser for each of the nodes in the XML reg = Registry() for node in root: target = "parse_%s" % node.tag globals()[target](reg, node) # Override some requested system types so that they fit more naturally in # our environment. eg, use appropriate C++ types, or cruft library # wrappers. reg.types['windows.h'].name = 'cruft/util/win32/windows.hpp' reg.types['void*'] = Placeholder('void*') reg.types['nullptr'] = Placeholder('nullptr') reg.types['VkEnum'] = AliasType('VkEnum', 'int32_t') reg.types['VK_DEFINE_NON_DISPATCHABLE_HANDLE'] = AliasType("VK_DEFINE_NON_DISPATCHABLE_HANDLE", "uint64_t") reg.types['VK_DEFINE_HANDLE'] = AliasType("VK_DEFINE_HANDLE", "void*") reg.types['VK_NULL_HANDLE'] = AliasValue("VK_NULL_HANDLE", "nullptr") #reg.types['vk_icdGetInstanceProcAddr'] = Command( # name='vk_icdGetInstanceProcAddr', # result='void*', # params=[ # Command.Param( # name='instance', # type='VkInstance', # param='VkInstance instance', # depends=['VkInstance'] # ), # Command.Param( # name='pName', # type='void*', # param='char const *pName', # depends=[] # ), # ] #) #reg.types['vk_icdGetPhysicalDeviceProcAddr'] = Command( # name='vk_icdGetPhysicalDeviceProcAddr', # result='void*', # params=[ # Command.Param( # name='instance', # type='VkInstance', # param='VkInstance instance', # depends=['VkInstance'] # ), # Command.Param( # name='pName', # type='void*', # param='char const *pName', # depends=[] # ), # ] #) #icd = Feature( # "__nerdcruft_icd", # requires=[ # Require(None, ["vk_icdGetInstanceProcAddr"]), # Require(None, ["vk_icdGetPhysicalDeviceProcAddr"]) # ] #) #reg.features['__nerdcruft_icd'] = icd #reg.types['__nerdcruft_icd'] = reg.features['__nerdcruft_icd'] # Request serialisation of all features #features = [Feature(n) for n in root.findall('./feature')] #features = dict((f.name,f) for f in features) #reg.extensions['VK_KHR_surface'].apply(reg, platform='xcb') # Request a minimal set of extensions that will almost certainly be # required for all applications. extensions = ["VK_KHR_swapchain", "VK_EXT_debug_report", "VK_KHR_external_memory"] q = reg.serialise(args.platform) # Finally write out the header, vtables, and dispatch code. with open(args.dst, 'w') as dst: write_header(dst, q, reg) with open(args.load, 'w') as dst: write_load(dst, q, reg) with open(args.dispatch, 'w') as dst: write_dispatch(dst, q, reg) # ----------------------------------------------------------------------------- if __name__ == '__main__': main()