| #!/usr/bin/python3 -i | 
 | # | 
 | # Copyright (c) 2013-2017 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,os,re,sys | 
 |  | 
 | 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. | 
 | #   str - string to convert | 
 | def noneStr(str): | 
 |     if (str): | 
 |         return str | 
 |     else: | 
 |         return "" | 
 |  | 
 | # enquote - returns string argument with surrounding quotes, | 
 | #   for serialization into Python code. | 
 | def enquote(str): | 
 |     if (str): | 
 |         return "'" + str + "'" | 
 |     else: | 
 |         return None | 
 |  | 
 | # apiName - returns True if name is a Vulkan name (vk/Vk/VK prefix, or a | 
 | # function pointer type), False otherwise. | 
 | def apiName(str): | 
 |     return str[0:2].lower() == 'vk' or str[0:3] == 'PFN' | 
 |  | 
 | # 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 | 
 |     elif (feature.category == 'ARB' or | 
 |           feature.category == 'KHR' or | 
 |           feature.category == 'OES'): | 
 |         return 1 | 
 |     else: | 
 |         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.version) | 
 |  | 
 | # 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 | 
 | #   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. | 
 | #   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, | 
 |                  filename = None, | 
 |                  directory = '.', | 
 |                  apiname = None, | 
 |                  profile = None, | 
 |                  versions = '.*', | 
 |                  emitversions = '.*', | 
 |                  defaultExtensions = None, | 
 |                  addExtensions = None, | 
 |                  removeExtensions = None, | 
 |                  sortProcedure = regSortFeatures): | 
 |         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.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 == None or pat == ''): | 
 |             return '_nomatch_^' | 
 |         else: | 
 |             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) - generate interface for a type | 
 | #   typeinfo - TypeInfo for a type | 
 | # genStruct(typeinfo,name) - generate interface for a C "struct" type. | 
 | #   typeinfo - TypeInfo for a type interpreted as a struct | 
 | # genGroup(groupinfo,name) - generate interface for a group of enums (C "enum") | 
 | #   groupinfo - GroupInfo for a group | 
 | # genEnum(enuminfo, name) - generate interface for an enum (constant) | 
 | #   enuminfo - EnumInfo for an enum | 
 | #   name - enum name | 
 | # genCmd(cmdinfo) - 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 != None): | 
 |                 write(strfile.getvalue(), file=self.errFile) | 
 |             raise UserWarning(strfile.getvalue()) | 
 |         elif (level == 'warn'): | 
 |             if (self.warnFile != None): | 
 |                 write('WARNING:', *args, file=self.warnFile) | 
 |         elif (level == 'diag'): | 
 |             if (self.diagFile != 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 | 
 |     #       Vulkan extension enum scheme in this function. | 
 |     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 != 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') | 
 |             numVal = int(value, 0) | 
 |             numVal = 1 << numVal | 
 |             value = '0x%08x' % numVal | 
 |             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 = -numVal | 
 |             value = '%d' % numVal | 
 |             # More logic needed! | 
 |             self.logMsg('diag', 'Enum', name, '-> offset [', numVal, ',', value, ']') | 
 |             return [numVal, value] | 
 |         return [None, None] | 
 |     # | 
 |     def makeDir(self, path): | 
 |         self.logMsg('diag', 'OutputGenerator::makeDir(' + path + ')') | 
 |         if not (path in self.madeDirs.keys()): | 
 |             # 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 != None): | 
 |             filename = self.genOpts.directory + '/' + self.genOpts.filename | 
 |             self.outFile = io.open(filename, 'w', encoding='utf-8') | 
 |         else: | 
 |             self.outFile = sys.stdout | 
 |     def endFile(self): | 
 |         self.errFile and self.errFile.flush() | 
 |         self.warnFile and self.warnFile.flush() | 
 |         self.diagFile and 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 == None): | 
 |             raise UserWarning('Attempt to generate', featureType, name, | 
 |                     'when not in feature') | 
 |     # | 
 |     # Type generation | 
 |     def genType(self, typeinfo, name): | 
 |         self.validateFeature('type', name) | 
 |     # | 
 |     # Struct (e.g. C "struct" type) generation | 
 |     def genStruct(self, typeinfo, name): | 
 |         self.validateFeature('struct', name) | 
 |     # | 
 |     # Group (e.g. C "enum" type) generation | 
 |     def genGroup(self, groupinfo, name): | 
 |         self.validateFeature('group', name) | 
 |     # | 
 |     # Enumerant (really, constant) generation | 
 |     def genEnum(self, enuminfo, name): | 
 |         self.validateFeature('enum', name) | 
 |     # | 
 |     # Command generation | 
 |     def genCmd(self, cmd, name): | 
 |         self.validateFeature('command', name) | 
 |     # | 
 |     # 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 (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 (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): | 
 |         return (elem.get('extname') is None or | 
 |                 re.match(self.genOpts.addExtensions, elem.get('extname')) is not None or | 
 |                 self.genOpts.defaultExtensions == elem.get('supported')) | 
 |     # | 
 |     # 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 | 
 |         # Might be able to doubly-nest the joins, e.g. | 
 |         #   ','.join(('_'.join([l[i] for i in range(0,len(l))]) | 
 |         n = len(params) | 
 |         # Indented parameters | 
 |         if n > 0: | 
 |             indentdecl = '(\n' | 
 |             for i in range(0,n): | 
 |                 paramdecl = self.makeCParamDecl(params[i], self.genOpts.alignFuncParam) | 
 |                 if (i < n - 1): | 
 |                     paramdecl += ',\n' | 
 |                 else: | 
 |                     paramdecl += ');' | 
 |                 indentdecl += paramdecl | 
 |         else: | 
 |             indentdecl = '(void);' | 
 |         # Non-indented parameters | 
 |         paramdecl = '(' | 
 |         if n > 0: | 
 |             for i in range(0,n): | 
 |                 paramdecl += ''.join([t for t in params[i].itertext()]) | 
 |                 if (i < n - 1): | 
 |                     paramdecl += ', ' | 
 |         else: | 
 |             paramdecl += 'void' | 
 |         paramdecl += ");"; | 
 |         return [ pdecl + indentdecl, tdecl + paramdecl ] | 
 |     # | 
 |     def newline(self): | 
 |         write('', file=self.outFile) | 
 |  | 
 |     def setRegistry(self, registry): | 
 |         self.registry = registry | 
 |         # |