diff options
Diffstat (limited to 'codegen/vulkan/scripts/generator.py')
-rw-r--r-- | codegen/vulkan/scripts/generator.py | 1204 |
1 files changed, 0 insertions, 1204 deletions
diff --git a/codegen/vulkan/scripts/generator.py b/codegen/vulkan/scripts/generator.py deleted file mode 100644 index cb5f0f59..00000000 --- a/codegen/vulkan/scripts/generator.py +++ /dev/null @@ -1,1204 +0,0 @@ -#!/usr/bin/python3 -i -# -# Copyright 2013-2021 The Khronos Group Inc. -# -# SPDX-License-Identifier: Apache-2.0 -"""Base class for source/header/doc generators, as well as some utility functions.""" - -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): - """Return string argument, or "" if argument is None. - - Used in converting etree Elements into text. - s - string to convert""" - if s: - return s - return "" - - -def enquote(s): - """Return string argument with surrounding quotes, - for serialization into Python code.""" - if s: - if isinstance(s, str): - return "'{}'".format(s) - else: - return s - return None - - -def regSortCategoryKey(feature): - """Sort key for regSortFeatures. - Sorts by category of the feature name string: - - - Core API features (those defined with a `<feature>` tag) - - ARB/KHR/OES (Khronos extensions) - - other (EXT/vendor extensions)""" - - if feature.elem.tag == 'feature': - return 0 - if (feature.category == 'ARB' - or feature.category == 'KHR' - or feature.category == 'OES'): - return 1 - - return 2 - - -def regSortOrderKey(feature): - """Sort key for regSortFeatures - key is the sortorder attribute.""" - - # print("regSortOrderKey {} -> {}".format(feature.name, feature.sortorder)) - return feature.sortorder - - -def regSortFeatureVersionKey(feature): - """Sort key for regSortFeatures - key is the feature version. - `<extension>` elements all have version number 0.""" - - return float(feature.versionNumber) - - -def regSortExtensionNumberKey(feature): - """Sort key for regSortFeatures - key is the extension number. - `<feature>` elements all have extension number 0.""" - - return int(feature.number) - - -def regSortFeatures(featureList): - """Default sort procedure for features. - - - Sorts by explicit sort order (default 0) relative to other features - - then by feature category ('feature' or 'extension'), - - then by version number (for features) - - then by extension number (for extensions)""" - featureList.sort(key=regSortExtensionNumberKey) - featureList.sort(key=regSortFeatureVersionKey) - featureList.sort(key=regSortCategoryKey) - featureList.sort(key=regSortOrderKey) - - -class GeneratorOptions: - """Base class for options used during header/documentation production. - - These options are target language independent, and used by - Registry.apiGen() and by base OutputGenerator objects.""" - - def __init__(self, - conventions=None, - filename=None, - directory='.', - genpath=None, - apiname=None, - profile=None, - versions='.*', - emitversions='.*', - defaultExtensions=None, - addExtensions=None, - removeExtensions=None, - emitExtensions=None, - emitSpirv=None, - reparentEnums=True, - sortProcedure=regSortFeatures, - requireCommandAliases=False, - ): - """Constructor. - - Arguments: - - - conventions - may be mandatory for some generators: - an object that implements ConventionsBase - - filename - basename of file to generate, or None to write to stdout. - - directory - directory in which to generate files - - genpath - path to previously generated files, such as api.py - - apiname - string matching `<api>` 'apiname' attribute, e.g. 'gl'. - - profile - string specifying API profile , e.g. 'core', or None. - - versions - regex matching API versions to process interfaces for. - Normally `'.*'` or `'[0-9][.][0-9]'` to match all defined versions. - - emitversions - regex matching API versions to actually emit - interfaces for (though all requested versions are considered - when deciding which interfaces to generate). For GL 4.3 glext.h, - this might be `'1[.][2-5]|[2-4][.][0-9]'`. - - defaultExtensions - If not None, a string which must in its - entirety match the pattern in the "supported" attribute of - the `<extension>`. Defaults to None. Usually the same as apiname. - - addExtensions - regex matching names of additional extensions - to include. Defaults to None. - - removeExtensions - regex matching names of extensions to - remove (after defaultExtensions and addExtensions). Defaults - to None. - - emitExtensions - regex matching names of extensions to actually emit - interfaces for (though all requested versions are considered when - deciding which interfaces to generate). - to None. - - emitSpirv - regex matching names of extensions and capabilities - to actually emit interfaces for. - - reparentEnums - move <enum> elements which extend an enumerated - type from <feature> or <extension> elements to the target <enums> - element. This is required for almost all purposes, but the - InterfaceGenerator relies on the list of interfaces in the <feature> - or <extension> being complete. Defaults to True. - - sortProcedure - takes a list of FeatureInfo objects and sorts - them in place to a preferred order in the generated output. - Default is core API versions, ARB/KHR/OES extensions, all other - extensions, by core API version number or extension number in each - group. - - The regex patterns can be None or empty, in which case they match - nothing.""" - self.conventions = conventions - """may be mandatory for some generators: - an object that implements ConventionsBase""" - - self.filename = filename - "basename of file to generate, or None to write to stdout." - - self.genpath = genpath - """path to previously generated files, such as api.py""" - - self.directory = directory - "directory in which to generate filename" - - self.apiname = apiname - "string matching `<api>` 'apiname' attribute, e.g. 'gl'." - - self.profile = profile - "string specifying API profile , e.g. 'core', or None." - - self.versions = self.emptyRegex(versions) - """regex matching API versions to process interfaces for. - Normally `'.*'` or `'[0-9][.][0-9]'` to match all defined versions.""" - - self.emitversions = self.emptyRegex(emitversions) - """regex matching API versions to actually emit - interfaces for (though all requested versions are considered - when deciding which interfaces to generate). For GL 4.3 glext.h, - this might be `'1[.][2-5]|[2-4][.][0-9]'`.""" - - self.defaultExtensions = defaultExtensions - """If not None, a string which must in its - entirety match the pattern in the "supported" attribute of - the `<extension>`. Defaults to None. Usually the same as apiname.""" - - self.addExtensions = self.emptyRegex(addExtensions) - """regex matching names of additional extensions - to include. Defaults to None.""" - - self.removeExtensions = self.emptyRegex(removeExtensions) - """regex matching names of extensions to - remove (after defaultExtensions and addExtensions). Defaults - to None.""" - - self.emitExtensions = self.emptyRegex(emitExtensions) - """regex matching names of extensions to actually emit - interfaces for (though all requested versions are considered when - deciding which interfaces to generate).""" - - self.emitSpirv = self.emptyRegex(emitSpirv) - """regex matching names of extensions and capabilities - to actually emit interfaces for.""" - - self.reparentEnums = reparentEnums - """boolean specifying whether to remove <enum> elements from - <feature> or <extension> when extending an <enums> type.""" - - self.sortProcedure = sortProcedure - """takes a list of FeatureInfo objects and sorts - them in place to a preferred order in the generated output. - Default is core API versions, ARB/KHR/OES extensions, all - other extensions, alphabetically within each group.""" - - self.codeGenerator = False - """True if this generator makes compilable code""" - - self.requireCommandAliases = requireCommandAliases - """True if alias= attributes of <command> tags are transitively - required.""" - - def emptyRegex(self, pat): - """Substitute a regular expression which matches no version - or extension names for None or the empty string.""" - if not pat: - return '_nomatch_^' - - return pat - - -class OutputGenerator: - """Generate specified API interfaces in a specific style, such as a C header. - - Base class for generating API interfaces. - Manages basic logic, logging, and output file control. - Derived classes actually generate formatted output. - """ - - # categoryToPath - map XML 'category' to include file directory name - categoryToPath = { - 'bitmask': 'flags', - 'enum': 'enums', - 'funcpointer': 'funcpointers', - 'handle': 'handles', - 'define': 'defines', - 'basetype': 'basetypes', - } - - def breakName(self, name, msg): - """Break into debugger if this is a special name""" - - # List of string names to break on - 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): - """Constructor - - - errFile, warnFile, diagFile - file handles to write errors, - warnings, diagnostics to. May be None to not write.""" - self.outFile = None - self.errFile = errFile - self.warnFile = warnFile - self.diagFile = diagFile - # Internal state - self.featureName = None - self.featureType = None - self.genOpts = None - self.registry = None - self.featureDictionary = {} - # Used for extension enum value generation - self.extBase = 1000000000 - self.extBlockSize = 1000 - self.madeDirs = {} - - # API dictionary, which may be loaded by the beginFile method of - # derived generators. - self.apidict = None - - def logMsg(self, level, *args): - """Write a message of different categories to different - destinations. - - - `level` - - 'diag' (diagnostic, voluminous) - - 'warn' (warning) - - 'error' (fatal error - raises exception after logging) - - - `*args` - print()-style arguments to direct to corresponding log""" - 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): - """Parse and convert an `<enum>` tag into a value. - - Returns a list: - - - first element - integer representation of the value, or None - if needsNum is False. The value must be a legal number - if needsNum is True. - - second element - string representation of the value - - There are several possible representations of values. - - - A 'value' attribute simply contains the value. - - A 'bitpos' attribute defines a value by specifying the bit - position which is set in that value. - - An 'offset','extbase','extends' triplet specifies a value - as an offset to a base value defined by the specified - 'extbase' extension name, which is then cast to the - typename specified by 'extends'. This requires probing - the registry database, and imbeds knowledge of the - API extension enum scheme in this function. - - An 'alias' attribute contains the name of another enum - which this is an alias of. The other enum must be - declared first when emitting this enum.""" - name = elem.get('name') - numVal = None - if 'value' in elem.keys(): - value = elem.get('value') - # print('About to translate value =', value, 'type =', type(value)) - if needsNum: - numVal = int(value, 0) - # If there's a non-integer, numeric 'type' attribute (e.g. 'u' or - # 'ull'), append it to the string value. - # t = enuminfo.elem.get('type') - # if t is not None and t != '' and t != 'i' and t != 's': - # value += enuminfo.type - 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: - value = value + 'ULL' - elif forceSuffix: - value = value + 'U' - self.logMsg('diag', 'Enum', name, '-> bitpos [', numVal, ',', value, ']') - return [numVal, value] - if 'offset' in elem.keys(): - # Obtain values in the mapping from the attributes - 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) - # Now determine the actual enumerant value, as defined - # in the "Layers and Extensions" appendix of the spec. - numVal = self.extBase + (extnumber - 1) * self.extBlockSize + offset - if enumNegative: - numVal *= -1 - value = '%d' % numVal - # More logic needed! - self.logMsg('diag', 'Enum', name, '-> offset [', numVal, ',', value, ']') - return [numVal, value] - if 'alias' in elem.keys(): - return [None, elem.get('alias')] - return [None, None] - - def checkDuplicateEnums(self, enums): - """Check enumerated values for duplicates. - - - enums - list of `<enum>` Elements - - returns the list with duplicates stripped""" - # Dictionaries indexed by name and numeric value. - # Entries are [ Element, numVal, strVal ] matching name or value - - nameMap = {} - valueMap = {} - - stripped = [] - for elem in enums: - name = elem.get('name') - (numVal, strVal) = self.enumToValue(elem, True) - - if name in nameMap: - # Duplicate name found; check values - (name2, numVal2, strVal2) = nameMap[name] - - # Duplicate enum values for the same name are benign. This - # happens when defining the same enum conditionally in - # several extension blocks. - if (strVal2 == strVal or (numVal is not None - and numVal == numVal2)): - True - # self.logMsg('info', 'checkDuplicateEnums: Duplicate enum (' + name + - # ') found with the same value:' + strVal) - else: - self.logMsg('warn', 'checkDuplicateEnums: Duplicate enum (' + name - + ') found with different values:' + strVal - + ' and ' + strVal2) - - # Don't add the duplicate to the returned list - continue - elif numVal in valueMap: - # Duplicate value found (such as an alias); report it, but - # still add this enum to the list. - (name2, numVal2, strVal2) = valueMap[numVal] - - msg = 'Two enums found with the same value: {} = {} = {}'.format( - name, name2.get('name'), strVal) - self.logMsg('error', msg) - - # Track this enum to detect followon duplicates - nameMap[name] = [elem, numVal, strVal] - if numVal is not None: - valueMap[numVal] = [elem, numVal, strVal] - - # Add this enum to the list - stripped.append(elem) - - # Return the list - return stripped - - def misracstyle(self): - return False; - - def misracppstyle(self): - return False; - - def buildEnumCDecl(self, expand, groupinfo, groupName): - """Generate the C declaration for an enum""" - groupElem = groupinfo.elem - - # Determine the required bit width for the enum group. - # 32 is the default, which generates C enum types for the values. - bitwidth = 32 - - # If the constFlagBits preference is set, 64 is the default for bitmasks - if self.genOpts.conventions.constFlagBits and groupElem.get('type') == 'bitmask': - bitwidth = 64 - - # Check for an explicitly defined bitwidth, which will override any defaults. - 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 - - # Bitmask flags can be generated as either "static const uint{32,64}_t" values, - # or as 32-bit C enums. 64-bit types must use uint64_t values. - if groupElem.get('type') == 'bitmask': - if bitwidth > 32 or self.misracppstyle(): - usebitmask = True - if self.misracstyle(): - usedefine = True - - if usedefine or usebitmask: - # Validate the bitwidth and generate values appropriately - 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: - # Validate the bitwidth and generate values appropriately - 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): - """Generate the C declaration for an "enum" that is actually a - set of flag bits""" - groupElem = groupinfo.elem - flagTypeName = groupElem.get('name') - - # Prefix - body = "// Flag bits for " + flagTypeName + "\n" - - if bitwidth == 64: - body += "typedef VkFlags64 %s;\n" % flagTypeName; - else: - body += "typedef VkFlags %s;\n" % flagTypeName; - - # Maximum allowable value for a flag (unsigned 64-bit integer) - maxValidValue = 2**(64) - 1 - minValidValue = 0 - - # Get a list of nested 'enum' tags. - enums = groupElem.findall('enum') - - # Check for and report duplicates, and return a list with them - # removed. - enums = self.checkDuplicateEnums(enums) - - # Accumulate non-numeric enumerant values separately and append - # them following the numeric values, to allow for aliases. - # NOTE: this doesn't do a topological sort yet, so aliases of - # aliases can still get in the wrong order. - aliasText = '' - - # Loop over the nested 'enum' tags. - for elem in enums: - # Convert the value to an integer and use that to track min/max. - # Values of form -(number) are accepted but nothing more complex. - # Should catch exceptions here for more complex constructs. Not yet. - (numVal, strVal) = self.enumToValue(elem, True, bitwidth, True) - name = elem.get('name') - - # Range check for the enum value - 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: - # Some C compilers only allow initializing a 'static const' variable with a literal value. - # So initializing an alias from another 'static const' value would fail to compile. - # Work around this by chasing the aliases to get the actual value. - while numVal is None: - alias = self.registry.tree.find("enums/enum[@name='" + strVal + "']") - (numVal, strVal) = self.enumToValue(alias, True, bitwidth, True) - 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' - - # Now append the non-numeric enumerant values - body += aliasText - - # Postfix - - return ("bitmask", body) - - def buildEnumCDecl_Enum(self, expand, groupinfo, groupName): - """Generate the C declaration for an enumerated type""" - groupElem = groupinfo.elem - - # Break the group name into prefix and suffix portions for range - # enum generation - 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() - # Strip off the suffix from the prefix - expandPrefix = expandName.rsplit(expandSuffix, 1)[0] - - # Prefix - body = ["typedef enum %s {" % groupName] - - # @@ Should use the type="bitmask" attribute instead - isEnum = ('FLAG_BITS' not in expandPrefix) - - # Allowable range for a C enum - which is that of a signed 32-bit integer - maxValidValue = 2**(32 - 1) - 1 - minValidValue = (maxValidValue * -1) - 1 - - - # Get a list of nested 'enum' tags. - enums = groupElem.findall('enum') - - # Check for and report duplicates, and return a list with them - # removed. - enums = self.checkDuplicateEnums(enums) - - # Loop over the nested 'enum' tags. Keep track of the minimum and - # maximum numeric values, if they can be determined; but only for - # core API enumerants, not extension enumerants. This is inferred - # by looking for 'extends' attributes. - minName = None - - # Accumulate non-numeric enumerant values separately and append - # them following the numeric values, to allow for aliases. - # NOTE: this doesn't do a topological sort yet, so aliases of - # aliases can still get in the wrong order. - aliasText = [] - - for elem in enums: - # Convert the value to an integer and use that to track min/max. - # Values of form -(number) are accepted but nothing more complex. - # Should catch exceptions here for more complex constructs. Not yet. - (numVal, strVal) = self.enumToValue(elem, True) - name = elem.get('name') - - # Extension enumerants are only included if they are required - if self.isEnumRequired(elem): - decl = '' - - protect = elem.get('protect') - if protect is not None: - decl += '#ifdef {}\n'.format(protect) - - # Indent requirements comment, if there is one - 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) - - # Range check for the enum value - 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) - - # Don't track min/max for non-numbers (numVal is None) - if isEnum and numVal is not None and elem.get('extends') is None: - if minName is None: - minName = maxName = name - minValue = maxValue = numVal - elif numVal < minValue: - minName = name - minValue = numVal - elif numVal > maxValue: - maxName = name - maxValue = numVal - - # Now append the non-numeric enumerant values - body.extend(aliasText) - - # Generate min/max value tokens - legacy use case. - if isEnum and expand: - body.extend((" {}_BEGIN_RANGE{} = {},".format(expandPrefix, expandSuffix, minName), - " {}_END_RANGE{} = {},".format( - expandPrefix, expandSuffix, maxName), - " {}_RANGE_SIZE{} = ({} - {} + 1),".format(expandPrefix, expandSuffix, maxName, minName))) - - # Generate a range-padding value to ensure the enum is 32 bits, but - # only in code generators, so it doesn't appear in documentation - if (self.genOpts.codeGenerator or - self.conventions.generate_max_enum_in_docs): - body.append(" {}_MAX_ENUM{} = 0x7FFFFFFF".format( - expandPrefix, expandSuffix)) - - # Postfix - body.append("} %s;" % groupName) - - # Determine appropriate section for this declaration - if groupElem.get('type') == 'bitmask': - section = 'bitmask' - else: - section = 'group' - - return (section, '\n'.join(body)) - - def buildConstantCDecl(self, enuminfo, name, alias): - """Generate the C declaration for a constant (a single <enum> - value). - - <enum> tags may specify their values in several ways, but are - usually just integers or floating-point numbers.""" - - (_, strVal) = self.enumToValue(enuminfo.elem, False) - - if self.misracppstyle() and enuminfo.elem.get('type') and not alias: - # Generate e.g.: static constexpr uint32_t x = ~static_cast<uint32_t>(1U); - # This appeases MISRA "underlying type" rules. - 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: - # Generate e.g.: #define x (~0ULL) - 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): - """Create a directory, if not already done. - - Generally called from derived generators creating hierarchies.""" - self.logMsg('diag', 'OutputGenerator::makeDir(' + path + ')') - if path not in self.madeDirs: - # This can get race conditions with multiple writers, see - # https://stackoverflow.com/questions/273192/ - if not os.path.exists(path): - os.makedirs(path) - self.madeDirs[path] = None - - def beginFile(self, genOpts, suppress = False): - """Start a new interface file - - - genOpts - GeneratorOptions controlling what's generated and how""" - self.suppress = suppress - self.genOpts = genOpts - self.should_insert_may_alias_macro = \ - self.genOpts.conventions.should_insert_may_alias_macro(self.genOpts) - - # Try to import the API dictionary, api.py, if it exists. Nothing in - # api.py cannot be extracted directly from the XML, and in the - # future we should do that. - if self.genOpts.genpath is not None: - try: - sys.path.insert(0, self.genOpts.genpath) - import api - self.apidict = api - except ImportError: - self.apidict = None - - self.conventions = genOpts.conventions - - # Open a temporary file for accumulating output. - if self.genOpts.filename is not None and not self.suppress: - 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 != sys.stdout and self.outFile != sys.stderr: - self.outFile.close() - - # On successfully generating output, move the temporary file to the - # target file. - 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): - """Write interface for a feature and tag generated features as having been done. - - - interface - element for the `<version>` / `<extension>` to generate - - emit - actually write to the header only when True""" - self.emit = emit - self.featureName = interface.get('name') - self.featureType = interface.get('type') - # If there's an additional 'protect' attribute in the feature, save it - self.featureExtraProtect = interface.get('protect') - - def endFeature(self): - """Finish an interface file, closing it when done. - - Derived classes responsible for emitting feature""" - self.featureName = None - self.featureType = None - self.featureExtraProtect = None - - def genRequirements(self, name, mustBeFound = True): - """Generate text showing what core versions and extensions introduce - an API. This exists in the base Generator class because it's used by - the shared enumerant-generating interfaces (buildEnumCDecl, etc.). - Here it returns an empty string for most generators, but can be - overridden by e.g. DocGenerator. - - - name - name of the API - - mustBeFound - If True, when requirements for 'name' cannot be - determined, a warning comment is generated. - """ - - return '' - - def validateFeature(self, featureType, featureName): - """Validate we're generating something only inside a `<feature>` tag""" - if self.featureName is None: - raise UserWarning('Attempt to generate', featureType, - featureName, 'when not in feature') - - def genType(self, typeinfo, name, alias): - """Generate interface for a type - - - typeinfo - TypeInfo for a type - - Extend to generate as desired in your derived class.""" - self.validateFeature('type', name) - - def genStruct(self, typeinfo, typeName, alias): - """Generate interface for a C "struct" type. - - - typeinfo - TypeInfo for a type interpreted as a struct - - Extend to generate as desired in your derived class.""" - self.validateFeature('struct', typeName) - - # The mixed-mode <member> tags may contain no-op <comment> tags. - # It is convenient to remove them here where all output generators - # will benefit. - for member in typeinfo.elem.findall('.//member'): - for comment in member.findall('comment'): - member.remove(comment) - - def genGroup(self, groupinfo, groupName, alias): - """Generate interface for a group of enums (C "enum") - - - groupinfo - GroupInfo for a group. - - Extend to generate as desired in your derived class.""" - - self.validateFeature('group', groupName) - - def genEnum(self, enuminfo, typeName, alias): - """Generate interface for an enum (constant). - - - enuminfo - EnumInfo for an enum - - name - enum name - - Extend to generate as desired in your derived class.""" - self.validateFeature('enum', typeName) - - def genCmd(self, cmd, cmdinfo, alias): - """Generate interface for a command. - - - cmdinfo - CmdInfo for a command - - Extend to generate as desired in your derived class.""" - self.validateFeature('command', cmdinfo) - - def genSpirv(self, spirv, spirvinfo, alias): - """Generate interface for a spirv element. - - - spirvinfo - SpirvInfo for a command - - Extend to generate as desired in your derived class.""" - return - - def makeProtoName(self, name, tail): - """Turn a `<proto>` `<name>` into C-language prototype - and typedef declarations for that name. - - - name - contents of `<name>` tag - - tail - whatever text follows that tag in the Element""" - return self.genOpts.apientry + name + tail - - def makeTypedefName(self, name, tail): - """Make the function-pointer typedef name for a command.""" - return '(' + self.genOpts.apientryp + 'PFN_' + name + tail + ')' - - def makeCParamDecl(self, param, aligncol): - """Return a string which is an indented, formatted - declaration for a `<param>` or `<member>` block (e.g. function parameter - or structure/union member). - - - param - Element (`<param>` or `<member>`) to format - - aligncol - if non-zero, attempt to align the nested `<name>` element - at this column""" - 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): - # OpenXR-specific macro insertion - but not in apiinc for the spec - 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) - # Align at specified column, if possible - paramdecl = paramdecl.rstrip() - oldLen = len(paramdecl) - # This works around a problem where very long type names - - # longer than the alignment column - would run into the tail - # text. - 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): - # Change pointer type order from e.g. "const void *" to "void const *". - # If the string starts with 'const', reorder it to be after the first type. - paramdecl += prefix.replace('const ', '') + text + ' const' + tail - else: - paramdecl += prefix + text + tail - - # Clear prefix for subsequent iterations - prefix = '' - if aligncol == 0: - # Squeeze out multiple spaces other than the indentation - paramdecl = indent + ' '.join(paramdecl.split()) - return paramdecl - - def getCParamTypeLength(self, param): - """Return the length of the type field is an indented, formatted - declaration for a `<param>` or `<member>` block (e.g. function parameter - or structure/union member). - - - param - Element (`<param>` or `<member>`) to identify""" - - # Allow for missing <name> tag - 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): - # OpenXR-specific macro insertion - tail = self.genOpts.conventions.make_voidpointer_alias(tail) - if elem.tag == 'name': - # Align at specified column, if possible - newLen = len(paramdecl.rstrip()) - self.logMsg('diag', 'Identifying length of', elem.text, 'as', newLen) - paramdecl += text + tail - - return newLen - - def getMaxCParamTypeLength(self, info): - """Return the length of the longest type field for a member/parameter. - - - info - TypeInfo or CommandInfo. - """ - lengths = (self.getCParamTypeLength(member) - for member in info.getMembers()) - return max(lengths) - - def getHandleParent(self, typename): - """Get the parent of a handle object.""" - 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): - """Iterate through the ancestors of a handle type.""" - current = self.getHandleParent(typename) - while current is not None: - yield current - current = self.getHandleParent(current) - - def getHandleAncestors(self, typename): - """Get the ancestors of a handle object.""" - return list(self.iterateHandleAncestors(typename)) - - def getTypeCategory(self, typename): - """Get the category of a type.""" - 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): - """Try to do check if a structure is always considered valid (i.e. there's no rules to its acceptance).""" - # A conventions object is required for this call. - if not self.conventions: - raise RuntimeError("To use isStructAlwaysValid, be sure your options include a Conventions object.") - - 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) - assert(info is not None) - - 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 isEnumRequired(self, elem): - """Return True if this `<enum>` element is - required, False otherwise - - - elem - `<enum>` element to test""" - required = elem.get('required') is not None - self.logMsg('diag', 'isEnumRequired:', elem.get('name'), - '->', required) - return required - - # @@@ This code is overridden by equivalent code now run in - # @@@ Registry.generateFeature - - required = False - - extname = elem.get('extname') - if extname is not None: - # 'supported' attribute was injected when the <enum> element was - # moved into the <enums> group in Registry.parseTree() - 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): - """Return C prototype and function pointer typedef for a - `<command>` Element, as a two-element list of strings. - - - cmd - Element containing a `<command>` tag""" - proto = cmd.find('proto') - params = cmd.findall('param') - # Begin accumulating prototype and typedef strings - pdecl = self.genOpts.apicall - tdecl = 'typedef ' - - # Insert the function return type/name. - # For prototypes, add APIENTRY macro before the name - # For typedefs, add (APIENTRY *<name>) around the name and - # use the PFN_cmdnameproc naming convention. - # Done by walking the tree for <proto> element by element. - # etree has elem.text followed by (elem[i], elem[i].tail) - # for each child element and any following text - # Leading text - pdecl += noneStr(proto.text) - tdecl += noneStr(proto.text) - # For each child element, if it's a <name> wrap in appropriate - # declaration. Otherwise append its contents and tail contents. - 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: - # Squeeze out multiple spaces - there is no indentation - pdecl = ' '.join(pdecl.split()) - tdecl = ' '.join(tdecl.split()) - - # Now add the parameter declaration list, which is identical - # for prototypes and typedefs. Concatenate all the text from - # a <param> node without the tags. No tree walking required - # since all tags are ignored. - # Uses: self.indentFuncProto - # self.indentFuncPointer - # self.alignFuncParam - n = len(params) - # Indented parameters - if n > 0: - indentdecl = '(\n' - indentdecl += ',\n'.join(self.makeCParamDecl(p, self.genOpts.alignFuncParam) - for p in params) - indentdecl += ');' - else: - indentdecl = '(void);' - # Non-indented parameters - 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: - # Change pointer type order from e.g. "const void *" to "void const *". - # If the string starts with 'const', reorder it to be after the first type. - if (prefix.find('const ') != -1): - param += prefix.replace('const ', '') + t + ' const ' - else: - param += prefix + t - # Clear prefix for subsequent iterations - 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): - """Print a newline to the output file (utility function)""" - write('', file=self.outFile) - - def setRegistry(self, registry): - self.registry = registry |