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