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