libcruft-vk/tools/spec.py

456 lines
12 KiB
Python
Executable File

#!/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.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 { %(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 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(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 <cstdint>
#include <cstddef>
#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)
// 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)