from __future__ import unicode_literals
import io
import os
import pdb
import re
import shutil
import sys
import tempfile
try:
from pathlib import Path
except ImportError:
from pathlib2 import Path
from spec_tools.util import getElemName, getElemType
def write(*args, **kwargs):
file = kwargs.pop('file', sys.stdout)
end = kwargs.pop('end', '\n')
file.write(' '.join(str(arg) for arg in args))
file.write(end)
def noneStr(s):
if s:
return s
return ""
def enquote(s):
if s:
if isinstance(s, str):
return f"'{s}'"
else:
return s
return None
def regSortCategoryKey(feature):
if feature.elem.tag == 'feature':
if feature.name.startswith('VKSC'):
return 0.5
else:
return 0
if feature.category.upper() in ['ARB', 'KHR', 'OES']:
return 1
return 2
def regSortOrderKey(feature):
return feature.sortorder
def regSortNameKey(feature):
return feature.name
def regSortFeatureVersionKey(feature):
return float(feature.versionNumber)
def regSortExtensionNumberKey(feature):
return int(feature.number)
def regSortFeatures(featureList):
featureList.sort(key=regSortExtensionNumberKey)
featureList.sort(key=regSortFeatureVersionKey)
featureList.sort(key=regSortCategoryKey)
featureList.sort(key=regSortOrderKey)
class MissingGeneratorOptionsError(RuntimeError):
def __init__(self, msg=None):
full_msg = 'Missing generator options object self.genOpts'
if msg:
full_msg += ': ' + msg
super().__init__(full_msg)
class MissingRegistryError(RuntimeError):
def __init__(self, msg=None):
full_msg = 'Missing Registry object self.registry'
if msg:
full_msg += ': ' + msg
super().__init__(full_msg)
class MissingGeneratorOptionsConventionsError(RuntimeError):
def __init__(self, msg=None):
full_msg = 'Missing Conventions object self.genOpts.conventions'
if msg:
full_msg += ': ' + msg
super().__init__(full_msg)
class GeneratorOptions:
def __init__(self,
conventions=None,
filename=None,
directory='.',
genpath=None,
apiname=None,
mergeApiNames=None,
profile=None,
versions='.*',
emitversions='.*',
defaultExtensions=None,
addExtensions=None,
removeExtensions=None,
emitExtensions=None,
emitSpirv=None,
emitFormats=None,
reparentEnums=True,
sortProcedure=regSortFeatures,
requireCommandAliases=False,
requireDepends=True,
):
self.conventions = conventions
self.filename = filename
"basename of file to generate, or None to write to stdout."
self.genpath = genpath
self.directory = directory
"directory in which to generate filename"
self.apiname = apiname
"string matching `<api>` 'apiname' attribute, e.g. 'gl'."
self.mergeApiNames = mergeApiNames
"comma separated list of API names to merge into the API specified by 'apiname'"
self.profile = profile
"string specifying API profile , e.g. 'core', or None."
self.versions = self.emptyRegex(versions)
self.emitversions = self.emptyRegex(emitversions)
self.defaultExtensions = defaultExtensions
self.addExtensions = self.emptyRegex(addExtensions)
self.removeExtensions = self.emptyRegex(removeExtensions)
self.emitExtensions = self.emptyRegex(emitExtensions)
self.emitSpirv = self.emptyRegex(emitSpirv)
self.emitFormats = self.emptyRegex(emitFormats)
self.reparentEnums = reparentEnums
self.sortProcedure = sortProcedure
self.codeGenerator = False
self.registry = None
self.requireCommandAliases = requireCommandAliases
self.requireDepends = requireDepends
def emptyRegex(self, pat):
if not pat:
return '_nomatch_^'
return pat
class OutputGenerator:
categoryToPath = {
'bitmask': 'flags',
'enum': 'enums',
'funcpointer': 'funcpointers',
'handle': 'handles',
'define': 'defines',
'basetype': 'basetypes',
}
def breakName(self, name, msg):
bad = (
)
if name in bad and True:
print('breakName {}: {}'.format(name, msg))
pdb.set_trace()
def __init__(self, errFile=sys.stderr, warnFile=sys.stderr, diagFile=sys.stdout):
self.outFile = None
self.errFile = errFile
self.warnFile = warnFile
self.diagFile = diagFile
self.featureName = None
self.genOpts = None
self.registry = None
self.featureDictionary = {}
self.extBase = 1000000000
self.extBlockSize = 1000
self.madeDirs = {}
self.apidict = None
self.file_suffix = ''
def logMsg(self, level, *args):
if level == 'error':
strfile = io.StringIO()
write('ERROR:', *args, file=strfile)
if self.errFile is not None:
write(strfile.getvalue(), file=self.errFile)
raise UserWarning(strfile.getvalue())
elif level == 'warn':
if self.warnFile is not None:
write('WARNING:', *args, file=self.warnFile)
elif level == 'diag':
if self.diagFile is not None:
write('DIAG:', *args, file=self.diagFile)
else:
raise UserWarning(
'*** FATAL ERROR in Generator.logMsg: unknown level:' + level)
def enumToValue(self, elem, needsNum, bitwidth = 32,
forceSuffix = False, parent_for_alias_dereference=None):
if self.genOpts is None:
raise MissingGeneratorOptionsError()
if self.genOpts.conventions is None:
raise MissingGeneratorOptionsConventionsError()
name = elem.get('name')
numVal = None
if 'value' in elem.keys():
value = elem.get('value')
if needsNum:
numVal = int(value, 0)
if forceSuffix:
if bitwidth == 64:
value = value + 'ULL'
else:
value = value + 'U'
self.logMsg('diag', 'Enum', name, '-> value [', numVal, ',', value, ']')
return [numVal, value]
if 'bitpos' in elem.keys():
value = elem.get('bitpos')
bitpos = int(value, 0)
numVal = 1 << bitpos
value = '0x%08x' % numVal
if bitwidth == 64 or bitpos >= 32:
value = value + 'ULL'
elif forceSuffix:
value = value + 'U'
self.logMsg('diag', 'Enum', name, '-> bitpos [', numVal, ',', value, ']')
return [numVal, value]
if 'offset' in elem.keys():
enumNegative = False
offset = int(elem.get('offset'), 0)
extnumber = int(elem.get('extnumber'), 0)
extends = elem.get('extends')
if 'dir' in elem.keys():
enumNegative = True
self.logMsg('diag', 'Enum', name, 'offset =', offset,
'extnumber =', extnumber, 'extends =', extends,
'enumNegative =', enumNegative)
numVal = self.extBase + (extnumber - 1) * self.extBlockSize + offset
if enumNegative:
numVal *= -1
value = '%d' % numVal
self.logMsg('diag', 'Enum', name, '-> offset [', numVal, ',', value, ']')
return [numVal, value]
if 'alias' in elem.keys():
alias_of = elem.get('alias')
if parent_for_alias_dereference is None:
return (None, alias_of)
siblings = parent_for_alias_dereference.findall('enum')
for sib in siblings:
sib_name = sib.get('name')
if sib_name == alias_of:
return self.enumToValue(sib, needsNum)
raise RuntimeError("Could not find the aliased enum value")
return [None, None]
def checkDuplicateEnums(self, enums):
nameMap = {}
valueMap = {}
stripped = []
for elem in enums:
name = elem.get('name')
(numVal, strVal) = self.enumToValue(elem, True)
if name in nameMap:
(name2, numVal2, strVal2) = nameMap[name]
if (strVal2 == strVal or (numVal is not None
and numVal == numVal2)):
True
else:
self.logMsg('warn', 'checkDuplicateEnums: Duplicate enum (' + name
+ ') found with different values:' + strVal
+ ' and ' + strVal2)
continue
elif numVal in valueMap:
(name2, numVal2, strVal2) = valueMap[numVal]
msg = 'Two enums found with the same value: {} = {} = {}'.format(
name, name2.get('name'), strVal)
self.logMsg('error', msg)
nameMap[name] = [elem, numVal, strVal]
if numVal is not None:
valueMap[numVal] = [elem, numVal, strVal]
stripped.append(elem)
return stripped
def misracstyle(self):
return False;
def misracppstyle(self):
return False;
def buildEnumCDecl(self, expand, groupinfo, groupName):
if self.genOpts is None:
raise MissingGeneratorOptionsError()
if self.genOpts.conventions is None:
raise MissingGeneratorOptionsConventionsError()
groupElem = groupinfo.elem
bitwidth = 32
if self.genOpts.conventions.constFlagBits and groupElem.get('type') == 'bitmask':
bitwidth = 64
if groupElem.get('bitwidth'):
try:
bitwidth = int(groupElem.get('bitwidth'))
except ValueError as ve:
self.logMsg('error', 'Invalid value for bitwidth attribute (', groupElem.get('bitwidth'), ') for ', groupName, ' - must be an integer value\n')
exit(1)
usebitmask = False
usedefine = False
if groupElem.get('type') == 'bitmask':
if bitwidth > 32 or self.misracppstyle():
usebitmask = True
if self.misracstyle():
usedefine = True
if usedefine or usebitmask:
if bitwidth > 64:
self.logMsg('error', 'Invalid value for bitwidth attribute (', groupElem.get('bitwidth'), ') for bitmask type ', groupName, ' - must be less than or equal to 64\n')
exit(1)
else:
return self.buildEnumCDecl_BitmaskOrDefine(groupinfo, groupName, bitwidth, usedefine)
else:
if bitwidth > 32:
self.logMsg('error', 'Invalid value for bitwidth attribute (', groupElem.get('bitwidth'), ') for enum type ', groupName, ' - must be less than or equal to 32\n')
exit(1)
else:
return self.buildEnumCDecl_Enum(expand, groupinfo, groupName)
def buildEnumCDecl_BitmaskOrDefine(self, groupinfo, groupName, bitwidth, usedefine):
groupElem = groupinfo.elem
flagTypeName = groupElem.get('name')
body = "// Flag bits for " + flagTypeName + "\n"
if bitwidth == 64:
body += "typedef VkFlags64 %s;\n" % flagTypeName;
else:
body += "typedef VkFlags %s;\n" % flagTypeName;
maxValidValue = 2**(64) - 1
minValidValue = 0
enums = groupElem.findall('enum')
enums = self.checkDuplicateEnums(enums)
aliasText = ''
for elem in enums:
(numVal, strVal) = self.enumToValue(elem, True, bitwidth, True)
name = elem.get('name')
if numVal is not None and (numVal > maxValidValue or numVal < minValidValue):
self.logMsg('error', 'Allowable range for flag types in C is [', minValidValue, ',', maxValidValue, '], but', name, 'flag has a value outside of this (', strVal, ')\n')
exit(1)
decl = self.genRequirements(name, mustBeFound = False)
if self.isEnumRequired(elem):
protect = elem.get('protect')
if protect is not None:
body += '#ifdef {}\n'.format(protect)
if usedefine:
decl += "#define {} {}\n".format(name, strVal)
elif self.misracppstyle():
decl += "static constexpr {} {} {{{}}};\n".format(flagTypeName, name, strVal)
else:
while numVal is None:
alias = self.registry.tree.find("enums/enum[@name='" + strVal + "']")
if alias is not None:
(numVal, strVal) = self.enumToValue(alias, True, bitwidth, True)
else:
self.logMsg('error', 'No such alias {} for enum {}'.format(strVal, name))
decl += "static const {} {} = {};\n".format(flagTypeName, name, strVal)
if numVal is not None:
body += decl
else:
aliasText += decl
if protect is not None:
body += '#endif\n'
body += aliasText
return ("bitmask", body)
def buildEnumCDecl_Enum(self, expand, groupinfo, groupName):
groupElem = groupinfo.elem
expandName = re.sub(r'([0-9]+|[a-z_])([A-Z0-9])', r'\1_\2', groupName).upper()
expandPrefix = expandName
expandSuffix = ''
expandSuffixMatch = re.search(r'[A-Z][A-Z]+$', groupName)
if expandSuffixMatch:
expandSuffix = '_' + expandSuffixMatch.group()
expandPrefix = expandName.rsplit(expandSuffix, 1)[0]
body = ["typedef enum %s {" % groupName]
isEnum = ('FLAG_BITS' not in expandPrefix)
maxValidValue = 2**(32 - 1) - 1
minValidValue = (maxValidValue * -1) - 1
enums = groupElem.findall('enum')
enums = self.checkDuplicateEnums(enums)
minName = None
aliasText = []
maxName = None
minValue = None
maxValue = None
for elem in enums:
(numVal, strVal) = self.enumToValue(elem, True)
name = elem.get('name')
if self.isEnumRequired(elem):
decl = ''
protect = elem.get('protect')
if protect is not None:
decl += '#ifdef {}\n'.format(protect)
requirements = self.genRequirements(name, mustBeFound = False)
if requirements != '':
requirements = ' ' + requirements
decl += requirements
decl += ' {} = {},'.format(name, strVal)
if protect is not None:
decl += '\n#endif'
if numVal is not None:
body.append(decl)
else:
aliasText.append(decl)
if numVal is not None and (numVal > maxValidValue or numVal < minValidValue):
self.logMsg('error', 'Allowable range for C enum types is [', minValidValue, ',', maxValidValue, '], but', name, 'has a value outside of this (', strVal, ')\n')
exit(1)
if isEnum and numVal is not None and elem.get('extends') is None:
if minName is None:
minName = maxName = name
minValue = maxValue = numVal
elif minValue is None or numVal < minValue:
minName = name
minValue = numVal
elif maxValue is None or numVal > maxValue:
maxName = name
maxValue = numVal
body.extend(aliasText)
if isEnum and expand:
body.extend((f' {expandPrefix}_BEGIN_RANGE{expandSuffix} = {minName},',
f' {expandPrefix}_END_RANGE{expandSuffix} = {maxName},',
f' {expandPrefix}_RANGE_SIZE{expandSuffix} = ({maxName} - {minName} + 1),'))
if (self.genOpts.codeGenerator or
self.conventions.generate_max_enum_in_docs):
body.append(f' {expandPrefix}_MAX_ENUM{expandSuffix} = 0x7FFFFFFF')
body.append("} %s;" % groupName)
if groupElem.get('type') == 'bitmask':
section = 'bitmask'
else:
section = 'group'
return (section, '\n'.join(body))
def buildConstantCDecl(self, enuminfo, name, alias):
(_, strVal) = self.enumToValue(enuminfo.elem, False)
if self.misracppstyle() and enuminfo.elem.get('type') and not alias:
typeStr = enuminfo.elem.get('type');
invert = '~' in strVal
number = strVal.strip("()~UL")
if typeStr != "float":
number += 'U'
strVal = "~" if invert else ""
strVal += "static_cast<" + typeStr + ">(" + number + ")"
body = 'static constexpr ' + typeStr.ljust(9) + name.ljust(33) + ' {' + strVal + '};'
elif enuminfo.elem.get('type') and not alias:
typeStr = enuminfo.elem.get('type');
invert = '~' in strVal
paren = '(' in strVal
number = strVal.strip("()~UL")
if typeStr != "float":
if typeStr == "uint64_t":
number += 'ULL'
else:
number += 'U'
strVal = "~" if invert else ""
strVal += number
if paren:
strVal = "(" + strVal + ")";
body = '#define ' + name.ljust(33) + ' ' + strVal;
else:
body = '#define ' + name.ljust(33) + ' ' + strVal
return body
def makeDir(self, path):
self.logMsg('diag', 'OutputGenerator::makeDir(' + path + ')')
if path not in self.madeDirs:
if not os.path.exists(path):
os.makedirs(path)
self.madeDirs[path] = None
def beginFile(self, genOpts):
self.genOpts = genOpts
if self.genOpts is None:
raise MissingGeneratorOptionsError()
if self.genOpts.conventions is None:
raise MissingGeneratorOptionsConventionsError()
self.should_insert_may_alias_macro = \
self.genOpts.conventions.should_insert_may_alias_macro(self.genOpts)
self.file_suffix = self.genOpts.conventions.file_suffix
if self.genOpts.genpath is not None:
try:
sys.path.insert(0, self.genOpts.genpath)
import apimap
self.apidict = apimap
except ImportError:
self.apidict = None
self.conventions = genOpts.conventions
if self.genOpts.filename is not None:
self.outFile = tempfile.NamedTemporaryFile(mode='w', encoding='utf-8', newline='\n', delete=False)
else:
self.outFile = sys.stdout
def endFile(self):
if self.errFile:
self.errFile.flush()
if self.warnFile:
self.warnFile.flush()
if self.diagFile:
self.diagFile.flush()
if self.outFile:
self.outFile.flush()
if self.outFile != sys.stdout and self.outFile != sys.stderr:
self.outFile.close()
if self.genOpts is None:
raise MissingGeneratorOptionsError()
if self.genOpts.filename is not None:
if sys.platform == 'win32':
directory = Path(self.genOpts.directory)
if not Path.exists(directory):
os.makedirs(directory)
shutil.copy(self.outFile.name, self.genOpts.directory + '/' + self.genOpts.filename)
os.remove(self.outFile.name)
self.genOpts = None
def beginFeature(self, interface, emit):
self.emit = emit
self.featureName = interface.get('name')
self.featureExtraProtect = interface.get('protect')
def endFeature(self):
self.featureName = None
self.featureExtraProtect = None
def genRequirements(self, name, mustBeFound = True):
return ''
def validateFeature(self, featureType, featureName):
if self.featureName is None:
raise UserWarning('Attempt to generate', featureType,
featureName, 'when not in feature')
def genType(self, typeinfo, name, alias):
self.validateFeature('type', name)
def genStruct(self, typeinfo, typeName, alias):
self.validateFeature('struct', typeName)
for member in typeinfo.elem.findall('.//member'):
for comment in member.findall('comment'):
member.remove(comment)
def genGroup(self, groupinfo, groupName, alias):
self.validateFeature('group', groupName)
def genEnum(self, enuminfo, typeName, alias):
self.validateFeature('enum', typeName)
def genCmd(self, cmd, cmdinfo, alias):
self.validateFeature('command', cmdinfo)
def genSpirv(self, spirv, spirvinfo, alias):
return
def genFormat(self, format, formatinfo, alias):
return
def genSyncStage(self, stageinfo):
return
def genSyncAccess(self, accessinfo):
return
def genSyncPipeline(self, pipelineinfo):
return
def makeProtoName(self, name, tail):
if self.genOpts is None:
raise MissingGeneratorOptionsError()
return self.genOpts.apientry + name + tail
def makeTypedefName(self, name, tail):
if self.genOpts is None:
raise MissingGeneratorOptionsError()
return '(' + self.genOpts.apientryp + 'PFN_' + name + tail + ')'
def makeCParamDecl(self, param, aligncol):
if self.genOpts is None:
raise MissingGeneratorOptionsError()
if self.genOpts.conventions is None:
raise MissingGeneratorOptionsConventionsError()
indent = ' '
paramdecl = indent
prefix = noneStr(param.text)
for elem in param:
text = noneStr(elem.text)
tail = noneStr(elem.tail)
if self.should_insert_may_alias_macro and self.genOpts.conventions.is_voidpointer_alias(elem.tag, text, tail):
tail = self.genOpts.conventions.make_voidpointer_alias(tail)
if elem.tag == 'name' and aligncol > 0:
self.logMsg('diag', 'Aligning parameter', elem.text, 'to column', self.genOpts.alignFuncParam)
paramdecl = paramdecl.rstrip()
oldLen = len(paramdecl)
paramdecl = paramdecl.ljust(aligncol - 1) + ' '
newLen = len(paramdecl)
self.logMsg('diag', 'Adjust length of parameter decl from', oldLen, 'to', newLen, ':', paramdecl)
if (self.misracppstyle() and prefix.find('const ') != -1):
paramdecl += prefix.replace('const ', '') + text + ' const' + tail
else:
paramdecl += prefix + text + tail
prefix = ''
paramdecl = paramdecl + prefix
if aligncol == 0:
paramdecl = indent + ' '.join(paramdecl.split())
return paramdecl
def getCParamTypeLength(self, param):
if self.genOpts is None:
raise MissingGeneratorOptionsError()
if self.genOpts.conventions is None:
raise MissingGeneratorOptionsConventionsError()
newLen = 0
paramdecl = ' ' + noneStr(param.text)
for elem in param:
text = noneStr(elem.text)
tail = noneStr(elem.tail)
if self.should_insert_may_alias_macro and self.genOpts.conventions.is_voidpointer_alias(elem.tag, text, tail):
tail = self.genOpts.conventions.make_voidpointer_alias(tail)
if elem.tag == 'name':
newLen = len(paramdecl.rstrip())
self.logMsg('diag', 'Identifying length of', elem.text, 'as', newLen)
paramdecl += text + tail
return newLen
def getMaxCParamTypeLength(self, info):
lengths = (self.getCParamTypeLength(member)
for member in info.getMembers())
return max(lengths)
def getHandleParent(self, typename):
if self.registry is None:
raise MissingRegistryError()
info = self.registry.typedict.get(typename)
if info is None:
return None
elem = info.elem
if elem is not None:
return elem.get('parent')
return None
def iterateHandleAncestors(self, typename):
current = self.getHandleParent(typename)
while current is not None:
yield current
current = self.getHandleParent(current)
def getHandleAncestors(self, typename):
return list(self.iterateHandleAncestors(typename))
def getTypeCategory(self, typename):
if self.registry is None:
raise MissingRegistryError()
info = self.registry.typedict.get(typename)
if info is None:
return None
elem = info.elem
if elem is not None:
return elem.get('category')
return None
def isStructAlwaysValid(self, structname):
if not self.conventions:
raise RuntimeError("To use isStructAlwaysValid, be sure your options include a Conventions object.")
if self.registry is None:
raise MissingRegistryError()
if self.conventions.type_always_valid(structname):
return True
category = self.getTypeCategory(structname)
if self.conventions.category_requires_validation(category):
return False
info = self.registry.typedict.get(structname)
if info is None:
self.logMsg('error', f'isStructAlwaysValid({structname}) - structure not found in typedict')
members = info.getMembers()
for member in members:
member_name = getElemName(member)
if member_name in (self.conventions.structtype_member_name,
self.conventions.nextpointer_member_name):
return False
if member.get('noautovalidity'):
return False
member_type = getElemType(member)
if member_type in ('void', 'char') or self.paramIsArray(member) or self.paramIsPointer(member):
return False
if self.conventions.type_always_valid(member_type):
continue
member_category = self.getTypeCategory(member_type)
if self.conventions.category_requires_validation(member_category):
return False
if member_category in ('struct', 'union'):
if self.isStructAlwaysValid(member_type) is False:
return False
return True
def paramIsArray(self, param):
return param.get('len') is not None
def paramIsPointer(self, param):
tail = param.find('type').tail
return tail is not None and '*' in tail
def isEnumRequired(self, elem):
required = elem.get('required') is not None
self.logMsg('diag', 'isEnumRequired:', elem.get('name'),
'->', required)
return required
required = False
extname = elem.get('extname')
if extname is not None:
if self.genOpts.defaultExtensions == elem.get('supported'):
required = True
elif re.match(self.genOpts.addExtensions, extname) is not None:
required = True
elif elem.get('version') is not None:
required = re.match(self.genOpts.emitversions, elem.get('version')) is not None
else:
required = True
return required
def makeCDecls(self, cmd):
if self.genOpts is None:
raise MissingGeneratorOptionsError()
proto = cmd.find('proto')
params = cmd.findall('param')
pdecl = self.genOpts.apicall
tdecl = 'typedef '
pdecl += noneStr(proto.text)
tdecl += noneStr(proto.text)
for elem in proto:
text = noneStr(elem.text)
tail = noneStr(elem.tail)
if elem.tag == 'name':
pdecl += self.makeProtoName(text, tail)
tdecl += self.makeTypedefName(text, tail)
else:
pdecl += text + tail
tdecl += text + tail
if self.genOpts.alignFuncParam == 0:
pdecl = ' '.join(pdecl.split())
tdecl = ' '.join(tdecl.split())
n = len(params)
if n > 0:
indentdecl = '(\n'
indentdecl += ',\n'.join(self.makeCParamDecl(p, self.genOpts.alignFuncParam)
for p in params)
indentdecl += ');'
else:
indentdecl = '(void);'
paramdecl = '('
if n > 0:
paramnames = []
if self.misracppstyle():
for p in params:
param = ''
firstIter = True;
for t in p.itertext():
if (firstIter):
prefix = t
firstIter = False
else:
if (prefix.find('const ') != -1):
param += prefix.replace('const ', '') + t + ' const '
else:
param += prefix + t
prefix = ''
paramnames.append(param);
else:
paramnames = (''.join(t for t in p.itertext())
for p in params)
paramdecl += ', '.join(paramnames)
else:
paramdecl += 'void'
paramdecl += ");"
return [pdecl + indentdecl, tdecl + paramdecl]
def newline(self):
write('', file=self.outFile)
def setRegistry(self, registry):
self.registry = registry