Show More
Commit Description:
Fix issue with simulation....
Commit Description:
Fix issue with simulation.
Previously, if there was more than one tick being advanced at once, it would
overshoot how many ticks it covered. So if it was covering 5 ticks and each
tick happens every 100 units, rather than recording that it had simulated
through t= 500, it would increase the cumulative time for each tick, recording
that it had simulated through t=2500.
Add error message, too.
References:
File last commit:
Show/Diff file:
Action:
FNA/lib/FNA3D/Vulkan-Headers/registry/generator.py
1036 lines | 40.3 KiB | text/x-python | PythonLexer
1036 lines | 40.3 KiB | text/x-python | PythonLexer
r690 | #!/usr/bin/python3 -i | |||
# | ||||
# Copyright (c) 2013-2020 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: | ||||
return "'{}'".format(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): | ||||
"""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""" | ||||
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 __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.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): | ||||
"""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 | ||||
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 bitpos >= 32: | ||||
value = value + 'ULL' | ||||
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 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) | ||||
# Bitmask types support 64-bit flags, so have different handling | ||||
if groupElem.get('type') == 'bitmask': | ||||
# Validate the bitwidth and generate values appropriately | ||||
# Bitmask flags up to 64-bit are generated as static const uint64_t values | ||||
# Bitmask flags up to 32-bit are generated as C enum values | ||||
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) | ||||
elif bitwidth > 32: | ||||
return self.buildEnumCDecl_Bitmask(groupinfo, groupName) | ||||
else: | ||||
return self.buildEnumCDecl_Enum(expand, groupinfo, groupName) | ||||
else: | ||||
# Validate the bitwidth and generate values appropriately | ||||
# Enum group types up to 32-bit are generated as C enum values | ||||
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_Bitmask(self, groupinfo, groupName): | ||||
"""Generate the C declaration for an "enum" that is actually a | ||||
set of flag bits""" | ||||
groupElem = groupinfo.elem | ||||
flagTypeName = groupinfo.flagType.elem.get('name') | ||||
# Prefix | ||||
body = "// Flag bits for " + flagTypeName + "\n" | ||||
# Maximum allowable value for a flag (unsigned 64-bit integer) | ||||
maxValidValue = 2**(64) - 1 | ||||
minValidValue = 0 | ||||
# Loop over the nested 'enum' tags. | ||||
for elem in groupElem.findall('enum'): | ||||
# 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') | ||||
# 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) | ||||
body += self.genRequirements(name, mustBeFound = False) | ||||
body += "static const {} {} = {};\n".format(flagTypeName, name, strVal) | ||||
# 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-9a-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): | ||||
# Indent requirements comment, if there is one | ||||
decl = self.genRequirements(name, mustBeFound = False) | ||||
if decl != '': | ||||
decl = ' ' + decl | ||||
decl += " {} = {},".format(name, strVal) | ||||
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 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): | ||||
"""Start a new interface file | ||||
- genOpts - GeneratorOptions controlling what's generated and how""" | ||||
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: | ||||
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() | ||||
self.outFile.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') | ||||
# 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.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 + 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) | ||||
paramdecl += text + tail | ||||
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 = (''.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 | ||||