1#!/usr/bin/python3 -i 2# 3# Copyright (c) 2013-2015 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 17import io,os,re,string,sys 18import xml.etree.ElementTree as etree 19 20# matchAPIProfile - returns whether an API and profile 21# being generated matches an element's profile 22# api - string naming the API to match 23# profile - string naming the profile to match 24# elem - Element which (may) have 'api' and 'profile' 25# attributes to match to. 26# If a tag is not present in the Element, the corresponding API 27# or profile always matches. 28# Otherwise, the tag must exactly match the API or profile. 29# Thus, if 'profile' = core: 30# <remove> with no attribute will match 31# <remove profile='core'> will match 32# <remove profile='compatibility'> will not match 33# Possible match conditions: 34# Requested Element 35# Profile Profile 36# --------- -------- 37# None None Always matches 38# 'string' None Always matches 39# None 'string' Does not match. Can't generate multiple APIs 40# or profiles, so if an API/profile constraint 41# is present, it must be asked for explicitly. 42# 'string' 'string' Strings must match 43# 44# ** In the future, we will allow regexes for the attributes, 45# not just strings, so that api="^(gl|gles2)" will match. Even 46# this isn't really quite enough, we might prefer something 47# like "gl(core)|gles1(common-lite)". 48def matchAPIProfile(api, profile, elem): 49 """Match a requested API & profile name to a api & profile attributes of an Element""" 50 match = True 51 # Match 'api', if present 52 if ('api' in elem.attrib): 53 if (api == None): 54 raise UserWarning("No API requested, but 'api' attribute is present with value '" + 55 elem.get('api') + "'") 56 elif (api != elem.get('api')): 57 # Requested API doesn't match attribute 58 return False 59 if ('profile' in elem.attrib): 60 if (profile == None): 61 raise UserWarning("No profile requested, but 'profile' attribute is present with value '" + 62 elem.get('profile') + "'") 63 elif (profile != elem.get('profile')): 64 # Requested profile doesn't match attribute 65 return False 66 return True 67 68# BaseInfo - base class for information about a registry feature 69# (type/group/enum/command/API/extension). 70# required - should this feature be defined during header generation 71# (has it been removed by a profile or version)? 72# declared - has this feature been defined already? 73# elem - etree Element for this feature 74# resetState() - reset required/declared to initial values. Used 75# prior to generating a new API interface. 76class BaseInfo: 77 """Represents the state of a registry feature, used during API generation""" 78 def __init__(self, elem): 79 self.required = False 80 self.declared = False 81 self.elem = elem 82 def resetState(self): 83 self.required = False 84 self.declared = False 85 86# TypeInfo - registry information about a type. No additional state 87# beyond BaseInfo is required. 88class TypeInfo(BaseInfo): 89 """Represents the state of a registry type""" 90 def __init__(self, elem): 91 BaseInfo.__init__(self, elem) 92 93# GroupInfo - registry information about a group of related enums 94# in an <enums> block, generally corresponding to a C "enum" type. 95class GroupInfo(BaseInfo): 96 """Represents the state of a registry <enums> group""" 97 def __init__(self, elem): 98 BaseInfo.__init__(self, elem) 99 100# EnumInfo - registry information about an enum 101# type - numeric type of the value of the <enum> tag 102# ( '' for GLint, 'u' for GLuint, 'ull' for GLuint64 ) 103class EnumInfo(BaseInfo): 104 """Represents the state of a registry enum""" 105 def __init__(self, elem): 106 BaseInfo.__init__(self, elem) 107 self.type = elem.get('type') 108 if (self.type == None): 109 self.type = '' 110 111# CmdInfo - registry information about a command 112class CmdInfo(BaseInfo): 113 """Represents the state of a registry command""" 114 def __init__(self, elem): 115 BaseInfo.__init__(self, elem) 116 117# FeatureInfo - registry information about an API <feature> 118# or <extension> 119# name - feature name string (e.g. 'vk_ext_khr_surface') 120# version - feature version number (e.g. 1.2). <extension> 121# features are unversioned and assigned version number 0. 122# ** This is confusingly taken from the 'number' attribute of <feature>. 123# Needs fixing. 124# number - extension number, used for ordering and for 125# assigning enumerant offsets. <feature> features do 126# not have extension numbers and are assigned number 0. 127# category - category, e.g. VERSION or khr/vendor tag 128# emit - has this feature been defined already? 129class FeatureInfo(BaseInfo): 130 """Represents the state of an API feature (version/extension)""" 131 def __init__(self, elem): 132 BaseInfo.__init__(self, elem) 133 self.name = elem.get('name') 134 # Determine element category (vendor). Only works 135 # for <extension> elements. 136 if (elem.tag == 'feature'): 137 self.category = 'VERSION' 138 self.version = elem.get('number') 139 self.number = "0" 140 else: 141 self.category = self.name.split('_', 2)[1] 142 self.version = "0" 143 self.number = elem.get('number') 144 self.emit = False 145 146from generator import write, GeneratorOptions, OutputGenerator 147 148# Registry - object representing an API registry, loaded from an XML file 149# Members 150# tree - ElementTree containing the root <registry> 151# typedict - dictionary of TypeInfo objects keyed by type name 152# groupdict - dictionary of GroupInfo objects keyed by group name 153# enumdict - dictionary of EnumInfo objects keyed by enum name 154# cmddict - dictionary of CmdInfo objects keyed by command name 155# apidict - dictionary of <api> Elements keyed by API name 156# extensions - list of <extension> Elements 157# extdict - dictionary of <extension> Elements keyed by extension name 158# gen - OutputGenerator object used to write headers / messages 159# genOpts - GeneratorOptions object used to control which 160# fetures to write and how to format them 161# emitFeatures - True to actually emit features for a version / extension, 162# or False to just treat them as emitted 163# Public methods 164# loadElementTree(etree) - load registry from specified ElementTree 165# loadFile(filename) - load registry from XML file 166# setGenerator(gen) - OutputGenerator to use 167# parseTree() - parse the registry once loaded & create dictionaries 168# dumpReg(maxlen, filehandle) - diagnostic to dump the dictionaries 169# to specified file handle (default stdout). Truncates type / 170# enum / command elements to maxlen characters (default 80) 171# generator(g) - specify the output generator object 172# apiGen(apiname, genOpts) - generate API headers for the API type 173# and profile specified in genOpts, but only for the versions and 174# extensions specified there. 175# apiReset() - call between calls to apiGen() to reset internal state 176# Private methods 177# addElementInfo(elem,info,infoName,dictionary) - add feature info to dict 178# lookupElementInfo(fname,dictionary) - lookup feature info in dict 179class Registry: 180 """Represents an API registry loaded from XML""" 181 def __init__(self): 182 self.tree = None 183 self.typedict = {} 184 self.groupdict = {} 185 self.enumdict = {} 186 self.cmddict = {} 187 self.apidict = {} 188 self.extensions = [] 189 self.extdict = {} 190 # A default output generator, so commands prior to apiGen can report 191 # errors via the generator object. 192 self.gen = OutputGenerator() 193 self.genOpts = None 194 self.emitFeatures = False 195 def loadElementTree(self, tree): 196 """Load ElementTree into a Registry object and parse it""" 197 self.tree = tree 198 self.parseTree() 199 def loadFile(self, file): 200 """Load an API registry XML file into a Registry object and parse it""" 201 self.tree = etree.parse(file) 202 self.parseTree() 203 def setGenerator(self, gen): 204 """Specify output generator object. None restores the default generator""" 205 self.gen = gen 206 self.gen.setRegistry(self.tree) 207 208 # addElementInfo - add information about an element to the 209 # corresponding dictionary 210 # elem - <type>/<enums>/<enum>/<command>/<feature>/<extension> Element 211 # info - corresponding {Type|Group|Enum|Cmd|Feature}Info object 212 # infoName - 'type' / 'group' / 'enum' / 'command' / 'feature' / 'extension' 213 # dictionary - self.{type|group|enum|cmd|api|ext}dict 214 # If the Element has an 'api' attribute, the dictionary key is the 215 # tuple (name,api). If not, the key is the name. 'name' is an 216 # attribute of the Element 217 def addElementInfo(self, elem, info, infoName, dictionary): 218 if ('api' in elem.attrib): 219 key = (elem.get('name'),elem.get('api')) 220 else: 221 key = elem.get('name') 222 if key in dictionary: 223 self.gen.logMsg('warn', '*** Attempt to redefine', 224 infoName, 'with key:', key) 225 else: 226 dictionary[key] = info 227 # 228 # lookupElementInfo - find a {Type|Enum|Cmd}Info object by name. 229 # If an object qualified by API name exists, use that. 230 # fname - name of type / enum / command 231 # dictionary - self.{type|enum|cmd}dict 232 def lookupElementInfo(self, fname, dictionary): 233 key = (fname, self.genOpts.apiname) 234 if (key in dictionary): 235 # self.gen.logMsg('diag', 'Found API-specific element for feature', fname) 236 return dictionary[key] 237 elif (fname in dictionary): 238 # self.gen.logMsg('diag', 'Found generic element for feature', fname) 239 return dictionary[fname] 240 else: 241 return None 242 def parseTree(self): 243 """Parse the registry Element, once created""" 244 # This must be the Element for the root <registry> 245 self.reg = self.tree.getroot() 246 # 247 # Create dictionary of registry types from toplevel <types> tags 248 # and add 'name' attribute to each <type> tag (where missing) 249 # based on its <name> element. 250 # 251 # There's usually one <types> block; more are OK 252 # Required <type> attributes: 'name' or nested <name> tag contents 253 self.typedict = {} 254 for type in self.reg.findall('types/type'): 255 # If the <type> doesn't already have a 'name' attribute, set 256 # it from contents of its <name> tag. 257 if (type.get('name') == None): 258 type.attrib['name'] = type.find('name').text 259 self.addElementInfo(type, TypeInfo(type), 'type', self.typedict) 260 # 261 # Create dictionary of registry enum groups from <enums> tags. 262 # 263 # Required <enums> attributes: 'name'. If no name is given, one is 264 # generated, but that group can't be identified and turned into an 265 # enum type definition - it's just a container for <enum> tags. 266 self.groupdict = {} 267 for group in self.reg.findall('enums'): 268 self.addElementInfo(group, GroupInfo(group), 'group', self.groupdict) 269 # 270 # Create dictionary of registry enums from <enum> tags 271 # 272 # <enums> tags usually define different namespaces for the values 273 # defined in those tags, but the actual names all share the 274 # same dictionary. 275 # Required <enum> attributes: 'name', 'value' 276 # For containing <enums> which have type="enum" or type="bitmask", 277 # tag all contained <enum>s are required. This is a stopgap until 278 # a better scheme for tagging core and extension enums is created. 279 self.enumdict = {} 280 for enums in self.reg.findall('enums'): 281 required = (enums.get('type') != None) 282 for enum in enums.findall('enum'): 283 enumInfo = EnumInfo(enum) 284 enumInfo.required = required 285 self.addElementInfo(enum, enumInfo, 'enum', self.enumdict) 286 # 287 # Create dictionary of registry commands from <command> tags 288 # and add 'name' attribute to each <command> tag (where missing) 289 # based on its <proto><name> element. 290 # 291 # There's usually only one <commands> block; more are OK. 292 # Required <command> attributes: 'name' or <proto><name> tag contents 293 self.cmddict = {} 294 for cmd in self.reg.findall('commands/command'): 295 # If the <command> doesn't already have a 'name' attribute, set 296 # it from contents of its <proto><name> tag. 297 if (cmd.get('name') == None): 298 cmd.attrib['name'] = cmd.find('proto/name').text 299 ci = CmdInfo(cmd) 300 self.addElementInfo(cmd, ci, 'command', self.cmddict) 301 # 302 # Create dictionaries of API and extension interfaces 303 # from toplevel <api> and <extension> tags. 304 # 305 self.apidict = {} 306 for feature in self.reg.findall('feature'): 307 featureInfo = FeatureInfo(feature) 308 self.addElementInfo(feature, featureInfo, 'feature', self.apidict) 309 self.extensions = self.reg.findall('extensions/extension') 310 self.extdict = {} 311 for feature in self.extensions: 312 featureInfo = FeatureInfo(feature) 313 self.addElementInfo(feature, featureInfo, 'extension', self.extdict) 314 315 # Add additional enums defined only in <extension> tags 316 # to the corresponding core type. 317 # When seen here, a copy, processed to contain the numeric enum 318 # value, is added to the corresponding <enums> element, as well 319 # as adding to the enum dictionary. Also add a 'extnumber' 320 # attribute containing the extension number. 321 # 322 # For <enum> tags which are actually just constants, if there's 323 # no 'extends' tag but there is a 'value' or 'bitpos' tag, just 324 # add an EnumInfo record to the dictionary. That works because 325 # output generation of constants is purely dependency-based, and 326 # doesn't need to iterate through the XML tags. 327 # 328 # Something like this will need to be done for 'feature's up 329 # above, if we use the same mechanism for adding to the core 330 # API in 1.1. 331 for enum in feature.findall('require/enum'): 332 addEnumInfo = False 333 groupName = enum.get('extends') 334 if (groupName != None): 335 # self.gen.logMsg('diag', '*** Found extension enum', 336 # enum.get('name')) 337 # Add extension number attribute to the <enum> element 338 enum.attrib['extnumber'] = featureInfo.number 339 # Look up the GroupInfo with matching groupName 340 if (groupName in self.groupdict.keys()): 341 # self.gen.logMsg('diag', '*** Matching group', 342 # groupName, 'found, adding element...') 343 gi = self.groupdict[groupName] 344 gi.elem.append(enum) 345 else: 346 self.gen.logMsg('warn', '*** NO matching group', 347 groupName, 'for enum', enum.get('name'), 'found.') 348 addEnumInfo = True 349 elif (enum.get('value') or enum.get('bitpos')): 350 # self.gen.logMsg('diag', '*** Adding extension constant "enum"', 351 # enum.get('name')) 352 addEnumInfo = True 353 if (addEnumInfo): 354 enumInfo = EnumInfo(enum) 355 self.addElementInfo(enum, enumInfo, 'enum', self.enumdict) 356 def dumpReg(self, maxlen = 40, filehandle = sys.stdout): 357 """Dump all the dictionaries constructed from the Registry object""" 358 write('***************************************', file=filehandle) 359 write(' ** Dumping Registry contents **', file=filehandle) 360 write('***************************************', file=filehandle) 361 write('// Types', file=filehandle) 362 for name in self.typedict: 363 tobj = self.typedict[name] 364 write(' Type', name, '->', etree.tostring(tobj.elem)[0:maxlen], file=filehandle) 365 write('// Groups', file=filehandle) 366 for name in self.groupdict: 367 gobj = self.groupdict[name] 368 write(' Group', name, '->', etree.tostring(gobj.elem)[0:maxlen], file=filehandle) 369 write('// Enums', file=filehandle) 370 for name in self.enumdict: 371 eobj = self.enumdict[name] 372 write(' Enum', name, '->', etree.tostring(eobj.elem)[0:maxlen], file=filehandle) 373 write('// Commands', file=filehandle) 374 for name in self.cmddict: 375 cobj = self.cmddict[name] 376 write(' Command', name, '->', etree.tostring(cobj.elem)[0:maxlen], file=filehandle) 377 write('// APIs', file=filehandle) 378 for key in self.apidict: 379 write(' API Version ', key, '->', 380 etree.tostring(self.apidict[key].elem)[0:maxlen], file=filehandle) 381 write('// Extensions', file=filehandle) 382 for key in self.extdict: 383 write(' Extension', key, '->', 384 etree.tostring(self.extdict[key].elem)[0:maxlen], file=filehandle) 385 # write('***************************************', file=filehandle) 386 # write(' ** Dumping XML ElementTree **', file=filehandle) 387 # write('***************************************', file=filehandle) 388 # write(etree.tostring(self.tree.getroot(),pretty_print=True), file=filehandle) 389 # 390 # typename - name of type 391 # required - boolean (to tag features as required or not) 392 def markTypeRequired(self, typename, required): 393 """Require (along with its dependencies) or remove (but not its dependencies) a type""" 394 self.gen.logMsg('diag', '*** tagging type:', typename, '-> required =', required) 395 # Get TypeInfo object for <type> tag corresponding to typename 396 type = self.lookupElementInfo(typename, self.typedict) 397 if (type != None): 398 if (required): 399 # Tag type dependencies in 'required' attributes as 400 # required. This DOES NOT un-tag dependencies in a <remove> 401 # tag. See comments in markRequired() below for the reason. 402 if ('requires' in type.elem.attrib): 403 depType = type.elem.get('requires') 404 self.gen.logMsg('diag', '*** Generating dependent type', 405 depType, 'for type', typename) 406 self.markTypeRequired(depType, required) 407 # Tag types used in defining this type (e.g. in nested 408 # <type> tags) 409 # Look for <type> in entire <command> tree, 410 # not just immediate children 411 for subtype in type.elem.findall('.//type'): 412 self.gen.logMsg('diag', '*** markRequired: type requires dependent <type>', subtype.text) 413 self.markTypeRequired(subtype.text, required) 414 # Tag enums used in defining this type, for example in 415 # <member><name>member</name>[<enum>MEMBER_SIZE</enum>]</member> 416 for subenum in type.elem.findall('.//enum'): 417 self.gen.logMsg('diag', '*** markRequired: type requires dependent <enum>', subenum.text) 418 self.markEnumRequired(subenum.text, required) 419 type.required = required 420 else: 421 self.gen.logMsg('warn', '*** type:', typename , 'IS NOT DEFINED') 422 # 423 # enumname - name of enum 424 # required - boolean (to tag features as required or not) 425 def markEnumRequired(self, enumname, required): 426 self.gen.logMsg('diag', '*** tagging enum:', enumname, '-> required =', required) 427 enum = self.lookupElementInfo(enumname, self.enumdict) 428 if (enum != None): 429 enum.required = required 430 else: 431 self.gen.logMsg('warn', '*** enum:', enumname , 'IS NOT DEFINED') 432 # 433 # features - Element for <require> or <remove> tag 434 # required - boolean (to tag features as required or not) 435 def markRequired(self, features, required): 436 """Require or remove features specified in the Element""" 437 self.gen.logMsg('diag', '*** markRequired (features = <too long to print>, required =', required, ')') 438 # Loop over types, enums, and commands in the tag 439 # @@ It would be possible to respect 'api' and 'profile' attributes 440 # in individual features, but that's not done yet. 441 for typeElem in features.findall('type'): 442 self.markTypeRequired(typeElem.get('name'), required) 443 for enumElem in features.findall('enum'): 444 self.markEnumRequired(enumElem.get('name'), required) 445 for cmdElem in features.findall('command'): 446 name = cmdElem.get('name') 447 self.gen.logMsg('diag', '*** tagging command:', name, '-> required =', required) 448 cmd = self.lookupElementInfo(name, self.cmddict) 449 if (cmd != None): 450 cmd.required = required 451 # Tag all parameter types of this command as required. 452 # This DOES NOT remove types of commands in a <remove> 453 # tag, because many other commands may use the same type. 454 # We could be more clever and reference count types, 455 # instead of using a boolean. 456 if (required): 457 # Look for <type> in entire <command> tree, 458 # not just immediate children 459 for type in cmd.elem.findall('.//type'): 460 self.gen.logMsg('diag', '*** markRequired: command implicitly requires dependent type', type.text) 461 self.markTypeRequired(type.text, required) 462 else: 463 self.gen.logMsg('warn', '*** command:', name, 'IS NOT DEFINED') 464 # 465 # interface - Element for <version> or <extension>, containing 466 # <require> and <remove> tags 467 # api - string specifying API name being generated 468 # profile - string specifying API profile being generated 469 def requireAndRemoveFeatures(self, interface, api, profile): 470 """Process <recquire> and <remove> tags for a <version> or <extension>""" 471 # <require> marks things that are required by this version/profile 472 for feature in interface.findall('require'): 473 if (matchAPIProfile(api, profile, feature)): 474 self.markRequired(feature,True) 475 # <remove> marks things that are removed by this version/profile 476 for feature in interface.findall('remove'): 477 if (matchAPIProfile(api, profile, feature)): 478 self.markRequired(feature,False) 479 # 480 # generateFeature - generate a single type / enum group / enum / command, 481 # and all its dependencies as needed. 482 # fname - name of feature (<type>/<enum>/<command>) 483 # ftype - type of feature, 'type' | 'enum' | 'command' 484 # dictionary - of *Info objects - self.{type|enum|cmd}dict 485 def generateFeature(self, fname, ftype, dictionary): 486 f = self.lookupElementInfo(fname, dictionary) 487 if (f == None): 488 # No such feature. This is an error, but reported earlier 489 self.gen.logMsg('diag', '*** No entry found for feature', fname, 490 'returning!') 491 return 492 # 493 # If feature isn't required, or has already been declared, return 494 if (not f.required): 495 self.gen.logMsg('diag', '*** Skipping', ftype, fname, '(not required)') 496 return 497 if (f.declared): 498 self.gen.logMsg('diag', '*** Skipping', ftype, fname, '(already declared)') 499 return 500 # Always mark feature declared, as though actually emitted 501 f.declared = True 502 # 503 # Pull in dependent declaration(s) of the feature. 504 # For types, there may be one type in the 'required' attribute of 505 # the element, as well as many in imbedded <type> and <enum> tags 506 # within the element. 507 # For commands, there may be many in <type> tags within the element. 508 # For enums, no dependencies are allowed (though perhaps if you 509 # have a uint64 enum, it should require GLuint64). 510 genProc = None 511 if (ftype == 'type'): 512 genProc = self.gen.genType 513 if ('requires' in f.elem.attrib): 514 depname = f.elem.get('requires') 515 self.gen.logMsg('diag', '*** Generating required dependent type', 516 depname) 517 self.generateFeature(depname, 'type', self.typedict) 518 for subtype in f.elem.findall('.//type'): 519 self.gen.logMsg('diag', '*** Generating required dependent <type>', 520 subtype.text) 521 self.generateFeature(subtype.text, 'type', self.typedict) 522 for subtype in f.elem.findall('.//enum'): 523 self.gen.logMsg('diag', '*** Generating required dependent <enum>', 524 subtype.text) 525 self.generateFeature(subtype.text, 'enum', self.enumdict) 526 # If the type is an enum group, look up the corresponding 527 # group in the group dictionary and generate that instead. 528 if (f.elem.get('category') == 'enum'): 529 self.gen.logMsg('diag', '*** Type', fname, 'is an enum group, so generate that instead') 530 group = self.lookupElementInfo(fname, self.groupdict) 531 if (group == None): 532 # Unless this is tested for, it's probably fatal to call below 533 genProc = None 534 self.logMsg('warn', '*** NO MATCHING ENUM GROUP FOUND!!!') 535 else: 536 genProc = self.gen.genGroup 537 f = group 538 elif (ftype == 'command'): 539 genProc = self.gen.genCmd 540 for type in f.elem.findall('.//type'): 541 depname = type.text 542 self.gen.logMsg('diag', '*** Generating required parameter type', 543 depname) 544 self.generateFeature(depname, 'type', self.typedict) 545 elif (ftype == 'enum'): 546 genProc = self.gen.genEnum 547 # Actually generate the type only if emitting declarations 548 if self.emitFeatures: 549 self.gen.logMsg('diag', '*** Emitting', ftype, 'decl for', fname) 550 genProc(f, fname) 551 else: 552 self.gen.logMsg('diag', '*** Skipping', ftype, fname, 553 '(not emitting this feature)') 554 # 555 # generateRequiredInterface - generate all interfaces required 556 # by an API version or extension 557 # interface - Element for <version> or <extension> 558 def generateRequiredInterface(self, interface): 559 """Generate required C interface for specified API version/extension""" 560 # 561 # Loop over all features inside all <require> tags. 562 # <remove> tags are ignored (handled in pass 1). 563 for features in interface.findall('require'): 564 for t in features.findall('type'): 565 self.generateFeature(t.get('name'), 'type', self.typedict) 566 for e in features.findall('enum'): 567 self.generateFeature(e.get('name'), 'enum', self.enumdict) 568 for c in features.findall('command'): 569 self.generateFeature(c.get('name'), 'command', self.cmddict) 570 # 571 # apiGen(genOpts) - generate interface for specified versions 572 # genOpts - GeneratorOptions object with parameters used 573 # by the Generator object. 574 def apiGen(self, genOpts): 575 """Generate interfaces for the specified API type and range of versions""" 576 # 577 self.gen.logMsg('diag', '*******************************************') 578 self.gen.logMsg('diag', ' Registry.apiGen file:', genOpts.filename, 579 'api:', genOpts.apiname, 580 'profile:', genOpts.profile) 581 self.gen.logMsg('diag', '*******************************************') 582 # 583 self.genOpts = genOpts 584 # Reset required/declared flags for all features 585 self.apiReset() 586 # 587 # Compile regexps used to select versions & extensions 588 regVersions = re.compile(self.genOpts.versions) 589 regEmitVersions = re.compile(self.genOpts.emitversions) 590 regAddExtensions = re.compile(self.genOpts.addExtensions) 591 regRemoveExtensions = re.compile(self.genOpts.removeExtensions) 592 # 593 # Get all matching API versions & add to list of FeatureInfo 594 features = [] 595 apiMatch = False 596 for key in self.apidict: 597 fi = self.apidict[key] 598 api = fi.elem.get('api') 599 if (api == self.genOpts.apiname): 600 apiMatch = True 601 if (regVersions.match(fi.version)): 602 # Matches API & version #s being generated. Mark for 603 # emission and add to the features[] list . 604 # @@ Could use 'declared' instead of 'emit'? 605 fi.emit = (regEmitVersions.match(fi.version) != None) 606 features.append(fi) 607 if (not fi.emit): 608 self.gen.logMsg('diag', '*** NOT tagging feature api =', api, 609 'name =', fi.name, 'version =', fi.version, 610 'for emission (does not match emitversions pattern)') 611 else: 612 self.gen.logMsg('diag', '*** NOT including feature api =', api, 613 'name =', fi.name, 'version =', fi.version, 614 '(does not match requested versions)') 615 else: 616 self.gen.logMsg('diag', '*** NOT including feature api =', api, 617 'name =', fi.name, 618 '(does not match requested API)') 619 if (not apiMatch): 620 self.gen.logMsg('warn', '*** No matching API versions found!') 621 # 622 # Get all matching extensions, in order by their extension number, 623 # and add to the list of features. 624 # Start with extensions tagged with 'api' pattern matching the API 625 # being generated. Add extensions matching the pattern specified in 626 # regExtensions, then remove extensions matching the pattern 627 # specified in regRemoveExtensions 628 for (extName,ei) in sorted(self.extdict.items(),key = lambda x : x[1].number): 629 extName = ei.name 630 include = False 631 # 632 # Include extension if defaultExtensions is not None and if the 633 # 'supported' attribute matches defaultExtensions. The regexp in 634 # 'supported' must exactly match defaultExtensions, so bracket 635 # it with ^(pat)$. 636 pat = '^(' + ei.elem.get('supported') + ')$' 637 if (self.genOpts.defaultExtensions and 638 re.match(pat, self.genOpts.defaultExtensions)): 639 self.gen.logMsg('diag', '*** Including extension', 640 extName, "(defaultExtensions matches the 'supported' attribute)") 641 include = True 642 # 643 # Include additional extensions if the extension name matches 644 # the regexp specified in the generator options. This allows 645 # forcing extensions into an interface even if they're not 646 # tagged appropriately in the registry. 647 if (regAddExtensions.match(extName) != None): 648 self.gen.logMsg('diag', '*** Including extension', 649 extName, '(matches explicitly requested extensions to add)') 650 include = True 651 # Remove extensions if the name matches the regexp specified 652 # in generator options. This allows forcing removal of 653 # extensions from an interface even if they're tagged that 654 # way in the registry. 655 if (regRemoveExtensions.match(extName) != None): 656 self.gen.logMsg('diag', '*** Removing extension', 657 extName, '(matches explicitly requested extensions to remove)') 658 include = False 659 # 660 # If the extension is to be included, add it to the 661 # extension features list. 662 if (include): 663 ei.emit = True 664 features.append(ei) 665 else: 666 self.gen.logMsg('diag', '*** NOT including extension', 667 extName, '(does not match api attribute or explicitly requested extensions)') 668 # 669 # Sort the extension features list, if a sort procedure is defined 670 if (self.genOpts.sortProcedure): 671 self.genOpts.sortProcedure(features) 672 # 673 # Pass 1: loop over requested API versions and extensions tagging 674 # types/commands/features as required (in an <require> block) or no 675 # longer required (in an <remove> block). It is possible to remove 676 # a feature in one version and restore it later by requiring it in 677 # a later version. 678 # If a profile other than 'None' is being generated, it must 679 # match the profile attribute (if any) of the <require> and 680 # <remove> tags. 681 self.gen.logMsg('diag', '*** PASS 1: TAG FEATURES ********************************************') 682 for f in features: 683 self.gen.logMsg('diag', '*** PASS 1: Tagging required and removed features for', 684 f.name) 685 self.requireAndRemoveFeatures(f.elem, self.genOpts.apiname, self.genOpts.profile) 686 # 687 # Pass 2: loop over specified API versions and extensions printing 688 # declarations for required things which haven't already been 689 # generated. 690 self.gen.logMsg('diag', '*** PASS 2: GENERATE INTERFACES FOR FEATURES ************************') 691 self.gen.beginFile(self.genOpts) 692 for f in features: 693 self.gen.logMsg('diag', '*** PASS 2: Generating interface for', 694 f.name) 695 emit = self.emitFeatures = f.emit 696 if (not emit): 697 self.gen.logMsg('diag', '*** PASS 2: NOT declaring feature', 698 f.elem.get('name'), 'because it is not tagged for emission') 699 # Generate the interface (or just tag its elements as having been 700 # emitted, if they haven't been). 701 self.gen.beginFeature(f.elem, emit) 702 self.generateRequiredInterface(f.elem) 703 self.gen.endFeature() 704 self.gen.endFile() 705 # 706 # apiReset - use between apiGen() calls to reset internal state 707 # 708 def apiReset(self): 709 """Reset type/enum/command dictionaries before generating another API""" 710 for type in self.typedict: 711 self.typedict[type].resetState() 712 for enum in self.enumdict: 713 self.enumdict[enum].resetState() 714 for cmd in self.cmddict: 715 self.cmddict[cmd].resetState() 716 for cmd in self.apidict: 717 self.apidict[cmd].resetState() 718 # 719 # validateGroups - check that group= attributes match actual groups 720 # 721 def validateGroups(self): 722 """Validate group= attributes on <param> and <proto> tags""" 723 # Keep track of group names not in <group> tags 724 badGroup = {} 725 self.gen.logMsg('diag', '*** VALIDATING GROUP ATTRIBUTES ***') 726 for cmd in self.reg.findall('commands/command'): 727 proto = cmd.find('proto') 728 funcname = cmd.find('proto/name').text 729 if ('group' in proto.attrib.keys()): 730 group = proto.get('group') 731 # self.gen.logMsg('diag', '*** Command ', funcname, ' has return group ', group) 732 if (group not in self.groupdict.keys()): 733 # self.gen.logMsg('diag', '*** Command ', funcname, ' has UNKNOWN return group ', group) 734 if (group not in badGroup.keys()): 735 badGroup[group] = 1 736 else: 737 badGroup[group] = badGroup[group] + 1 738 for param in cmd.findall('param'): 739 pname = param.find('name') 740 if (pname != None): 741 pname = pname.text 742 else: 743 pname = type.get('name') 744 if ('group' in param.attrib.keys()): 745 group = param.get('group') 746 if (group not in self.groupdict.keys()): 747 # self.gen.logMsg('diag', '*** Command ', funcname, ' param ', pname, ' has UNKNOWN group ', group) 748 if (group not in badGroup.keys()): 749 badGroup[group] = 1 750 else: 751 badGroup[group] = badGroup[group] + 1 752 if (len(badGroup.keys()) > 0): 753 self.gen.logMsg('diag', '*** SUMMARY OF UNRECOGNIZED GROUPS ***') 754 for key in sorted(badGroup.keys()): 755 self.gen.logMsg('diag', ' ', key, ' occurred ', badGroup[key], ' times') 756