summaryrefslogtreecommitdiff
path: root/codegen/vulkan/scripts/generator.py
diff options
context:
space:
mode:
Diffstat (limited to 'codegen/vulkan/scripts/generator.py')
-rw-r--r--codegen/vulkan/scripts/generator.py1204
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