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