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