1#!/usr/bin/python3 -i
2#
3# Copyright 2013-2021 The Khronos Group Inc.
4#
5# SPDX-License-Identifier: Apache-2.0
6"""Base class for source/header/doc generators, as well as some utility functions."""
7
8from __future__ import unicode_literals
9
10import io
11import os
12import pdb
13import re
14import shutil
15import sys
16import tempfile
17try:
18    from pathlib import Path
19except ImportError:
20    from pathlib2 import Path
21
22from spec_tools.util import getElemName, getElemType
23
24
25def write(*args, **kwargs):
26    file = kwargs.pop('file', sys.stdout)
27    end = kwargs.pop('end', '\n')
28    file.write(' '.join(str(arg) for arg in args))
29    file.write(end)
30
31
32def noneStr(s):
33    """Return string argument, or "" if argument is None.
34
35    Used in converting etree Elements into text.
36    s - string to convert"""
37    if s:
38        return s
39    return ""
40
41
42def enquote(s):
43    """Return string argument with surrounding quotes,
44      for serialization into Python code."""
45    if s:
46        if isinstance(s, str):
47            return "'{}'".format(s)
48        else:
49            return s
50    return None
51
52
53def regSortCategoryKey(feature):
54    """Sort key for regSortFeatures.
55    Sorts by category of the feature name string:
56
57    - Core API features (those defined with a `<feature>` tag)
58        - (sort VKSC after VK)
59    - ARB/KHR/OES (Khronos extensions)
60    - other       (EXT/vendor extensions)"""
61
62    if feature.elem.tag == 'feature':
63        if feature.name.startswith('VKSC'):
64            return 0.5
65        return 0
66    if (feature.category == 'ARB'
67        or feature.category == 'KHR'
68            or feature.category == 'OES'):
69        return 1
70
71    return 2
72
73
74def regSortOrderKey(feature):
75    """Sort key for regSortFeatures - key is the sortorder attribute."""
76
77    # print("regSortOrderKey {} -> {}".format(feature.name, feature.sortorder))
78    return feature.sortorder
79
80
81def regSortFeatureVersionKey(feature):
82    """Sort key for regSortFeatures - key is the feature version.
83    `<extension>` elements all have version number 0."""
84
85    return float(feature.versionNumber)
86
87
88def regSortExtensionNumberKey(feature):
89    """Sort key for regSortFeatures - key is the extension number.
90    `<feature>` elements all have extension number 0."""
91
92    return int(feature.number)
93
94
95def regSortFeatures(featureList):
96    """Default sort procedure for features.
97
98    - Sorts by explicit sort order (default 0) relative to other features
99    - then by feature category ('feature' or 'extension'),
100    - then by version number (for features)
101    - then by extension number (for extensions)"""
102    featureList.sort(key=regSortExtensionNumberKey)
103    featureList.sort(key=regSortFeatureVersionKey)
104    featureList.sort(key=regSortCategoryKey)
105    featureList.sort(key=regSortOrderKey)
106
107
108class GeneratorOptions:
109    """Base class for options used during header/documentation production.
110
111    These options are target language independent, and used by
112    Registry.apiGen() and by base OutputGenerator objects."""
113
114    def __init__(self,
115                 conventions=None,
116                 filename=None,
117                 directory='.',
118                 genpath=None,
119                 apiname=None,
120                 profile=None,
121                 versions='.*',
122                 emitversions='.*',
123                 defaultExtensions=None,
124                 addExtensions=None,
125                 removeExtensions=None,
126                 emitExtensions=None,
127                 emitSpirv=None,
128                 emitFormats=None,
129                 reparentEnums=True,
130                 sortProcedure=regSortFeatures,
131                 requireCommandAliases=False,
132                ):
133        """Constructor.
134
135        Arguments:
136
137        - conventions - may be mandatory for some generators:
138        an object that implements ConventionsBase
139        - filename - basename of file to generate, or None to write to stdout.
140        - directory - directory in which to generate files
141        - genpath - path to previously generated files, such as api.py
142        - apiname - string matching `<api>` 'apiname' attribute, e.g. 'gl'.
143        - profile - string specifying API profile , e.g. 'core', or None.
144        - versions - regex matching API versions to process interfaces for.
145        Normally `'.*'` or `'[0-9][.][0-9]'` to match all defined versions.
146        - emitversions - regex matching API versions to actually emit
147        interfaces for (though all requested versions are considered
148        when deciding which interfaces to generate). For GL 4.3 glext.h,
149        this might be `'1[.][2-5]|[2-4][.][0-9]'`.
150        - defaultExtensions - If not None, a string which must in its
151        entirety match the pattern in the "supported" attribute of
152        the `<extension>`. Defaults to None. Usually the same as apiname.
153        - addExtensions - regex matching names of additional extensions
154        to include. Defaults to None.
155        - removeExtensions - regex matching names of extensions to
156        remove (after defaultExtensions and addExtensions). Defaults
157        to None.
158        - emitExtensions - regex matching names of extensions to actually emit
159        interfaces for (though all requested versions are considered when
160        deciding which interfaces to generate).
161        to None.
162        - emitSpirv - regex matching names of extensions and capabilities
163        to actually emit interfaces for.
164        - emitFormats - regex matching names of formats to actually emit
165        interfaces for.
166        - reparentEnums - move <enum> elements which extend an enumerated
167        type from <feature> or <extension> elements to the target <enums>
168        element. This is required for almost all purposes, but the
169        InterfaceGenerator relies on the list of interfaces in the <feature>
170        or <extension> being complete. Defaults to True.
171        - sortProcedure - takes a list of FeatureInfo objects and sorts
172        them in place to a preferred order in the generated output.
173        Default is core API versions, ARB/KHR/OES extensions, all other
174        extensions, by core API version number or extension number in each
175        group.
176
177        The regex patterns can be None or empty, in which case they match
178        nothing."""
179        self.conventions = conventions
180        """may be mandatory for some generators:
181        an object that implements ConventionsBase"""
182
183        self.filename = filename
184        "basename of file to generate, or None to write to stdout."
185
186        self.genpath = genpath
187        """path to previously generated files, such as api.py"""
188
189        self.directory = directory
190        "directory in which to generate filename"
191
192        self.apiname = apiname
193        "string matching `<api>` 'apiname' attribute, e.g. 'gl'."
194
195        self.profile = profile
196        "string specifying API profile , e.g. 'core', or None."
197
198        self.versions = self.emptyRegex(versions)
199        """regex matching API versions to process interfaces for.
200        Normally `'.*'` or `'[0-9][.][0-9]'` to match all defined versions."""
201
202        self.emitversions = self.emptyRegex(emitversions)
203        """regex matching API versions to actually emit
204        interfaces for (though all requested versions are considered
205        when deciding which interfaces to generate). For GL 4.3 glext.h,
206        this might be `'1[.][2-5]|[2-4][.][0-9]'`."""
207
208        self.defaultExtensions = defaultExtensions
209        """If not None, a string which must in its
210        entirety match the pattern in the "supported" attribute of
211        the `<extension>`. Defaults to None. Usually the same as apiname."""
212
213        self.addExtensions = self.emptyRegex(addExtensions)
214        """regex matching names of additional extensions
215        to include. Defaults to None."""
216
217        self.removeExtensions = self.emptyRegex(removeExtensions)
218        """regex matching names of extensions to
219        remove (after defaultExtensions and addExtensions). Defaults
220        to None."""
221
222        self.emitExtensions = self.emptyRegex(emitExtensions)
223        """regex matching names of extensions to actually emit
224        interfaces for (though all requested versions are considered when
225        deciding which interfaces to generate)."""
226
227        self.emitSpirv = self.emptyRegex(emitSpirv)
228        """regex matching names of extensions and capabilities
229        to actually emit interfaces for."""
230
231        self.emitFormats = self.emptyRegex(emitFormats)
232        """regex matching names of formats
233        to actually emit interfaces for."""
234
235        self.reparentEnums = reparentEnums
236        """boolean specifying whether to remove <enum> elements from
237        <feature> or <extension> when extending an <enums> type."""
238
239        self.sortProcedure = sortProcedure
240        """takes a list of FeatureInfo objects and sorts
241        them in place to a preferred order in the generated output.
242        Default is core API versions, ARB/KHR/OES extensions, all
243        other extensions, alphabetically within each group."""
244
245        self.codeGenerator = False
246        """True if this generator makes compilable code"""
247
248        self.requireCommandAliases = requireCommandAliases
249        """True if alias= attributes of <command> tags are transitively
250        required."""
251
252    def emptyRegex(self, pat):
253        """Substitute a regular expression which matches no version
254        or extension names for None or the empty string."""
255        if not pat:
256            return '_nomatch_^'
257
258        return pat
259
260
261class OutputGenerator:
262    """Generate specified API interfaces in a specific style, such as a C header.
263
264    Base class for generating API interfaces.
265    Manages basic logic, logging, and output file control.
266    Derived classes actually generate formatted output.
267    """
268
269    # categoryToPath - map XML 'category' to include file directory name
270    categoryToPath = {
271        'bitmask': 'flags',
272        'enum': 'enums',
273        'funcpointer': 'funcpointers',
274        'handle': 'handles',
275        'define': 'defines',
276        'basetype': 'basetypes',
277    }
278
279    def breakName(self, name, msg):
280        """Break into debugger if this is a special name"""
281
282        # List of string names to break on
283        bad = (
284        )
285
286        if name in bad and True:
287            print('breakName {}: {}'.format(name, msg))
288            pdb.set_trace()
289
290    def __init__(self, errFile=sys.stderr, warnFile=sys.stderr, diagFile=sys.stdout):
291        """Constructor
292
293        - errFile, warnFile, diagFile - file handles to write errors,
294          warnings, diagnostics to. May be None to not write."""
295        self.outFile = None
296        self.errFile = errFile
297        self.warnFile = warnFile
298        self.diagFile = diagFile
299        # Internal state
300        self.featureName = None
301        self.genOpts = None
302        self.registry = None
303        self.featureDictionary = {}
304        # Used for extension enum value generation
305        self.extBase = 1000000000
306        self.extBlockSize = 1000
307        self.madeDirs = {}
308
309        # API dictionary, which may be loaded by the beginFile method of
310        # derived generators.
311        self.apidict = None
312
313    def logMsg(self, level, *args):
314        """Write a message of different categories to different
315        destinations.
316
317        - `level`
318          - 'diag' (diagnostic, voluminous)
319          - 'warn' (warning)
320          - 'error' (fatal error - raises exception after logging)
321
322        - `*args` - print()-style arguments to direct to corresponding log"""
323        if level == 'error':
324            strfile = io.StringIO()
325            write('ERROR:', *args, file=strfile)
326            if self.errFile is not None:
327                write(strfile.getvalue(), file=self.errFile)
328            raise UserWarning(strfile.getvalue())
329        elif level == 'warn':
330            if self.warnFile is not None:
331                write('WARNING:', *args, file=self.warnFile)
332        elif level == 'diag':
333            if self.diagFile is not None:
334                write('DIAG:', *args, file=self.diagFile)
335        else:
336            raise UserWarning(
337                '*** FATAL ERROR in Generator.logMsg: unknown level:' + level)
338
339    def enumToValue(self, elem, needsNum, bitwidth = 32, forceSuffix = False):
340        """Parse and convert an `<enum>` tag into a value.
341
342        Returns a list:
343
344        - first element - integer representation of the value, or None
345          if needsNum is False. The value must be a legal number
346          if needsNum is True.
347        - second element - string representation of the value
348
349        There are several possible representations of values.
350
351        - A 'value' attribute simply contains the value.
352        - A 'bitpos' attribute defines a value by specifying the bit
353          position which is set in that value.
354        - An 'offset','extbase','extends' triplet specifies a value
355          as an offset to a base value defined by the specified
356          'extbase' extension name, which is then cast to the
357          typename specified by 'extends'. This requires probing
358          the registry database, and imbeds knowledge of the
359          API extension enum scheme in this function.
360        - An 'alias' attribute contains the name of another enum
361          which this is an alias of. The other enum must be
362          declared first when emitting this enum."""
363        name = elem.get('name')
364        numVal = None
365        if 'value' in elem.keys():
366            value = elem.get('value')
367            # print('About to translate value =', value, 'type =', type(value))
368            if needsNum:
369                numVal = int(value, 0)
370            # If there is a non-integer, numeric 'type' attribute (e.g. 'u' or
371            # 'ull'), append it to the string value.
372            # t = enuminfo.elem.get('type')
373            # if t is not None and t != '' and t != 'i' and t != 's':
374            #     value += enuminfo.type
375            if forceSuffix:
376              if bitwidth == 64:
377                value = value + 'ULL'
378              else:
379                value = value + 'U'
380            self.logMsg('diag', 'Enum', name, '-> value [', numVal, ',', value, ']')
381            return [numVal, value]
382        if 'bitpos' in elem.keys():
383            value = elem.get('bitpos')
384            bitpos = int(value, 0)
385            numVal = 1 << bitpos
386            value = '0x%08x' % numVal
387            if bitwidth == 64:
388              value = value + 'ULL'
389            elif forceSuffix:
390              value = value + 'U'
391            self.logMsg('diag', 'Enum', name, '-> bitpos [', numVal, ',', value, ']')
392            return [numVal, value]
393        if 'offset' in elem.keys():
394            # Obtain values in the mapping from the attributes
395            enumNegative = False
396            offset = int(elem.get('offset'), 0)
397            extnumber = int(elem.get('extnumber'), 0)
398            extends = elem.get('extends')
399            if 'dir' in elem.keys():
400                enumNegative = True
401            self.logMsg('diag', 'Enum', name, 'offset =', offset,
402                        'extnumber =', extnumber, 'extends =', extends,
403                        'enumNegative =', enumNegative)
404            # Now determine the actual enumerant value, as defined
405            # in the "Layers and Extensions" appendix of the spec.
406            numVal = self.extBase + (extnumber - 1) * self.extBlockSize + offset
407            if enumNegative:
408                numVal *= -1
409            value = '%d' % numVal
410            # More logic needed!
411            self.logMsg('diag', 'Enum', name, '-> offset [', numVal, ',', value, ']')
412            return [numVal, value]
413        if 'alias' in elem.keys():
414            return [None, elem.get('alias')]
415        return [None, None]
416
417    def checkDuplicateEnums(self, enums):
418        """Check enumerated values for duplicates.
419
420        -  enums - list of `<enum>` Elements
421
422        returns the list with duplicates stripped"""
423        # Dictionaries indexed by name and numeric value.
424        # Entries are [ Element, numVal, strVal ] matching name or value
425
426        nameMap = {}
427        valueMap = {}
428
429        stripped = []
430        for elem in enums:
431            name = elem.get('name')
432            (numVal, strVal) = self.enumToValue(elem, True)
433
434            if name in nameMap:
435                # Duplicate name found; check values
436                (name2, numVal2, strVal2) = nameMap[name]
437
438                # Duplicate enum values for the same name are benign. This
439                # happens when defining the same enum conditionally in
440                # several extension blocks.
441                if (strVal2 == strVal or (numVal is not None
442                                          and numVal == numVal2)):
443                    True
444                    # self.logMsg('info', 'checkDuplicateEnums: Duplicate enum (' + name +
445                    #             ') found with the same value:' + strVal)
446                else:
447                    self.logMsg('warn', 'checkDuplicateEnums: Duplicate enum (' + name
448                                + ') found with different values:' + strVal
449                                + ' and ' + strVal2)
450
451                # Do not add the duplicate to the returned list
452                continue
453            elif numVal in valueMap:
454                # Duplicate value found (such as an alias); report it, but
455                # still add this enum to the list.
456                (name2, numVal2, strVal2) = valueMap[numVal]
457
458                msg = 'Two enums found with the same value: {} = {} = {}'.format(
459                    name, name2.get('name'), strVal)
460                self.logMsg('error', msg)
461
462            # Track this enum to detect followon duplicates
463            nameMap[name] = [elem, numVal, strVal]
464            if numVal is not None:
465                valueMap[numVal] = [elem, numVal, strVal]
466
467            # Add this enum to the list
468            stripped.append(elem)
469
470        # Return the list
471        return stripped
472
473    def misracstyle(self):
474        return False;
475
476    def misracppstyle(self):
477        return False;
478
479    def buildEnumCDecl(self, expand, groupinfo, groupName):
480        """Generate the C declaration for an enum"""
481        groupElem = groupinfo.elem
482
483        # Determine the required bit width for the enum group.
484        # 32 is the default, which generates C enum types for the values.
485        bitwidth = 32
486
487        # If the constFlagBits preference is set, 64 is the default for bitmasks
488        if self.genOpts.conventions.constFlagBits and groupElem.get('type') == 'bitmask':
489            bitwidth = 64
490
491        # Check for an explicitly defined bitwidth, which will override any defaults.
492        if groupElem.get('bitwidth'):
493            try:
494                bitwidth = int(groupElem.get('bitwidth'))
495            except ValueError as ve:
496                self.logMsg('error', 'Invalid value for bitwidth attribute (', groupElem.get('bitwidth'), ') for ', groupName, ' - must be an integer value\n')
497                exit(1)
498
499        usebitmask = False
500        usedefine = False
501
502        # Bitmask flags can be generated as either "static const uint{32,64}_t" values,
503        # or as 32-bit C enums. 64-bit types must use uint64_t values.
504        if groupElem.get('type') == 'bitmask':
505            if bitwidth > 32 or self.misracppstyle():
506                usebitmask = True
507            if self.misracstyle():
508                usedefine = True
509
510        if usedefine or usebitmask:
511            # Validate the bitwidth and generate values appropriately
512            if bitwidth > 64:
513                self.logMsg('error', 'Invalid value for bitwidth attribute (', groupElem.get('bitwidth'), ') for bitmask type ', groupName, ' - must be less than or equal to 64\n')
514                exit(1)
515            else:
516                return self.buildEnumCDecl_BitmaskOrDefine(groupinfo, groupName, bitwidth, usedefine)
517        else:
518            # Validate the bitwidth and generate values appropriately
519            if bitwidth > 32:
520                self.logMsg('error', 'Invalid value for bitwidth attribute (', groupElem.get('bitwidth'), ') for enum type ', groupName, ' - must be less than or equal to 32\n')
521                exit(1)
522            else:
523                return self.buildEnumCDecl_Enum(expand, groupinfo, groupName)
524
525    def buildEnumCDecl_BitmaskOrDefine(self, groupinfo, groupName, bitwidth, usedefine):
526        """Generate the C declaration for an "enum" that is actually a
527        set of flag bits"""
528        groupElem = groupinfo.elem
529        flagTypeName = groupElem.get('name')
530
531        # Prefix
532        body = "// Flag bits for " + flagTypeName + "\n"
533
534        if bitwidth == 64:
535            body += "typedef VkFlags64 %s;\n" % flagTypeName;
536        else:
537            body += "typedef VkFlags %s;\n" % flagTypeName;
538
539        # Maximum allowable value for a flag (unsigned 64-bit integer)
540        maxValidValue = 2**(64) - 1
541        minValidValue = 0
542
543        # Get a list of nested 'enum' tags.
544        enums = groupElem.findall('enum')
545
546        # Check for and report duplicates, and return a list with them
547        # removed.
548        enums = self.checkDuplicateEnums(enums)
549
550        # Accumulate non-numeric enumerant values separately and append
551        # them following the numeric values, to allow for aliases.
552        # NOTE: this does not do a topological sort yet, so aliases of
553        # aliases can still get in the wrong order.
554        aliasText = ''
555
556        # Loop over the nested 'enum' tags.
557        for elem in enums:
558            # Convert the value to an integer and use that to track min/max.
559            # Values of form -(number) are accepted but nothing more complex.
560            # Should catch exceptions here for more complex constructs. Not yet.
561            (numVal, strVal) = self.enumToValue(elem, True, bitwidth, True)
562            name = elem.get('name')
563
564            # Range check for the enum value
565            if numVal is not None and (numVal > maxValidValue or numVal < minValidValue):
566                self.logMsg('error', 'Allowable range for flag types in C is [', minValidValue, ',', maxValidValue, '], but', name, 'flag has a value outside of this (', strVal, ')\n')
567                exit(1)
568
569            decl = self.genRequirements(name, mustBeFound = False)
570
571            if self.isEnumRequired(elem):
572                protect = elem.get('protect')
573                if protect is not None:
574                    body += '#ifdef {}\n'.format(protect)
575
576                if usedefine:
577                    decl += "#define {} {}\n".format(name, strVal)
578                elif self.misracppstyle():
579                    decl += "static constexpr {} {} {{{}}};\n".format(flagTypeName, name, strVal)
580                else:
581                    # Some C compilers only allow initializing a 'static const' variable with a literal value.
582                    # So initializing an alias from another 'static const' value would fail to compile.
583                    # Work around this by chasing the aliases to get the actual value.
584                    while numVal is None:
585                        alias = self.registry.tree.find("enums/enum[@name='" + strVal + "']")
586                        (numVal, strVal) = self.enumToValue(alias, True, bitwidth, True)
587                    decl += "static const {} {} = {};\n".format(flagTypeName, name, strVal)
588
589                if numVal is not None:
590                    body += decl
591                else:
592                    aliasText += decl
593
594                if protect is not None:
595                    body += '#endif\n'
596
597        # Now append the non-numeric enumerant values
598        body += aliasText
599
600        # Postfix
601
602        return ("bitmask", body)
603
604    def buildEnumCDecl_Enum(self, expand, groupinfo, groupName):
605        """Generate the C declaration for an enumerated type"""
606        groupElem = groupinfo.elem
607
608        # Break the group name into prefix and suffix portions for range
609        # enum generation
610        expandName = re.sub(r'([0-9]+|[a-z_])([A-Z0-9])', r'\1_\2', groupName).upper()
611        expandPrefix = expandName
612        expandSuffix = ''
613        expandSuffixMatch = re.search(r'[A-Z][A-Z]+$', groupName)
614        if expandSuffixMatch:
615            expandSuffix = '_' + expandSuffixMatch.group()
616            # Strip off the suffix from the prefix
617            expandPrefix = expandName.rsplit(expandSuffix, 1)[0]
618
619        # Prefix
620        body = ["typedef enum %s {" % groupName]
621
622        # @@ Should use the type="bitmask" attribute instead
623        isEnum = ('FLAG_BITS' not in expandPrefix)
624
625        # Allowable range for a C enum - which is that of a signed 32-bit integer
626        maxValidValue = 2**(32 - 1) - 1
627        minValidValue = (maxValidValue * -1) - 1
628
629
630        # Get a list of nested 'enum' tags.
631        enums = groupElem.findall('enum')
632
633        # Check for and report duplicates, and return a list with them
634        # removed.
635        enums = self.checkDuplicateEnums(enums)
636
637        # Loop over the nested 'enum' tags. Keep track of the minimum and
638        # maximum numeric values, if they can be determined; but only for
639        # core API enumerants, not extension enumerants. This is inferred
640        # by looking for 'extends' attributes.
641        minName = None
642
643        # Accumulate non-numeric enumerant values separately and append
644        # them following the numeric values, to allow for aliases.
645        # NOTE: this does not do a topological sort yet, so aliases of
646        # aliases can still get in the wrong order.
647        aliasText = []
648
649        for elem in enums:
650            # Convert the value to an integer and use that to track min/max.
651            # Values of form -(number) are accepted but nothing more complex.
652            # Should catch exceptions here for more complex constructs. Not yet.
653            (numVal, strVal) = self.enumToValue(elem, True)
654            name = elem.get('name')
655
656            # Extension enumerants are only included if they are required
657            if self.isEnumRequired(elem):
658                decl = ''
659
660                protect = elem.get('protect')
661                if protect is not None:
662                    decl += '#ifdef {}\n'.format(protect)
663
664                # Indent requirements comment, if there is one
665                requirements = self.genRequirements(name, mustBeFound = False)
666                if requirements != '':
667                    requirements = '  ' + requirements
668                decl += requirements
669                decl += '    {} = {},'.format(name, strVal)
670
671                if protect is not None:
672                    decl += '\n#endif'
673
674                if numVal is not None:
675                    body.append(decl)
676                else:
677                    aliasText.append(decl)
678
679            # Range check for the enum value
680            if numVal is not None and (numVal > maxValidValue or numVal < minValidValue):
681                self.logMsg('error', 'Allowable range for C enum types is [', minValidValue, ',', maxValidValue, '], but', name, 'has a value outside of this (', strVal, ')\n')
682                exit(1)
683
684            # Do not track min/max for non-numbers (numVal is None)
685            if isEnum and numVal is not None and elem.get('extends') is None:
686                if minName is None:
687                    minName = maxName = name
688                    minValue = maxValue = numVal
689                elif numVal < minValue:
690                    minName = name
691                    minValue = numVal
692                elif numVal > maxValue:
693                    maxName = name
694                    maxValue = numVal
695
696        # Now append the non-numeric enumerant values
697        body.extend(aliasText)
698
699        # Generate min/max value tokens - legacy use case.
700        if isEnum and expand:
701            body.extend(("    {}_BEGIN_RANGE{} = {},".format(expandPrefix, expandSuffix, minName),
702                         "    {}_END_RANGE{} = {},".format(
703                             expandPrefix, expandSuffix, maxName),
704                         "    {}_RANGE_SIZE{} = ({} - {} + 1),".format(expandPrefix, expandSuffix, maxName, minName)))
705
706        # Generate a range-padding value to ensure the enum is 32 bits, but
707        # only in code generators, so it does not appear in documentation
708        if (self.genOpts.codeGenerator or
709            self.conventions.generate_max_enum_in_docs):
710            body.append("    {}_MAX_ENUM{} = 0x7FFFFFFF".format(
711                expandPrefix, expandSuffix))
712
713        # Postfix
714        body.append("} %s;" % groupName)
715
716        # Determine appropriate section for this declaration
717        if groupElem.get('type') == 'bitmask':
718            section = 'bitmask'
719        else:
720            section = 'group'
721
722        return (section, '\n'.join(body))
723
724    def buildConstantCDecl(self, enuminfo, name, alias):
725        """Generate the C declaration for a constant (a single <enum>
726        value).
727
728        <enum> tags may specify their values in several ways, but are
729        usually just integers or floating-point numbers."""
730
731        (_, strVal) = self.enumToValue(enuminfo.elem, False)
732
733        if self.misracppstyle() and enuminfo.elem.get('type') and not alias:
734            # Generate e.g.: static constexpr uint32_t x = ~static_cast<uint32_t>(1U);
735            # This appeases MISRA "underlying type" rules.
736            typeStr = enuminfo.elem.get('type');
737            invert = '~' in strVal
738            number = strVal.strip("()~UL")
739            if typeStr != "float":
740                number += 'U'
741            strVal = "~" if invert else ""
742            strVal += "static_cast<" + typeStr + ">(" + number + ")"
743            body = 'static constexpr ' + typeStr.ljust(9) + name.ljust(33) + ' {' + strVal + '};'
744        elif enuminfo.elem.get('type') and not alias:
745            # Generate e.g.: #define x (~0ULL)
746            typeStr = enuminfo.elem.get('type');
747            invert = '~' in strVal
748            paren = '(' in strVal
749            number = strVal.strip("()~UL")
750            if typeStr != "float":
751                if typeStr == "uint64_t":
752                    number += 'ULL'
753                else:
754                    number += 'U'
755            strVal = "~" if invert else ""
756            strVal += number
757            if paren:
758                strVal = "(" + strVal + ")";
759            body = '#define ' + name.ljust(33) + ' ' + strVal;
760        else:
761            body = '#define ' + name.ljust(33) + ' ' + strVal
762
763        return body
764
765    def makeDir(self, path):
766        """Create a directory, if not already done.
767
768        Generally called from derived generators creating hierarchies."""
769        self.logMsg('diag', 'OutputGenerator::makeDir(' + path + ')')
770        if path not in self.madeDirs:
771            # This can get race conditions with multiple writers, see
772            # https://stackoverflow.com/questions/273192/
773            if not os.path.exists(path):
774                os.makedirs(path)
775            self.madeDirs[path] = None
776
777    def beginFile(self, genOpts):
778        """Start a new interface file
779
780        - genOpts - GeneratorOptions controlling what is generated and how"""
781        self.genOpts = genOpts
782        self.should_insert_may_alias_macro = \
783            self.genOpts.conventions.should_insert_may_alias_macro(self.genOpts)
784
785        # Try to import the API dictionary, api.py, if it exists. Nothing in
786        # api.py cannot be extracted directly from the XML, and in the
787        # future we should do that.
788        if self.genOpts.genpath is not None:
789            try:
790                sys.path.insert(0, self.genOpts.genpath)
791                import api
792                self.apidict = api
793            except ImportError:
794                self.apidict = None
795
796        self.conventions = genOpts.conventions
797
798        # Open a temporary file for accumulating output.
799        if self.genOpts.filename is not None:
800            self.outFile = tempfile.NamedTemporaryFile(mode='w', encoding='utf-8', newline='\n', delete=False)
801        else:
802            self.outFile = sys.stdout
803
804    def endFile(self):
805        if self.errFile:
806            self.errFile.flush()
807        if self.warnFile:
808            self.warnFile.flush()
809        if self.diagFile:
810            self.diagFile.flush()
811        if self.outFile != sys.stdout and self.outFile != sys.stderr:
812            self.outFile.close()
813
814        # On successfully generating output, move the temporary file to the
815        # target file.
816        if self.genOpts.filename is not None:
817            if sys.platform == 'win32':
818                directory = Path(self.genOpts.directory)
819                if not Path.exists(directory):
820                    os.makedirs(directory)
821            shutil.copy(self.outFile.name, self.genOpts.directory + '/' + self.genOpts.filename)
822            os.remove(self.outFile.name)
823        self.genOpts = None
824
825    def beginFeature(self, interface, emit):
826        """Write interface for a feature and tag generated features as having been done.
827
828        - interface - element for the `<version>` / `<extension>` to generate
829        - emit - actually write to the header only when True"""
830        self.emit = emit
831        self.featureName = interface.get('name')
832        # If there is an additional 'protect' attribute in the feature, save it
833        self.featureExtraProtect = interface.get('protect')
834
835    def endFeature(self):
836        """Finish an interface file, closing it when done.
837
838        Derived classes responsible for emitting feature"""
839        self.featureName = None
840        self.featureExtraProtect = None
841
842    def genRequirements(self, name, mustBeFound = True):
843        """Generate text showing what core versions and extensions introduce
844        an API. This exists in the base Generator class because it is used by
845        the shared enumerant-generating interfaces (buildEnumCDecl, etc.).
846        Here it returns an empty string for most generators, but can be
847        overridden by e.g. DocGenerator.
848
849        - name - name of the API
850        - mustBeFound - If True, when requirements for 'name' cannot be
851          determined, a warning comment is generated.
852        """
853
854        return ''
855
856    def validateFeature(self, featureType, featureName):
857        """Validate we are generating something only inside a `<feature>` tag"""
858        if self.featureName is None:
859            raise UserWarning('Attempt to generate', featureType,
860                              featureName, 'when not in feature')
861
862    def genType(self, typeinfo, name, alias):
863        """Generate interface for a type
864
865        - typeinfo - TypeInfo for a type
866
867        Extend to generate as desired in your derived class."""
868        self.validateFeature('type', name)
869
870    def genStruct(self, typeinfo, typeName, alias):
871        """Generate interface for a C "struct" type.
872
873        - typeinfo - TypeInfo for a type interpreted as a struct
874
875        Extend to generate as desired in your derived class."""
876        self.validateFeature('struct', typeName)
877
878        # The mixed-mode <member> tags may contain no-op <comment> tags.
879        # It is convenient to remove them here where all output generators
880        # will benefit.
881        for member in typeinfo.elem.findall('.//member'):
882            for comment in member.findall('comment'):
883                member.remove(comment)
884
885    def genGroup(self, groupinfo, groupName, alias):
886        """Generate interface for a group of enums (C "enum")
887
888        - groupinfo - GroupInfo for a group.
889
890        Extend to generate as desired in your derived class."""
891
892        self.validateFeature('group', groupName)
893
894    def genEnum(self, enuminfo, typeName, alias):
895        """Generate interface for an enum (constant).
896
897        - enuminfo - EnumInfo for an enum
898        - name - enum name
899
900        Extend to generate as desired in your derived class."""
901        self.validateFeature('enum', typeName)
902
903    def genCmd(self, cmd, cmdinfo, alias):
904        """Generate interface for a command.
905
906        - cmdinfo - CmdInfo for a command
907
908        Extend to generate as desired in your derived class."""
909        self.validateFeature('command', cmdinfo)
910
911    def genSpirv(self, spirv, spirvinfo, alias):
912        """Generate interface for a spirv element.
913
914        - spirvinfo - SpirvInfo for a command
915
916        Extend to generate as desired in your derived class."""
917        return
918
919    def genFormat(self, format, formatinfo, alias):
920        """Generate interface for a format element.
921
922        - formatinfo - FormatInfo
923
924        Extend to generate as desired in your derived class."""
925        return
926
927    def makeProtoName(self, name, tail):
928        """Turn a `<proto>` `<name>` into C-language prototype
929        and typedef declarations for that name.
930
931        - name - contents of `<name>` tag
932        - tail - whatever text follows that tag in the Element"""
933        return self.genOpts.apientry + name + tail
934
935    def makeTypedefName(self, name, tail):
936        """Make the function-pointer typedef name for a command."""
937        return '(' + self.genOpts.apientryp + 'PFN_' + name + tail + ')'
938
939    def makeCParamDecl(self, param, aligncol):
940        """Return a string which is an indented, formatted
941        declaration for a `<param>` or `<member>` block (e.g. function parameter
942        or structure/union member).
943
944        - param - Element (`<param>` or `<member>`) to format
945        - aligncol - if non-zero, attempt to align the nested `<name>` element
946          at this column"""
947        indent = '    '
948        paramdecl = indent
949        prefix = noneStr(param.text)
950
951        for elem in param:
952            text = noneStr(elem.text)
953            tail = noneStr(elem.tail)
954
955            if self.should_insert_may_alias_macro and self.genOpts.conventions.is_voidpointer_alias(elem.tag, text, tail):
956                # OpenXR-specific macro insertion - but not in apiinc for the spec
957                tail = self.genOpts.conventions.make_voidpointer_alias(tail)
958            if elem.tag == 'name' and aligncol > 0:
959                self.logMsg('diag', 'Aligning parameter', elem.text, 'to column', self.genOpts.alignFuncParam)
960                # Align at specified column, if possible
961                paramdecl = paramdecl.rstrip()
962                oldLen = len(paramdecl)
963                # This works around a problem where very long type names -
964                # longer than the alignment column - would run into the tail
965                # text.
966                paramdecl = paramdecl.ljust(aligncol - 1) + ' '
967                newLen = len(paramdecl)
968                self.logMsg('diag', 'Adjust length of parameter decl from', oldLen, 'to', newLen, ':', paramdecl)
969
970            if (self.misracppstyle() and prefix.find('const ') != -1):
971                # Change pointer type order from e.g. "const void *" to "void const *".
972                # If the string starts with 'const', reorder it to be after the first type.
973                paramdecl += prefix.replace('const ', '') + text + ' const' + tail
974            else:
975                paramdecl += prefix + text + tail
976
977            # Clear prefix for subsequent iterations
978            prefix = ''
979
980        paramdecl = paramdecl + prefix
981
982        if aligncol == 0:
983            # Squeeze out multiple spaces other than the indentation
984            paramdecl = indent + ' '.join(paramdecl.split())
985        return paramdecl
986
987    def getCParamTypeLength(self, param):
988        """Return the length of the type field is an indented, formatted
989        declaration for a `<param>` or `<member>` block (e.g. function parameter
990        or structure/union member).
991
992        - param - Element (`<param>` or `<member>`) to identify"""
993
994        # Allow for missing <name> tag
995        newLen = 0
996        paramdecl = '    ' + noneStr(param.text)
997        for elem in param:
998            text = noneStr(elem.text)
999            tail = noneStr(elem.tail)
1000
1001            if self.should_insert_may_alias_macro and self.genOpts.conventions.is_voidpointer_alias(elem.tag, text, tail):
1002                # OpenXR-specific macro insertion
1003                tail = self.genOpts.conventions.make_voidpointer_alias(tail)
1004            if elem.tag == 'name':
1005                # Align at specified column, if possible
1006                newLen = len(paramdecl.rstrip())
1007                self.logMsg('diag', 'Identifying length of', elem.text, 'as', newLen)
1008            paramdecl += text + tail
1009
1010        return newLen
1011
1012    def getMaxCParamTypeLength(self, info):
1013        """Return the length of the longest type field for a member/parameter.
1014
1015        - info - TypeInfo or CommandInfo.
1016        """
1017        lengths = (self.getCParamTypeLength(member)
1018                   for member in info.getMembers())
1019        return max(lengths)
1020
1021    def getHandleParent(self, typename):
1022        """Get the parent of a handle object."""
1023        info = self.registry.typedict.get(typename)
1024        if info is None:
1025            return None
1026
1027        elem = info.elem
1028        if elem is not None:
1029            return elem.get('parent')
1030
1031        return None
1032
1033    def iterateHandleAncestors(self, typename):
1034        """Iterate through the ancestors of a handle type."""
1035        current = self.getHandleParent(typename)
1036        while current is not None:
1037            yield current
1038            current = self.getHandleParent(current)
1039
1040    def getHandleAncestors(self, typename):
1041        """Get the ancestors of a handle object."""
1042        return list(self.iterateHandleAncestors(typename))
1043
1044    def getTypeCategory(self, typename):
1045        """Get the category of a type."""
1046        info = self.registry.typedict.get(typename)
1047        if info is None:
1048            return None
1049
1050        elem = info.elem
1051        if elem is not None:
1052            return elem.get('category')
1053        return None
1054
1055    def isStructAlwaysValid(self, structname):
1056        """Try to do check if a structure is always considered valid (i.e. there is no rules to its acceptance)."""
1057        # A conventions object is required for this call.
1058        if not self.conventions:
1059            raise RuntimeError("To use isStructAlwaysValid, be sure your options include a Conventions object.")
1060
1061        if self.conventions.type_always_valid(structname):
1062            return True
1063
1064        category = self.getTypeCategory(structname)
1065        if self.conventions.category_requires_validation(category):
1066            return False
1067
1068        info = self.registry.typedict.get(structname)
1069        if info is None:
1070            self.logMsg('error', f'isStructAlwaysValid({structname}) - structure not found in typedict')
1071
1072        members = info.getMembers()
1073
1074        for member in members:
1075            member_name = getElemName(member)
1076            if member_name in (self.conventions.structtype_member_name,
1077                               self.conventions.nextpointer_member_name):
1078                return False
1079
1080            if member.get('noautovalidity'):
1081                return False
1082
1083            member_type = getElemType(member)
1084
1085            if member_type in ('void', 'char') or self.paramIsArray(member) or self.paramIsPointer(member):
1086                return False
1087
1088            if self.conventions.type_always_valid(member_type):
1089                continue
1090
1091            member_category = self.getTypeCategory(member_type)
1092
1093            if self.conventions.category_requires_validation(member_category):
1094                return False
1095
1096            if member_category in ('struct', 'union'):
1097                if self.isStructAlwaysValid(member_type) is False:
1098                    return False
1099
1100        return True
1101
1102    def isEnumRequired(self, elem):
1103        """Return True if this `<enum>` element is
1104        required, False otherwise
1105
1106        - elem - `<enum>` element to test"""
1107        required = elem.get('required') is not None
1108        self.logMsg('diag', 'isEnumRequired:', elem.get('name'),
1109                    '->', required)
1110        return required
1111
1112        # @@@ This code is overridden by equivalent code now run in
1113        # @@@ Registry.generateFeature
1114
1115        required = False
1116
1117        extname = elem.get('extname')
1118        if extname is not None:
1119            # 'supported' attribute was injected when the <enum> element was
1120            # moved into the <enums> group in Registry.parseTree()
1121            if self.genOpts.defaultExtensions == elem.get('supported'):
1122                required = True
1123            elif re.match(self.genOpts.addExtensions, extname) is not None:
1124                required = True
1125        elif elem.get('version') is not None:
1126            required = re.match(self.genOpts.emitversions, elem.get('version')) is not None
1127        else:
1128            required = True
1129
1130        return required
1131
1132    def makeCDecls(self, cmd):
1133        """Return C prototype and function pointer typedef for a
1134        `<command>` Element, as a two-element list of strings.
1135
1136        - cmd - Element containing a `<command>` tag"""
1137        proto = cmd.find('proto')
1138        params = cmd.findall('param')
1139        # Begin accumulating prototype and typedef strings
1140        pdecl = self.genOpts.apicall
1141        tdecl = 'typedef '
1142
1143        # Insert the function return type/name.
1144        # For prototypes, add APIENTRY macro before the name
1145        # For typedefs, add (APIENTRY *<name>) around the name and
1146        #   use the PFN_cmdnameproc naming convention.
1147        # Done by walking the tree for <proto> element by element.
1148        # etree has elem.text followed by (elem[i], elem[i].tail)
1149        #   for each child element and any following text
1150        # Leading text
1151        pdecl += noneStr(proto.text)
1152        tdecl += noneStr(proto.text)
1153        # For each child element, if it is a <name> wrap in appropriate
1154        # declaration. Otherwise append its contents and tail contents.
1155        for elem in proto:
1156            text = noneStr(elem.text)
1157            tail = noneStr(elem.tail)
1158            if elem.tag == 'name':
1159                pdecl += self.makeProtoName(text, tail)
1160                tdecl += self.makeTypedefName(text, tail)
1161            else:
1162                pdecl += text + tail
1163                tdecl += text + tail
1164
1165        if self.genOpts.alignFuncParam == 0:
1166            # Squeeze out multiple spaces - there is no indentation
1167            pdecl = ' '.join(pdecl.split())
1168            tdecl = ' '.join(tdecl.split())
1169
1170        # Now add the parameter declaration list, which is identical
1171        # for prototypes and typedefs. Concatenate all the text from
1172        # a <param> node without the tags. No tree walking required
1173        # since all tags are ignored.
1174        # Uses: self.indentFuncProto
1175        # self.indentFuncPointer
1176        # self.alignFuncParam
1177        n = len(params)
1178        # Indented parameters
1179        if n > 0:
1180            indentdecl = '(\n'
1181            indentdecl += ',\n'.join(self.makeCParamDecl(p, self.genOpts.alignFuncParam)
1182                                     for p in params)
1183            indentdecl += ');'
1184        else:
1185            indentdecl = '(void);'
1186        # Non-indented parameters
1187        paramdecl = '('
1188        if n > 0:
1189            paramnames = []
1190            if self.misracppstyle():
1191                for p in params:
1192                    param = ''
1193                    firstIter = True;
1194                    for t in p.itertext():
1195                        if (firstIter):
1196                            prefix = t
1197                            firstIter = False
1198                        else:
1199                            # Change pointer type order from e.g. "const void *" to "void const *".
1200                            # If the string starts with 'const', reorder it to be after the first type.
1201                            if (prefix.find('const ') != -1):
1202                                param += prefix.replace('const ', '') + t + ' const '
1203                            else:
1204                                param += prefix + t
1205                            # Clear prefix for subsequent iterations
1206                            prefix = ''
1207                    paramnames.append(param);
1208            else:
1209                paramnames = (''.join(t for t in p.itertext())
1210                              for p in params)
1211            paramdecl += ', '.join(paramnames)
1212        else:
1213            paramdecl += 'void'
1214        paramdecl += ");"
1215        return [pdecl + indentdecl, tdecl + paramdecl]
1216
1217    def newline(self):
1218        """Print a newline to the output file (utility function)"""
1219        write('', file=self.outFile)
1220
1221    def setRegistry(self, registry):
1222        self.registry = registry
1223