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