#!/usr/bin/env python3 import logging import xml.etree.ElementTree as ET ############################################################################### class type(object): def __init__(self, name): self.name = name def depends(self): return [] def declare(self): return "" def define(self): return "" ##----------------------------------------------------------------------------- class basetype(type): def __init__(self, node): assert(node.tag == 'type') assert(node.attrib['category'] == 'basetype') super().__init__(node.find('name').text) self._type = node.find('type').text def depends(self): return [self._type] def define(self): return "using %s = %s;" % (self.name, self._type) ##----------------------------------------------------------------------------- class handle(type): def __init__(self, node): assert (node.tag == "type") assert (node.attrib['category'] == 'handle') super().__init__(node.find('name').text) def declare(self): return "typedef struct object_%(name)s* %(name)s;" % { 'name': self.name } return "using %(name)s = struct object_%(name)s*;" % { 'name': self.name } ##----------------------------------------------------------------------------- class constant(type): def __init__(self,node): assert(node.tag == 'enum') super().__init__(node.attrib['name']) self._value = node.attrib['value'] def define(self): return "constexpr auto %s = %s;" % (self.name, self._value) ##----------------------------------------------------------------------------- # a high level 'bitmask' enum that doesn't necessarily define anything. # inherits the values from something else instead. super fucking weird.. class bitmask(type): def __init__(self, node): assert(node.tag == 'type') assert(node.attrib['category'] == 'bitmask') super().__init__(node.find('name').text) self._requires = node.attrib.get('requires', None) self._type = node.find('type').text def declare(self): return "" def depends(self): if self._requires: return [self._type, self._requires] else: return [self._type] def define(self): return "using %s = %s;" % (self.name, self._type) ##----------------------------------------------------------------------------- # an enum with bit values that will be used by a bitmask enum. weirdly... class bitflag(type): def __init__(self, node): assert(node.tag == "enums") assert(node.attrib['type'] == 'bitmask') name = node.attrib['name'] super ().__init__(name) self._depends = node.attrib.get('requires', None) self.values = [] for v in node.findall("./enum"): if 'value' in v.attrib: self.add(v.attrib['name'], v.attrib['value']) elif 'bitpos' in v.attrib: self.add(v.attrib['name'], '1 << %s' % v.attrib['bitpos']) else: assert False, "unhandled bitmask type" def add(self, name, value=None): self.values.append({'name': name, 'value': value}) def depends(self): if self._depends: return [self._depends] else: return [] def declare(self): return "" def define(self): values = [] for v in self.values: if 'value' in v: values.append("%(name)s = %(value)s" % v) else: values.append(v['name']) return "enum %s { %s };" % (self.name, ", ".join(values)) ##----------------------------------------------------------------------------- class enum(type): def __init__(self, node): assert(node.tag == "enums") name = node.attrib['name'] super().__init__(name) self.values = list(map( lambda x: { 'name' : x.attrib['name'], 'value': x.attrib['value'] }, node.findall('./enum') )) def add(self,name,value=None): self.values.append({'name': name, 'value': value}) def declare(self): return "" def define(self): values = [] for v in self.values: if v['value']: values.append("%(name)s = %(value)s" % v) else: values.append(v['name']) return "enum %s { %s };" % ( self.name, ", ".join(values) ) ##----------------------------------------------------------------------------- class funcpointer(type): def __init__(self, node): assert(node.tag == 'type') assert(node.attrib['category'] == "funcpointer") name = node.find('name').text super().__init__(name) self.params = list(map(lambda x: x.text, node.findall('./type'))) self.text = "".join(node.itertext()) def depends(self): return ['VkBool32'] + self.params def declare(self): return self.text def define(self): return ""; ##----------------------------------------------------------------------------- class pod(type): def __init__(self,node): assert(node.tag == 'type') assert(node.attrib['category'] in ['struct', 'union']) super().__init__(node.attrib['name']) self._category = node.attrib['category'] # sometimes there are enums hiding in the member fields being used as array sizes self._depends = [] self._depends += list(e.text for e in node.findall('.//enum')) self._depends += list(t.text for t in node.findall('.//type')) self._members = list(map( lambda x: { 'type': x.find('type').text, 'name': x.find('name').text, # we must include a space separator otherwise we get run-on # types/names in the member definitions. 'code': " ".join(x.itertext()) }, node.findall('./member')) ) def depends(self): return self._depends def declare(self): return "%s %s;" % (self._category, self.name) def define(self): return "%(category)s %(name)s {\n%(members)s\n};" % { 'category': self._category, 'name': 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) ############################################################################### def parse_types(nodes): types = [] for n in nodes: # we need to parse too much unstructured text to actually do anything # useful with these declarations for t in n.findall("./type[@category='handle']"): types.append(handle(t)) for t in n.findall('./type[@category="bitmask"]'): types.append(bitmask(t)) for t in n.findall('./type[@category="basetype"]'): types.append(basetype(t)) for t in n.findall("./type[@category='funcpointer']"): types.append(funcpointer(t)) for t in n.findall("./type[@category='union']"): types.append(union(t)) for t in n.findall("./type[@category='struct']"): types.append(struct(t)) return types ############################################################################### def parse_enums(nodes): enums = [] for n in nodes: if n.attrib['name'] == "API Constants": for c in n.findall('./enum'): enums.append(constant(c)) elif n.attrib['type'] == 'bitmask': enums.append(bitflag(n)) elif n.attrib['type'] == 'enum': enums.append(enum(n)) else: assert False, "unhandled enum type" return enums ############################################################################### class command(type): def __init__(self, node): assert(node.tag == "command") proto = node.find('proto') super().__init__(proto.find('name').text) self._result = proto.find('type').text self._params = [] self._depends = [] for p in node.findall('./param'): self._depends.append(p.find('type').text) self._params.append("".join(p.itertext())) def depends(self): return [self._result] + self._depends def declare(self): return "%(result)s %(name)s (%(params)s) noexcept;" % { 'name': self.name, 'result': self._result, 'params': ", ".join(self._params) } ##----------------------------------------------------------------------------- def parse_commands(nodes): commands = [] for n in nodes: for c in n.findall('./command'): commands.append(command(c)) return commands ############################################################################### def parse_extension(types, node): r = node.find('require') for enum in r.findall('./enum'): if 'extends' in enum.attrib: types[enum.attrib['extends']].add(name=enum.attrib['name']) for command in r.findall('./command'): if not command.attrib['name'] in types: raise "unknown command" ############################################################################### def write_type(dst, types, t): logging.info("writing: %s", t.name) for d in t.depends(): if d in types: write_type(dst, types, types[d]) if t.name in types: dst.write(t.declare()) dst.write('\n') dst.write(t.define()) dst.write('\n') del types[t.name] ##----------------------------------------------------------------------------- def write_types(dst, types): keys = list(types.keys()) for k in keys: if k in types: write_type(dst, types, types[k]) ##----------------------------------------------------------------------------- def write_root(dst, node): dst.write (""" #ifndef __VK_HPP #define __VK_HPP #include #include #if defined(__cplusplus) extern "C" { #endif struct Display; struct VisualID; using Window = unsigned long; struct ANativeWindow; struct MirConnection; struct MirSurface; struct wl_display; struct wl_surface; using HANDLE = void*; using HINSTANCE = HANDLE; using HWND = HANDLE; using SECURITY_ATTRIBUTES = HANDLE; using DWORD = uint32_t; using LPCWSTR = char16_t*; struct xcb_connection_t; struct xcb_visualid_t; using xcb_window_t = uint32_t; using XID = unsigned long; using RROutput = XID; #define VKAPI_PTR /*VKAPI_PTR*/ // because, in its wisdom, the spec doesn't actually allow us to // extract the value for VK_NULL_HANDLE from the XML we'll just // hard code it here. #define VK_NULL_HANDLE nullptr // TODO: make this correspond to a required version #define VK_VERSION_1_0 """) types = [] types += parse_types(node.findall('./types')) types += parse_enums(node.findall('./enums')) types += parse_commands(node.findall('./commands')) types = dict((t.name,t) for t in types) for n in node.findall('./extensions/extension'): parse_extension(types, n) write_types(dst, types) #dst.writelines("\n".join(map(lambda x: x.declare(), types))) #dst.writelines("\n".join(map(lambda x: x.define(), types))) #dst.writelines("\n".join(map(lambda x: x.declare(), commands))) dst.write (""" #if defined(__cplusplus) } #endif #endif """) ############################################################################### import argparse ##----------------------------------------------------------------------------- if __name__ == '__main__': #logging.getLogger().setLevel(logging.INFO) 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') args = parser.parse_args() src = open(args.src, 'r') dst = open(args.dst, 'w') tree = ET.parse(src) root = tree.getroot() write_root(dst, root)