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
15
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
256
257class Registry:
258    """Object representing an API registry, loaded from an XML file."""
259
260    def __init__(self, gen=None, genOpts=None):
261        if gen is None:
262            # If not specified, give a default object so messaging will work
263            self.gen = OutputGenerator()
264        else:
265            self.gen = gen
266        "Output generator used to write headers / messages"
267
268        if genOpts is None:
269            self.genOpts = GeneratorOptions()
270        else:
271            self.genOpts = genOpts
272        "Options controlling features to write and how to format them"
273
274        self.gen.registry = self
275        self.gen.genOpts = self.genOpts
276        self.gen.genOpts.registry = self
277
278        self.tree = None
279        "ElementTree containing the root `<registry>`"
280
281        self.typedict = {}
282        "dictionary of TypeInfo objects keyed by type name"
283
284        self.groupdict = {}
285        "dictionary of GroupInfo objects keyed by group name"
286
287        self.enumdict = {}
288        "dictionary of EnumInfo objects keyed by enum name"
289
290        self.cmddict = {}
291        "dictionary of CmdInfo objects keyed by command name"
292
293        self.apidict = {}
294        "dictionary of FeatureInfo objects for `<feature>` elements keyed by API name"
295
296        self.extensions = []
297        "list of `<extension>` Elements"
298
299        self.extdict = {}
300        "dictionary of FeatureInfo objects for `<extension>` elements keyed by extension name"
301
302        self.emitFeatures = False
303        """True to actually emit features for a version / extension,
304        or False to just treat them as emitted"""
305
306        self.breakPat = None
307        "regexp pattern to break on when generating names"
308        # self.breakPat     = re.compile('VkFenceImportFlagBits.*')
309
310        self.requiredextensions = []  # Hack - can remove it after validity generator goes away
311
312        # ** Global types for automatic source generation **
313        # Length Member data
314        self.commandextensiontuple = namedtuple('commandextensiontuple',
315                                                ['command',        # The name of the command being modified
316                                                 'value',          # The value to append to the command
317                                                 'extension'])     # The name of the extension that added it
318        self.validextensionstructs = defaultdict(list)
319        self.commandextensionsuccesses = []
320        self.commandextensionerrors = []
321
322        self.filename     = None
323
324    def loadElementTree(self, tree):
325        """Load ElementTree into a Registry object and parse it."""
326        self.tree = tree
327        self.parseTree()
328
329    def loadFile(self, file):
330        """Load an API registry XML file into a Registry object and parse it"""
331        self.filename = file
332        self.tree = etree.parse(file)
333        self.parseTree()
334
335    def setGenerator(self, gen):
336        """Specify output generator object.
337
338        `None` restores the default generator."""
339        self.gen = gen
340        self.gen.setRegistry(self)
341
342    def addElementInfo(self, elem, info, infoName, dictionary):
343        """Add information about an element to the corresponding dictionary.
344
345        Intended for internal use only.
346
347        - elem - `<type>`/`<enums>`/`<enum>`/`<command>`/`<feature>`/`<extension>` Element
348        - info - corresponding {Type|Group|Enum|Cmd|Feature}Info object
349        - infoName - 'type' / 'group' / 'enum' / 'command' / 'feature' / 'extension'
350        - dictionary - self.{type|group|enum|cmd|api|ext}dict
351
352        If the Element has an 'api' attribute, the dictionary key is the
353        tuple (name,api). If not, the key is the name. 'name' is an
354        attribute of the Element"""
355        # self.gen.logMsg('diag', 'Adding ElementInfo.required =',
356        #     info.required, 'name =', elem.get('name'))
357        api = elem.get('api')
358        if api:
359            key = (elem.get('name'), api)
360        else:
361            key = elem.get('name')
362        if key in dictionary:
363            if not dictionary[key].compareElem(info, infoName):
364                self.gen.logMsg('warn', 'Attempt to redefine', key,
365                                '(this should not happen)')
366            else:
367                True
368        else:
369            dictionary[key] = info
370
371    def lookupElementInfo(self, fname, dictionary):
372        """Find a {Type|Enum|Cmd}Info object by name.
373
374        Intended for internal use only.
375
376        If an object qualified by API name exists, use that.
377
378        - fname - name of type / enum / command
379        - dictionary - self.{type|enum|cmd}dict"""
380        key = (fname, self.genOpts.apiname)
381        if key in dictionary:
382            # self.gen.logMsg('diag', 'Found API-specific element for feature', fname)
383            return dictionary[key]
384        if fname in dictionary:
385            # self.gen.logMsg('diag', 'Found generic element for feature', fname)
386            return dictionary[fname]
387
388        return None
389
390    def breakOnName(self, regexp):
391        """Specify a feature name regexp to break on when generating features."""
392        self.breakPat = re.compile(regexp)
393
394    def parseTree(self):
395        """Parse the registry Element, once created"""
396        # This must be the Element for the root <registry>
397        self.reg = self.tree.getroot()
398
399        # Create dictionary of registry types from toplevel <types> tags
400        # and add 'name' attribute to each <type> tag (where missing)
401        # based on its <name> element.
402        #
403        # There's usually one <types> block; more are OK
404        # Required <type> attributes: 'name' or nested <name> tag contents
405        self.typedict = {}
406        for type_elem in self.reg.findall('types/type'):
407            # If the <type> doesn't already have a 'name' attribute, set
408            # it from contents of its <name> tag.
409            if type_elem.get('name') is None:
410                type_elem.set('name', type_elem.find('name').text)
411            self.addElementInfo(type_elem, TypeInfo(type_elem), 'type', self.typedict)
412
413        # Create dictionary of registry enum groups from <enums> tags.
414        #
415        # Required <enums> attributes: 'name'. If no name is given, one is
416        # generated, but that group can't be identified and turned into an
417        # enum type definition - it's just a container for <enum> tags.
418        self.groupdict = {}
419        for group in self.reg.findall('enums'):
420            self.addElementInfo(group, GroupInfo(group), 'group', self.groupdict)
421
422        # Create dictionary of registry enums from <enum> tags
423        #
424        # <enums> tags usually define different namespaces for the values
425        #   defined in those tags, but the actual names all share the
426        #   same dictionary.
427        # Required <enum> attributes: 'name', 'value'
428        # For containing <enums> which have type="enum" or type="bitmask",
429        # tag all contained <enum>s are required. This is a stopgap until
430        # a better scheme for tagging core and extension enums is created.
431        self.enumdict = {}
432        for enums in self.reg.findall('enums'):
433            required = (enums.get('type') is not None)
434            for enum in enums.findall('enum'):
435                enumInfo = EnumInfo(enum)
436                enumInfo.required = required
437                self.addElementInfo(enum, enumInfo, 'enum', self.enumdict)
438
439        # Create dictionary of registry commands from <command> tags
440        # and add 'name' attribute to each <command> tag (where missing)
441        # based on its <proto><name> element.
442        #
443        # There's usually only one <commands> block; more are OK.
444        # Required <command> attributes: 'name' or <proto><name> tag contents
445        self.cmddict = {}
446        # List of commands which alias others. Contains
447        #   [ aliasName, element ]
448        # for each alias
449        cmdAlias = []
450        for cmd in self.reg.findall('commands/command'):
451            # If the <command> doesn't already have a 'name' attribute, set
452            # it from contents of its <proto><name> tag.
453            name = cmd.get('name')
454            if name is None:
455                name = cmd.set('name', cmd.find('proto/name').text)
456            ci = CmdInfo(cmd)
457            self.addElementInfo(cmd, ci, 'command', self.cmddict)
458            alias = cmd.get('alias')
459            if alias:
460                cmdAlias.append([name, alias, cmd])
461
462        # Now loop over aliases, injecting a copy of the aliased command's
463        # Element with the aliased prototype name replaced with the command
464        # name - if it exists.
465        for (name, alias, cmd) in cmdAlias:
466            if alias in self.cmddict:
467                aliasInfo = self.cmddict[alias]
468                cmdElem = copy.deepcopy(aliasInfo.elem)
469                cmdElem.find('proto/name').text = name
470                cmdElem.set('name', name)
471                cmdElem.set('alias', alias)
472                ci = CmdInfo(cmdElem)
473                # Replace the dictionary entry for the CmdInfo element
474                self.cmddict[name] = ci
475
476                # @  newString = etree.tostring(base, encoding="unicode").replace(aliasValue, aliasName)
477                # @elem.append(etree.fromstring(replacement))
478            else:
479                self.gen.logMsg('warn', 'No matching <command> found for command',
480                                cmd.get('name'), 'alias', alias)
481
482        # Create dictionaries of API and extension interfaces
483        #   from toplevel <api> and <extension> tags.
484        self.apidict = {}
485        for feature in self.reg.findall('feature'):
486            featureInfo = FeatureInfo(feature)
487            self.addElementInfo(feature, featureInfo, 'feature', self.apidict)
488
489            # Add additional enums defined only in <feature> tags
490            # to the corresponding enumerated type.
491            # When seen here, the <enum> element, processed to contain the
492            # numeric enum value, is added to the corresponding <enums>
493            # element, as well as adding to the enum dictionary. It is no
494            # longer removed from the <require> element it is introduced in.
495            # Instead, generateRequiredInterface ignores <enum> elements
496            # that extend enumerated types.
497            #
498            # For <enum> tags which are actually just constants, if there's
499            # no 'extends' tag but there is a 'value' or 'bitpos' tag, just
500            # add an EnumInfo record to the dictionary. That works because
501            # output generation of constants is purely dependency-based, and
502            # doesn't need to iterate through the XML tags.
503            for elem in feature.findall('require'):
504                for enum in elem.findall('enum'):
505                    addEnumInfo = False
506                    groupName = enum.get('extends')
507                    if groupName is not None:
508                        # self.gen.logMsg('diag', 'Found extension enum',
509                        #     enum.get('name'))
510                        # Add version number attribute to the <enum> element
511                        enum.set('version', featureInfo.version)
512                        # Look up the GroupInfo with matching groupName
513                        if groupName in self.groupdict:
514                            # self.gen.logMsg('diag', 'Matching group',
515                            #     groupName, 'found, adding element...')
516                            gi = self.groupdict[groupName]
517                            gi.elem.append(copy.deepcopy(enum))
518                        else:
519                            self.gen.logMsg('warn', 'NO matching group',
520                                            groupName, 'for enum', enum.get('name'), 'found.')
521                        addEnumInfo = True
522                    elif enum.get('value') or enum.get('bitpos') or enum.get('alias'):
523                        # self.gen.logMsg('diag', 'Adding extension constant "enum"',
524                        #     enum.get('name'))
525                        addEnumInfo = True
526                    if addEnumInfo:
527                        enumInfo = EnumInfo(enum)
528                        self.addElementInfo(enum, enumInfo, 'enum', self.enumdict)
529
530        self.extensions = self.reg.findall('extensions/extension')
531        self.extdict = {}
532        for feature in self.extensions:
533            featureInfo = FeatureInfo(feature)
534            self.addElementInfo(feature, featureInfo, 'extension', self.extdict)
535
536            # Add additional enums defined only in <extension> tags
537            # to the corresponding core type.
538            # Algorithm matches that of enums in a "feature" tag as above.
539            #
540            # This code also adds a 'extnumber' attribute containing the
541            # extension number, used for enumerant value calculation.
542            for elem in feature.findall('require'):
543                for enum in elem.findall('enum'):
544                    addEnumInfo = False
545                    groupName = enum.get('extends')
546                    if groupName is not None:
547                        # self.gen.logMsg('diag', 'Found extension enum',
548                        #     enum.get('name'))
549
550                        # Add <extension> block's extension number attribute to
551                        # the <enum> element unless specified explicitly, such
552                        # as when redefining an enum in another extension.
553                        extnumber = enum.get('extnumber')
554                        if not extnumber:
555                            enum.set('extnumber', featureInfo.number)
556
557                        enum.set('extname', featureInfo.name)
558                        enum.set('supported', featureInfo.supported)
559                        # Look up the GroupInfo with matching groupName
560                        if groupName in self.groupdict:
561                            # self.gen.logMsg('diag', 'Matching group',
562                            #     groupName, 'found, adding element...')
563                            gi = self.groupdict[groupName]
564                            gi.elem.append(copy.deepcopy(enum))
565                        else:
566                            self.gen.logMsg('warn', 'NO matching group',
567                                            groupName, 'for enum', enum.get('name'), 'found.')
568                        addEnumInfo = True
569                    elif enum.get('value') or enum.get('bitpos') or enum.get('alias'):
570                        # self.gen.logMsg('diag', 'Adding extension constant "enum"',
571                        #     enum.get('name'))
572                        addEnumInfo = True
573                    if addEnumInfo:
574                        enumInfo = EnumInfo(enum)
575                        self.addElementInfo(enum, enumInfo, 'enum', self.enumdict)
576
577        # Construct a "validextensionstructs" list for parent structures
578        # based on "structextends" tags in child structures
579        disabled_types = []
580        for disabled_ext in self.reg.findall('extensions/extension[@supported="disabled"]'):
581            for type_elem in disabled_ext.findall("*/type"):
582                disabled_types.append(type_elem.get('name'))
583        for type_elem in self.reg.findall('types/type'):
584            if type_elem.get('name') not in disabled_types:
585                parentStructs = type_elem.get('structextends')
586                if parentStructs is not None:
587                    for parent in parentStructs.split(','):
588                        # self.gen.logMsg('diag', type.get('name'), 'extends', parent)
589                        self.validextensionstructs[parent].append(type_elem.get('name'))
590        # Sort the lists so they don't depend on the XML order
591        for parent in self.validextensionstructs:
592            self.validextensionstructs[parent].sort()
593
594    def dumpReg(self, maxlen=120, filehandle=sys.stdout):
595        """Dump all the dictionaries constructed from the Registry object.
596
597        Diagnostic to dump the dictionaries to specified file handle (default stdout).
598        Truncates type / enum / command elements to maxlen characters (default 120)"""
599        write('***************************************', file=filehandle)
600        write('    ** Dumping Registry contents **',     file=filehandle)
601        write('***************************************', file=filehandle)
602        write('// Types', file=filehandle)
603        for name in self.typedict:
604            tobj = self.typedict[name]
605            write('    Type', name, '->', etree.tostring(tobj.elem)[0:maxlen], file=filehandle)
606        write('// Groups', file=filehandle)
607        for name in self.groupdict:
608            gobj = self.groupdict[name]
609            write('    Group', name, '->', etree.tostring(gobj.elem)[0:maxlen], file=filehandle)
610        write('// Enums', file=filehandle)
611        for name in self.enumdict:
612            eobj = self.enumdict[name]
613            write('    Enum', name, '->', etree.tostring(eobj.elem)[0:maxlen], file=filehandle)
614        write('// Commands', file=filehandle)
615        for name in self.cmddict:
616            cobj = self.cmddict[name]
617            write('    Command', name, '->', etree.tostring(cobj.elem)[0:maxlen], file=filehandle)
618        write('// APIs', file=filehandle)
619        for key in self.apidict:
620            write('    API Version ', key, '->',
621                  etree.tostring(self.apidict[key].elem)[0:maxlen], file=filehandle)
622        write('// Extensions', file=filehandle)
623        for key in self.extdict:
624            write('    Extension', key, '->',
625                  etree.tostring(self.extdict[key].elem)[0:maxlen], file=filehandle)
626
627    def markTypeRequired(self, typename, required):
628        """Require (along with its dependencies) or remove (but not its dependencies) a type.
629
630        - typename - name of type
631        - required - boolean (to tag features as required or not)
632        """
633        self.gen.logMsg('diag', 'tagging type:', typename, '-> required =', required)
634        # Get TypeInfo object for <type> tag corresponding to typename
635        typeinfo = self.lookupElementInfo(typename, self.typedict)
636        if typeinfo is not None:
637            if required:
638                # Tag type dependencies in 'alias' and 'required' attributes as
639                # required. This does not un-tag dependencies in a <remove>
640                # tag. See comments in markRequired() below for the reason.
641                for attrib_name in ['requires', 'alias']:
642                    depname = typeinfo.elem.get(attrib_name)
643                    if depname:
644                        self.gen.logMsg('diag', 'Generating dependent type',
645                                        depname, 'for', attrib_name, 'type', typename)
646                        # Don't recurse on self-referential structures.
647                        if typename != depname:
648                            self.markTypeRequired(depname, required)
649                        else:
650                            self.gen.logMsg('diag', 'type', typename, 'is self-referential')
651                # Tag types used in defining this type (e.g. in nested
652                # <type> tags)
653                # Look for <type> in entire <command> tree,
654                # not just immediate children
655                for subtype in typeinfo.elem.findall('.//type'):
656                    self.gen.logMsg('diag', 'markRequired: type requires dependent <type>', subtype.text)
657                    if typename != subtype.text:
658                        self.markTypeRequired(subtype.text, required)
659                    else:
660                        self.gen.logMsg('diag', 'type', typename, 'is self-referential')
661                # Tag enums used in defining this type, for example in
662                #   <member><name>member</name>[<enum>MEMBER_SIZE</enum>]</member>
663                for subenum in typeinfo.elem.findall('.//enum'):
664                    self.gen.logMsg('diag', 'markRequired: type requires dependent <enum>', subenum.text)
665                    self.markEnumRequired(subenum.text, required)
666                # Tag type dependency in 'bitvalues' attributes as
667                # required. This ensures that the bit values for a flag
668                # are emitted
669                depType = typeinfo.elem.get('bitvalues')
670                if depType:
671                    self.gen.logMsg('diag', 'Generating bitflag type',
672                                    depType, 'for type', typename)
673                    self.markTypeRequired(depType, required)
674                    group = self.lookupElementInfo(depType, self.groupdict)
675                    if group is not None:
676                        group.flagType = typeinfo
677
678            typeinfo.required = required
679        elif '.h' not in typename:
680            self.gen.logMsg('warn', 'type:', typename, 'IS NOT DEFINED')
681
682    def markEnumRequired(self, enumname, required):
683        """Mark an enum as required or not.
684
685        - enumname - name of enum
686        - required - boolean (to tag features as required or not)"""
687
688        self.gen.logMsg('diag', 'tagging enum:', enumname, '-> required =', required)
689        enum = self.lookupElementInfo(enumname, self.enumdict)
690        if enum is not None:
691            # If the enum is part of a group, and is being removed, then
692            # look it up in that <group> tag and remove it there, so that it
693            # isn't visible to generators (which traverse the <group> tag
694            # elements themselves).
695            # This isn't the most robust way of doing this, since a removed
696            # enum that's later required again will no longer have a group
697            # element, but it makes the change non-intrusive on generator
698            # code.
699            if required is False:
700                groupName = enum.elem.get('extends')
701                if groupName is not None:
702                    # Look up the Info with matching groupName
703                    if groupName in self.groupdict:
704                        gi = self.groupdict[groupName]
705                        gienum = gi.elem.find("enum[@name='" + enumname + "']")
706                        if gienum is not None:
707                            # Remove copy of this enum from the group
708                            gi.elem.remove(gienum)
709                        else:
710                            self.gen.logMsg('warn', 'Cannot remove enum',
711                                            enumname, 'not found in group',
712                                            groupName)
713                    else:
714                        self.gen.logMsg('warn', 'Cannot remove enum',
715                                        enumname, 'from nonexistent group',
716                                        groupName)
717
718            enum.required = required
719            # Tag enum dependencies in 'alias' attribute as required
720            depname = enum.elem.get('alias')
721            if depname:
722                self.gen.logMsg('diag', 'Generating dependent enum',
723                                depname, 'for alias', enumname, 'required =', enum.required)
724                self.markEnumRequired(depname, required)
725        else:
726            self.gen.logMsg('warn', 'enum:', enumname, 'IS NOT DEFINED')
727
728    def markCmdRequired(self, cmdname, required):
729        """Mark a command as required or not.
730
731        - cmdname - name of command
732        - required - boolean (to tag features as required or not)"""
733        self.gen.logMsg('diag', 'tagging command:', cmdname, '-> required =', required)
734        cmd = self.lookupElementInfo(cmdname, self.cmddict)
735        if cmd is not None:
736            cmd.required = required
737            # Tag command dependencies in 'alias' attribute as required
738            depname = cmd.elem.get('alias')
739            if depname:
740                self.gen.logMsg('diag', 'Generating dependent command',
741                                depname, 'for alias', cmdname)
742                self.markCmdRequired(depname, required)
743            # Tag all parameter types of this command as required.
744            # This DOES NOT remove types of commands in a <remove>
745            # tag, because many other commands may use the same type.
746            # We could be more clever and reference count types,
747            # instead of using a boolean.
748            if required:
749                # Look for <type> in entire <command> tree,
750                # not just immediate children
751                for type_elem in cmd.elem.findall('.//type'):
752                    self.gen.logMsg('diag', 'markRequired: command implicitly requires dependent type', type_elem.text)
753                    self.markTypeRequired(type_elem.text, required)
754        else:
755            self.gen.logMsg('warn', 'command:', cmdname, 'IS NOT DEFINED')
756
757    def markRequired(self, featurename, feature, required):
758        """Require or remove features specified in the Element.
759
760        - featurename - name of the feature
761        - feature - Element for `<require>` or `<remove>` tag
762        - required - boolean (to tag features as required or not)"""
763        self.gen.logMsg('diag', 'markRequired (feature = <too long to print>, required =', required, ')')
764
765        # Loop over types, enums, and commands in the tag
766        # @@ It would be possible to respect 'api' and 'profile' attributes
767        #  in individual features, but that's not done yet.
768        for typeElem in feature.findall('type'):
769            self.markTypeRequired(typeElem.get('name'), required)
770        for enumElem in feature.findall('enum'):
771            self.markEnumRequired(enumElem.get('name'), required)
772        for cmdElem in feature.findall('command'):
773            self.markCmdRequired(cmdElem.get('name'), required)
774
775        # Extensions may need to extend existing commands or other items in the future.
776        # So, look for extend tags.
777        for extendElem in feature.findall('extend'):
778            extendType = extendElem.get('type')
779            if extendType == 'command':
780                commandName = extendElem.get('name')
781                successExtends = extendElem.get('successcodes')
782                if successExtends is not None:
783                    for success in successExtends.split(','):
784                        self.commandextensionsuccesses.append(self.commandextensiontuple(command=commandName,
785                                                                                         value=success,
786                                                                                         extension=featurename))
787                errorExtends = extendElem.get('errorcodes')
788                if errorExtends is not None:
789                    for error in errorExtends.split(','):
790                        self.commandextensionerrors.append(self.commandextensiontuple(command=commandName,
791                                                                                      value=error,
792                                                                                      extension=featurename))
793            else:
794                self.gen.logMsg('warn', 'extend type:', extendType, 'IS NOT SUPPORTED')
795
796    def getAlias(self, elem, dict):
797        """Check for an alias in the same require block.
798
799        - elem - Element to check for an alias"""
800
801        # Try to find an alias
802        alias = elem.get('alias')
803        if alias is None:
804            name = elem.get('name')
805            typeinfo = self.lookupElementInfo(name, dict)
806            alias = typeinfo.elem.get('alias')
807
808        return alias
809
810    def checkForCorrectionAliases(self, alias, require, tag):
811        """Check for an alias in the same require block.
812
813        - alias - String name of the alias
814        - require -  `<require>` block from the registry
815        - tag - tag to look for in the require block"""
816
817        if alias and require.findall(tag + "[@name='" + alias + "']"):
818            return True
819
820        return False
821
822    def fillFeatureDictionary(self, interface, featurename, api, profile):
823        """Capture added interfaces for a `<version>` or `<extension>`.
824
825        - interface - Element for `<version>` or `<extension>`, containing
826          `<require>` and `<remove>` tags
827        - featurename - name of the feature
828        - api - string specifying API name being generated
829        - profile - string specifying API profile being generated"""
830
831        # Explicitly initialize known types - errors for unhandled categories
832        self.gen.featureDictionary[featurename] = {
833            "enumconstant": {},
834            "command": {},
835            "enum": {},
836            "struct": {},
837            "handle": {},
838            "basetype": {},
839            "include": {},
840            "define": {},
841            "bitmask": {},
842            "union": {},
843            "funcpointer": {},
844        }
845
846        # <require> marks things that are required by this version/profile
847        for require in interface.findall('require'):
848            if matchAPIProfile(api, profile, require):
849
850                # Determine the required extension or version needed for a require block
851                # Assumes that only one of these is specified
852                required_key = require.get('feature')
853                if required_key is None:
854                    required_key = require.get('extension')
855
856                # Loop over types, enums, and commands in the tag
857                for typeElem in require.findall('type'):
858                    typename = typeElem.get('name')
859                    typeinfo = self.lookupElementInfo(typename, self.typedict)
860
861                    if typeinfo:
862                        # Remove aliases in the same extension/feature; these are always added as a correction. Don't need the original to be visible.
863                        alias = self.getAlias(typeElem, self.typedict)
864                        if not self.checkForCorrectionAliases(alias, require, 'type'):
865                            # Resolve the type info to the actual type, so we get an accurate read for 'structextends'
866                            while alias:
867                                typeinfo = self.lookupElementInfo(alias, self.typedict)
868                                alias = typeinfo.elem.get('alias')
869
870                            typecat = typeinfo.elem.get('category')
871                            typeextends = typeinfo.elem.get('structextends')
872                            if not required_key in self.gen.featureDictionary[featurename][typecat]:
873                                self.gen.featureDictionary[featurename][typecat][required_key] = {}
874                            if not typeextends in self.gen.featureDictionary[featurename][typecat][required_key]:
875                                self.gen.featureDictionary[featurename][typecat][required_key][typeextends] = []
876                            self.gen.featureDictionary[featurename][typecat][required_key][typeextends].append(typename)
877
878                for enumElem in require.findall('enum'):
879                    enumname = enumElem.get('name')
880                    typeinfo = self.lookupElementInfo(enumname, self.enumdict)
881
882                    # Remove aliases in the same extension/feature; these are always added as a correction. Don't need the original to be visible.
883                    alias = self.getAlias(enumElem, self.enumdict)
884                    if not self.checkForCorrectionAliases(alias, require, 'enum'):
885                        enumextends = enumElem.get('extends')
886                        if not required_key in self.gen.featureDictionary[featurename]['enumconstant']:
887                            self.gen.featureDictionary[featurename]['enumconstant'][required_key] = {}
888                        if not enumextends in self.gen.featureDictionary[featurename]['enumconstant'][required_key]:
889                            self.gen.featureDictionary[featurename]['enumconstant'][required_key][enumextends] = []
890                        self.gen.featureDictionary[featurename]['enumconstant'][required_key][enumextends].append(enumname)
891
892                for cmdElem in require.findall('command'):
893
894                    # Remove aliases in the same extension/feature; these are always added as a correction. Don't need the original to be visible.
895                    alias = self.getAlias(cmdElem, self.cmddict)
896                    if not self.checkForCorrectionAliases(alias, require, 'command'):
897                        if not required_key in self.gen.featureDictionary[featurename]['command']:
898                            self.gen.featureDictionary[featurename]['command'][required_key] = []
899                        self.gen.featureDictionary[featurename]['command'][required_key].append(cmdElem.get('name'))
900
901
902    def requireAndRemoveFeatures(self, interface, featurename, api, profile):
903        """Process `<require>` and `<remove>` tags for a `<version>` or `<extension>`.
904
905        - interface - Element for `<version>` or `<extension>`, containing
906          `<require>` and `<remove>` tags
907        - featurename - name of the feature
908        - api - string specifying API name being generated
909        - profile - string specifying API profile being generated"""
910        # <require> marks things that are required by this version/profile
911        for feature in interface.findall('require'):
912            if matchAPIProfile(api, profile, feature):
913                self.markRequired(featurename, feature, True)
914        # <remove> marks things that are removed by this version/profile
915        for feature in interface.findall('remove'):
916            if matchAPIProfile(api, profile, feature):
917                self.markRequired(featurename, feature, False)
918
919    def assignAdditionalValidity(self, interface, api, profile):
920        # Loop over all usage inside all <require> tags.
921        for feature in interface.findall('require'):
922            if matchAPIProfile(api, profile, feature):
923                for v in feature.findall('usage'):
924                    if v.get('command'):
925                        self.cmddict[v.get('command')].additionalValidity.append(copy.deepcopy(v))
926                    if v.get('struct'):
927                        self.typedict[v.get('struct')].additionalValidity.append(copy.deepcopy(v))
928
929        # Loop over all usage inside all <remove> tags.
930        for feature in interface.findall('remove'):
931            if matchAPIProfile(api, profile, feature):
932                for v in feature.findall('usage'):
933                    if v.get('command'):
934                        self.cmddict[v.get('command')].removedValidity.append(copy.deepcopy(v))
935                    if v.get('struct'):
936                        self.typedict[v.get('struct')].removedValidity.append(copy.deepcopy(v))
937
938    def generateFeature(self, fname, ftype, dictionary):
939        """Generate a single type / enum group / enum / command,
940        and all its dependencies as needed.
941
942        - fname - name of feature (`<type>`/`<enum>`/`<command>`)
943        - ftype - type of feature, 'type' | 'enum' | 'command'
944        - dictionary - of *Info objects - self.{type|enum|cmd}dict"""
945
946        self.gen.logMsg('diag', 'generateFeature: generating', ftype, fname)
947        f = self.lookupElementInfo(fname, dictionary)
948        if f is None:
949            # No such feature. This is an error, but reported earlier
950            self.gen.logMsg('diag', 'No entry found for feature', fname,
951                            'returning!')
952            return
953
954        # If feature isn't required, or has already been declared, return
955        if not f.required:
956            self.gen.logMsg('diag', 'Skipping', ftype, fname, '(not required)')
957            return
958        if f.declared:
959            self.gen.logMsg('diag', 'Skipping', ftype, fname, '(already declared)')
960            return
961        # Always mark feature declared, as though actually emitted
962        f.declared = True
963
964        # Determine if this is an alias, and of what, if so
965        alias = f.elem.get('alias')
966        if alias:
967            self.gen.logMsg('diag', fname, 'is an alias of', alias)
968
969        # Pull in dependent declaration(s) of the feature.
970        # For types, there may be one type in the 'requires' attribute of
971        #   the element, one in the 'alias' attribute, and many in
972        #   embedded <type> and <enum> tags within the element.
973        # For commands, there may be many in <type> tags within the element.
974        # For enums, no dependencies are allowed (though perhaps if you
975        #   have a uint64 enum, it should require that type).
976        genProc = None
977        followupFeature = None
978        if ftype == 'type':
979            genProc = self.gen.genType
980
981            # Generate type dependencies in 'alias' and 'requires' attributes
982            if alias:
983                self.generateFeature(alias, 'type', self.typedict)
984            requires = f.elem.get('requires')
985            if requires:
986                self.gen.logMsg('diag', 'Generating required dependent type',
987                                requires)
988                self.generateFeature(requires, 'type', self.typedict)
989
990            # Generate types used in defining this type (e.g. in nested
991            # <type> tags)
992            # Look for <type> in entire <command> tree,
993            # not just immediate children
994            for subtype in f.elem.findall('.//type'):
995                self.gen.logMsg('diag', 'Generating required dependent <type>',
996                                subtype.text)
997                self.generateFeature(subtype.text, 'type', self.typedict)
998
999            # Generate enums used in defining this type, for example in
1000            #   <member><name>member</name>[<enum>MEMBER_SIZE</enum>]</member>
1001            for subtype in f.elem.findall('.//enum'):
1002                self.gen.logMsg('diag', 'Generating required dependent <enum>',
1003                                subtype.text)
1004                self.generateFeature(subtype.text, 'enum', self.enumdict)
1005
1006            # If the type is an enum group, look up the corresponding
1007            # group in the group dictionary and generate that instead.
1008            if f.elem.get('category') == 'enum':
1009                self.gen.logMsg('diag', 'Type', fname, 'is an enum group, so generate that instead')
1010                group = self.lookupElementInfo(fname, self.groupdict)
1011                if alias is not None:
1012                    # An alias of another group name.
1013                    # Pass to genGroup with 'alias' parameter = aliased name
1014                    self.gen.logMsg('diag', 'Generating alias', fname,
1015                                    'for enumerated type', alias)
1016                    # Now, pass the *aliased* GroupInfo to the genGroup, but
1017                    # with an additional parameter which is the alias name.
1018                    genProc = self.gen.genGroup
1019                    f = self.lookupElementInfo(alias, self.groupdict)
1020                elif group is None:
1021                    self.gen.logMsg('warn', 'Skipping enum type', fname,
1022                                    ': No matching enumerant group')
1023                    return
1024                else:
1025                    genProc = self.gen.genGroup
1026                    f = group
1027
1028                    # @ The enum group is not ready for generation. At this
1029                    # @   point, it contains all <enum> tags injected by
1030                    # @   <extension> tags without any verification of whether
1031                    # @   they're required or not. It may also contain
1032                    # @   duplicates injected by multiple consistent
1033                    # @   definitions of an <enum>.
1034
1035                    # @ Pass over each enum, marking its enumdict[] entry as
1036                    # @ required or not. Mark aliases of enums as required,
1037                    # @ too.
1038
1039                    enums = group.elem.findall('enum')
1040
1041                    self.gen.logMsg('diag', 'generateFeature: checking enums for group', fname)
1042
1043                    # Check for required enums, including aliases
1044                    # LATER - Check for, report, and remove duplicates?
1045                    enumAliases = []
1046                    for elem in enums:
1047                        name = elem.get('name')
1048
1049                        required = False
1050
1051                        extname = elem.get('extname')
1052                        version = elem.get('version')
1053                        if extname is not None:
1054                            # 'supported' attribute was injected when the <enum> element was
1055                            # moved into the <enums> group in Registry.parseTree()
1056                            if self.genOpts.defaultExtensions == elem.get('supported'):
1057                                required = True
1058                            elif re.match(self.genOpts.addExtensions, extname) is not None:
1059                                required = True
1060                        elif version is not None:
1061                            required = re.match(self.genOpts.emitversions, version) is not None
1062                        else:
1063                            required = True
1064
1065                        self.gen.logMsg('diag', '* required =', required, 'for', name)
1066                        if required:
1067                            # Mark this element as required (in the element, not the EnumInfo)
1068                            elem.set('required', 'true')
1069                            # If it's an alias, track that for later use
1070                            enumAlias = elem.get('alias')
1071                            if enumAlias:
1072                                enumAliases.append(enumAlias)
1073                    for elem in enums:
1074                        name = elem.get('name')
1075                        if name in enumAliases:
1076                            elem.set('required', 'true')
1077                            self.gen.logMsg('diag', '* also need to require alias', name)
1078            if f.elem.get('category') == 'bitmask':
1079                followupFeature = f.elem.get('bitvalues')
1080        elif ftype == 'command':
1081            # Generate command dependencies in 'alias' attribute
1082            if alias:
1083                self.generateFeature(alias, 'command', self.cmddict)
1084
1085            genProc = self.gen.genCmd
1086            for type_elem in f.elem.findall('.//type'):
1087                depname = type_elem.text
1088                self.gen.logMsg('diag', 'Generating required parameter type',
1089                                depname)
1090                self.generateFeature(depname, 'type', self.typedict)
1091        elif ftype == 'enum':
1092            # Generate enum dependencies in 'alias' attribute
1093            if alias:
1094                self.generateFeature(alias, 'enum', self.enumdict)
1095            genProc = self.gen.genEnum
1096
1097        # Actually generate the type only if emitting declarations
1098        if self.emitFeatures:
1099            self.gen.logMsg('diag', 'Emitting', ftype, 'decl for', fname)
1100            genProc(f, fname, alias)
1101        else:
1102            self.gen.logMsg('diag', 'Skipping', ftype, fname,
1103                            '(should not be emitted)')
1104
1105        if followupFeature:
1106            self.gen.logMsg('diag', 'Generating required bitvalues <enum>',
1107                            followupFeature)
1108            self.generateFeature(followupFeature, "type", self.typedict)
1109
1110    def generateRequiredInterface(self, interface):
1111        """Generate all interfaces required by an API version or extension.
1112
1113        - interface - Element for `<version>` or `<extension>`"""
1114
1115        # Loop over all features inside all <require> tags.
1116        for features in interface.findall('require'):
1117            for t in features.findall('type'):
1118                self.generateFeature(t.get('name'), 'type', self.typedict)
1119            for e in features.findall('enum'):
1120                # If this is an enum extending an enumerated type, don't
1121                # generate it - this has already been done in reg.parseTree,
1122                # by copying this element into the enumerated type.
1123                enumextends = e.get('extends')
1124                if not enumextends:
1125                    self.generateFeature(e.get('name'), 'enum', self.enumdict)
1126            for c in features.findall('command'):
1127                self.generateFeature(c.get('name'), 'command', self.cmddict)
1128
1129    def apiGen(self):
1130        """Generate interface for specified versions using the current
1131        generator and generator options"""
1132
1133        self.gen.logMsg('diag', '*******************************************')
1134        self.gen.logMsg('diag', '  Registry.apiGen file:', self.genOpts.filename,
1135                        'api:', self.genOpts.apiname,
1136                        'profile:', self.genOpts.profile)
1137        self.gen.logMsg('diag', '*******************************************')
1138
1139        # Reset required/declared flags for all features
1140        self.apiReset()
1141
1142        # Compile regexps used to select versions & extensions
1143        regVersions = re.compile(self.genOpts.versions)
1144        regEmitVersions = re.compile(self.genOpts.emitversions)
1145        regAddExtensions = re.compile(self.genOpts.addExtensions)
1146        regRemoveExtensions = re.compile(self.genOpts.removeExtensions)
1147        regEmitExtensions = re.compile(self.genOpts.emitExtensions)
1148
1149        # Get all matching API feature names & add to list of FeatureInfo
1150        # Note we used to select on feature version attributes, not names.
1151        features = []
1152        apiMatch = False
1153        for key in self.apidict:
1154            fi = self.apidict[key]
1155            api = fi.elem.get('api')
1156            if apiNameMatch(self.genOpts.apiname, api):
1157                apiMatch = True
1158                if regVersions.match(fi.name):
1159                    # Matches API & version #s being generated. Mark for
1160                    # emission and add to the features[] list .
1161                    # @@ Could use 'declared' instead of 'emit'?
1162                    fi.emit = (regEmitVersions.match(fi.name) is not None)
1163                    features.append(fi)
1164                    if not fi.emit:
1165                        self.gen.logMsg('diag', 'NOT tagging feature api =', api,
1166                                        'name =', fi.name, 'version =', fi.version,
1167                                        'for emission (does not match emitversions pattern)')
1168                    else:
1169                        self.gen.logMsg('diag', 'Including feature api =', api,
1170                                        'name =', fi.name, 'version =', fi.version,
1171                                        'for emission (matches emitversions pattern)')
1172                else:
1173                    self.gen.logMsg('diag', 'NOT including feature api =', api,
1174                                    'name =', fi.name, 'version =', fi.version,
1175                                    '(does not match requested versions)')
1176            else:
1177                self.gen.logMsg('diag', 'NOT including feature api =', api,
1178                                'name =', fi.name,
1179                                '(does not match requested API)')
1180        if not apiMatch:
1181            self.gen.logMsg('warn', 'No matching API versions found!')
1182
1183        # Get all matching extensions, in order by their extension number,
1184        # and add to the list of features.
1185        # Start with extensions tagged with 'api' pattern matching the API
1186        # being generated. Add extensions matching the pattern specified in
1187        # regExtensions, then remove extensions matching the pattern
1188        # specified in regRemoveExtensions
1189        for (extName, ei) in sorted(self.extdict.items(), key=lambda x: x[1].number if x[1].number is not None else '0'):
1190            extName = ei.name
1191            include = False
1192
1193            # Include extension if defaultExtensions is not None and is
1194            # exactly matched by the 'supported' attribute.
1195            if apiNameMatch(self.genOpts.defaultExtensions,
1196                            ei.elem.get('supported')):
1197                self.gen.logMsg('diag', 'Including extension',
1198                                extName, "(defaultExtensions matches the 'supported' attribute)")
1199                include = True
1200
1201            # Include additional extensions if the extension name matches
1202            # the regexp specified in the generator options. This allows
1203            # forcing extensions into an interface even if they're not
1204            # tagged appropriately in the registry.
1205            if regAddExtensions.match(extName) is not None:
1206                self.gen.logMsg('diag', 'Including extension',
1207                                extName, '(matches explicitly requested extensions to add)')
1208                include = True
1209            # Remove extensions if the name matches the regexp specified
1210            # in generator options. This allows forcing removal of
1211            # extensions from an interface even if they're tagged that
1212            # way in the registry.
1213            if regRemoveExtensions.match(extName) is not None:
1214                self.gen.logMsg('diag', 'Removing extension',
1215                                extName, '(matches explicitly requested extensions to remove)')
1216                include = False
1217
1218            # If the extension is to be included, add it to the
1219            # extension features list.
1220            if include:
1221                ei.emit = (regEmitExtensions.match(extName) is not None)
1222                features.append(ei)
1223                if not ei.emit:
1224                    self.gen.logMsg('diag', 'NOT tagging extension',
1225                                    extName,
1226                                    'for emission (does not match emitextensions pattern)')
1227
1228                # Hack - can be removed when validity generator goes away
1229                # (Jon) I'm not sure what this does, or if it should respect
1230                # the ei.emit flag above.
1231                self.requiredextensions.append(extName)
1232            else:
1233                self.gen.logMsg('diag', 'NOT including extension',
1234                                extName, '(does not match api attribute or explicitly requested extensions)')
1235
1236        # Sort the features list, if a sort procedure is defined
1237        if self.genOpts.sortProcedure:
1238            self.genOpts.sortProcedure(features)
1239            # print('sortProcedure ->', [f.name for f in features])
1240
1241        # Pass 1: loop over requested API versions and extensions tagging
1242        #   types/commands/features as required (in an <require> block) or no
1243        #   longer required (in an <remove> block). It is possible to remove
1244        #   a feature in one version and restore it later by requiring it in
1245        #   a later version.
1246        # If a profile other than 'None' is being generated, it must
1247        #   match the profile attribute (if any) of the <require> and
1248        #   <remove> tags.
1249        self.gen.logMsg('diag', 'PASS 1: TAG FEATURES')
1250        for f in features:
1251            self.gen.logMsg('diag', 'PASS 1: Tagging required and removed features for',
1252                            f.name)
1253            self.fillFeatureDictionary(f.elem, f.name, self.genOpts.apiname, self.genOpts.profile)
1254            self.requireAndRemoveFeatures(f.elem, f.name, self.genOpts.apiname, self.genOpts.profile)
1255            self.assignAdditionalValidity(f.elem, self.genOpts.apiname, self.genOpts.profile)
1256
1257        # Pass 2: loop over specified API versions and extensions printing
1258        #   declarations for required things which haven't already been
1259        #   generated.
1260        self.gen.logMsg('diag', 'PASS 2: GENERATE INTERFACES FOR FEATURES')
1261        self.gen.beginFile(self.genOpts)
1262        for f in features:
1263            self.gen.logMsg('diag', 'PASS 2: Generating interface for',
1264                            f.name)
1265            emit = self.emitFeatures = f.emit
1266            if not emit:
1267                self.gen.logMsg('diag', 'PASS 2: NOT declaring feature',
1268                                f.elem.get('name'), 'because it is not tagged for emission')
1269            # Generate the interface (or just tag its elements as having been
1270            # emitted, if they haven't been).
1271            self.gen.beginFeature(f.elem, emit)
1272            self.generateRequiredInterface(f.elem)
1273            self.gen.endFeature()
1274        self.gen.endFile()
1275
1276    def apiReset(self):
1277        """Reset type/enum/command dictionaries before generating another API.
1278
1279        Use between apiGen() calls to reset internal state."""
1280        for datatype in self.typedict:
1281            self.typedict[datatype].resetState()
1282        for enum in self.enumdict:
1283            self.enumdict[enum].resetState()
1284        for cmd in self.cmddict:
1285            self.cmddict[cmd].resetState()
1286        for cmd in self.apidict:
1287            self.apidict[cmd].resetState()
1288
1289    def validateGroups(self):
1290        """Validate `group=` attributes on `<param>` and `<proto>` tags.
1291
1292        Check that `group=` attributes match actual groups"""
1293        # Keep track of group names not in <group> tags
1294        badGroup = {}
1295        self.gen.logMsg('diag', 'VALIDATING GROUP ATTRIBUTES')
1296        for cmd in self.reg.findall('commands/command'):
1297            proto = cmd.find('proto')
1298            # funcname = cmd.find('proto/name').text
1299            group = proto.get('group')
1300            if group is not None and group not in self.groupdict:
1301                # self.gen.logMsg('diag', '*** Command ', funcname, ' has UNKNOWN return group ', group)
1302                if group not in badGroup:
1303                    badGroup[group] = 1
1304                else:
1305                    badGroup[group] = badGroup[group] + 1
1306
1307            for param in cmd.findall('param'):
1308                pname = param.find('name')
1309                if pname is not None:
1310                    pname = pname.text
1311                else:
1312                    pname = param.get('name')
1313                group = param.get('group')
1314                if group is not None and group not in self.groupdict:
1315                    # self.gen.logMsg('diag', '*** Command ', funcname, ' param ', pname, ' has UNKNOWN group ', group)
1316                    if group not in badGroup:
1317                        badGroup[group] = 1
1318                    else:
1319                        badGroup[group] = badGroup[group] + 1
1320
1321        if badGroup:
1322            self.gen.logMsg('diag', 'SUMMARY OF UNRECOGNIZED GROUPS')
1323            for key in sorted(badGroup.keys()):
1324                self.gen.logMsg('diag', '    ', key, ' occurred ', badGroup[key], ' times')
1325