• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/python3 -i
2#
3# Copyright 2013-2021 The Khronos Group Inc.
4#
5# SPDX-License-Identifier: Apache-2.0
6
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
766            # Tag command dependencies in 'alias' attribute as required
767            #
768            # This is usually not done, because command 'aliases' are not
769            # actual C language aliases like type and enum aliases. Instead
770            # they are just duplicates of the function signature of the
771            # alias. This means that there is no dependency of a command
772            # alias on what it aliases. One exception is validity includes,
773            # where the spec markup needs the promoted-to validity include
774            # even if only the promoted-from command is being built.
775            if self.genOpts.requireCommandAliases:
776                depname = cmd.elem.get('alias')
777                if depname:
778                    self.gen.logMsg('diag', 'Generating dependent command',
779                                    depname, 'for alias', cmdname)
780                    self.markCmdRequired(depname, required)
781
782            # Tag all parameter types of this command as required.
783            # This DOES NOT remove types of commands in a <remove>
784            # tag, because many other commands may use the same type.
785            # We could be more clever and reference count types,
786            # instead of using a boolean.
787            if required:
788                # Look for <type> in entire <command> tree,
789                # not just immediate children
790                for type_elem in cmd.elem.findall('.//type'):
791                    self.gen.logMsg('diag', 'markRequired: command implicitly requires dependent type', type_elem.text)
792                    self.markTypeRequired(type_elem.text, required)
793        else:
794            self.gen.logMsg('warn', 'command:', cmdname, 'IS NOT DEFINED')
795
796    def markRequired(self, featurename, feature, required):
797        """Require or remove features specified in the Element.
798
799        - featurename - name of the feature
800        - feature - Element for `<require>` or `<remove>` tag
801        - required - boolean (to tag features as required or not)"""
802        self.gen.logMsg('diag', 'markRequired (feature = <too long to print>, required =', required, ')')
803
804        # Loop over types, enums, and commands in the tag
805        # @@ It would be possible to respect 'api' and 'profile' attributes
806        #  in individual features, but that's not done yet.
807        for typeElem in feature.findall('type'):
808            self.markTypeRequired(typeElem.get('name'), required)
809        for enumElem in feature.findall('enum'):
810            self.markEnumRequired(enumElem.get('name'), required)
811        for cmdElem in feature.findall('command'):
812            self.markCmdRequired(cmdElem.get('name'), required)
813
814        # Extensions may need to extend existing commands or other items in the future.
815        # So, look for extend tags.
816        for extendElem in feature.findall('extend'):
817            extendType = extendElem.get('type')
818            if extendType == 'command':
819                commandName = extendElem.get('name')
820                successExtends = extendElem.get('successcodes')
821                if successExtends is not None:
822                    for success in successExtends.split(','):
823                        self.commandextensionsuccesses.append(self.commandextensiontuple(command=commandName,
824                                                                                         value=success,
825                                                                                         extension=featurename))
826                errorExtends = extendElem.get('errorcodes')
827                if errorExtends is not None:
828                    for error in errorExtends.split(','):
829                        self.commandextensionerrors.append(self.commandextensiontuple(command=commandName,
830                                                                                      value=error,
831                                                                                      extension=featurename))
832            else:
833                self.gen.logMsg('warn', 'extend type:', extendType, 'IS NOT SUPPORTED')
834
835    def getAlias(self, elem, dict):
836        """Check for an alias in the same require block.
837
838        - elem - Element to check for an alias"""
839
840        # Try to find an alias
841        alias = elem.get('alias')
842        if alias is None:
843            name = elem.get('name')
844            typeinfo = self.lookupElementInfo(name, dict)
845            alias = typeinfo.elem.get('alias')
846
847        return alias
848
849    def checkForCorrectionAliases(self, alias, require, tag):
850        """Check for an alias in the same require block.
851
852        - alias - String name of the alias
853        - require -  `<require>` block from the registry
854        - tag - tag to look for in the require block"""
855
856        # For the time being, the code below is bypassed. It has the effect
857        # of excluding "spelling aliases" created to comply with the style
858        # guide, but this leaves references out of the specification and
859        # causes broken internal links.
860        #
861        # if alias and require.findall(tag + "[@name='" + alias + "']"):
862        #     return True
863
864        return False
865
866    def fillFeatureDictionary(self, interface, featurename, api, profile):
867        """Capture added interfaces for a `<version>` or `<extension>`.
868
869        - interface - Element for `<version>` or `<extension>`, containing
870          `<require>` and `<remove>` tags
871        - featurename - name of the feature
872        - api - string specifying API name being generated
873        - profile - string specifying API profile being generated"""
874
875        # Explicitly initialize known types - errors for unhandled categories
876        self.gen.featureDictionary[featurename] = {
877            "enumconstant": {},
878            "command": {},
879            "enum": {},
880            "struct": {},
881            "handle": {},
882            "basetype": {},
883            "include": {},
884            "define": {},
885            "bitmask": {},
886            "union": {},
887            "funcpointer": {},
888        }
889
890        # <require> marks things that are required by this version/profile
891        for require in interface.findall('require'):
892            if matchAPIProfile(api, profile, require):
893
894                # Determine the required extension or version needed for a require block
895                # Assumes that only one of these is specified
896                required_key = require.get('feature')
897                if required_key is None:
898                    required_key = require.get('extension')
899
900                # Loop over types, enums, and commands in the tag
901                for typeElem in require.findall('type'):
902                    typename = typeElem.get('name')
903                    typeinfo = self.lookupElementInfo(typename, self.typedict)
904
905                    if typeinfo:
906                        # Remove aliases in the same extension/feature; these are always added as a correction. Don't need the original to be visible.
907                        alias = self.getAlias(typeElem, self.typedict)
908                        if not self.checkForCorrectionAliases(alias, require, 'type'):
909                            # Resolve the type info to the actual type, so we get an accurate read for 'structextends'
910                            while alias:
911                                typeinfo = self.lookupElementInfo(alias, self.typedict)
912                                alias = typeinfo.elem.get('alias')
913
914                            typecat = typeinfo.elem.get('category')
915                            typeextends = typeinfo.elem.get('structextends')
916                            if not required_key in self.gen.featureDictionary[featurename][typecat]:
917                                self.gen.featureDictionary[featurename][typecat][required_key] = {}
918                            if not typeextends in self.gen.featureDictionary[featurename][typecat][required_key]:
919                                self.gen.featureDictionary[featurename][typecat][required_key][typeextends] = []
920                            self.gen.featureDictionary[featurename][typecat][required_key][typeextends].append(typename)
921                        else:
922                            self.gen.logMsg('warn', 'fillFeatureDictionary: NOT filling for {}'.format(typename))
923
924
925                for enumElem in require.findall('enum'):
926                    enumname = enumElem.get('name')
927                    typeinfo = self.lookupElementInfo(enumname, self.enumdict)
928
929                    # Remove aliases in the same extension/feature; these are always added as a correction. Don't need the original to be visible.
930                    alias = self.getAlias(enumElem, self.enumdict)
931                    if not self.checkForCorrectionAliases(alias, require, 'enum'):
932                        enumextends = enumElem.get('extends')
933                        if not required_key in self.gen.featureDictionary[featurename]['enumconstant']:
934                            self.gen.featureDictionary[featurename]['enumconstant'][required_key] = {}
935                        if not enumextends in self.gen.featureDictionary[featurename]['enumconstant'][required_key]:
936                            self.gen.featureDictionary[featurename]['enumconstant'][required_key][enumextends] = []
937                        self.gen.featureDictionary[featurename]['enumconstant'][required_key][enumextends].append(enumname)
938                    else:
939                        self.gen.logMsg('warn', 'fillFeatureDictionary: NOT filling for {}'.format(typename))
940
941                for cmdElem in require.findall('command'):
942                    # Remove aliases in the same extension/feature; these are always added as a correction. Don't need the original to be visible.
943                    alias = self.getAlias(cmdElem, self.cmddict)
944                    if not self.checkForCorrectionAliases(alias, require, 'command'):
945                        if not required_key in self.gen.featureDictionary[featurename]['command']:
946                            self.gen.featureDictionary[featurename]['command'][required_key] = []
947                        self.gen.featureDictionary[featurename]['command'][required_key].append(cmdElem.get('name'))
948                    else:
949                        self.gen.logMsg('warn', 'fillFeatureDictionary: NOT filling for {}'.format(typename))
950
951    def requireAndRemoveFeatures(self, interface, featurename, api, profile):
952        """Process `<require>` and `<remove>` tags for a `<version>` or `<extension>`.
953
954        - interface - Element for `<version>` or `<extension>`, containing
955          `<require>` and `<remove>` tags
956        - featurename - name of the feature
957        - api - string specifying API name being generated
958        - profile - string specifying API profile being generated"""
959
960        # <require> marks things that are required by this version/profile
961        for feature in interface.findall('require'):
962            if matchAPIProfile(api, profile, feature):
963                self.markRequired(featurename, feature, True)
964
965        # <remove> marks things that are removed by this version/profile
966        for feature in interface.findall('remove'):
967            if matchAPIProfile(api, profile, feature):
968                self.markRequired(featurename, feature, False)
969
970    def assignAdditionalValidity(self, interface, api, profile):
971        # Loop over all usage inside all <require> tags.
972        for feature in interface.findall('require'):
973            if matchAPIProfile(api, profile, feature):
974                for v in feature.findall('usage'):
975                    if v.get('command'):
976                        self.cmddict[v.get('command')].additionalValidity.append(copy.deepcopy(v))
977                    if v.get('struct'):
978                        self.typedict[v.get('struct')].additionalValidity.append(copy.deepcopy(v))
979
980        # Loop over all usage inside all <remove> tags.
981        for feature in interface.findall('remove'):
982            if matchAPIProfile(api, profile, feature):
983                for v in feature.findall('usage'):
984                    if v.get('command'):
985                        self.cmddict[v.get('command')].removedValidity.append(copy.deepcopy(v))
986                    if v.get('struct'):
987                        self.typedict[v.get('struct')].removedValidity.append(copy.deepcopy(v))
988
989    def generateFeature(self, fname, ftype, dictionary):
990        """Generate a single type / enum group / enum / command,
991        and all its dependencies as needed.
992
993        - fname - name of feature (`<type>`/`<enum>`/`<command>`)
994        - ftype - type of feature, 'type' | 'enum' | 'command'
995        - dictionary - of *Info objects - self.{type|enum|cmd}dict"""
996
997        self.gen.logMsg('diag', 'generateFeature: generating', ftype, fname)
998        f = self.lookupElementInfo(fname, dictionary)
999        if f is None:
1000            # No such feature. This is an error, but reported earlier
1001            self.gen.logMsg('diag', 'No entry found for feature', fname,
1002                            'returning!')
1003            return
1004
1005        # If feature isn't required, or has already been declared, return
1006        if not f.required:
1007            self.gen.logMsg('diag', 'Skipping', ftype, fname, '(not required)')
1008            return
1009        if f.declared:
1010            self.gen.logMsg('diag', 'Skipping', ftype, fname, '(already declared)')
1011            return
1012        # Always mark feature declared, as though actually emitted
1013        f.declared = True
1014
1015        # Determine if this is an alias, and of what, if so
1016        alias = f.elem.get('alias')
1017        if alias:
1018            self.gen.logMsg('diag', fname, 'is an alias of', alias)
1019
1020        # Pull in dependent declaration(s) of the feature.
1021        # For types, there may be one type in the 'requires' attribute of
1022        #   the element, one in the 'alias' attribute, and many in
1023        #   embedded <type> and <enum> tags within the element.
1024        # For commands, there may be many in <type> tags within the element.
1025        # For enums, no dependencies are allowed (though perhaps if you
1026        #   have a uint64 enum, it should require that type).
1027        genProc = None
1028        followupFeature = None
1029        if ftype == 'type':
1030            genProc = self.gen.genType
1031
1032            # Generate type dependencies in 'alias' and 'requires' attributes
1033            if alias:
1034                self.generateFeature(alias, 'type', self.typedict)
1035            requires = f.elem.get('requires')
1036            if requires:
1037                self.gen.logMsg('diag', 'Generating required dependent type',
1038                                requires)
1039                self.generateFeature(requires, 'type', self.typedict)
1040
1041            # Generate types used in defining this type (e.g. in nested
1042            # <type> tags)
1043            # Look for <type> in entire <command> tree,
1044            # not just immediate children
1045            for subtype in f.elem.findall('.//type'):
1046                self.gen.logMsg('diag', 'Generating required dependent <type>',
1047                                subtype.text)
1048                self.generateFeature(subtype.text, 'type', self.typedict)
1049
1050            # Generate enums used in defining this type, for example in
1051            #   <member><name>member</name>[<enum>MEMBER_SIZE</enum>]</member>
1052            for subtype in f.elem.findall('.//enum'):
1053                self.gen.logMsg('diag', 'Generating required dependent <enum>',
1054                                subtype.text)
1055                self.generateFeature(subtype.text, 'enum', self.enumdict)
1056
1057            # If the type is an enum group, look up the corresponding
1058            # group in the group dictionary and generate that instead.
1059            if f.elem.get('category') == 'enum':
1060                self.gen.logMsg('diag', 'Type', fname, 'is an enum group, so generate that instead')
1061                group = self.lookupElementInfo(fname, self.groupdict)
1062                if alias is not None:
1063                    # An alias of another group name.
1064                    # Pass to genGroup with 'alias' parameter = aliased name
1065                    self.gen.logMsg('diag', 'Generating alias', fname,
1066                                    'for enumerated type', alias)
1067                    # Now, pass the *aliased* GroupInfo to the genGroup, but
1068                    # with an additional parameter which is the alias name.
1069                    genProc = self.gen.genGroup
1070                    f = self.lookupElementInfo(alias, self.groupdict)
1071                elif group is None:
1072                    self.gen.logMsg('warn', 'Skipping enum type', fname,
1073                                    ': No matching enumerant group')
1074                    return
1075                else:
1076                    genProc = self.gen.genGroup
1077                    f = group
1078
1079                    # @ The enum group is not ready for generation. At this
1080                    # @   point, it contains all <enum> tags injected by
1081                    # @   <extension> tags without any verification of whether
1082                    # @   they're required or not. It may also contain
1083                    # @   duplicates injected by multiple consistent
1084                    # @   definitions of an <enum>.
1085
1086                    # @ Pass over each enum, marking its enumdict[] entry as
1087                    # @ required or not. Mark aliases of enums as required,
1088                    # @ too.
1089
1090                    enums = group.elem.findall('enum')
1091
1092                    self.gen.logMsg('diag', 'generateFeature: checking enums for group', fname)
1093
1094                    # Check for required enums, including aliases
1095                    # LATER - Check for, report, and remove duplicates?
1096                    enumAliases = []
1097                    for elem in enums:
1098                        name = elem.get('name')
1099
1100                        required = False
1101
1102                        extname = elem.get('extname')
1103                        version = elem.get('version')
1104                        if extname is not None:
1105                            # 'supported' attribute was injected when the <enum> element was
1106                            # moved into the <enums> group in Registry.parseTree()
1107                            if self.genOpts.defaultExtensions == elem.get('supported'):
1108                                required = True
1109                            elif re.match(self.genOpts.addExtensions, extname) is not None:
1110                                required = True
1111                        elif version is not None:
1112                            required = re.match(self.genOpts.emitversions, version) is not None
1113                        else:
1114                            required = True
1115
1116                        self.gen.logMsg('diag', '* required =', required, 'for', name)
1117                        if required:
1118                            # Mark this element as required (in the element, not the EnumInfo)
1119                            elem.set('required', 'true')
1120                            # If it's an alias, track that for later use
1121                            enumAlias = elem.get('alias')
1122                            if enumAlias:
1123                                enumAliases.append(enumAlias)
1124                    for elem in enums:
1125                        name = elem.get('name')
1126                        if name in enumAliases:
1127                            elem.set('required', 'true')
1128                            self.gen.logMsg('diag', '* also need to require alias', name)
1129            if f.elem.get('category') == 'bitmask':
1130                followupFeature = f.elem.get('bitvalues')
1131        elif ftype == 'command':
1132            # Generate command dependencies in 'alias' attribute
1133            if alias:
1134                self.generateFeature(alias, 'command', self.cmddict)
1135
1136            genProc = self.gen.genCmd
1137            for type_elem in f.elem.findall('.//type'):
1138                depname = type_elem.text
1139                self.gen.logMsg('diag', 'Generating required parameter type',
1140                                depname)
1141                self.generateFeature(depname, 'type', self.typedict)
1142        elif ftype == 'enum':
1143            # Generate enum dependencies in 'alias' attribute
1144            if alias:
1145                self.generateFeature(alias, 'enum', self.enumdict)
1146            genProc = self.gen.genEnum
1147
1148        # Actually generate the type only if emitting declarations
1149        if self.emitFeatures:
1150            self.gen.logMsg('diag', 'Emitting', ftype, 'decl for', fname)
1151            genProc(f, fname, alias)
1152        else:
1153            self.gen.logMsg('diag', 'Skipping', ftype, fname,
1154                            '(should not be emitted)')
1155
1156        if followupFeature:
1157            self.gen.logMsg('diag', 'Generating required bitvalues <enum>',
1158                            followupFeature)
1159            self.generateFeature(followupFeature, "type", self.typedict)
1160
1161    def generateRequiredInterface(self, interface):
1162        """Generate all interfaces required by an API version or extension.
1163
1164        - interface - Element for `<version>` or `<extension>`"""
1165
1166        # Loop over all features inside all <require> tags.
1167        for features in interface.findall('require'):
1168            for t in features.findall('type'):
1169                self.generateFeature(t.get('name'), 'type', self.typedict)
1170            for e in features.findall('enum'):
1171                # If this is an enum extending an enumerated type, don't
1172                # generate it - this has already been done in reg.parseTree,
1173                # by copying this element into the enumerated type.
1174                enumextends = e.get('extends')
1175                if not enumextends:
1176                    self.generateFeature(e.get('name'), 'enum', self.enumdict)
1177            for c in features.findall('command'):
1178                self.generateFeature(c.get('name'), 'command', self.cmddict)
1179
1180    def generateSpirv(self, spirv, dictionary):
1181        if spirv is None:
1182            self.gen.logMsg('diag', 'No entry found for element', name,
1183                            'returning!')
1184            return
1185
1186        name = spirv.elem.get('name')
1187        # No known alias for spirv elements
1188        alias = None
1189        if spirv.emit:
1190            genProc = self.gen.genSpirv
1191            genProc(spirv, name, alias)
1192
1193    def stripUnsupportedAPIs(self, dictionary, attribute, supportedDictionary):
1194        """Strip unsupported APIs from attributes of APIs.
1195           dictionary - *Info dictionary of APIs to be updated
1196           attribute - attribute name to look for in each API
1197           supportedDictionary - dictionary in which to look for supported
1198            API elements in the attribute"""
1199
1200        for key in dictionary:
1201            eleminfo = dictionary[key]
1202            attribstring = eleminfo.elem.get(attribute)
1203            if attribstring is not None:
1204                apis = []
1205                stripped = False
1206                for api in attribstring.split(','):
1207                    ##print('Checking API {} referenced by {}'.format(api, key))
1208                    if supportedDictionary[api].required:
1209                        apis.append(api)
1210                    else:
1211                        stripped = True
1212                        ##print('\t**STRIPPING API {} from {}'.format(api, key))
1213
1214                # Update the attribute after stripping stuff.
1215                # Could sort apis before joining, but it's not a clear win
1216                if stripped:
1217                    eleminfo.elem.set(attribute, ','.join(apis))
1218
1219    def apiGen(self):
1220        """Generate interface for specified versions using the current
1221        generator and generator options"""
1222
1223        self.gen.logMsg('diag', '*******************************************')
1224        self.gen.logMsg('diag', '  Registry.apiGen file:', self.genOpts.filename,
1225                        'api:', self.genOpts.apiname,
1226                        'profile:', self.genOpts.profile)
1227        self.gen.logMsg('diag', '*******************************************')
1228
1229        # Could reset required/declared flags for all features here.
1230        # This has been removed as never used. The initial motivation was
1231        # the idea of calling apiGen() repeatedly for different targets, but
1232        # this has never been done. The 20% or so build-time speedup that
1233        # might result is not worth the effort to make it actually work.
1234        #
1235        #@@ self.apiReset()
1236
1237        # Compile regexps used to select versions & extensions
1238        regVersions = re.compile(self.genOpts.versions)
1239        regEmitVersions = re.compile(self.genOpts.emitversions)
1240        regAddExtensions = re.compile(self.genOpts.addExtensions)
1241        regRemoveExtensions = re.compile(self.genOpts.removeExtensions)
1242        regEmitExtensions = re.compile(self.genOpts.emitExtensions)
1243        regEmitSpirv = re.compile(self.genOpts.emitSpirv)
1244
1245        # Get all matching API feature names & add to list of FeatureInfo
1246        # Note we used to select on feature version attributes, not names.
1247        features = []
1248        apiMatch = False
1249        for key in self.apidict:
1250            fi = self.apidict[key]
1251            api = fi.elem.get('api')
1252            if apiNameMatch(self.genOpts.apiname, api):
1253                apiMatch = True
1254                if regVersions.match(fi.name):
1255                    # Matches API & version #s being generated. Mark for
1256                    # emission and add to the features[] list .
1257                    # @@ Could use 'declared' instead of 'emit'?
1258                    fi.emit = (regEmitVersions.match(fi.name) is not None)
1259                    features.append(fi)
1260                    if not fi.emit:
1261                        self.gen.logMsg('diag', 'NOT tagging feature api =', api,
1262                                        'name =', fi.name, 'version =', fi.version,
1263                                        'for emission (does not match emitversions pattern)')
1264                    else:
1265                        self.gen.logMsg('diag', 'Including feature api =', api,
1266                                        'name =', fi.name, 'version =', fi.version,
1267                                        'for emission (matches emitversions pattern)')
1268                else:
1269                    self.gen.logMsg('diag', 'NOT including feature api =', api,
1270                                    'name =', fi.name, 'version =', fi.version,
1271                                    '(does not match requested versions)')
1272            else:
1273                self.gen.logMsg('diag', 'NOT including feature api =', api,
1274                                'name =', fi.name,
1275                                '(does not match requested API)')
1276        if not apiMatch:
1277            self.gen.logMsg('warn', 'No matching API versions found!')
1278
1279        # Get all matching extensions, in order by their extension number,
1280        # and add to the list of features.
1281        # Start with extensions tagged with 'api' pattern matching the API
1282        # being generated. Add extensions matching the pattern specified in
1283        # regExtensions, then remove extensions matching the pattern
1284        # specified in regRemoveExtensions
1285        for (extName, ei) in sorted(self.extdict.items(), key=lambda x: x[1].number if x[1].number is not None else '0'):
1286            extName = ei.name
1287            include = False
1288
1289            # Include extension if defaultExtensions is not None and is
1290            # exactly matched by the 'supported' attribute.
1291            if apiNameMatch(self.genOpts.defaultExtensions,
1292                            ei.elem.get('supported')):
1293                self.gen.logMsg('diag', 'Including extension',
1294                                extName, "(defaultExtensions matches the 'supported' attribute)")
1295                include = True
1296
1297            # Include additional extensions if the extension name matches
1298            # the regexp specified in the generator options. This allows
1299            # forcing extensions into an interface even if they're not
1300            # tagged appropriately in the registry.
1301            # However we still respect the 'supported' attribute.
1302            if regAddExtensions.match(extName) is not None:
1303                if not apiNameMatch(self.genOpts.apiname, ei.elem.get('supported')):
1304                    self.gen.logMsg('diag', 'NOT including extension',
1305                                    extName, '(matches explicitly requested, but does not match the \'supported\' attribute)')
1306                    include = False
1307                else:
1308                    self.gen.logMsg('diag', 'Including extension',
1309                                    extName, '(matches explicitly requested extensions to add)')
1310                    include = True
1311            # Remove extensions if the name matches the regexp specified
1312            # in generator options. This allows forcing removal of
1313            # extensions from an interface even if they're tagged that
1314            # way in the registry.
1315            if regRemoveExtensions.match(extName) is not None:
1316                self.gen.logMsg('diag', 'Removing extension',
1317                                extName, '(matches explicitly requested extensions to remove)')
1318                include = False
1319
1320            # If the extension is to be included, add it to the
1321            # extension features list.
1322            if include:
1323                ei.emit = (regEmitExtensions.match(extName) is not None)
1324                features.append(ei)
1325                if not ei.emit:
1326                    self.gen.logMsg('diag', 'NOT tagging extension',
1327                                    extName,
1328                                    'for emission (does not match emitextensions pattern)')
1329
1330                # Hack - can be removed when validity generator goes away
1331                # (Jon) I'm not sure what this does, or if it should respect
1332                # the ei.emit flag above.
1333                self.requiredextensions.append(extName)
1334            else:
1335                self.gen.logMsg('diag', 'NOT including extension',
1336                                extName, '(does not match api attribute or explicitly requested extensions)')
1337
1338        # Add all spirv elements to list
1339        # generators decide to emit them all or not
1340        # Currently no filtering as no client of these elements needs filtering
1341        spirvexts = []
1342        for key in self.spirvextdict:
1343            si = self.spirvextdict[key]
1344            si.emit = (regEmitSpirv.match(key) is not None)
1345            spirvexts.append(si)
1346        spirvcaps = []
1347        for key in self.spirvcapdict:
1348            si = self.spirvcapdict[key]
1349            si.emit = (regEmitSpirv.match(key) is not None)
1350            spirvcaps.append(si)
1351
1352        # Sort the features list, if a sort procedure is defined
1353        if self.genOpts.sortProcedure:
1354            self.genOpts.sortProcedure(features)
1355            # print('sortProcedure ->', [f.name for f in features])
1356
1357        # Pass 1: loop over requested API versions and extensions tagging
1358        #   types/commands/features as required (in an <require> block) or no
1359        #   longer required (in an <remove> block). It is possible to remove
1360        #   a feature in one version and restore it later by requiring it in
1361        #   a later version.
1362        # If a profile other than 'None' is being generated, it must
1363        #   match the profile attribute (if any) of the <require> and
1364        #   <remove> tags.
1365        self.gen.logMsg('diag', 'PASS 1: TAG FEATURES')
1366        for f in features:
1367            self.gen.logMsg('diag', 'PASS 1: Tagging required and removed features for',
1368                            f.name)
1369            self.fillFeatureDictionary(f.elem, f.name, self.genOpts.apiname, self.genOpts.profile)
1370            self.requireAndRemoveFeatures(f.elem, f.name, self.genOpts.apiname, self.genOpts.profile)
1371            self.assignAdditionalValidity(f.elem, self.genOpts.apiname, self.genOpts.profile)
1372
1373        # Now, strip references to APIs that are not required.
1374        # At present such references may occur in:
1375        #   Structs in <type category="struct"> 'structextends' attributes
1376        #   Enums in <command> 'successcodes' and 'errorcodes' attributes
1377        self.stripUnsupportedAPIs(self.typedict, 'structextends', self.typedict)
1378        self.stripUnsupportedAPIs(self.cmddict, 'successcodes', self.enumdict)
1379        self.stripUnsupportedAPIs(self.cmddict, 'errorcodes', self.enumdict)
1380
1381        # @@May need to strip <spirvcapability> / <spirvextension> <enable>
1382        # tags of these forms:
1383        #   <enable version="VK_API_VERSION_1_0"/>
1384        #   <enable struct="VkPhysicalDeviceFeatures" feature="geometryShader" requires="VK_VERSION_1_0"/>
1385        #   <enable extension="VK_KHR_shader_draw_parameters"/>
1386        #   <enable property="VkPhysicalDeviceVulkan12Properties" member="shaderDenormPreserveFloat16" value="VK_TRUE" requires="VK_VERSION_1_2,VK_KHR_shader_float_controls"/>
1387
1388        # Pass 2: loop over specified API versions and extensions printing
1389        #   declarations for required things which haven't already been
1390        #   generated.
1391        self.gen.logMsg('diag', 'PASS 2: GENERATE INTERFACES FOR FEATURES')
1392        self.gen.beginFile(self.genOpts)
1393        for f in features:
1394            self.gen.logMsg('diag', 'PASS 2: Generating interface for',
1395                            f.name)
1396            emit = self.emitFeatures = f.emit
1397            if not emit:
1398                self.gen.logMsg('diag', 'PASS 2: NOT declaring feature',
1399                                f.elem.get('name'), 'because it is not tagged for emission')
1400            # Generate the interface (or just tag its elements as having been
1401            # emitted, if they haven't been).
1402            self.gen.beginFeature(f.elem, emit)
1403            self.generateRequiredInterface(f.elem)
1404            self.gen.endFeature()
1405        # Generate spirv elements
1406        for s in spirvexts:
1407            self.generateSpirv(s, self.spirvextdict)
1408        for s in spirvcaps:
1409            self.generateSpirv(s, self.spirvcapdict)
1410        self.gen.endFile()
1411
1412    def apiReset(self):
1413        """Reset type/enum/command dictionaries before generating another API.
1414
1415        Use between apiGen() calls to reset internal state."""
1416        for datatype in self.typedict:
1417            self.typedict[datatype].resetState()
1418        for enum in self.enumdict:
1419            self.enumdict[enum].resetState()
1420        for cmd in self.cmddict:
1421            self.cmddict[cmd].resetState()
1422        for cmd in self.apidict:
1423            self.apidict[cmd].resetState()
1424
1425    def __validateStructLimittypes(self, struct):
1426        """Validate 'limittype' attributes for a single struct."""
1427        limittypeDiags = namedtuple('limittypeDiags', ['missing', 'invalid'])
1428        badFields = defaultdict(lambda : limittypeDiags(missing=[], invalid=[]))
1429        validLimittypes = { 'min', 'max', 'bitmask', 'range', 'struct', 'noauto' }
1430        for member in struct.getMembers():
1431            memberName = member.findtext('name')
1432            if memberName in ['sType', 'pNext']:
1433                continue
1434            limittype = member.get('limittype')
1435            if not limittype:
1436                badFields[struct.elem.get('name')].missing.append(memberName)
1437            elif limittype == 'struct':
1438                typeName = member.findtext('type')
1439                memberType = self.typedict[typeName]
1440                badFields.update(self.__validateStructLimittypes(memberType))
1441            elif limittype not in validLimittypes:
1442                badFields[struct.elem.get('name')].invalid.append(memberName)
1443        return badFields
1444
1445    def __validateLimittype(self):
1446        """Validate 'limittype' attributes."""
1447        self.gen.logMsg('diag', 'VALIDATING LIMITTYPE ATTRIBUTES')
1448        badFields = self.__validateStructLimittypes(self.typedict['VkPhysicalDeviceProperties2'])
1449        for featStructName in self.validextensionstructs['VkPhysicalDeviceProperties2']:
1450            featStruct = self.typedict[featStructName]
1451            badFields.update(self.__validateStructLimittypes(featStruct))
1452
1453        if badFields:
1454            self.gen.logMsg('diag', 'SUMMARY OF FIELDS WITH INCORRECT LIMITTYPES')
1455            for key in sorted(badFields.keys()):
1456                diags = badFields[key]
1457                if diags.missing:
1458                    self.gen.logMsg('diag', '    ', key, 'missing limittype:', ', '.join(badFields[key].missing))
1459                if diags.invalid:
1460                    self.gen.logMsg('diag', '    ', key, 'invalid limittype:', ', '.join(badFields[key].invalid))
1461            return False
1462        return True
1463
1464    def validateRegistry(self):
1465        """Validate properties of the registry."""
1466        return self.__validateLimittype()
1467