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