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