• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/python3 -i
2#
3# Copyright 2013-2020 The Khronos Group Inc.
4#
5# SPDX-License-Identifier: Apache-2.0
6
7"""Types and classes for manipulating an API registry."""
8
9import copy
10import re
11import sys
12import xml.etree.ElementTree as etree
13from collections import defaultdict, namedtuple
14from generator import OutputGenerator, GeneratorOptions, write
15import pdb
16
17def apiNameMatch(str, supported):
18    """Return whether a required api name matches a pattern specified for an
19    XML <feature> 'api' attribute or <extension> 'supported' attribute.
20
21    - str - api name such as 'vulkan' or 'openxr'
22    - supported - comma-separated list of XML API names"""
23
24    return (str is not None and str in supported.split(','))
25
26
27def matchAPIProfile(api, profile, elem):
28    """Return whether an API and profile
29    being generated matches an element's profile
30
31    - api - string naming the API to match
32    - profile - string naming the profile to match
33    - elem - Element which (may) have 'api' and 'profile'
34      attributes to match to.
35
36    If a tag is not present in the Element, the corresponding API
37      or profile always matches.
38
39    Otherwise, the tag must exactly match the API or profile.
40
41    Thus, if 'profile' = core:
42
43    - `<remove>`  with no attribute will match
44    - `<remove profile="core">` will match
45    - `<remove profile="compatibility">` will not match
46
47    Possible match conditions:
48
49    ```
50      Requested   Element
51      Profile     Profile
52      ---------   --------
53      None        None        Always matches
54      'string'    None        Always matches
55      None        'string'    Does not match. Can't generate multiple APIs
56                              or profiles, so if an API/profile constraint
57                              is present, it must be asked for explicitly.
58      'string'    'string'    Strings must match
59    ```
60
61    ** In the future, we will allow regexes for the attributes,
62    not just strings, so that `api="^(gl|gles2)"` will match. Even
63    this isn't really quite enough, we might prefer something
64    like `"gl(core)|gles1(common-lite)"`."""
65    # Match 'api', if present
66    elem_api = elem.get('api')
67    if elem_api:
68        if api is None:
69            raise UserWarning("No API requested, but 'api' attribute is present with value '"
70                              + elem_api + "'")
71        elif api != elem_api:
72            # Requested API doesn't match attribute
73            return False
74    elem_profile = elem.get('profile')
75    if elem_profile:
76        if profile is None:
77            raise UserWarning("No profile requested, but 'profile' attribute is present with value '"
78                              + elem_profile + "'")
79        elif profile != elem_profile:
80            # Requested profile doesn't match attribute
81            return False
82    return True
83
84
85class BaseInfo:
86    """Base class for information about a registry feature
87    (type/group/enum/command/API/extension).
88
89    Represents the state of a registry feature, used during API generation.
90    """
91
92    def __init__(self, elem):
93        self.required = False
94        """should this feature be defined during header generation
95        (has it been removed by a profile or version)?"""
96
97        self.declared = False
98        "has this feature been defined already?"
99
100        self.elem = elem
101        "etree Element for this feature"
102
103    def resetState(self):
104        """Reset required/declared to initial values. Used
105        prior to generating a new API interface."""
106        self.required = False
107        self.declared = False
108
109    def compareKeys(self, info, key, required = False):
110        """Return True if self.elem and info.elem have the same attribute
111           value for key.
112           If 'required' is not True, also returns True if neither element
113           has an attribute value for key."""
114
115        if required and key not in self.elem.keys():
116            return False
117        return self.elem.get(key) == info.elem.get(key)
118
119    def compareElem(self, info, infoName):
120        """Return True if self.elem and info.elem have the same definition.
121        info - the other object
122        infoName - 'type' / 'group' / 'enum' / 'command' / 'feature' /
123                   'extension'"""
124
125        if infoName == 'enum':
126            if self.compareKeys(info, 'extends'):
127                # Either both extend the same type, or no type
128                if (self.compareKeys(info, 'value', required = True) or
129                    self.compareKeys(info, 'bitpos', required = True)):
130                    # If both specify the same value or bit position,
131                    # they're equal
132                    return True
133                elif (self.compareKeys(info, 'extnumber') and
134                      self.compareKeys(info, 'offset') and
135                      self.compareKeys(info, 'dir')):
136                    # If both specify the same relative offset, they're equal
137                    return True
138                elif (self.compareKeys(info, 'alias')):
139                    # If both are aliases of the same value
140                    return True
141                else:
142                    return False
143            else:
144                # The same enum can't extend two different types
145                return False
146        else:
147            # Non-<enum>s should never be redefined
148            return False
149
150
151class TypeInfo(BaseInfo):
152    """Registry information about a type. No additional state
153      beyond BaseInfo is required."""
154
155    def __init__(self, elem):
156        BaseInfo.__init__(self, elem)
157        self.additionalValidity = []
158        self.removedValidity = []
159
160    def getMembers(self):
161        """Get a collection of all member elements for this type, if any."""
162        return self.elem.findall('member')
163
164    def resetState(self):
165        BaseInfo.resetState(self)
166        self.additionalValidity = []
167        self.removedValidity = []
168
169
170class GroupInfo(BaseInfo):
171    """Registry information about a group of related enums
172    in an <enums> block, generally corresponding to a C "enum" type."""
173
174    def __init__(self, elem):
175        BaseInfo.__init__(self, elem)
176
177
178class EnumInfo(BaseInfo):
179    """Registry information about an enum"""
180
181    def __init__(self, elem):
182        BaseInfo.__init__(self, elem)
183        self.type = elem.get('type')
184        """numeric type of the value of the <enum> tag
185        ( '' for GLint, 'u' for GLuint, 'ull' for GLuint64 )"""
186        if self.type is None:
187            self.type = ''
188
189
190class CmdInfo(BaseInfo):
191    """Registry information about a command"""
192
193    def __init__(self, elem):
194        BaseInfo.__init__(self, elem)
195        self.additionalValidity = []
196        self.removedValidity = []
197
198    def getParams(self):
199        """Get a collection of all param elements for this command, if any."""
200        return self.elem.findall('param')
201
202    def resetState(self):
203        BaseInfo.resetState(self)
204        self.additionalValidity = []
205        self.removedValidity = []
206
207
208class FeatureInfo(BaseInfo):
209    """Registry information about an API <feature>
210    or <extension>."""
211
212    def __init__(self, elem):
213        BaseInfo.__init__(self, elem)
214        self.name = elem.get('name')
215        "feature name string (e.g. 'VK_KHR_surface')"
216
217        self.emit = False
218        "has this feature been defined already?"
219
220        self.sortorder = int(elem.get('sortorder', 0))
221        """explicit numeric sort key within feature and extension groups.
222        Defaults to 0."""
223
224        # Determine element category (vendor). Only works
225        # for <extension> elements.
226        if elem.tag == 'feature':
227            # Element category (vendor) is meaningless for <feature>
228            self.category = 'VERSION'
229            """category, e.g. VERSION or khr/vendor tag"""
230
231            self.version = elem.get('name')
232            """feature name string"""
233
234            self.versionNumber = elem.get('number')
235            """versionNumber - API version number, taken from the 'number'
236               attribute of <feature>. Extensions do not have API version
237               numbers and are assigned number 0."""
238
239            self.number = "0"
240            self.supported = None
241        else:
242            # Extract vendor portion of <APIprefix>_<vendor>_<name>
243            self.category = self.name.split('_', 2)[1]
244            self.version = "0"
245            self.versionNumber = "0"
246            self.number = elem.get('number')
247            """extension number, used for ordering and for assigning
248            enumerant offsets. <feature> features do not have extension
249            numbers and are assigned number 0."""
250
251            # If there's no 'number' attribute, use 0, so sorting works
252            if self.number is None:
253                self.number = 0
254            self.supported = elem.get('supported')
255
256class SpirvInfo(BaseInfo):
257    """Registry information about an API <spirvextensions>
258    or <spirvcapability>."""
259
260    def __init__(self, elem):
261        BaseInfo.__init__(self, elem)
262
263class Registry:
264    """Object representing an API registry, loaded from an XML file."""
265
266    def __init__(self, gen=None, genOpts=None):
267        if gen is None:
268            # If not specified, give a default object so messaging will work
269            self.gen = OutputGenerator()
270        else:
271            self.gen = gen
272        "Output generator used to write headers / messages"
273
274        if genOpts is None:
275            self.genOpts = GeneratorOptions()
276        else:
277            self.genOpts = genOpts
278        "Options controlling features to write and how to format them"
279
280        self.gen.registry = self
281        self.gen.genOpts = self.genOpts
282        self.gen.genOpts.registry = self
283
284        self.tree = None
285        "ElementTree containing the root `<registry>`"
286
287        self.typedict = {}
288        "dictionary of TypeInfo objects keyed by type name"
289
290        self.groupdict = {}
291        "dictionary of GroupInfo objects keyed by group name"
292
293        self.enumdict = {}
294        "dictionary of EnumInfo objects keyed by enum name"
295
296        self.cmddict = {}
297        "dictionary of CmdInfo objects keyed by command name"
298
299        self.apidict = {}
300        "dictionary of FeatureInfo objects for `<feature>` elements keyed by API name"
301
302        self.extensions = []
303        "list of `<extension>` Elements"
304
305        self.extdict = {}
306        "dictionary of FeatureInfo objects for `<extension>` elements keyed by extension name"
307
308        self.spirvextdict = {}
309        "dictionary of FeatureInfo objects for `<spirvextension>` elements keyed by spirv extension name"
310
311        self.spirvcapdict = {}
312        "dictionary of FeatureInfo objects for `<spirvcapability>` elements keyed by spirv capability name"
313
314        self.emitFeatures = False
315        """True to actually emit features for a version / extension,
316        or False to just treat them as emitted"""
317
318        self.breakPat = None
319        "regexp pattern to break on when generating names"
320        # self.breakPat     = re.compile('VkFenceImportFlagBits.*')
321
322        self.requiredextensions = []  # Hack - can remove it after validity generator goes away
323
324        # ** Global types for automatic source generation **
325        # Length Member data
326        self.commandextensiontuple = namedtuple('commandextensiontuple',
327                                                ['command',        # The name of the command being modified
328                                                 'value',          # The value to append to the command
329                                                 'extension'])     # The name of the extension that added it
330        self.validextensionstructs = defaultdict(list)
331        self.commandextensionsuccesses = []
332        self.commandextensionerrors = []
333
334        self.filename     = None
335
336    def loadElementTree(self, tree):
337        """Load ElementTree into a Registry object and parse it."""
338        self.tree = tree
339        self.parseTree()
340
341    def loadFile(self, file):
342        """Load an API registry XML file into a Registry object and parse it"""
343        self.filename = file
344        self.tree = etree.parse(file)
345        self.parseTree()
346
347    def setGenerator(self, gen):
348        """Specify output generator object.
349
350        `None` restores the default generator."""
351        self.gen = gen
352        self.gen.setRegistry(self)
353
354    def addElementInfo(self, elem, info, infoName, dictionary):
355        """Add information about an element to the corresponding dictionary.
356
357        Intended for internal use only.
358
359        - elem - `<type>`/`<enums>`/`<enum>`/`<command>`/`<feature>`/`<extension>`/`<spirvextension>`/`<spirvcapability>` Element
360        - info - corresponding {Type|Group|Enum|Cmd|Feature|Spirv}Info object
361        - infoName - 'type' / 'group' / 'enum' / 'command' / 'feature' / 'extension' / 'spirvextension' / 'spirvcapability'
362        - dictionary - self.{type|group|enum|cmd|api|ext|spirvext|spirvcap}dict
363
364        If the Element has an 'api' attribute, the dictionary key is the
365        tuple (name,api). If not, the key is the name. 'name' is an
366        attribute of the Element"""
367        # self.gen.logMsg('diag', 'Adding ElementInfo.required =',
368        #     info.required, 'name =', elem.get('name'))
369        api = elem.get('api')
370        if api:
371            key = (elem.get('name'), api)
372        else:
373            key = elem.get('name')
374        if key in dictionary:
375            if not dictionary[key].compareElem(info, infoName):
376                self.gen.logMsg('warn', 'Attempt to redefine', key,
377                                '(this should not happen)')
378            else:
379                True
380        else:
381            dictionary[key] = info
382
383    def lookupElementInfo(self, fname, dictionary):
384        """Find a {Type|Enum|Cmd}Info object by name.
385
386        Intended for internal use only.
387
388        If an object qualified by API name exists, use that.
389
390        - fname - name of type / enum / command
391        - dictionary - self.{type|enum|cmd}dict"""
392        key = (fname, self.genOpts.apiname)
393        if key in dictionary:
394            # self.gen.logMsg('diag', 'Found API-specific element for feature', fname)
395            return dictionary[key]
396        if fname in dictionary:
397            # self.gen.logMsg('diag', 'Found generic element for feature', fname)
398            return dictionary[fname]
399
400        return None
401
402    def breakOnName(self, regexp):
403        """Specify a feature name regexp to break on when generating features."""
404        self.breakPat = re.compile(regexp)
405
406    def parseTree(self):
407        """Parse the registry Element, once created"""
408        # This must be the Element for the root <registry>
409        self.reg = self.tree.getroot()
410
411        # Create dictionary of registry types from toplevel <types> tags
412        # and add 'name' attribute to each <type> tag (where missing)
413        # based on its <name> element.
414        #
415        # There's usually one <types> block; more are OK
416        # Required <type> attributes: 'name' or nested <name> tag contents
417        self.typedict = {}
418        for type_elem in self.reg.findall('types/type'):
419            # If the <type> doesn't already have a 'name' attribute, set
420            # it from contents of its <name> tag.
421            if type_elem.get('name') is None:
422                type_elem.set('name', type_elem.find('name').text)
423            self.addElementInfo(type_elem, TypeInfo(type_elem), 'type', self.typedict)
424
425        # Create dictionary of registry enum groups from <enums> tags.
426        #
427        # Required <enums> attributes: 'name'. If no name is given, one is
428        # generated, but that group can't be identified and turned into an
429        # enum type definition - it's just a container for <enum> tags.
430        self.groupdict = {}
431        for group in self.reg.findall('enums'):
432            self.addElementInfo(group, GroupInfo(group), 'group', self.groupdict)
433
434        # Create dictionary of registry enums from <enum> tags
435        #
436        # <enums> tags usually define different namespaces for the values
437        #   defined in those tags, but the actual names all share the
438        #   same dictionary.
439        # Required <enum> attributes: 'name', 'value'
440        # For containing <enums> which have type="enum" or type="bitmask",
441        # tag all contained <enum>s are required. This is a stopgap until
442        # a better scheme for tagging core and extension enums is created.
443        self.enumdict = {}
444        for enums in self.reg.findall('enums'):
445            required = (enums.get('type') is not None)
446            for enum in enums.findall('enum'):
447                enumInfo = EnumInfo(enum)
448                enumInfo.required = required
449                self.addElementInfo(enum, enumInfo, 'enum', self.enumdict)
450
451        # Create dictionary of registry commands from <command> tags
452        # and add 'name' attribute to each <command> tag (where missing)
453        # based on its <proto><name> element.
454        #
455        # There's usually only one <commands> block; more are OK.
456        # Required <command> attributes: 'name' or <proto><name> tag contents
457        self.cmddict = {}
458        # List of commands which alias others. Contains
459        #   [ aliasName, element ]
460        # for each alias
461        cmdAlias = []
462        for cmd in self.reg.findall('commands/command'):
463            # If the <command> doesn't already have a 'name' attribute, set
464            # it from contents of its <proto><name> tag.
465            name = cmd.get('name')
466            if name is None:
467                name = cmd.set('name', cmd.find('proto/name').text)
468            ci = CmdInfo(cmd)
469            self.addElementInfo(cmd, ci, 'command', self.cmddict)
470            alias = cmd.get('alias')
471            if alias:
472                cmdAlias.append([name, alias, cmd])
473
474        # Now loop over aliases, injecting a copy of the aliased command's
475        # Element with the aliased prototype name replaced with the command
476        # name - if it exists.
477        for (name, alias, cmd) in cmdAlias:
478            if alias in self.cmddict:
479                aliasInfo = self.cmddict[alias]
480                cmdElem = copy.deepcopy(aliasInfo.elem)
481                cmdElem.find('proto/name').text = name
482                cmdElem.set('name', name)
483                cmdElem.set('alias', alias)
484                ci = CmdInfo(cmdElem)
485                # Replace the dictionary entry for the CmdInfo element
486                self.cmddict[name] = ci
487
488                # @  newString = etree.tostring(base, encoding="unicode").replace(aliasValue, aliasName)
489                # @elem.append(etree.fromstring(replacement))
490            else:
491                self.gen.logMsg('warn', 'No matching <command> found for command',
492                                cmd.get('name'), 'alias', alias)
493
494        # Create dictionaries of API and extension interfaces
495        #   from toplevel <api> and <extension> tags.
496        self.apidict = {}
497        for feature in self.reg.findall('feature'):
498            featureInfo = FeatureInfo(feature)
499            self.addElementInfo(feature, featureInfo, 'feature', self.apidict)
500
501            # Add additional enums defined only in <feature> tags
502            # to the corresponding enumerated type.
503            # When seen here, the <enum> element, processed to contain the
504            # numeric enum value, is added to the corresponding <enums>
505            # element, as well as adding to the enum dictionary. It is no
506            # longer removed from the <require> element it is introduced in.
507            # Instead, generateRequiredInterface ignores <enum> elements
508            # that extend enumerated types.
509            #
510            # For <enum> tags which are actually just constants, if there's
511            # no 'extends' tag but there is a 'value' or 'bitpos' tag, just
512            # add an EnumInfo record to the dictionary. That works because
513            # output generation of constants is purely dependency-based, and
514            # doesn't need to iterate through the XML tags.
515            for elem in feature.findall('require'):
516                for enum in elem.findall('enum'):
517                    addEnumInfo = False
518                    groupName = enum.get('extends')
519                    if groupName is not None:
520                        # self.gen.logMsg('diag', 'Found extension enum',
521                        #     enum.get('name'))
522                        # Add version number attribute to the <enum> element
523                        enum.set('version', featureInfo.version)
524                        # Look up the GroupInfo with matching groupName
525                        if groupName in self.groupdict:
526                            # self.gen.logMsg('diag', 'Matching group',
527                            #     groupName, 'found, adding element...')
528                            gi = self.groupdict[groupName]
529                            gi.elem.append(copy.deepcopy(enum))
530                        else:
531                            self.gen.logMsg('warn', 'NO matching group',
532                                            groupName, 'for enum', enum.get('name'), 'found.')
533                        addEnumInfo = True
534                    elif enum.get('value') or enum.get('bitpos') or enum.get('alias'):
535                        # self.gen.logMsg('diag', 'Adding extension constant "enum"',
536                        #     enum.get('name'))
537                        addEnumInfo = True
538                    if addEnumInfo:
539                        enumInfo = EnumInfo(enum)
540                        self.addElementInfo(enum, enumInfo, 'enum', self.enumdict)
541
542        self.extensions = self.reg.findall('extensions/extension')
543        self.extdict = {}
544        for feature in self.extensions:
545            featureInfo = FeatureInfo(feature)
546            self.addElementInfo(feature, featureInfo, 'extension', self.extdict)
547
548            # Add additional enums defined only in <extension> tags
549            # to the corresponding core type.
550            # Algorithm matches that of enums in a "feature" tag as above.
551            #
552            # This code also adds a 'extnumber' attribute containing the
553            # extension number, used for enumerant value calculation.
554            for elem in feature.findall('require'):
555                for enum in elem.findall('enum'):
556                    addEnumInfo = False
557                    groupName = enum.get('extends')
558                    if groupName is not None:
559                        # self.gen.logMsg('diag', 'Found extension enum',
560                        #     enum.get('name'))
561
562                        # Add <extension> block's extension number attribute to
563                        # the <enum> element unless specified explicitly, such
564                        # as when redefining an enum in another extension.
565                        extnumber = enum.get('extnumber')
566                        if not extnumber:
567                            enum.set('extnumber', featureInfo.number)
568
569                        enum.set('extname', featureInfo.name)
570                        enum.set('supported', featureInfo.supported)
571                        # Look up the GroupInfo with matching groupName
572                        if groupName in self.groupdict:
573                            # self.gen.logMsg('diag', 'Matching group',
574                            #     groupName, 'found, adding element...')
575                            gi = self.groupdict[groupName]
576                            gi.elem.append(copy.deepcopy(enum))
577                        else:
578                            self.gen.logMsg('warn', 'NO matching group',
579                                            groupName, 'for enum', enum.get('name'), 'found.')
580                        addEnumInfo = True
581                    elif enum.get('value') or enum.get('bitpos') or enum.get('alias'):
582                        # self.gen.logMsg('diag', 'Adding extension constant "enum"',
583                        #     enum.get('name'))
584                        addEnumInfo = True
585                    if addEnumInfo:
586                        enumInfo = EnumInfo(enum)
587                        self.addElementInfo(enum, enumInfo, 'enum', self.enumdict)
588
589        # Construct a "validextensionstructs" list for parent structures
590        # based on "structextends" tags in child structures
591        disabled_types = []
592        for disabled_ext in self.reg.findall('extensions/extension[@supported="disabled"]'):
593            for type_elem in disabled_ext.findall("*/type"):
594                disabled_types.append(type_elem.get('name'))
595        for type_elem in self.reg.findall('types/type'):
596            if type_elem.get('name') not in disabled_types:
597                parentStructs = type_elem.get('structextends')
598                if parentStructs is not None:
599                    for parent in parentStructs.split(','):
600                        # self.gen.logMsg('diag', type.get('name'), 'extends', parent)
601                        self.validextensionstructs[parent].append(type_elem.get('name'))
602        # Sort the lists so they don't depend on the XML order
603        for parent in self.validextensionstructs:
604            self.validextensionstructs[parent].sort()
605
606        # Parse out all spirv tags in dictionaries
607        # Use addElementInfo to catch duplicates
608        for spirv in self.reg.findall('spirvextensions/spirvextension'):
609            spirvInfo = SpirvInfo(spirv)
610            self.addElementInfo(spirv, spirvInfo, 'spirvextension', self.spirvextdict)
611        for spirv in self.reg.findall('spirvcapabilities/spirvcapability'):
612            spirvInfo = SpirvInfo(spirv)
613            self.addElementInfo(spirv, spirvInfo, 'spirvcapability', self.spirvcapdict)
614
615    def dumpReg(self, maxlen=120, filehandle=sys.stdout):
616        """Dump all the dictionaries constructed from the Registry object.
617
618        Diagnostic to dump the dictionaries to specified file handle (default stdout).
619        Truncates type / enum / command elements to maxlen characters (default 120)"""
620        write('***************************************', file=filehandle)
621        write('    ** Dumping Registry contents **',     file=filehandle)
622        write('***************************************', file=filehandle)
623        write('// Types', file=filehandle)
624        for name in self.typedict:
625            tobj = self.typedict[name]
626            write('    Type', name, '->', etree.tostring(tobj.elem)[0:maxlen], file=filehandle)
627        write('// Groups', file=filehandle)
628        for name in self.groupdict:
629            gobj = self.groupdict[name]
630            write('    Group', name, '->', etree.tostring(gobj.elem)[0:maxlen], file=filehandle)
631        write('// Enums', file=filehandle)
632        for name in self.enumdict:
633            eobj = self.enumdict[name]
634            write('    Enum', name, '->', etree.tostring(eobj.elem)[0:maxlen], file=filehandle)
635        write('// Commands', file=filehandle)
636        for name in self.cmddict:
637            cobj = self.cmddict[name]
638            write('    Command', name, '->', etree.tostring(cobj.elem)[0:maxlen], file=filehandle)
639        write('// APIs', file=filehandle)
640        for key in self.apidict:
641            write('    API Version ', key, '->',
642                  etree.tostring(self.apidict[key].elem)[0:maxlen], file=filehandle)
643        write('// Extensions', file=filehandle)
644        for key in self.extdict:
645            write('    Extension', key, '->',
646                  etree.tostring(self.extdict[key].elem)[0:maxlen], file=filehandle)
647        write('// SPIR-V', file=filehandle)
648        for key in self.spirvextdict:
649            write('    SPIR-V Extension', key, '->',
650                  etree.tostring(self.spirvextdict[key].elem)[0:maxlen], file=filehandle)
651        for key in self.spirvcapdict:
652            write('    SPIR-V Capability', key, '->',
653                  etree.tostring(self.spirvcapdict[key].elem)[0:maxlen], file=filehandle)
654
655    def markTypeRequired(self, typename, required):
656        """Require (along with its dependencies) or remove (but not its dependencies) a type.
657
658        - typename - name of type
659        - required - boolean (to tag features as required or not)
660        """
661        self.gen.logMsg('diag', 'tagging type:', typename, '-> required =', required)
662        # Get TypeInfo object for <type> tag corresponding to typename
663        typeinfo = self.lookupElementInfo(typename, self.typedict)
664        if typeinfo is not None:
665            if required:
666                # Tag type dependencies in 'alias' and 'required' attributes as
667                # required. This does not un-tag dependencies in a <remove>
668                # tag. See comments in markRequired() below for the reason.
669                for attrib_name in ['requires', 'alias']:
670                    depname = typeinfo.elem.get(attrib_name)
671                    if depname:
672                        self.gen.logMsg('diag', 'Generating dependent type',
673                                        depname, 'for', attrib_name, 'type', typename)
674                        # Don't recurse on self-referential structures.
675                        if typename != depname:
676                            self.markTypeRequired(depname, required)
677                        else:
678                            self.gen.logMsg('diag', 'type', typename, 'is self-referential')
679                # Tag types used in defining this type (e.g. in nested
680                # <type> tags)
681                # Look for <type> in entire <command> tree,
682                # not just immediate children
683                for subtype in typeinfo.elem.findall('.//type'):
684                    self.gen.logMsg('diag', 'markRequired: type requires dependent <type>', subtype.text)
685                    if typename != subtype.text:
686                        self.markTypeRequired(subtype.text, required)
687                    else:
688                        self.gen.logMsg('diag', 'type', typename, 'is self-referential')
689                # Tag enums used in defining this type, for example in
690                #   <member><name>member</name>[<enum>MEMBER_SIZE</enum>]</member>
691                for subenum in typeinfo.elem.findall('.//enum'):
692                    self.gen.logMsg('diag', 'markRequired: type requires dependent <enum>', subenum.text)
693                    self.markEnumRequired(subenum.text, required)
694                # Tag type dependency in 'bitvalues' attributes as
695                # required. This ensures that the bit values for a flag
696                # are emitted
697                depType = typeinfo.elem.get('bitvalues')
698                if depType:
699                    self.gen.logMsg('diag', 'Generating bitflag type',
700                                    depType, 'for type', typename)
701                    self.markTypeRequired(depType, required)
702                    group = self.lookupElementInfo(depType, self.groupdict)
703                    if group is not None:
704                        group.flagType = typeinfo
705
706            typeinfo.required = required
707        elif '.h' not in typename:
708            self.gen.logMsg('warn', 'type:', typename, 'IS NOT DEFINED')
709
710    def markEnumRequired(self, enumname, required):
711        """Mark an enum as required or not.
712
713        - enumname - name of enum
714        - required - boolean (to tag features as required or not)"""
715
716        self.gen.logMsg('diag', 'tagging enum:', enumname, '-> required =', required)
717        enum = self.lookupElementInfo(enumname, self.enumdict)
718        if enum is not None:
719            # If the enum is part of a group, and is being removed, then
720            # look it up in that <group> tag and remove it there, so that it
721            # isn't visible to generators (which traverse the <group> tag
722            # elements themselves).
723            # This isn't the most robust way of doing this, since a removed
724            # enum that's later required again will no longer have a group
725            # element, but it makes the change non-intrusive on generator
726            # code.
727            if required is False:
728                groupName = enum.elem.get('extends')
729                if groupName is not None:
730                    # Look up the Info with matching groupName
731                    if groupName in self.groupdict:
732                        gi = self.groupdict[groupName]
733                        gienum = gi.elem.find("enum[@name='" + enumname + "']")
734                        if gienum is not None:
735                            # Remove copy of this enum from the group
736                            gi.elem.remove(gienum)
737                        else:
738                            self.gen.logMsg('warn', 'Cannot remove enum',
739                                            enumname, 'not found in group',
740                                            groupName)
741                    else:
742                        self.gen.logMsg('warn', 'Cannot remove enum',
743                                        enumname, 'from nonexistent group',
744                                        groupName)
745
746            enum.required = required
747            # Tag enum dependencies in 'alias' attribute as required
748            depname = enum.elem.get('alias')
749            if depname:
750                self.gen.logMsg('diag', 'Generating dependent enum',
751                                depname, 'for alias', enumname, 'required =', enum.required)
752                self.markEnumRequired(depname, required)
753        else:
754            self.gen.logMsg('warn', 'enum:', enumname, 'IS NOT DEFINED')
755
756    def markCmdRequired(self, cmdname, required):
757        """Mark a command as required or not.
758
759        - cmdname - name of command
760        - required - boolean (to tag features as required or not)"""
761        self.gen.logMsg('diag', 'tagging command:', cmdname, '-> required =', required)
762        cmd = self.lookupElementInfo(cmdname, self.cmddict)
763        if cmd is not None:
764            cmd.required = required
765            # Tag command dependencies in 'alias' attribute as required
766            depname = cmd.elem.get('alias')
767            if depname:
768                self.gen.logMsg('diag', 'Generating dependent command',
769                                depname, 'for alias', cmdname)
770                self.markCmdRequired(depname, required)
771            # Tag all parameter types of this command as required.
772            # This DOES NOT remove types of commands in a <remove>
773            # tag, because many other commands may use the same type.
774            # We could be more clever and reference count types,
775            # instead of using a boolean.
776            if required:
777                # Look for <type> in entire <command> tree,
778                # not just immediate children
779                for type_elem in cmd.elem.findall('.//type'):
780                    self.gen.logMsg('diag', 'markRequired: command implicitly requires dependent type', type_elem.text)
781                    self.markTypeRequired(type_elem.text, required)
782        else:
783            self.gen.logMsg('warn', 'command:', cmdname, 'IS NOT DEFINED')
784
785    def markRequired(self, featurename, feature, required):
786        """Require or remove features specified in the Element.
787
788        - featurename - name of the feature
789        - feature - Element for `<require>` or `<remove>` tag
790        - required - boolean (to tag features as required or not)"""
791        self.gen.logMsg('diag', 'markRequired (feature = <too long to print>, required =', required, ')')
792
793        # Loop over types, enums, and commands in the tag
794        # @@ It would be possible to respect 'api' and 'profile' attributes
795        #  in individual features, but that's not done yet.
796        for typeElem in feature.findall('type'):
797            self.markTypeRequired(typeElem.get('name'), required)
798        for enumElem in feature.findall('enum'):
799            self.markEnumRequired(enumElem.get('name'), required)
800        for cmdElem in feature.findall('command'):
801            self.markCmdRequired(cmdElem.get('name'), required)
802
803        # Extensions may need to extend existing commands or other items in the future.
804        # So, look for extend tags.
805        for extendElem in feature.findall('extend'):
806            extendType = extendElem.get('type')
807            if extendType == 'command':
808                commandName = extendElem.get('name')
809                successExtends = extendElem.get('successcodes')
810                if successExtends is not None:
811                    for success in successExtends.split(','):
812                        self.commandextensionsuccesses.append(self.commandextensiontuple(command=commandName,
813                                                                                         value=success,
814                                                                                         extension=featurename))
815                errorExtends = extendElem.get('errorcodes')
816                if errorExtends is not None:
817                    for error in errorExtends.split(','):
818                        self.commandextensionerrors.append(self.commandextensiontuple(command=commandName,
819                                                                                      value=error,
820                                                                                      extension=featurename))
821            else:
822                self.gen.logMsg('warn', 'extend type:', extendType, 'IS NOT SUPPORTED')
823
824    def getAlias(self, elem, dict):
825        """Check for an alias in the same require block.
826
827        - elem - Element to check for an alias"""
828
829        # Try to find an alias
830        alias = elem.get('alias')
831        if alias is None:
832            name = elem.get('name')
833            typeinfo = self.lookupElementInfo(name, dict)
834            alias = typeinfo.elem.get('alias')
835
836        return alias
837
838    def checkForCorrectionAliases(self, alias, require, tag):
839        """Check for an alias in the same require block.
840
841        - alias - String name of the alias
842        - require -  `<require>` block from the registry
843        - tag - tag to look for in the require block"""
844
845        if alias and require.findall(tag + "[@name='" + alias + "']"):
846            return True
847
848        return False
849
850    def fillFeatureDictionary(self, interface, featurename, api, profile):
851        """Capture added interfaces for a `<version>` or `<extension>`.
852
853        - interface - Element for `<version>` or `<extension>`, containing
854          `<require>` and `<remove>` tags
855        - featurename - name of the feature
856        - api - string specifying API name being generated
857        - profile - string specifying API profile being generated"""
858
859        # Explicitly initialize known types - errors for unhandled categories
860        self.gen.featureDictionary[featurename] = {
861            "enumconstant": {},
862            "command": {},
863            "enum": {},
864            "struct": {},
865            "handle": {},
866            "basetype": {},
867            "include": {},
868            "define": {},
869            "bitmask": {},
870            "union": {},
871            "funcpointer": {},
872        }
873
874        # <require> marks things that are required by this version/profile
875        for require in interface.findall('require'):
876            if matchAPIProfile(api, profile, require):
877
878                # Determine the required extension or version needed for a require block
879                # Assumes that only one of these is specified
880                required_key = require.get('feature')
881                if required_key is None:
882                    required_key = require.get('extension')
883
884                # Loop over types, enums, and commands in the tag
885                for typeElem in require.findall('type'):
886                    typename = typeElem.get('name')
887                    typeinfo = self.lookupElementInfo(typename, self.typedict)
888
889                    if typeinfo:
890                        # Remove aliases in the same extension/feature; these are always added as a correction. Don't need the original to be visible.
891                        alias = self.getAlias(typeElem, self.typedict)
892                        if not self.checkForCorrectionAliases(alias, require, 'type'):
893                            # Resolve the type info to the actual type, so we get an accurate read for 'structextends'
894                            while alias:
895                                typeinfo = self.lookupElementInfo(alias, self.typedict)
896                                alias = typeinfo.elem.get('alias')
897
898                            typecat = typeinfo.elem.get('category')
899                            typeextends = typeinfo.elem.get('structextends')
900                            if not required_key in self.gen.featureDictionary[featurename][typecat]:
901                                self.gen.featureDictionary[featurename][typecat][required_key] = {}
902                            if not typeextends in self.gen.featureDictionary[featurename][typecat][required_key]:
903                                self.gen.featureDictionary[featurename][typecat][required_key][typeextends] = []
904                            self.gen.featureDictionary[featurename][typecat][required_key][typeextends].append(typename)
905
906                for enumElem in require.findall('enum'):
907                    enumname = enumElem.get('name')
908                    typeinfo = self.lookupElementInfo(enumname, self.enumdict)
909
910                    # Remove aliases in the same extension/feature; these are always added as a correction. Don't need the original to be visible.
911                    alias = self.getAlias(enumElem, self.enumdict)
912                    if not self.checkForCorrectionAliases(alias, require, 'enum'):
913                        enumextends = enumElem.get('extends')
914                        if not required_key in self.gen.featureDictionary[featurename]['enumconstant']:
915                            self.gen.featureDictionary[featurename]['enumconstant'][required_key] = {}
916                        if not enumextends in self.gen.featureDictionary[featurename]['enumconstant'][required_key]:
917                            self.gen.featureDictionary[featurename]['enumconstant'][required_key][enumextends] = []
918                        self.gen.featureDictionary[featurename]['enumconstant'][required_key][enumextends].append(enumname)
919
920                for cmdElem in require.findall('command'):
921
922                    # Remove aliases in the same extension/feature; these are always added as a correction. Don't need the original to be visible.
923                    alias = self.getAlias(cmdElem, self.cmddict)
924                    if not self.checkForCorrectionAliases(alias, require, 'command'):
925                        if not required_key in self.gen.featureDictionary[featurename]['command']:
926                            self.gen.featureDictionary[featurename]['command'][required_key] = []
927                        self.gen.featureDictionary[featurename]['command'][required_key].append(cmdElem.get('name'))
928
929
930    def requireAndRemoveFeatures(self, interface, featurename, api, profile):
931        """Process `<require>` and `<remove>` tags for a `<version>` or `<extension>`.
932
933        - interface - Element for `<version>` or `<extension>`, containing
934          `<require>` and `<remove>` tags
935        - featurename - name of the feature
936        - api - string specifying API name being generated
937        - profile - string specifying API profile being generated"""
938        # <require> marks things that are required by this version/profile
939        for feature in interface.findall('require'):
940            if matchAPIProfile(api, profile, feature):
941                self.markRequired(featurename, feature, True)
942        # <remove> marks things that are removed by this version/profile
943        for feature in interface.findall('remove'):
944            if matchAPIProfile(api, profile, feature):
945                self.markRequired(featurename, feature, False)
946
947    def assignAdditionalValidity(self, interface, api, profile):
948        # Loop over all usage inside all <require> tags.
949        for feature in interface.findall('require'):
950            if matchAPIProfile(api, profile, feature):
951                for v in feature.findall('usage'):
952                    if v.get('command'):
953                        self.cmddict[v.get('command')].additionalValidity.append(copy.deepcopy(v))
954                    if v.get('struct'):
955                        self.typedict[v.get('struct')].additionalValidity.append(copy.deepcopy(v))
956
957        # Loop over all usage inside all <remove> tags.
958        for feature in interface.findall('remove'):
959            if matchAPIProfile(api, profile, feature):
960                for v in feature.findall('usage'):
961                    if v.get('command'):
962                        self.cmddict[v.get('command')].removedValidity.append(copy.deepcopy(v))
963                    if v.get('struct'):
964                        self.typedict[v.get('struct')].removedValidity.append(copy.deepcopy(v))
965
966    def generateFeature(self, fname, ftype, dictionary):
967        """Generate a single type / enum group / enum / command,
968        and all its dependencies as needed.
969
970        - fname - name of feature (`<type>`/`<enum>`/`<command>`)
971        - ftype - type of feature, 'type' | 'enum' | 'command'
972        - dictionary - of *Info objects - self.{type|enum|cmd}dict"""
973
974        self.gen.logMsg('diag', 'generateFeature: generating', ftype, fname)
975        f = self.lookupElementInfo(fname, dictionary)
976        if f is None:
977            # No such feature. This is an error, but reported earlier
978            self.gen.logMsg('diag', 'No entry found for feature', fname,
979                            'returning!')
980            return
981
982        # If feature isn't required, or has already been declared, return
983        if not f.required:
984            self.gen.logMsg('diag', 'Skipping', ftype, fname, '(not required)')
985            return
986        if f.declared:
987            self.gen.logMsg('diag', 'Skipping', ftype, fname, '(already declared)')
988            return
989        # Always mark feature declared, as though actually emitted
990        f.declared = True
991
992        # Determine if this is an alias, and of what, if so
993        alias = f.elem.get('alias')
994        if alias:
995            self.gen.logMsg('diag', fname, 'is an alias of', alias)
996
997        # Pull in dependent declaration(s) of the feature.
998        # For types, there may be one type in the 'requires' attribute of
999        #   the element, one in the 'alias' attribute, and many in
1000        #   embedded <type> and <enum> tags within the element.
1001        # For commands, there may be many in <type> tags within the element.
1002        # For enums, no dependencies are allowed (though perhaps if you
1003        #   have a uint64 enum, it should require that type).
1004        genProc = None
1005        followupFeature = None
1006        if ftype == 'type':
1007            genProc = self.gen.genType
1008
1009            # Generate type dependencies in 'alias' and 'requires' attributes
1010            if alias:
1011                self.generateFeature(alias, 'type', self.typedict)
1012            requires = f.elem.get('requires')
1013            if requires:
1014                self.gen.logMsg('diag', 'Generating required dependent type',
1015                                requires)
1016                self.generateFeature(requires, 'type', self.typedict)
1017
1018            # Generate types used in defining this type (e.g. in nested
1019            # <type> tags)
1020            # Look for <type> in entire <command> tree,
1021            # not just immediate children
1022            for subtype in f.elem.findall('.//type'):
1023                self.gen.logMsg('diag', 'Generating required dependent <type>',
1024                                subtype.text)
1025                self.generateFeature(subtype.text, 'type', self.typedict)
1026
1027            # Generate enums used in defining this type, for example in
1028            #   <member><name>member</name>[<enum>MEMBER_SIZE</enum>]</member>
1029            for subtype in f.elem.findall('.//enum'):
1030                self.gen.logMsg('diag', 'Generating required dependent <enum>',
1031                                subtype.text)
1032                self.generateFeature(subtype.text, 'enum', self.enumdict)
1033
1034            # If the type is an enum group, look up the corresponding
1035            # group in the group dictionary and generate that instead.
1036            if f.elem.get('category') == 'enum':
1037                self.gen.logMsg('diag', 'Type', fname, 'is an enum group, so generate that instead')
1038                group = self.lookupElementInfo(fname, self.groupdict)
1039                if alias is not None:
1040                    # An alias of another group name.
1041                    # Pass to genGroup with 'alias' parameter = aliased name
1042                    self.gen.logMsg('diag', 'Generating alias', fname,
1043                                    'for enumerated type', alias)
1044                    # Now, pass the *aliased* GroupInfo to the genGroup, but
1045                    # with an additional parameter which is the alias name.
1046                    genProc = self.gen.genGroup
1047                    f = self.lookupElementInfo(alias, self.groupdict)
1048                elif group is None:
1049                    self.gen.logMsg('warn', 'Skipping enum type', fname,
1050                                    ': No matching enumerant group')
1051                    return
1052                else:
1053                    genProc = self.gen.genGroup
1054                    f = group
1055
1056                    # @ The enum group is not ready for generation. At this
1057                    # @   point, it contains all <enum> tags injected by
1058                    # @   <extension> tags without any verification of whether
1059                    # @   they're required or not. It may also contain
1060                    # @   duplicates injected by multiple consistent
1061                    # @   definitions of an <enum>.
1062
1063                    # @ Pass over each enum, marking its enumdict[] entry as
1064                    # @ required or not. Mark aliases of enums as required,
1065                    # @ too.
1066
1067                    enums = group.elem.findall('enum')
1068
1069                    self.gen.logMsg('diag', 'generateFeature: checking enums for group', fname)
1070
1071                    # Check for required enums, including aliases
1072                    # LATER - Check for, report, and remove duplicates?
1073                    enumAliases = []
1074                    for elem in enums:
1075                        name = elem.get('name')
1076
1077                        required = False
1078
1079                        extname = elem.get('extname')
1080                        version = elem.get('version')
1081                        if extname is not None:
1082                            # 'supported' attribute was injected when the <enum> element was
1083                            # moved into the <enums> group in Registry.parseTree()
1084                            if self.genOpts.defaultExtensions == elem.get('supported'):
1085                                required = True
1086                            elif re.match(self.genOpts.addExtensions, extname) is not None:
1087                                required = True
1088                        elif version is not None:
1089                            required = re.match(self.genOpts.emitversions, version) is not None
1090                        else:
1091                            required = True
1092
1093                        self.gen.logMsg('diag', '* required =', required, 'for', name)
1094                        if required:
1095                            # Mark this element as required (in the element, not the EnumInfo)
1096                            elem.set('required', 'true')
1097                            # If it's an alias, track that for later use
1098                            enumAlias = elem.get('alias')
1099                            if enumAlias:
1100                                enumAliases.append(enumAlias)
1101                    for elem in enums:
1102                        name = elem.get('name')
1103                        if name in enumAliases:
1104                            elem.set('required', 'true')
1105                            self.gen.logMsg('diag', '* also need to require alias', name)
1106            if f.elem.get('category') == 'bitmask':
1107                followupFeature = f.elem.get('bitvalues')
1108        elif ftype == 'command':
1109            # Generate command dependencies in 'alias' attribute
1110            if alias:
1111                self.generateFeature(alias, 'command', self.cmddict)
1112
1113            genProc = self.gen.genCmd
1114            for type_elem in f.elem.findall('.//type'):
1115                depname = type_elem.text
1116                self.gen.logMsg('diag', 'Generating required parameter type',
1117                                depname)
1118                self.generateFeature(depname, 'type', self.typedict)
1119        elif ftype == 'enum':
1120            # Generate enum dependencies in 'alias' attribute
1121            if alias:
1122                self.generateFeature(alias, 'enum', self.enumdict)
1123            genProc = self.gen.genEnum
1124
1125        # Actually generate the type only if emitting declarations
1126        if self.emitFeatures:
1127            self.gen.logMsg('diag', 'Emitting', ftype, 'decl for', fname)
1128            genProc(f, fname, alias)
1129        else:
1130            self.gen.logMsg('diag', 'Skipping', ftype, fname,
1131                            '(should not be emitted)')
1132
1133        if followupFeature:
1134            self.gen.logMsg('diag', 'Generating required bitvalues <enum>',
1135                            followupFeature)
1136            self.generateFeature(followupFeature, "type", self.typedict)
1137
1138    def generateRequiredInterface(self, interface):
1139        """Generate all interfaces required by an API version or extension.
1140
1141        - interface - Element for `<version>` or `<extension>`"""
1142
1143        # Loop over all features inside all <require> tags.
1144        for features in interface.findall('require'):
1145            for t in features.findall('type'):
1146                self.generateFeature(t.get('name'), 'type', self.typedict)
1147            for e in features.findall('enum'):
1148                # If this is an enum extending an enumerated type, don't
1149                # generate it - this has already been done in reg.parseTree,
1150                # by copying this element into the enumerated type.
1151                enumextends = e.get('extends')
1152                if not enumextends:
1153                    self.generateFeature(e.get('name'), 'enum', self.enumdict)
1154            for c in features.findall('command'):
1155                self.generateFeature(c.get('name'), 'command', self.cmddict)
1156
1157    def generateSpirv(self, spirv, dictionary):
1158        if spirv is None:
1159            self.gen.logMsg('diag', 'No entry found for element', name,
1160                            'returning!')
1161            return
1162
1163        name = spirv.elem.get('name')
1164        # No known alias for spirv elements
1165        alias = None
1166        if spirv.emit:
1167            genProc = self.gen.genSpirv
1168            genProc(spirv, name, alias)
1169
1170    def apiGen(self):
1171        """Generate interface for specified versions using the current
1172        generator and generator options"""
1173
1174        self.gen.logMsg('diag', '*******************************************')
1175        self.gen.logMsg('diag', '  Registry.apiGen file:', self.genOpts.filename,
1176                        'api:', self.genOpts.apiname,
1177                        'profile:', self.genOpts.profile)
1178        self.gen.logMsg('diag', '*******************************************')
1179
1180        # Reset required/declared flags for all features
1181        self.apiReset()
1182
1183        # Compile regexps used to select versions & extensions
1184        regVersions = re.compile(self.genOpts.versions)
1185        regEmitVersions = re.compile(self.genOpts.emitversions)
1186        regAddExtensions = re.compile(self.genOpts.addExtensions)
1187        regRemoveExtensions = re.compile(self.genOpts.removeExtensions)
1188        regEmitExtensions = re.compile(self.genOpts.emitExtensions)
1189        regEmitSpirv = re.compile(self.genOpts.emitSpirv)
1190
1191        # Get all matching API feature names & add to list of FeatureInfo
1192        # Note we used to select on feature version attributes, not names.
1193        features = []
1194        apiMatch = False
1195        for key in self.apidict:
1196            fi = self.apidict[key]
1197            api = fi.elem.get('api')
1198            if apiNameMatch(self.genOpts.apiname, api):
1199                apiMatch = True
1200                if regVersions.match(fi.name):
1201                    # Matches API & version #s being generated. Mark for
1202                    # emission and add to the features[] list .
1203                    # @@ Could use 'declared' instead of 'emit'?
1204                    fi.emit = (regEmitVersions.match(fi.name) is not None)
1205                    features.append(fi)
1206                    if not fi.emit:
1207                        self.gen.logMsg('diag', 'NOT tagging feature api =', api,
1208                                        'name =', fi.name, 'version =', fi.version,
1209                                        'for emission (does not match emitversions pattern)')
1210                    else:
1211                        self.gen.logMsg('diag', 'Including feature api =', api,
1212                                        'name =', fi.name, 'version =', fi.version,
1213                                        'for emission (matches emitversions pattern)')
1214                else:
1215                    self.gen.logMsg('diag', 'NOT including feature api =', api,
1216                                    'name =', fi.name, 'version =', fi.version,
1217                                    '(does not match requested versions)')
1218            else:
1219                self.gen.logMsg('diag', 'NOT including feature api =', api,
1220                                'name =', fi.name,
1221                                '(does not match requested API)')
1222        if not apiMatch:
1223            self.gen.logMsg('warn', 'No matching API versions found!')
1224
1225        # Get all matching extensions, in order by their extension number,
1226        # and add to the list of features.
1227        # Start with extensions tagged with 'api' pattern matching the API
1228        # being generated. Add extensions matching the pattern specified in
1229        # regExtensions, then remove extensions matching the pattern
1230        # specified in regRemoveExtensions
1231        for (extName, ei) in sorted(self.extdict.items(), key=lambda x: x[1].number if x[1].number is not None else '0'):
1232            extName = ei.name
1233            include = False
1234
1235            # Include extension if defaultExtensions is not None and is
1236            # exactly matched by the 'supported' attribute.
1237            if apiNameMatch(self.genOpts.defaultExtensions,
1238                            ei.elem.get('supported')):
1239                self.gen.logMsg('diag', 'Including extension',
1240                                extName, "(defaultExtensions matches the 'supported' attribute)")
1241                include = True
1242
1243            # Include additional extensions if the extension name matches
1244            # the regexp specified in the generator options. This allows
1245            # forcing extensions into an interface even if they're not
1246            # tagged appropriately in the registry.
1247            if regAddExtensions.match(extName) is not None:
1248                self.gen.logMsg('diag', 'Including extension',
1249                                extName, '(matches explicitly requested extensions to add)')
1250                include = True
1251            # Remove extensions if the name matches the regexp specified
1252            # in generator options. This allows forcing removal of
1253            # extensions from an interface even if they're tagged that
1254            # way in the registry.
1255            if regRemoveExtensions.match(extName) is not None:
1256                self.gen.logMsg('diag', 'Removing extension',
1257                                extName, '(matches explicitly requested extensions to remove)')
1258                include = False
1259
1260            # If the extension is to be included, add it to the
1261            # extension features list.
1262            if include:
1263                ei.emit = (regEmitExtensions.match(extName) is not None)
1264                features.append(ei)
1265                if not ei.emit:
1266                    self.gen.logMsg('diag', 'NOT tagging extension',
1267                                    extName,
1268                                    'for emission (does not match emitextensions pattern)')
1269
1270                # Hack - can be removed when validity generator goes away
1271                # (Jon) I'm not sure what this does, or if it should respect
1272                # the ei.emit flag above.
1273                self.requiredextensions.append(extName)
1274            else:
1275                self.gen.logMsg('diag', 'NOT including extension',
1276                                extName, '(does not match api attribute or explicitly requested extensions)')
1277
1278        # Add all spirv elements to list
1279        # generators decide to emit them all or not
1280        # Currently no filtering as no client of these elements needs filtering
1281        spirvexts = []
1282        for key in self.spirvextdict:
1283            si = self.spirvextdict[key]
1284            si.emit = (regEmitSpirv.match(key) is not None)
1285            spirvexts.append(si)
1286        spirvcaps = []
1287        for key in self.spirvcapdict:
1288            si = self.spirvcapdict[key]
1289            si.emit = (regEmitSpirv.match(key) is not None)
1290            spirvcaps.append(si)
1291
1292        # Sort the features list, if a sort procedure is defined
1293        if self.genOpts.sortProcedure:
1294            self.genOpts.sortProcedure(features)
1295            # print('sortProcedure ->', [f.name for f in features])
1296
1297        # Pass 1: loop over requested API versions and extensions tagging
1298        #   types/commands/features as required (in an <require> block) or no
1299        #   longer required (in an <remove> block). It is possible to remove
1300        #   a feature in one version and restore it later by requiring it in
1301        #   a later version.
1302        # If a profile other than 'None' is being generated, it must
1303        #   match the profile attribute (if any) of the <require> and
1304        #   <remove> tags.
1305        self.gen.logMsg('diag', 'PASS 1: TAG FEATURES')
1306        for f in features:
1307            self.gen.logMsg('diag', 'PASS 1: Tagging required and removed features for',
1308                            f.name)
1309            self.fillFeatureDictionary(f.elem, f.name, self.genOpts.apiname, self.genOpts.profile)
1310            self.requireAndRemoveFeatures(f.elem, f.name, self.genOpts.apiname, self.genOpts.profile)
1311            self.assignAdditionalValidity(f.elem, self.genOpts.apiname, self.genOpts.profile)
1312
1313        # Pass 2: loop over specified API versions and extensions printing
1314        #   declarations for required things which haven't already been
1315        #   generated.
1316        self.gen.logMsg('diag', 'PASS 2: GENERATE INTERFACES FOR FEATURES')
1317        self.gen.beginFile(self.genOpts)
1318        for f in features:
1319            self.gen.logMsg('diag', 'PASS 2: Generating interface for',
1320                            f.name)
1321            emit = self.emitFeatures = f.emit
1322            if not emit:
1323                self.gen.logMsg('diag', 'PASS 2: NOT declaring feature',
1324                                f.elem.get('name'), 'because it is not tagged for emission')
1325            # Generate the interface (or just tag its elements as having been
1326            # emitted, if they haven't been).
1327            self.gen.beginFeature(f.elem, emit)
1328            self.generateRequiredInterface(f.elem)
1329            self.gen.endFeature()
1330        # Generate spirv elements
1331        for s in spirvexts:
1332            self.generateSpirv(s, self.spirvextdict)
1333        for s in spirvcaps:
1334            self.generateSpirv(s, self.spirvcapdict)
1335        self.gen.endFile()
1336
1337    def apiReset(self):
1338        """Reset type/enum/command dictionaries before generating another API.
1339
1340        Use between apiGen() calls to reset internal state."""
1341        for datatype in self.typedict:
1342            self.typedict[datatype].resetState()
1343        for enum in self.enumdict:
1344            self.enumdict[enum].resetState()
1345        for cmd in self.cmddict:
1346            self.cmddict[cmd].resetState()
1347        for cmd in self.apidict:
1348            self.apidict[cmd].resetState()
1349
1350    def validateGroups(self):
1351        """Validate `group=` attributes on `<param>` and `<proto>` tags.
1352
1353        Check that `group=` attributes match actual groups"""
1354        # Keep track of group names not in <group> tags
1355        badGroup = {}
1356        self.gen.logMsg('diag', 'VALIDATING GROUP ATTRIBUTES')
1357        for cmd in self.reg.findall('commands/command'):
1358            proto = cmd.find('proto')
1359            # funcname = cmd.find('proto/name').text
1360            group = proto.get('group')
1361            if group is not None and group not in self.groupdict:
1362                # self.gen.logMsg('diag', '*** Command ', funcname, ' has UNKNOWN return group ', group)
1363                if group not in badGroup:
1364                    badGroup[group] = 1
1365                else:
1366                    badGroup[group] = badGroup[group] + 1
1367
1368            for param in cmd.findall('param'):
1369                pname = param.find('name')
1370                if pname is not None:
1371                    pname = pname.text
1372                else:
1373                    pname = param.get('name')
1374                group = param.get('group')
1375                if group is not None and group not in self.groupdict:
1376                    # self.gen.logMsg('diag', '*** Command ', funcname, ' param ', pname, ' has UNKNOWN group ', group)
1377                    if group not in badGroup:
1378                        badGroup[group] = 1
1379                    else:
1380                        badGroup[group] = badGroup[group] + 1
1381
1382        if badGroup:
1383            self.gen.logMsg('diag', 'SUMMARY OF UNRECOGNIZED GROUPS')
1384            for key in sorted(badGroup.keys()):
1385                self.gen.logMsg('diag', '    ', key, ' occurred ', badGroup[key], ' times')
1386