libcruft-vk/tools/spec.py

573 lines
16 KiB
Python
Raw Normal View History

#!/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:
2017-09-05 17:19:51 +10:00
self.add(v.attrib['name'], v.attrib['value'])
elif 'bitpos' in v.attrib:
2017-09-05 17:19:51 +10:00
self.add(v.attrib['name'], '1 << %s' % v.attrib['bitpos'])
else:
assert False, "unhandled bitmask type"
2017-09-05 17:19:51 +10:00
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):
2017-09-05 17:19:51 +10:00
values = []
for v in self.values:
if 'value' in v:
values.append(
"%(name)s = %(value)s" % {
'name': rename(v['name']),
'value': v['value']
}
)
2017-09-05 17:19:51 +10:00
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)
2017-09-05 17:19:51 +10:00
self.values = list(map(
lambda x: {
'name' : x.attrib['name'],
'value': x.attrib['value']
},
node.findall('./enum')
2017-09-05 17:19:51 +10:00
))
def add(self,name,value=None):
self.values.append({'name': name, 'value': value})
def declare(self):
return ""
def define(self,types):
2017-09-05 17:19:51 +10:00
values = []
for v in self.values:
if v['value']:
values.append(
"%(name)s = %(value)s" % {
'name': rename(v['name']),
'value': v['value']
}
)
2017-09-05 17:19:51 +10:00
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
2017-09-05 17:19:51 +10:00
###############################################################################
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])
2017-09-05 17:19:51 +10:00
##-----------------------------------------------------------------------------
def write_root(dst, node):
dst.write ("""
#ifndef __VK_HPP
#define __VK_HPP
#include <type_traits>
template <typename T>
struct enable_bitops : public std::false_type { };
#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 nullptr
2017-09-05 17:19:51 +10:00
// 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)
2017-09-05 17:19:51 +10:00
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 <typename T>
std::enable_if_t<enable_bitops<T>::value, T>
operator| (T a, T b)
{
return T (
static_cast<std::underlying_type_t<T>> (a) |
static_cast<std::underlying_type_t<T>> (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)