#!/usr/bin/env python3 import logging import xml.etree.ElementTree as ET import re ############################################################################### def camel_to_snake(name): name = re.sub('([a-z])([A-Z])', r'\1_\2', name) return name.lower() def remove_namespace(name): name = re.sub('^VK_', '', name) name = re.sub('^[vV][kK]', '', name) return name ############################################################################### def rename(name): return name name = remove_namespace(name) name = camel_to_snake(name) return name ##----------------------------------------------------------------------------- def rename_enum(type, value): return value value = rename(value) value = re.sub("^%s_" % type, '', value) return value ############################################################################### class type(object): def __init__(self, name): self.name = name def depends(self): return [] def declare(self): return "" def define(self,types): 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, types): return "using %(name)s = %(type)s;" % { 'name': rename(self.name), 'type': 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 "using %(name)s = struct _%(name)s*;" % { 'name': rename(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, types): return "constexpr auto %(name)s = %(value)s;" % { 'name': self.name, 'value': 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 depends(self): if self._requires: return [self._type, self._requires] else: return [self._type] def declare(self): return "" def define(self, types): if not self._requires: return "using %(name)s = %(type)s;" % { 'name': rename(self.name), 'type': rename(self._type) } return "using %(name)s = %(requires)s;" % { 'name': rename(self.name), 'requires': rename(self._requires) } members = types[self._requires].values return "enum class %(name)s : %(type)s { %(members)s };" % { 'name': rename(self.name), 'type': rename(self._type), 'members': "\n".join("%(name)s = %(value)s," % x for x in members) } ##----------------------------------------------------------------------------- # 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 values(self): return self.values def depends(self): if self._depends: return ['VkFlags',self._depends] else: return ['VkFlags'] def declare(self): return "" def define(self,types): values = [] for v in self.values: if 'value' in v: values.append( "%(name)s = %(value)s" % { 'name': rename(v['name']), 'value': v['value'] } ) else: values.append(rename(v['name'])) return """ enum %(name)s : %(vkflags)s { %(members)s }; """ % { 'name': rename(self.name), 'vkflags': rename('VkFlags'), 'members': ", ".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,types): values = [] for v in self.values: if v['value']: values.append( "%(name)s = %(value)s" % { 'name': rename(v['name']), 'value': v['value'] } ) else: values.append(rename(v['name'])) attribute = '[[nodiscard]]' if self.name == 'VkResult' else '' return "enum %(attribute)s %(name)s : int32_t { %(values)s };" % ({ 'name': rename(self.name), 'attribute': attribute, 'values': ", ".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,types): 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._node = node 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 = [] for member in node.findall('./member'): type = member.find('type').text name = member.find('name').text comment = member.find('comment') if not comment is None: member.remove(comment) code = " ".join(member.itertext()) #code = member.iter() #code = filter(lambda x: x.tag != 'comment', code) #code = map(lambda x: x.itertext(), code) #code = map(lambda x: "".join(x), code) #code = "".join(code) self._members.append({'code': code, 'type': type, 'name': name}) def depends(self): return self._depends def declare(self): return "%(category)s %(name)s;" % { 'category': self._category, 'name': rename(self.name) } def define(self,types): 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) ############################################################################### 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': rename(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, all, pending, t): logging.info("writing: %s", t.name) for d in t.depends(): if d in pending: write_type(dst, all, pending, pending[d]) if t.name in pending: dst.write(t.declare()) dst.write('\n') dst.write(t.define(all)) dst.write('\n') del pending[t.name] ##----------------------------------------------------------------------------- import copy def write_types(dst, types): all = types pending = copy.deepcopy(types) keys = list(types.keys()) for k in keys: if k in types: write_type(dst, all, pending, types[k]) ##----------------------------------------------------------------------------- def write_root(dst, node): dst.write (""" #ifndef __VK_HPP #define __VK_HPP #include template struct enable_bitops : public std::false_type { }; #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 """) for x in types.values(): if isinstance(x,bitflag): dst.write(""" template <> struct enable_bitops<%(name)s> : public std::true_type { }; """ % { 'name': x.name } ) dst.write(""" template std::enable_if_t::value, T> operator| (T a, T b) { return T ( static_cast> (a) | static_cast> (b) ); } #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)