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