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