• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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                        if alias is not None:
587                            (numVal, strVal) = self.enumToValue(alias, True, bitwidth, True)
588                        else:
589                            self.logMsg('error', 'No such alias {} for enum {}'.format(strVal, name))
590                    decl += "static const {} {} = {};\n".format(flagTypeName, name, strVal)
591
592                if numVal is not None:
593                    body += decl
594                else:
595                    aliasText += decl
596
597                if protect is not None:
598                    body += '#endif\n'
599
600        # Now append the non-numeric enumerant values
601        body += aliasText
602
603        # Postfix
604
605        return ("bitmask", body)
606
607    def buildEnumCDecl_Enum(self, expand, groupinfo, groupName):
608        """Generate the C declaration for an enumerated type"""
609        groupElem = groupinfo.elem
610
611        # Break the group name into prefix and suffix portions for range
612        # enum generation
613        expandName = re.sub(r'([0-9]+|[a-z_])([A-Z0-9])', r'\1_\2', groupName).upper()
614        expandPrefix = expandName
615        expandSuffix = ''
616        expandSuffixMatch = re.search(r'[A-Z][A-Z]+$', groupName)
617        if expandSuffixMatch:
618            expandSuffix = '_' + expandSuffixMatch.group()
619            # Strip off the suffix from the prefix
620            expandPrefix = expandName.rsplit(expandSuffix, 1)[0]
621
622        # Prefix
623        body = ["typedef enum %s {" % groupName]
624
625        # @@ Should use the type="bitmask" attribute instead
626        isEnum = ('FLAG_BITS' not in expandPrefix)
627
628        # Allowable range for a C enum - which is that of a signed 32-bit integer
629        maxValidValue = 2**(32 - 1) - 1
630        minValidValue = (maxValidValue * -1) - 1
631
632
633        # Get a list of nested 'enum' tags.
634        enums = groupElem.findall('enum')
635
636        # Check for and report duplicates, and return a list with them
637        # removed.
638        enums = self.checkDuplicateEnums(enums)
639
640        # Loop over the nested 'enum' tags. Keep track of the minimum and
641        # maximum numeric values, if they can be determined; but only for
642        # core API enumerants, not extension enumerants. This is inferred
643        # by looking for 'extends' attributes.
644        minName = None
645
646        # Accumulate non-numeric enumerant values separately and append
647        # them following the numeric values, to allow for aliases.
648        # NOTE: this does not do a topological sort yet, so aliases of
649        # aliases can still get in the wrong order.
650        aliasText = []
651
652        for elem in enums:
653            # Convert the value to an integer and use that to track min/max.
654            # Values of form -(number) are accepted but nothing more complex.
655            # Should catch exceptions here for more complex constructs. Not yet.
656            (numVal, strVal) = self.enumToValue(elem, True)
657            name = elem.get('name')
658
659            # Extension enumerants are only included if they are required
660            if self.isEnumRequired(elem):
661                decl = ''
662
663                protect = elem.get('protect')
664                if protect is not None:
665                    decl += '#ifdef {}\n'.format(protect)
666
667                # Indent requirements comment, if there is one
668                requirements = self.genRequirements(name, mustBeFound = False)
669                if requirements != '':
670                    requirements = '  ' + requirements
671                decl += requirements
672                decl += '    {} = {},'.format(name, strVal)
673
674                if protect is not None:
675                    decl += '\n#endif'
676
677                if numVal is not None:
678                    body.append(decl)
679                else:
680                    aliasText.append(decl)
681
682            # Range check for the enum value
683            if numVal is not None and (numVal > maxValidValue or numVal < minValidValue):
684                self.logMsg('error', 'Allowable range for C enum types is [', minValidValue, ',', maxValidValue, '], but', name, 'has a value outside of this (', strVal, ')\n')
685                exit(1)
686
687            # Do not track min/max for non-numbers (numVal is None)
688            if isEnum and numVal is not None and elem.get('extends') is None:
689                if minName is None:
690                    minName = maxName = name
691                    minValue = maxValue = numVal
692                elif numVal < minValue:
693                    minName = name
694                    minValue = numVal
695                elif numVal > maxValue:
696                    maxName = name
697                    maxValue = numVal
698
699        # Now append the non-numeric enumerant values
700        body.extend(aliasText)
701
702        # Generate min/max value tokens - legacy use case.
703        if isEnum and expand:
704            body.extend(("    {}_BEGIN_RANGE{} = {},".format(expandPrefix, expandSuffix, minName),
705                         "    {}_END_RANGE{} = {},".format(
706                             expandPrefix, expandSuffix, maxName),
707                         "    {}_RANGE_SIZE{} = ({} - {} + 1),".format(expandPrefix, expandSuffix, maxName, minName)))
708
709        # Generate a range-padding value to ensure the enum is 32 bits, but
710        # only in code generators, so it does not appear in documentation
711        if (self.genOpts.codeGenerator or
712            self.conventions.generate_max_enum_in_docs):
713            body.append("    {}_MAX_ENUM{} = 0x7FFFFFFF".format(
714                expandPrefix, expandSuffix))
715
716        # Postfix
717        body.append("} %s;" % groupName)
718
719        # Determine appropriate section for this declaration
720        if groupElem.get('type') == 'bitmask':
721            section = 'bitmask'
722        else:
723            section = 'group'
724
725        return (section, '\n'.join(body))
726
727    def buildConstantCDecl(self, enuminfo, name, alias):
728        """Generate the C declaration for a constant (a single <enum>
729        value).
730
731        <enum> tags may specify their values in several ways, but are
732        usually just integers or floating-point numbers."""
733
734        (_, strVal) = self.enumToValue(enuminfo.elem, False)
735
736        if self.misracppstyle() and enuminfo.elem.get('type') and not alias:
737            # Generate e.g.: static constexpr uint32_t x = ~static_cast<uint32_t>(1U);
738            # This appeases MISRA "underlying type" rules.
739            typeStr = enuminfo.elem.get('type');
740            invert = '~' in strVal
741            number = strVal.strip("()~UL")
742            if typeStr != "float":
743                number += 'U'
744            strVal = "~" if invert else ""
745            strVal += "static_cast<" + typeStr + ">(" + number + ")"
746            body = 'static constexpr ' + typeStr.ljust(9) + name.ljust(33) + ' {' + strVal + '};'
747        elif enuminfo.elem.get('type') and not alias:
748            # Generate e.g.: #define x (~0ULL)
749            typeStr = enuminfo.elem.get('type');
750            invert = '~' in strVal
751            paren = '(' in strVal
752            number = strVal.strip("()~UL")
753            if typeStr != "float":
754                if typeStr == "uint64_t":
755                    number += 'ULL'
756                else:
757                    number += 'U'
758            strVal = "~" if invert else ""
759            strVal += number
760            if paren:
761                strVal = "(" + strVal + ")";
762            body = '#define ' + name.ljust(33) + ' ' + strVal;
763        else:
764            body = '#define ' + name.ljust(33) + ' ' + strVal
765
766        return body
767
768    def makeDir(self, path):
769        """Create a directory, if not already done.
770
771        Generally called from derived generators creating hierarchies."""
772        self.logMsg('diag', 'OutputGenerator::makeDir(' + path + ')')
773        if path not in self.madeDirs:
774            # This can get race conditions with multiple writers, see
775            # https://stackoverflow.com/questions/273192/
776            if not os.path.exists(path):
777                os.makedirs(path)
778            self.madeDirs[path] = None
779
780    def beginFile(self, genOpts):
781        """Start a new interface file
782
783        - genOpts - GeneratorOptions controlling what is generated and how"""
784        self.genOpts = genOpts
785        self.should_insert_may_alias_macro = \
786            self.genOpts.conventions.should_insert_may_alias_macro(self.genOpts)
787
788        # Try to import the API dictionary, api.py, if it exists. Nothing in
789        # api.py cannot be extracted directly from the XML, and in the
790        # future we should do that.
791        if self.genOpts.genpath is not None:
792            try:
793                sys.path.insert(0, self.genOpts.genpath)
794                import api
795                self.apidict = api
796            except ImportError:
797                self.apidict = None
798
799        self.conventions = genOpts.conventions
800
801        # Open a temporary file for accumulating output.
802        if self.genOpts.filename is not None:
803            self.outFile = tempfile.NamedTemporaryFile(mode='w', encoding='utf-8', newline='\n', delete=False)
804        else:
805            self.outFile = sys.stdout
806
807    def endFile(self):
808        if self.errFile:
809            self.errFile.flush()
810        if self.warnFile:
811            self.warnFile.flush()
812        if self.diagFile:
813            self.diagFile.flush()
814        if self.outFile != sys.stdout and self.outFile != sys.stderr:
815            self.outFile.close()
816
817        # On successfully generating output, move the temporary file to the
818        # target file.
819        if self.genOpts.filename is not None:
820            if sys.platform == 'win32':
821                directory = Path(self.genOpts.directory)
822                if not Path.exists(directory):
823                    os.makedirs(directory)
824            shutil.copy(self.outFile.name, self.genOpts.directory + '/' + self.genOpts.filename)
825            os.remove(self.outFile.name)
826        self.genOpts = None
827
828    def beginFeature(self, interface, emit):
829        """Write interface for a feature and tag generated features as having been done.
830
831        - interface - element for the `<version>` / `<extension>` to generate
832        - emit - actually write to the header only when True"""
833        self.emit = emit
834        self.featureName = interface.get('name')
835        # If there is an additional 'protect' attribute in the feature, save it
836        self.featureExtraProtect = interface.get('protect')
837
838    def endFeature(self):
839        """Finish an interface file, closing it when done.
840
841        Derived classes responsible for emitting feature"""
842        self.featureName = None
843        self.featureExtraProtect = None
844
845    def genRequirements(self, name, mustBeFound = True):
846        """Generate text showing what core versions and extensions introduce
847        an API. This exists in the base Generator class because it is used by
848        the shared enumerant-generating interfaces (buildEnumCDecl, etc.).
849        Here it returns an empty string for most generators, but can be
850        overridden by e.g. DocGenerator.
851
852        - name - name of the API
853        - mustBeFound - If True, when requirements for 'name' cannot be
854          determined, a warning comment is generated.
855        """
856
857        return ''
858
859    def validateFeature(self, featureType, featureName):
860        """Validate we are generating something only inside a `<feature>` tag"""
861        if self.featureName is None:
862            raise UserWarning('Attempt to generate', featureType,
863                              featureName, 'when not in feature')
864
865    def genType(self, typeinfo, name, alias):
866        """Generate interface for a type
867
868        - typeinfo - TypeInfo for a type
869
870        Extend to generate as desired in your derived class."""
871        self.validateFeature('type', name)
872
873    def genStruct(self, typeinfo, typeName, alias):
874        """Generate interface for a C "struct" type.
875
876        - typeinfo - TypeInfo for a type interpreted as a struct
877
878        Extend to generate as desired in your derived class."""
879        self.validateFeature('struct', typeName)
880
881        # The mixed-mode <member> tags may contain no-op <comment> tags.
882        # It is convenient to remove them here where all output generators
883        # will benefit.
884        for member in typeinfo.elem.findall('.//member'):
885            for comment in member.findall('comment'):
886                member.remove(comment)
887
888    def genGroup(self, groupinfo, groupName, alias):
889        """Generate interface for a group of enums (C "enum")
890
891        - groupinfo - GroupInfo for a group.
892
893        Extend to generate as desired in your derived class."""
894
895        self.validateFeature('group', groupName)
896
897    def genEnum(self, enuminfo, typeName, alias):
898        """Generate interface for an enum (constant).
899
900        - enuminfo - EnumInfo for an enum
901        - name - enum name
902
903        Extend to generate as desired in your derived class."""
904        self.validateFeature('enum', typeName)
905
906    def genCmd(self, cmd, cmdinfo, alias):
907        """Generate interface for a command.
908
909        - cmdinfo - CmdInfo for a command
910
911        Extend to generate as desired in your derived class."""
912        self.validateFeature('command', cmdinfo)
913
914    def genSpirv(self, spirv, spirvinfo, alias):
915        """Generate interface for a spirv element.
916
917        - spirvinfo - SpirvInfo for a command
918
919        Extend to generate as desired in your derived class."""
920        return
921
922    def genFormat(self, format, formatinfo, alias):
923        """Generate interface for a format element.
924
925        - formatinfo - FormatInfo
926
927        Extend to generate as desired in your derived class."""
928        return
929
930    def makeProtoName(self, name, tail):
931        """Turn a `<proto>` `<name>` into C-language prototype
932        and typedef declarations for that name.
933
934        - name - contents of `<name>` tag
935        - tail - whatever text follows that tag in the Element"""
936        return self.genOpts.apientry + name + tail
937
938    def makeTypedefName(self, name, tail):
939        """Make the function-pointer typedef name for a command."""
940        return '(' + self.genOpts.apientryp + 'PFN_' + name + tail + ')'
941
942    def makeCParamDecl(self, param, aligncol):
943        """Return a string which is an indented, formatted
944        declaration for a `<param>` or `<member>` block (e.g. function parameter
945        or structure/union member).
946
947        - param - Element (`<param>` or `<member>`) to format
948        - aligncol - if non-zero, attempt to align the nested `<name>` element
949          at this column"""
950        indent = '    '
951        paramdecl = indent
952        prefix = noneStr(param.text)
953
954        for elem in param:
955            text = noneStr(elem.text)
956            tail = noneStr(elem.tail)
957
958            if self.should_insert_may_alias_macro and self.genOpts.conventions.is_voidpointer_alias(elem.tag, text, tail):
959                # OpenXR-specific macro insertion - but not in apiinc for the spec
960                tail = self.genOpts.conventions.make_voidpointer_alias(tail)
961            if elem.tag == 'name' and aligncol > 0:
962                self.logMsg('diag', 'Aligning parameter', elem.text, 'to column', self.genOpts.alignFuncParam)
963                # Align at specified column, if possible
964                paramdecl = paramdecl.rstrip()
965                oldLen = len(paramdecl)
966                # This works around a problem where very long type names -
967                # longer than the alignment column - would run into the tail
968                # text.
969                paramdecl = paramdecl.ljust(aligncol - 1) + ' '
970                newLen = len(paramdecl)
971                self.logMsg('diag', 'Adjust length of parameter decl from', oldLen, 'to', newLen, ':', paramdecl)
972
973            if (self.misracppstyle() and prefix.find('const ') != -1):
974                # Change pointer type order from e.g. "const void *" to "void const *".
975                # If the string starts with 'const', reorder it to be after the first type.
976                paramdecl += prefix.replace('const ', '') + text + ' const' + tail
977            else:
978                paramdecl += prefix + text + tail
979
980            # Clear prefix for subsequent iterations
981            prefix = ''
982
983        paramdecl = paramdecl + prefix
984
985        if aligncol == 0:
986            # Squeeze out multiple spaces other than the indentation
987            paramdecl = indent + ' '.join(paramdecl.split())
988        return paramdecl
989
990    def getCParamTypeLength(self, param):
991        """Return the length of the type field is an indented, formatted
992        declaration for a `<param>` or `<member>` block (e.g. function parameter
993        or structure/union member).
994
995        - param - Element (`<param>` or `<member>`) to identify"""
996
997        # Allow for missing <name> tag
998        newLen = 0
999        paramdecl = '    ' + noneStr(param.text)
1000        for elem in param:
1001            text = noneStr(elem.text)
1002            tail = noneStr(elem.tail)
1003
1004            if self.should_insert_may_alias_macro and self.genOpts.conventions.is_voidpointer_alias(elem.tag, text, tail):
1005                # OpenXR-specific macro insertion
1006                tail = self.genOpts.conventions.make_voidpointer_alias(tail)
1007            if elem.tag == 'name':
1008                # Align at specified column, if possible
1009                newLen = len(paramdecl.rstrip())
1010                self.logMsg('diag', 'Identifying length of', elem.text, 'as', newLen)
1011            paramdecl += text + tail
1012
1013        return newLen
1014
1015    def getMaxCParamTypeLength(self, info):
1016        """Return the length of the longest type field for a member/parameter.
1017
1018        - info - TypeInfo or CommandInfo.
1019        """
1020        lengths = (self.getCParamTypeLength(member)
1021                   for member in info.getMembers())
1022        return max(lengths)
1023
1024    def getHandleParent(self, typename):
1025        """Get the parent of a handle object."""
1026        info = self.registry.typedict.get(typename)
1027        if info is None:
1028            return None
1029
1030        elem = info.elem
1031        if elem is not None:
1032            return elem.get('parent')
1033
1034        return None
1035
1036    def iterateHandleAncestors(self, typename):
1037        """Iterate through the ancestors of a handle type."""
1038        current = self.getHandleParent(typename)
1039        while current is not None:
1040            yield current
1041            current = self.getHandleParent(current)
1042
1043    def getHandleAncestors(self, typename):
1044        """Get the ancestors of a handle object."""
1045        return list(self.iterateHandleAncestors(typename))
1046
1047    def getTypeCategory(self, typename):
1048        """Get the category of a type."""
1049        info = self.registry.typedict.get(typename)
1050        if info is None:
1051            return None
1052
1053        elem = info.elem
1054        if elem is not None:
1055            return elem.get('category')
1056        return None
1057
1058    def isStructAlwaysValid(self, structname):
1059        """Try to do check if a structure is always considered valid (i.e. there is no rules to its acceptance)."""
1060        # A conventions object is required for this call.
1061        if not self.conventions:
1062            raise RuntimeError("To use isStructAlwaysValid, be sure your options include a Conventions object.")
1063
1064        if self.conventions.type_always_valid(structname):
1065            return True
1066
1067        category = self.getTypeCategory(structname)
1068        if self.conventions.category_requires_validation(category):
1069            return False
1070
1071        info = self.registry.typedict.get(structname)
1072        if info is None:
1073            self.logMsg('error', f'isStructAlwaysValid({structname}) - structure not found in typedict')
1074
1075        members = info.getMembers()
1076
1077        for member in members:
1078            member_name = getElemName(member)
1079            if member_name in (self.conventions.structtype_member_name,
1080                               self.conventions.nextpointer_member_name):
1081                return False
1082
1083            if member.get('noautovalidity'):
1084                return False
1085
1086            member_type = getElemType(member)
1087
1088            if member_type in ('void', 'char') or self.paramIsArray(member) or self.paramIsPointer(member):
1089                return False
1090
1091            if self.conventions.type_always_valid(member_type):
1092                continue
1093
1094            member_category = self.getTypeCategory(member_type)
1095
1096            if self.conventions.category_requires_validation(member_category):
1097                return False
1098
1099            if member_category in ('struct', 'union'):
1100                if self.isStructAlwaysValid(member_type) is False:
1101                    return False
1102
1103        return True
1104
1105    def isEnumRequired(self, elem):
1106        """Return True if this `<enum>` element is
1107        required, False otherwise
1108
1109        - elem - `<enum>` element to test"""
1110        required = elem.get('required') is not None
1111        self.logMsg('diag', 'isEnumRequired:', elem.get('name'),
1112                    '->', required)
1113        return required
1114
1115        # @@@ This code is overridden by equivalent code now run in
1116        # @@@ Registry.generateFeature
1117
1118        required = False
1119
1120        extname = elem.get('extname')
1121        if extname is not None:
1122            # 'supported' attribute was injected when the <enum> element was
1123            # moved into the <enums> group in Registry.parseTree()
1124            if self.genOpts.defaultExtensions == elem.get('supported'):
1125                required = True
1126            elif re.match(self.genOpts.addExtensions, extname) is not None:
1127                required = True
1128        elif elem.get('version') is not None:
1129            required = re.match(self.genOpts.emitversions, elem.get('version')) is not None
1130        else:
1131            required = True
1132
1133        return required
1134
1135    def makeCDecls(self, cmd):
1136        """Return C prototype and function pointer typedef for a
1137        `<command>` Element, as a two-element list of strings.
1138
1139        - cmd - Element containing a `<command>` tag"""
1140        proto = cmd.find('proto')
1141        params = cmd.findall('param')
1142        # Begin accumulating prototype and typedef strings
1143        pdecl = self.genOpts.apicall
1144        tdecl = 'typedef '
1145
1146        # Insert the function return type/name.
1147        # For prototypes, add APIENTRY macro before the name
1148        # For typedefs, add (APIENTRY *<name>) around the name and
1149        #   use the PFN_cmdnameproc naming convention.
1150        # Done by walking the tree for <proto> element by element.
1151        # etree has elem.text followed by (elem[i], elem[i].tail)
1152        #   for each child element and any following text
1153        # Leading text
1154        pdecl += noneStr(proto.text)
1155        tdecl += noneStr(proto.text)
1156        # For each child element, if it is a <name> wrap in appropriate
1157        # declaration. Otherwise append its contents and tail contents.
1158        for elem in proto:
1159            text = noneStr(elem.text)
1160            tail = noneStr(elem.tail)
1161            if elem.tag == 'name':
1162                pdecl += self.makeProtoName(text, tail)
1163                tdecl += self.makeTypedefName(text, tail)
1164            else:
1165                pdecl += text + tail
1166                tdecl += text + tail
1167
1168        if self.genOpts.alignFuncParam == 0:
1169            # Squeeze out multiple spaces - there is no indentation
1170            pdecl = ' '.join(pdecl.split())
1171            tdecl = ' '.join(tdecl.split())
1172
1173        # Now add the parameter declaration list, which is identical
1174        # for prototypes and typedefs. Concatenate all the text from
1175        # a <param> node without the tags. No tree walking required
1176        # since all tags are ignored.
1177        # Uses: self.indentFuncProto
1178        # self.indentFuncPointer
1179        # self.alignFuncParam
1180        n = len(params)
1181        # Indented parameters
1182        if n > 0:
1183            indentdecl = '(\n'
1184            indentdecl += ',\n'.join(self.makeCParamDecl(p, self.genOpts.alignFuncParam)
1185                                     for p in params)
1186            indentdecl += ');'
1187        else:
1188            indentdecl = '(void);'
1189        # Non-indented parameters
1190        paramdecl = '('
1191        if n > 0:
1192            paramnames = []
1193            if self.misracppstyle():
1194                for p in params:
1195                    param = ''
1196                    firstIter = True;
1197                    for t in p.itertext():
1198                        if (firstIter):
1199                            prefix = t
1200                            firstIter = False
1201                        else:
1202                            # Change pointer type order from e.g. "const void *" to "void const *".
1203                            # If the string starts with 'const', reorder it to be after the first type.
1204                            if (prefix.find('const ') != -1):
1205                                param += prefix.replace('const ', '') + t + ' const '
1206                            else:
1207                                param += prefix + t
1208                            # Clear prefix for subsequent iterations
1209                            prefix = ''
1210                    paramnames.append(param);
1211            else:
1212                paramnames = (''.join(t for t in p.itertext())
1213                              for p in params)
1214            paramdecl += ', '.join(paramnames)
1215        else:
1216            paramdecl += 'void'
1217        paramdecl += ");"
1218        return [pdecl + indentdecl, tdecl + paramdecl]
1219
1220    def newline(self):
1221        """Print a newline to the output file (utility function)"""
1222        write('', file=self.outFile)
1223
1224    def setRegistry(self, registry):
1225        self.registry = registry
1226