739 lines
		
	
	
		
			30 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			739 lines
		
	
	
		
			30 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
#!/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
 |