diff --git a/CMakeLists.txt b/CMakeLists.txt index 61a76d3..f16a772 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,10 +14,7 @@ endif () ##----------------------------------------------------------------------------- -include (FindLibXslt) -if (NOT LIBXSLT_XSLTPROC_EXECUTABLE) - message (FATAL_ERROR "xsltproc is required to generate the API headers") -endif() +find_package(PythonInterp 3 REQUIRED) ############################################################################### @@ -29,14 +26,14 @@ add_custom_command ( OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/vk.hpp" COMMENT - "[xsltproc] vk.hpp" + "[spec.py] vk.hpp" COMMAND - "${LIBXSLT_XSLTPROC_EXECUTABLE}" - --output "${CMAKE_CURRENT_BINARY_DIR}/vk.hpp" - "${CMAKE_CURRENT_SOURCE_DIR}/vk.xsl" + "${PYTHON_EXECUTABLE}" + "${CMAKE_CURRENT_SOURCE_DIR}/tools/spec.py" "${CMAKE_CURRENT_SOURCE_DIR}/vk.xml" + "${CMAKE_CURRENT_BINARY_DIR}/vk.hpp" DEPENDS - "${CMAKE_CURRENT_SOURCE_DIR}/vk.xsl" + "${CMAKE_CURRENT_SOURCE_DIR}/tools/spec.py" "${CMAKE_CURRENT_SOURCE_DIR}/vk.xml" ) diff --git a/tools/spec.py b/tools/spec.py new file mode 100755 index 0000000..22689a6 --- /dev/null +++ b/tools/spec.py @@ -0,0 +1,418 @@ +#!/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 "using %s = uintptr_t;" % 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.values.append({'name': v.attrib['name'], 'value': v.attrib['value']}) + elif 'bitpos' in v.attrib: + self.values.append({'name': v.attrib['name'], 'value': '1 << %s' % v.attrib['bitpos']}) + else: + assert False, "unhandled bitmask type" + + def depends(self): + if self._depends: + return [self._depends] + else: + return [] + + def declare(self): + return "" + + def define(self): + values = map(lambda x: "%(name)s = %(value)s" % x, self.values) + 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 = map( + lambda x: { + 'name' : x.attrib['name'], + 'value': x.attrib['value'] + }, + node.findall('./enum') + ) + + def declare(self): + return "" + + def define(self): + values = map(lambda x: "%(name)s = %(value)s" % x, self.values) + + 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 { %(members)s };" % { + '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 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(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 uintptr_t(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) + + 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)