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