| #!/usr/bin/python3 -i |
| # |
| # Copyright (c) 2013-2019 The Khronos Group Inc. |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| |
| from __future__ import unicode_literals |
| |
| import io |
| import os |
| import re |
| import pdb |
| import sys |
| from pathlib import Path |
| |
| 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) |
| |
| # noneStr - returns string argument, or "" if argument is None. |
| # Used in converting etree Elements into text. |
| # s - string to convert |
| def noneStr(s): |
| if s: |
| return s |
| return "" |
| |
| # enquote - returns string argument with surrounding quotes, |
| # for serialization into Python code. |
| def enquote(s): |
| if s: |
| return "'{}'".format(s) |
| return None |
| |
| # Primary 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) |
| # This will need changing for Vulkan! |
| def regSortCategoryKey(feature): |
| if feature.elem.tag == 'feature': |
| return 0 |
| if (feature.category == 'ARB' or |
| feature.category == 'KHR' or |
| feature.category == 'OES'): |
| return 1 |
| |
| return 2 |
| |
| # Secondary sort key for regSortFeatures. |
| # Sorts by extension name. |
| def regSortNameKey(feature): |
| return feature.name |
| |
| # Second sort key for regSortFeatures. |
| # Sorts by feature version. <extension> elements all have version number "0" |
| def regSortFeatureVersionKey(feature): |
| return float(feature.versionNumber) |
| |
| # Tertiary sort key for regSortFeatures. |
| # Sorts by extension number. <feature> elements all have extension number 0. |
| def regSortExtensionNumberKey(feature): |
| return int(feature.number) |
| |
| # regSortFeatures - default sort procedure for features. |
| # Sorts by primary key of feature category ('feature' or 'extension') |
| # then by version number (for features) |
| # then by extension number (for extensions) |
| def regSortFeatures(featureList): |
| featureList.sort(key = regSortExtensionNumberKey) |
| featureList.sort(key = regSortFeatureVersionKey) |
| featureList.sort(key = regSortCategoryKey) |
| |
| # GeneratorOptions - base class for options used during header production |
| # These options are target language independent, and used by |
| # Registry.apiGen() and by base OutputGenerator objects. |
| # |
| # Members |
| # 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 filename |
| # 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). |
| # 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. |
| # The regex patterns can be None or empty, in which case they match |
| # nothing. |
| class GeneratorOptions: |
| """Represents options during header production from an API registry""" |
| |
| def __init__(self, |
| conventions = None, |
| filename = None, |
| directory = '.', |
| apiname = None, |
| profile = None, |
| versions = '.*', |
| emitversions = '.*', |
| defaultExtensions = None, |
| addExtensions = None, |
| removeExtensions = None, |
| emitExtensions = None, |
| sortProcedure = regSortFeatures): |
| self.conventions = conventions |
| self.filename = filename |
| self.directory = directory |
| self.apiname = apiname |
| self.profile = profile |
| self.versions = self.emptyRegex(versions) |
| self.emitversions = self.emptyRegex(emitversions) |
| self.defaultExtensions = defaultExtensions |
| self.addExtensions = self.emptyRegex(addExtensions) |
| self.removeExtensions = self.emptyRegex(removeExtensions) |
| self.emitExtensions = self.emptyRegex(emitExtensions) |
| self.sortProcedure = sortProcedure |
| |
| # Substitute a regular expression which matches no version |
| # or extension names for None or the empty string. |
| def emptyRegex(self, pat): |
| if pat is None or pat == '': |
| return '_nomatch_^' |
| |
| return pat |
| |
| # OutputGenerator - base class for generating API interfaces. |
| # Manages basic logic, logging, and output file control |
| # Derived classes actually generate formatted output. |
| # |
| # ---- methods ---- |
| # OutputGenerator(errFile, warnFile, diagFile) |
| # errFile, warnFile, diagFile - file handles to write errors, |
| # warnings, diagnostics to. May be None to not write. |
| # logMsg(level, *args) - log messages of different categories |
| # level - 'error', 'warn', or 'diag'. 'error' will also |
| # raise a UserWarning exception |
| # *args - print()-style arguments |
| # setExtMap(map) - specify a dictionary map from extension names to |
| # numbers, used in creating values for extension enumerants. |
| # makeDir(directory) - create a directory, if not already done. |
| # Generally called from derived generators creating hierarchies. |
| # beginFile(genOpts) - start a new interface file |
| # genOpts - GeneratorOptions controlling what's generated and how |
| # endFile() - finish an interface file, closing it when done |
| # beginFeature(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 |
| # endFeature() - finish an interface. |
| # genType(typeinfo,name,alias) - generate interface for a type |
| # typeinfo - TypeInfo for a type |
| # genStruct(typeinfo,name,alias) - generate interface for a C "struct" type. |
| # typeinfo - TypeInfo for a type interpreted as a struct |
| # genGroup(groupinfo,name,alias) - generate interface for a group of enums (C "enum") |
| # groupinfo - GroupInfo for a group |
| # genEnum(enuminfo,name,alias) - generate interface for an enum (constant) |
| # enuminfo - EnumInfo for an enum |
| # name - enum name |
| # genCmd(cmdinfo,name,alias) - generate interface for a command |
| # cmdinfo - CmdInfo for a command |
| # isEnumRequired(enumElem) - return True if this <enum> element is required |
| # elem - <enum> element to test |
| # makeCDecls(cmd) - return C prototype and function pointer typedef for a |
| # <command> Element, as a list of two strings |
| # cmd - Element for the <command> |
| # newline() - print a newline to the output file (utility function) |
| # |
| class OutputGenerator: |
| """Generate specified API interfaces in a specific style, such as a C header""" |
| |
| # categoryToPath - map XML 'category' to include file directory name |
| categoryToPath = { |
| 'bitmask' : 'flags', |
| 'enum' : 'enums', |
| 'funcpointer' : 'funcpointers', |
| 'handle' : 'handles', |
| 'define' : 'defines', |
| 'basetype' : 'basetypes', |
| } |
| |
| # Constructor |
| def __init__(self, |
| errFile = sys.stderr, |
| warnFile = sys.stderr, |
| diagFile = sys.stdout): |
| self.outFile = None |
| self.errFile = errFile |
| self.warnFile = warnFile |
| self.diagFile = diagFile |
| # Internal state |
| self.featureName = None |
| self.genOpts = None |
| self.registry = None |
| # Used for extension enum value generation |
| self.extBase = 1000000000 |
| self.extBlockSize = 1000 |
| self.madeDirs = {} |
| |
| # logMsg - 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 |
| def logMsg(self, level, *args): |
| """Log a message at the given level. Can be ignored or log to a file""" |
| 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) |
| |
| # enumToValue - parses and converts 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. |
| # A '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. |
| # A '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. |
| def enumToValue(self, elem, needsNum): |
| 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] |
| |
| # checkDuplicateEnums - sanity check for enumerated values |
| # enums - list of <enum> Elements |
| # returns the list with duplicates stripped |
| def checkDuplicateEnums(self, enums): |
| # 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] |
| |
| try: |
| self.logMsg('warn', 'Two enums found with the same value: ' |
| + name + ' = ' + name2.get('name') + ' = ' + strVal) |
| except: |
| pdb.set_trace() |
| |
| # 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 |
| |
| # buildEnumCDecl |
| # Generates the C declaration for an enum |
| def buildEnumCDecl(self, expand, groupinfo, groupName): |
| groupElem = groupinfo.elem |
| |
| if self.genOpts.conventions.constFlagBits and groupElem.get('type') == 'bitmask': |
| return self.buildEnumCDecl_Bitmask( groupinfo, groupName) |
| else: |
| return self.buildEnumCDecl_Enum(expand, groupinfo, groupName) |
| |
| # buildEnumCDecl_Bitmask |
| # Generates the C declaration for an "enum" that is actually a |
| # set of flag bits |
| def buildEnumCDecl_Bitmask(self, groupinfo, groupName): |
| groupElem = groupinfo.elem |
| flagTypeName = groupinfo.flagType.elem.get('name') |
| |
| # Prefix |
| body = "// Flag bits for " + flagTypeName + "\n" |
| |
| # 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. |
| (_, strVal) = self.enumToValue(elem, True) |
| name = elem.get('name') |
| body += "static const " + flagTypeName + " " + name + " = " + strVal + ";\n" |
| |
| # Postfix |
| |
| return ("bitmask", body) |
| |
| # Generates the C declaration for an enumerated type |
| def buildEnumCDecl_Enum(self, expand, groupinfo, groupName): |
| 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 " + groupName + " {\n" |
| |
| # @@ Should use the type="bitmask" attribute instead |
| isEnum = ('FLAG_BITS' not in expandPrefix) |
| |
| # 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 = " " + name + " = " + strVal + ",\n" |
| if numVal is not None: |
| body += decl |
| else: |
| aliasText += decl |
| |
| # 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 += aliasText |
| |
| # Generate min/max value tokens and a range-padding enum. Need some |
| # additional padding to generate correct names... |
| if isEnum and expand: |
| body += " " + expandPrefix + "_BEGIN_RANGE" + expandSuffix + " = " + minName + ",\n" |
| body += " " + expandPrefix + "_END_RANGE" + expandSuffix + " = " + maxName + ",\n" |
| body += " " + expandPrefix + "_RANGE_SIZE" + expandSuffix + " = (" + maxName + " - " + minName + " + 1),\n" |
| |
| # Always generate this to make sure the enumerated type is 32 bits |
| body += " " + expandPrefix + "_MAX_ENUM" + expandSuffix + " = 0x7FFFFFFF\n" |
| |
| # Postfix |
| body += "} " + groupName + ";" |
| |
| # Determine appropriate section for this declaration |
| if groupElem.get('type') == 'bitmask': |
| section = 'bitmask' |
| else: |
| section = 'group' |
| |
| return (section, body) |
| |
| def makeDir(self, path): |
| 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): |
| self.genOpts = genOpts |
| |
| # Open specified output file. Not done in constructor since a |
| # Generator can be used without writing to a 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) |
| self.outFile = (directory / self.genOpts.filename).open('w', encoding='utf-8') |
| else: |
| filename = self.genOpts.directory + '/' + self.genOpts.filename |
| self.outFile = io.open(filename, 'w', encoding='utf-8') |
| 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() |
| self.genOpts = None |
| |
| def beginFeature(self, interface, emit): |
| 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): |
| # Derived classes responsible for emitting feature |
| self.featureName = None |
| self.featureExtraProtect = None |
| |
| # Utility method to validate we're generating something only inside a |
| # <feature> tag |
| def validateFeature(self, featureType, featureName): |
| if self.featureName is None: |
| raise UserWarning('Attempt to generate', featureType, |
| featureName, 'when not in feature') |
| |
| # Type generation |
| def genType(self, typeinfo, name, alias): |
| self.validateFeature('type', name) |
| |
| # Struct (e.g. C "struct" type) generation |
| def genStruct(self, typeinfo, typeName, alias): |
| 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) |
| |
| # Group (e.g. C "enum" type) generation |
| def genGroup(self, groupinfo, groupName, alias): |
| self.validateFeature('group', groupName) |
| |
| # Enumerant (really, constant) generation |
| def genEnum(self, enuminfo, typeName, alias): |
| self.validateFeature('enum', typeName) |
| |
| # Command generation |
| def genCmd(self, cmd, cmdinfo, alias): |
| self.validateFeature('command', cmdinfo) |
| |
| # Utility functions - 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 |
| def makeProtoName(self, name, tail): |
| return self.genOpts.apientry + name + tail |
| |
| def makeTypedefName(self, name, tail): |
| return '(' + self.genOpts.apientryp + 'PFN_' + name + tail + ')' |
| |
| # makeCParamDecl - 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 |
| def makeCParamDecl(self, param, aligncol): |
| paramdecl = ' ' + noneStr(param.text) |
| for elem in param: |
| text = noneStr(elem.text) |
| tail = noneStr(elem.tail) |
| |
| if 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' 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 |
| return paramdecl |
| |
| # getCParamTypeLength - 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 |
| def getCParamTypeLength(self, param): |
| paramdecl = ' ' + noneStr(param.text) |
| for elem in param: |
| text = noneStr(elem.text) |
| tail = noneStr(elem.tail) |
| |
| if 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 |
| |
| # isEnumRequired(elem) - return True if this <enum> element is |
| # required, False otherwise |
| # elem - <enum> element to test |
| def isEnumRequired(self, elem): |
| 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 |
| |
| # makeCDecls - return C prototype and function pointer typedef for a |
| # command, as a two-element list of strings. |
| # cmd - Element containing a <command> tag |
| def makeCDecls(self, cmd): |
| """Generate C function pointer typedef for <command> Element""" |
| 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 |
| # 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): |
| write('', file=self.outFile) |
| |
| def setRegistry(self, registry): |
| self.registry = registry |