1#!/usr/bin/python3 -i 2# 3# Copyright 2013-2020 The Khronos Group Inc. 4# SPDX-License-Identifier: Apache-2.0 5 6import io,os,re,string,sys 7from lxml import etree 8 9def write(*args, **kwargs): 10 file = kwargs.pop('file', sys.stdout) 11 end = kwargs.pop('end', '\n') 12 file.write(' '.join([str(arg) for arg in args])) 13 file.write(end) 14 15# noneStr - returns string argument, or "" if argument is None. 16# Used in converting lxml Elements into text. 17# str - string to convert 18def noneStr(str): 19 if (str): 20 return str 21 else: 22 return "" 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)". 52def matchAPIProfile(api, profile, elem): 53 """Match a requested API & profile name to a api & profile attributes of an Element""" 54 match = True 55 # Match 'api', if present 56 if ('api' in elem.attrib): 57 if (api == None): 58 raise UserWarning("No API requested, but 'api' attribute is present with value '" + 59 elem.get('api') + "'") 60 elif (api != elem.get('api')): 61 # Requested API doesn't match attribute 62 return False 63 if ('profile' in elem.attrib): 64 if (profile == None): 65 raise UserWarning("No profile requested, but 'profile' attribute is present with value '" + 66 elem.get('profile') + "'") 67 elif (profile != elem.get('profile')): 68 # Requested profile doesn't match attribute 69 return False 70 return True 71 72# BaseInfo - base class for information about a registry feature 73# (type/group/enum/command/API/extension). 74# required - should this feature be defined during header generation 75# (has it been removed by a profile or version)? 76# declared - has this feature been defined already? 77# elem - lxml.etree Element for this feature 78# resetState() - reset required/declared to initial values. Used 79# prior to generating a new API interface. 80class BaseInfo: 81 """Represents the state of a registry feature, used during API generation""" 82 def __init__(self, elem): 83 self.required = False 84 self.declared = False 85 self.elem = elem 86 def resetState(self): 87 self.required = False 88 self.declared = False 89 90# TypeInfo - registry information about a type. No additional state 91# beyond BaseInfo is required. 92class TypeInfo(BaseInfo): 93 """Represents the state of a registry type""" 94 def __init__(self, elem): 95 BaseInfo.__init__(self, elem) 96 97# GroupInfo - registry information about a group of related enums. 98# enums - dictionary of enum names which are in the group 99class GroupInfo(BaseInfo): 100 """Represents the state of a registry enumerant group""" 101 def __init__(self, elem): 102 BaseInfo.__init__(self, elem) 103 self.enums = {} 104 105# EnumInfo - registry information about an enum 106# type - numeric type of the value of the <enum> tag 107# ( '' for GLint, 'u' for GLuint, 'ull' for GLuint64 ) 108class EnumInfo(BaseInfo): 109 """Represents the state of a registry enum""" 110 def __init__(self, elem): 111 BaseInfo.__init__(self, elem) 112 self.type = elem.get('type') 113 if (self.type == None): 114 self.type = '' 115 116# CmdInfo - registry information about a command 117# glxtype - type of GLX protocol { None, 'render', 'single', 'vendor' } 118# glxopcode - GLX protocol opcode { None, number } 119# glxequiv - equivalent command at GLX dispatch level { None, string } 120# vecequiv - equivalent vector form of a command taking multiple scalar args 121# { None, string } 122class CmdInfo(BaseInfo): 123 """Represents the state of a registry command""" 124 def __init__(self, elem): 125 BaseInfo.__init__(self, elem) 126 self.glxtype = None 127 self.glxopcode = None 128 self.glxequiv = None 129 self.vecequiv = None 130 131# FeatureInfo - registry information about an API <feature> 132# or <extension> 133# name - feature name string (e.g. 'GL_ARB_multitexture') 134# number - feature version number (e.g. 1.2). <extension> 135# features are unversioned and assigned version number 0. 136# category - category, e.g. VERSION or ARB/KHR/OES/ETC/vendor 137# emit - has this feature been defined already? 138class FeatureInfo(BaseInfo): 139 """Represents the state of an API feature (version/extension)""" 140 def __init__(self, elem): 141 BaseInfo.__init__(self, elem) 142 self.name = elem.get('name') 143 # Determine element category (vendor). Only works 144 # for <extension> elements. 145 if (elem.tag == 'feature'): 146 self.category = 'VERSION' 147 self.number = elem.get('number') 148 else: 149 self.category = self.name.split('_', 2)[1] 150 self.number = "0" 151 self.emit = False 152 153# Primary sort key for regSortFeatures. 154# Sorts by category of the feature name string: 155# Core API features (those defined with a <feature> tag) 156# ARB/KHR/OES (Khronos extensions) 157# other (EXT/vendor extensions) 158def regSortCategoryKey(feature): 159 if (feature.elem.tag == 'feature'): 160 return 0 161 elif (feature.category == 'ARB' or 162 feature.category == 'KHR' or 163 feature.category == 'OES'): 164 return 1 165 else: 166 return 2 167 168# Secondary sort key for regSortFeatures. 169# Sorts by extension name. 170def regSortNameKey(feature): 171 return feature.name 172 173# Tertiary sort key for regSortFeatures. 174# Sorts by feature version number. <extension> 175# elements all have version number "0" 176def regSortNumberKey(feature): 177 return feature.number 178 179# regSortFeatures - default sort procedure for features. 180# Sorts by primary key of feature category, 181# then by feature name within the category, 182# then by version number 183def regSortFeatures(featureList): 184 featureList.sort(key = regSortNumberKey) 185 featureList.sort(key = regSortNameKey) 186 featureList.sort(key = regSortCategoryKey) 187 188# GeneratorOptions - base class for options used during header production 189# These options are target language independent, and used by 190# Registry.apiGen() and by base OutputGenerator objects. 191# 192# Members 193# filename - name of file to generate, or None to write to stdout. 194# apiname - string matching <api> 'apiname' attribute, e.g. 'gl'. 195# profile - string specifying API profile , e.g. 'core', or None. 196# versions - regex matching API versions to process interfaces for. 197# Normally '.*' or '[0-9]\.[0-9]' to match all defined versions. 198# emitversions - regex matching API versions to actually emit 199# interfaces for (though all requested versions are considered 200# when deciding which interfaces to generate). For GL 4.3 glext.h, 201# this might be '1\.[2-5]|[2-4]\.[0-9]'. 202# defaultExtensions - If not None, a string which must in its 203# entirety match the pattern in the "supported" attribute of 204# the <extension>. Defaults to None. Usually the same as apiname. 205# addExtensions - regex matching names of additional extensions 206# to include. Defaults to None. 207# removeExtensions - regex matching names of extensions to 208# remove (after defaultExtensions and addExtensions). Defaults 209# to None. 210# sortProcedure - takes a list of FeatureInfo objects and sorts 211# them in place to a preferred order in the generated output. 212# Default is core API versions, ARB/KHR/OES extensions, all 213# other extensions, alphabetically within each group. 214# The regex patterns can be None or empty, in which case they match 215# nothing. 216class GeneratorOptions: 217 """Represents options during header production from an API registry""" 218 def __init__(self, 219 filename = None, 220 apiname = None, 221 profile = None, 222 versions = '.*', 223 emitversions = '.*', 224 defaultExtensions = None, 225 addExtensions = None, 226 removeExtensions = None, 227 sortProcedure = regSortFeatures): 228 self.filename = filename 229 self.apiname = apiname 230 self.profile = profile 231 self.versions = self.emptyRegex(versions) 232 self.emitversions = self.emptyRegex(emitversions) 233 self.defaultExtensions = defaultExtensions 234 self.addExtensions = self.emptyRegex(addExtensions) 235 self.removeExtensions = self.emptyRegex(removeExtensions) 236 self.sortProcedure = sortProcedure 237 # 238 # Substitute a regular expression which matches no version 239 # or extension names for None or the empty string. 240 def emptyRegex(self,pat): 241 if (pat == None or pat == ''): 242 return '_nomatch_^' 243 else: 244 return pat 245 246# CGeneratorOptions - subclass of GeneratorOptions. 247# 248# Adds options used by COutputGenerator objects during C language header 249# generation. 250# 251# Additional members 252# prefixText - list of strings to prefix generated header with 253# (usually a copyright statement + calling convention macros). 254# protectFile - True if multiple inclusion protection should be 255# generated (based on the filename) around the entire header. 256# protectFeature - True if #ifndef..#endif protection should be 257# generated around a feature interface in the header file. 258# genFuncPointers - True if function pointer typedefs should be 259# generated 260# protectProto - Controls cpp protection around prototypes: 261# False - no protection 262# 'nonzero' - protectProtoStr must be defined to a nonzero value 263# True - protectProtoStr must be defined 264# protectProtoStr - #ifdef symbol to use around prototype 265# declarations, if protected 266# apicall - string to use for the function declaration prefix, 267# such as APICALL on Windows. 268# apientry - string to use for the calling convention macro, 269# in typedefs, such as APIENTRY. 270# apientryp - string to use for the calling convention macro 271# in function pointer typedefs, such as APIENTRYP. 272class CGeneratorOptions(GeneratorOptions): 273 """Represents options during C header production from an API registry""" 274 def __init__(self, 275 filename = None, 276 apiname = None, 277 profile = None, 278 versions = '.*', 279 emitversions = '.*', 280 defaultExtensions = None, 281 addExtensions = None, 282 removeExtensions = None, 283 sortProcedure = regSortFeatures, 284 prefixText = "", 285 genFuncPointers = True, 286 protectFile = True, 287 protectFeature = True, 288 protectProto = True, 289 protectProtoStr = True, 290 apicall = '', 291 apientry = '', 292 apientryp = ''): 293 GeneratorOptions.__init__(self, filename, apiname, profile, 294 versions, emitversions, defaultExtensions, 295 addExtensions, removeExtensions, sortProcedure) 296 self.prefixText = prefixText 297 self.genFuncPointers = genFuncPointers 298 self.protectFile = protectFile 299 self.protectFeature = protectFeature 300 self.protectProto = protectProto 301 self.protectProtoStr = protectProtoStr 302 self.apicall = apicall 303 self.apientry = apientry 304 self.apientryp = apientryp 305 306# OutputGenerator - base class for generating API interfaces. 307# Manages basic logic, logging, and output file control 308# Derived classes actually generate formatted output. 309# 310# ---- methods ---- 311# OutputGenerator(errFile, warnFile, diagFile) 312# errFile, warnFile, diagFile - file handles to write errors, 313# warnings, diagnostics to. May be None to not write. 314# logMsg(level, *args) - log messages of different categories 315# level - 'error', 'warn', or 'diag'. 'error' will also 316# raise a UserWarning exception 317# *args - print()-style arguments 318# beginFile(genOpts) - start a new interface file 319# genOpts - GeneratorOptions controlling what's generated and how 320# endFile() - finish an interface file, closing it when done 321# beginFeature(interface, emit) - write interface for a feature 322# and tag generated features as having been done. 323# interface - element for the <version> / <extension> to generate 324# emit - actually write to the header only when True 325# endFeature() - finish an interface. 326# genType(typeinfo,name) - generate interface for a type 327# typeinfo - TypeInfo for a type 328# genEnum(enuminfo, name) - generate interface for an enum 329# enuminfo - EnumInfo for an enum 330# name - enum name 331# genCmd(cmdinfo) - generate interface for a command 332# cmdinfo - CmdInfo for a command 333class OutputGenerator: 334 """Generate specified API interfaces in a specific style, such as a C header""" 335 def __init__(self, 336 errFile = sys.stderr, 337 warnFile = sys.stderr, 338 diagFile = sys.stdout): 339 self.outFile = None 340 self.errFile = errFile 341 self.warnFile = warnFile 342 self.diagFile = diagFile 343 # Internal state 344 self.featureName = None 345 self.genOpts = None 346 # 347 # logMsg - write a message of different categories to different 348 # destinations. 349 # level - 350 # 'diag' (diagnostic, voluminous) 351 # 'warn' (warning) 352 # 'error' (fatal error - raises exception after logging) 353 # *args - print()-style arguments to direct to corresponding log 354 def logMsg(self, level, *args): 355 """Log a message at the given level. Can be ignored or log to a file""" 356 if (level == 'error'): 357 strfile = io.StringIO() 358 write('ERROR:', *args, file=strfile) 359 if (self.errFile != None): 360 write(strfile.getvalue(), file=self.errFile) 361 raise UserWarning(strfile.getvalue()) 362 elif (level == 'warn'): 363 if (self.warnFile != None): 364 write('WARNING:', *args, file=self.warnFile) 365 elif (level == 'diag'): 366 if (self.diagFile != None): 367 write('DIAG:', *args, file=self.diagFile) 368 else: 369 raise UserWarning( 370 '*** FATAL ERROR in Generator.logMsg: unknown level:' + level) 371 # 372 def beginFile(self, genOpts): 373 self.genOpts = genOpts 374 # 375 # Open specified output file. Not done in constructor since a 376 # Generator can be used without writing to a file. 377 if (self.genOpts.filename != None): 378 self.outFile = open(self.genOpts.filename, 'w') 379 else: 380 self.outFile = sys.stdout 381 def endFile(self): 382 self.errFile and self.errFile.flush() 383 self.warnFile and self.warnFile.flush() 384 self.diagFile and self.diagFile.flush() 385 self.outFile.flush() 386 if (self.outFile != sys.stdout and self.outFile != sys.stderr): 387 self.outFile.close() 388 self.genOpts = None 389 # 390 def beginFeature(self, interface, emit): 391 self.emit = emit 392 self.featureName = interface.get('name') 393 # If there's an additional 'protect' attribute in the feature, save it 394 self.featureExtraProtect = interface.get('protect') 395 def endFeature(self): 396 # Derived classes responsible for emitting feature 397 self.featureName = None 398 self.featureExtraProtect = None 399 # 400 # Type generation 401 def genType(self, typeinfo, name): 402 if (self.featureName == None): 403 raise UserWarning('Attempt to generate type', name, 404 'when not in feature') 405 # 406 # Enumerant generation 407 def genEnum(self, enuminfo, name): 408 if (self.featureName == None): 409 raise UserWarning('Attempt to generate enum', name, 410 'when not in feature') 411 # 412 # Command generation 413 def genCmd(self, cmd, name): 414 if (self.featureName == None): 415 raise UserWarning('Attempt to generate command', name, 416 'when not in feature') 417 418# COutputGenerator - subclass of OutputGenerator. 419# Generates C-language API interfaces. 420# 421# ---- methods ---- 422# COutputGenerator(errFile, warnFile, diagFile) - args as for 423# OutputGenerator. Defines additional internal state. 424# makeCDecls(cmd) - return C prototype and function pointer typedef for a 425# <command> Element, as a list of two strings 426# cmd - Element for the <command> 427# newline() - print a newline to the output file (utility function) 428# ---- methods overriding base class ---- 429# beginFile(genOpts) 430# endFile() 431# beginFeature(interface, emit) 432# endFeature() 433# genType(typeinfo,name) - generate interface for a type 434# genEnum(enuminfo, name) 435# genCmd(cmdinfo) 436class COutputGenerator(OutputGenerator): 437 """Generate specified API interfaces in a specific style, such as a C header""" 438 def __init__(self, 439 errFile = sys.stderr, 440 warnFile = sys.stderr, 441 diagFile = sys.stdout): 442 OutputGenerator.__init__(self, errFile, warnFile, diagFile) 443 # Internal state - accumulators for different inner block text 444 self.typeBody = '' 445 self.enumBody = '' 446 self.cmdBody = '' 447 # 448 # makeCDecls - return C prototype and function pointer typedef for a 449 # command, as a two-element list of strings. 450 # cmd - Element containing a <command> tag 451 def makeCDecls(self, cmd): 452 """Generate C function pointer typedef for <command> Element""" 453 proto = cmd.find('proto') 454 params = cmd.findall('param') 455 # Begin accumulating prototype and typedef strings 456 pdecl = self.genOpts.apicall 457 tdecl = 'typedef ' 458 # 459 # Insert the function return type/name. 460 # For prototypes, add APIENTRY macro before the name 461 # For typedefs, add (APIENTRYP <name>) around the name and 462 # use the PFNGLCMDNAMEPROC nameng convention. 463 # Done by walking the tree for <proto> element by element. 464 # lxml.etree has elem.text followed by (elem[i], elem[i].tail) 465 # for each child element and any following text 466 # Leading text 467 pdecl += noneStr(proto.text) 468 tdecl += noneStr(proto.text) 469 # For each child element, if it's a <name> wrap in appropriate 470 # declaration. Otherwise append its contents and tail contents. 471 for elem in proto: 472 text = noneStr(elem.text) 473 tail = noneStr(elem.tail) 474 if (elem.tag == 'name'): 475 pdecl += self.genOpts.apientry + text + tail 476 tdecl += '(' + self.genOpts.apientryp + 'PFN' + text.upper() + 'PROC' + tail + ')' 477 else: 478 pdecl += text + tail 479 tdecl += text + tail 480 # Now add the parameter declaration list, which is identical 481 # for prototypes and typedefs. Concatenate all the text from 482 # a <param> node without the tags. No tree walking required 483 # since all tags are ignored. 484 n = len(params) 485 paramdecl = ' (' 486 if n > 0: 487 for i in range(0,n): 488 paramdecl += ''.join([t for t in params[i].itertext()]) 489 if (i < n - 1): 490 paramdecl += ', ' 491 else: 492 paramdecl += 'void' 493 paramdecl += ');\n'; 494 return [ pdecl + paramdecl, tdecl + paramdecl ] 495 # 496 def newline(self): 497 write('', file=self.outFile) 498 # 499 def beginFile(self, genOpts): 500 OutputGenerator.beginFile(self, genOpts) 501 # C-specific 502 # 503 # Multiple inclusion protection & C++ wrappers. 504 if (genOpts.protectFile and self.genOpts.filename): 505 headerSym = '__' + self.genOpts.apiname + '_' + re.sub('\.h', '_h_', os.path.basename(self.genOpts.filename)) 506 write('#ifndef', headerSym, file=self.outFile) 507 write('#define', headerSym, '1', file=self.outFile) 508 self.newline() 509 write('#ifdef __cplusplus', file=self.outFile) 510 write('extern "C" {', file=self.outFile) 511 write('#endif', file=self.outFile) 512 self.newline() 513 # 514 # User-supplied prefix text, if any (list of strings) 515 if (genOpts.prefixText): 516 for s in genOpts.prefixText: 517 write(s, file=self.outFile) 518 # 519 # Some boilerplate describing what was generated - this 520 # will probably be removed later since the extensions 521 # pattern may be very long. 522 write('/* Generated C header for:', file=self.outFile) 523 write(' * API:', genOpts.apiname, file=self.outFile) 524 if (genOpts.profile): 525 write(' * Profile:', genOpts.profile, file=self.outFile) 526 write(' * Versions considered:', genOpts.versions, file=self.outFile) 527 write(' * Versions emitted:', genOpts.emitversions, file=self.outFile) 528 write(' * Default extensions included:', genOpts.defaultExtensions, file=self.outFile) 529 write(' * Additional extensions included:', genOpts.addExtensions, file=self.outFile) 530 write(' * Extensions removed:', genOpts.removeExtensions, file=self.outFile) 531 write(' */', file=self.outFile) 532 def endFile(self): 533 # C-specific 534 # Finish C++ wrapper and multiple inclusion protection 535 self.newline() 536 write('#ifdef __cplusplus', file=self.outFile) 537 write('}', file=self.outFile) 538 write('#endif', file=self.outFile) 539 if (self.genOpts.protectFile and self.genOpts.filename): 540 self.newline() 541 write('#endif', file=self.outFile) 542 # Finish processing in superclass 543 OutputGenerator.endFile(self) 544 def beginFeature(self, interface, emit): 545 # Start processing in superclass 546 OutputGenerator.beginFeature(self, interface, emit) 547 # C-specific 548 # Accumulate types, enums, function pointer typedefs, end function 549 # prototypes separately for this feature. They're only printed in 550 # endFeature(). 551 self.typeBody = '' 552 self.enumBody = '' 553 self.cmdPointerBody = '' 554 self.cmdBody = '' 555 def endFeature(self): 556 # C-specific 557 # Actually write the interface to the output file. 558 if (self.emit): 559 self.newline() 560 if (self.genOpts.protectFeature): 561 write('#ifndef', self.featureName, file=self.outFile) 562 write('#define', self.featureName, '1', file=self.outFile) 563 if (self.typeBody != ''): 564 write(self.typeBody, end='', file=self.outFile) 565 # 566 # Don't add additional protection for derived type declarations, 567 # which may be needed by other features later on. 568 if (self.featureExtraProtect != None): 569 write('#ifdef', self.featureExtraProtect, file=self.outFile) 570 if (self.enumBody != ''): 571 write(self.enumBody, end='', file=self.outFile) 572 if (self.genOpts.genFuncPointers and self.cmdPointerBody != ''): 573 write(self.cmdPointerBody, end='', file=self.outFile) 574 if (self.cmdBody != ''): 575 if (self.genOpts.protectProto == True): 576 prefix = '#ifdef ' + self.genOpts.protectProtoStr + '\n' 577 suffix = '#endif\n' 578 elif (self.genOpts.protectProto == 'nonzero'): 579 prefix = '#if ' + self.genOpts.protectProtoStr + '\n' 580 suffix = '#endif\n' 581 elif (self.genOpts.protectProto == False): 582 prefix = '' 583 suffix = '' 584 else: 585 self.gen.logMsg('warn', 586 '*** Unrecognized value for protectProto:', 587 self.genOpts.protectProto, 588 'not generating prototype wrappers') 589 prefix = '' 590 suffix = '' 591 592 write(prefix + self.cmdBody + suffix, end='', file=self.outFile) 593 if (self.featureExtraProtect != None): 594 write('#endif /*', self.featureExtraProtect, '*/', file=self.outFile) 595 if (self.genOpts.protectFeature): 596 write('#endif /*', self.featureName, '*/', file=self.outFile) 597 # Finish processing in superclass 598 OutputGenerator.endFeature(self) 599 # 600 # Type generation 601 def genType(self, typeinfo, name): 602 OutputGenerator.genType(self, typeinfo, name) 603 # 604 # Replace <apientry /> tags with an APIENTRY-style string 605 # (from self.genOpts). Copy other text through unchanged. 606 # If the resulting text is an empty string, don't emit it. 607 typeElem = typeinfo.elem 608 s = noneStr(typeElem.text) 609 for elem in typeElem: 610 if (elem.tag == 'apientry'): 611 s += self.genOpts.apientry + noneStr(elem.tail) 612 else: 613 s += noneStr(elem.text) + noneStr(elem.tail) 614 if (len(s) > 0): 615 self.typeBody += s + '\n' 616 # 617 # Enumerant generation 618 def genEnum(self, enuminfo, name): 619 OutputGenerator.genEnum(self, enuminfo, name) 620 # 621 # EnumInfo.type is a C value suffix (e.g. u, ull) 622 self.enumBody += '#define ' + name.ljust(33) + ' ' + enuminfo.elem.get('value') 623 # 624 # Handle non-integer 'type' fields by using it as the C value suffix 625 t = enuminfo.elem.get('type') 626 if (t != '' and t != 'i'): 627 self.enumBody += enuminfo.type 628 self.enumBody += '\n' 629 # 630 # Command generation 631 def genCmd(self, cmdinfo, name): 632 OutputGenerator.genCmd(self, cmdinfo, name) 633 # 634 decls = self.makeCDecls(cmdinfo.elem) 635 self.cmdBody += decls[0] 636 if (self.genOpts.genFuncPointers): 637 self.cmdPointerBody += decls[1] 638 639# Registry - object representing an API registry, loaded from an XML file 640# Members 641# tree - ElementTree containing the root <registry> 642# typedict - dictionary of TypeInfo objects keyed by type name 643# groupdict - dictionary of GroupInfo objects keyed by group name 644# enumdict - dictionary of EnumInfo objects keyed by enum name 645# cmddict - dictionary of CmdInfo objects keyed by command name 646# apidict - dictionary of <api> Elements keyed by API name 647# extensions - list of <extension> Elements 648# extdict - dictionary of <extension> Elements keyed by extension name 649# gen - OutputGenerator object used to write headers / messages 650# genOpts - GeneratorOptions object used to control which 651# fetures to write and how to format them 652# emitFeatures - True to actually emit features for a version / extension, 653# or False to just treat them as emitted 654# Public methods 655# loadElementTree(etree) - load registry from specified ElementTree 656# loadFile(filename) - load registry from XML file 657# setGenerator(gen) - OutputGenerator to use 658# parseTree() - parse the registry once loaded & create dictionaries 659# dumpReg(maxlen, filehandle) - diagnostic to dump the dictionaries 660# to specified file handle (default stdout). Truncates type / 661# enum / command elements to maxlen characters (default 80) 662# generator(g) - specify the output generator object 663# apiGen(apiname, genOpts) - generate API headers for the API type 664# and profile specified in genOpts, but only for the versions and 665# extensions specified there. 666# apiReset() - call between calls to apiGen() to reset internal state 667# validateGroups() - call to verify that each <proto> or <param> 668# with a 'group' attribute matches an actual existing group. 669# Private methods 670# addElementInfo(elem,info,infoName,dictionary) - add feature info to dict 671# lookupElementInfo(fname,dictionary) - lookup feature info in dict 672class Registry: 673 """Represents an API registry loaded from XML""" 674 def __init__(self): 675 self.tree = None 676 self.typedict = {} 677 self.groupdict = {} 678 self.enumdict = {} 679 self.cmddict = {} 680 self.apidict = {} 681 self.extensions = [] 682 self.extdict = {} 683 # A default output generator, so commands prior to apiGen can report 684 # errors via the generator object. 685 self.gen = OutputGenerator() 686 self.genOpts = None 687 self.emitFeatures = False 688 def loadElementTree(self, tree): 689 """Load ElementTree into a Registry object and parse it""" 690 self.tree = tree 691 self.parseTree() 692 def loadFile(self, file): 693 """Load an API registry XML file into a Registry object and parse it""" 694 self.tree = etree.parse(file) 695 self.parseTree() 696 def setGenerator(self, gen): 697 """Specify output generator object. None restores the default generator""" 698 self.gen = gen 699 # addElementInfo - add information about an element to the 700 # corresponding dictionary 701 # elem - <type>/<group>/<enum>/<command>/<feature>/<extension> Element 702 # info - corresponding {Type|Group|Enum|Cmd|Feature}Info object 703 # infoName - 'type' / 'group' / 'enum' / 'command' / 'feature' / 'extension' 704 # dictionary - self.{type|group|enum|cmd|api|ext}dict 705 # If the Element has an 'api' attribute, the dictionary key is the 706 # tuple (name,api). If not, the key is the name. 'name' is an 707 # attribute of the Element 708 def addElementInfo(self, elem, info, infoName, dictionary): 709 if ('api' in elem.attrib): 710 key = (elem.get('name'),elem.get('api')) 711 else: 712 key = elem.get('name') 713 if key in dictionary: 714 self.gen.logMsg('warn', '*** Attempt to redefine', 715 infoName, 'with key:', key) 716 else: 717 dictionary[key] = info 718 # 719 # lookupElementInfo - find a {Type|Enum|Cmd}Info object by name. 720 # If an object qualified by API name exists, use that. 721 # fname - name of type / enum / command 722 # dictionary - self.{type|enum|cmd}dict 723 def lookupElementInfo(self, fname, dictionary): 724 key = (fname, self.genOpts.apiname) 725 if (key in dictionary): 726 # self.gen.logMsg('diag', 'Found API-specific element for feature', fname) 727 return dictionary[key] 728 elif (fname in dictionary): 729 # self.gen.logMsg('diag', 'Found generic element for feature', fname) 730 return dictionary[fname] 731 else: 732 return None 733 def parseTree(self): 734 """Parse the registry Element, once created""" 735 # This must be the Element for the root <registry> 736 self.reg = self.tree.getroot() 737 # 738 # Create dictionary of registry types from toplevel <types> tags 739 # and add 'name' attribute to each <type> tag (where missing) 740 # based on its <name> element. 741 # 742 # There's usually one <types> block; more are OK 743 # Required <type> attributes: 'name' or nested <name> tag contents 744 self.typedict = {} 745 for type in self.reg.findall('types/type'): 746 # If the <type> doesn't already have a 'name' attribute, set 747 # it from contents of its <name> tag. 748 if (type.get('name') == None): 749 type.attrib['name'] = type.find('name').text 750 self.addElementInfo(type, TypeInfo(type), 'type', self.typedict) 751 # 752 # Create dictionary of registry groups from toplevel <groups> tags. 753 # 754 # There's usually one <groups> block; more are OK. 755 # Required <group> attributes: 'name' 756 self.groupdict = {} 757 for group in self.reg.findall('groups/group'): 758 self.addElementInfo(group, GroupInfo(group), 'group', self.groupdict) 759 # 760 # Create dictionary of registry enums from toplevel <enums> tags 761 # 762 # There are usually many <enums> tags in different namespaces, but 763 # these are functional namespaces of the values, while the actual 764 # enum names all share the dictionary. 765 # Required <enums> attributes: 'name', 'value' 766 self.enumdict = {} 767 for enum in self.reg.findall('enums/enum'): 768 self.addElementInfo(enum, EnumInfo(enum), 'enum', self.enumdict) 769 # 770 # Create dictionary of registry commands from <command> tags 771 # and add 'name' attribute to each <command> tag (where missing) 772 # based on its <proto><name> element. 773 # 774 # There's usually only one <commands> block; more are OK. 775 # Required <command> attributes: 'name' or <proto><name> tag contents 776 self.cmddict = {} 777 for cmd in self.reg.findall('commands/command'): 778 # If the <command> doesn't already have a 'name' attribute, set 779 # it from contents of its <proto><name> tag. 780 if (cmd.get('name') == None): 781 cmd.attrib['name'] = cmd.find('proto/name').text 782 ci = CmdInfo(cmd) 783 self.addElementInfo(cmd, ci, 'command', self.cmddict) 784 # 785 # Create dictionaries of API and extension interfaces 786 # from toplevel <api> and <extension> tags. 787 # 788 self.apidict = {} 789 for feature in self.reg.findall('feature'): 790 ai = FeatureInfo(feature) 791 self.addElementInfo(feature, ai, 'feature', self.apidict) 792 self.extensions = self.reg.findall('extensions/extension') 793 self.extdict = {} 794 for feature in self.extensions: 795 ei = FeatureInfo(feature) 796 self.addElementInfo(feature, ei, 'extension', self.extdict) 797 def dumpReg(self, maxlen = 40, filehandle = sys.stdout): 798 """Dump all the dictionaries constructed from the Registry object""" 799 write('***************************************', file=filehandle) 800 write(' ** Dumping Registry contents **', file=filehandle) 801 write('***************************************', file=filehandle) 802 write('// Types', file=filehandle) 803 for name in self.typedict: 804 tobj = self.typedict[name] 805 write(' Type', name, '->', etree.tostring(tobj.elem)[0:maxlen], file=filehandle) 806 write('// Groups', file=filehandle) 807 for name in self.groupdict: 808 gobj = self.groupdict[name] 809 write(' Group', name, '->', etree.tostring(gobj.elem)[0:maxlen], file=filehandle) 810 write('// Enums', file=filehandle) 811 for name in self.enumdict: 812 eobj = self.enumdict[name] 813 write(' Enum', name, '->', etree.tostring(eobj.elem)[0:maxlen], file=filehandle) 814 write('// Commands', file=filehandle) 815 for name in self.cmddict: 816 cobj = self.cmddict[name] 817 write(' Command', name, '->', etree.tostring(cobj.elem)[0:maxlen], file=filehandle) 818 write('// APIs', file=filehandle) 819 for key in self.apidict: 820 write(' API Version ', key, '->', 821 etree.tostring(self.apidict[key].elem)[0:maxlen], file=filehandle) 822 write('// Extensions', file=filehandle) 823 for key in self.extdict: 824 write(' Extension', key, '->', 825 etree.tostring(self.extdict[key].elem)[0:maxlen], file=filehandle) 826 # write('***************************************', file=filehandle) 827 # write(' ** Dumping XML ElementTree **', file=filehandle) 828 # write('***************************************', file=filehandle) 829 # write(etree.tostring(self.tree.getroot(),pretty_print=True), file=filehandle) 830 # 831 # typename - name of type 832 # required - boolean (to tag features as required or not) 833 def markTypeRequired(self, typename, required): 834 """Require (along with its dependencies) or remove (but not its dependencies) a type""" 835 self.gen.logMsg('diag', '*** tagging type:', typename, '-> required =', required) 836 # Get TypeInfo object for <type> tag corresponding to typename 837 type = self.lookupElementInfo(typename, self.typedict) 838 if (type != None): 839 # Tag required type dependencies as required. 840 # This DOES NOT un-tag dependencies in a <remove> tag. 841 # See comments in markRequired() below for the reason. 842 if (required and ('requires' in type.elem.attrib)): 843 depType = type.elem.get('requires') 844 self.gen.logMsg('diag', '*** Generating dependent type', 845 depType, 'for type', typename) 846 self.markTypeRequired(depType, required) 847 type.required = required 848 else: 849 self.gen.logMsg('warn', '*** type:', typename , 'IS NOT DEFINED') 850 # 851 # features - Element for <require> or <remove> tag 852 # required - boolean (to tag features as required or not) 853 def markRequired(self, features, required): 854 """Require or remove features specified in the Element""" 855 self.gen.logMsg('diag', '*** markRequired (features = <too long to print>, required =', required, ')') 856 # Loop over types, enums, and commands in the tag 857 # @@ It would be possible to respect 'api' and 'profile' attributes 858 # in individual features, but that's not done yet. 859 for typeElem in features.findall('type'): 860 self.markTypeRequired(typeElem.get('name'), required) 861 for enumElem in features.findall('enum'): 862 name = enumElem.get('name') 863 self.gen.logMsg('diag', '*** tagging enum:', name, '-> required =', required) 864 enum = self.lookupElementInfo(name, self.enumdict) 865 if (enum != None): 866 enum.required = required 867 else: 868 self.gen.logMsg('warn', '*** enum:', name , 'IS NOT DEFINED') 869 for cmdElem in features.findall('command'): 870 name = cmdElem.get('name') 871 self.gen.logMsg('diag', '*** tagging command:', name, '-> required =', required) 872 cmd = self.lookupElementInfo(name, self.cmddict) 873 if (cmd != None): 874 cmd.required = required 875 # Tag all parameter types of this command as required. 876 # This DOES NOT remove types of commands in a <remove> 877 # tag, because many other commands may use the same type. 878 # We could be more clever and reference count types, 879 # instead of using a boolean. 880 if (required): 881 # Look for <ptype> in entire <command> tree, 882 # not just immediate children 883 for ptype in cmd.elem.findall('.//ptype'): 884 self.gen.logMsg('diag', '*** markRequired: command implicitly requires dependent type', ptype.text) 885 self.markTypeRequired(ptype.text, required) 886 else: 887 self.gen.logMsg('warn', '*** command:', name, 'IS NOT DEFINED') 888 # 889 # interface - Element for <version> or <extension>, containing 890 # <require> and <remove> tags 891 # api - string specifying API name being generated 892 # profile - string specifying API profile being generated 893 def requireAndRemoveFeatures(self, interface, api, profile): 894 """Process <recquire> and <remove> tags for a <version> or <extension>""" 895 # <require> marks things that are required by this version/profile 896 for feature in interface.findall('require'): 897 if (matchAPIProfile(api, profile, feature)): 898 self.markRequired(feature,True) 899 # <remove> marks things that are removed by this version/profile 900 for feature in interface.findall('remove'): 901 if (matchAPIProfile(api, profile, feature)): 902 self.markRequired(feature,False) 903 # 904 # generateFeature - generate a single type / enum / command, 905 # and all its dependencies as needed. 906 # fname - name of feature (<type>/<enum>/<command> 907 # ftype - type of feature, 'type' | 'enum' | 'command' 908 # dictionary - of *Info objects - self.{type|enum|cmd}dict 909 # genProc - bound function pointer for self.gen.gen{Type|Enum|Cmd} 910 def generateFeature(self, fname, ftype, dictionary, genProc): 911 f = self.lookupElementInfo(fname, dictionary) 912 if (f == None): 913 # No such feature. This is an error, but reported earlier 914 self.gen.logMsg('diag', '*** No entry found for feature', fname, 915 'returning!') 916 return 917 # 918 # If feature isn't required, or has already been declared, return 919 if (not f.required): 920 self.gen.logMsg('diag', '*** Skipping', ftype, fname, '(not required)') 921 return 922 if (f.declared): 923 self.gen.logMsg('diag', '*** Skipping', ftype, fname, '(already declared)') 924 return 925 # 926 # Pull in dependent type declaration(s) of the feature. 927 # For types, there may be one in the 'required' attribute of the element 928 # For commands, there may be many in <ptype> tags within the element 929 # For enums, no dependencies are allowed (though perhasps if you 930 # have a uint64 enum, it should require GLuint64) 931 if (ftype == 'type'): 932 if ('requires' in f.elem.attrib): 933 depname = f.elem.get('requires') 934 self.gen.logMsg('diag', '*** Generating required dependent type', 935 depname) 936 self.generateFeature(depname, 'type', self.typedict, 937 self.gen.genType) 938 elif (ftype == 'command'): 939 for ptype in f.elem.findall('.//ptype'): 940 depname = ptype.text 941 self.gen.logMsg('diag', '*** Generating required parameter type', 942 depname) 943 self.generateFeature(depname, 'type', self.typedict, 944 self.gen.genType) 945 # 946 # Actually generate the type only if emitting declarations 947 if self.emitFeatures: 948 self.gen.logMsg('diag', '*** Emitting', ftype, 'decl for', fname) 949 genProc(f, fname) 950 else: 951 self.gen.logMsg('diag', '*** Skipping', ftype, fname, 952 '(not emitting this feature)') 953 # Always mark feature declared, as though actually emitted 954 f.declared = True 955 # 956 # generateRequiredInterface - generate all interfaces required 957 # by an API version or extension 958 # interface - Element for <version> or <extension> 959 def generateRequiredInterface(self, interface): 960 """Generate required C interface for specified API version/extension""" 961 # 962 # Loop over all features inside all <require> tags. 963 # <remove> tags are ignored (handled in pass 1). 964 for features in interface.findall('require'): 965 for t in features.findall('type'): 966 self.generateFeature(t.get('name'), 'type', self.typedict, 967 self.gen.genType) 968 for e in features.findall('enum'): 969 self.generateFeature(e.get('name'), 'enum', self.enumdict, 970 self.gen.genEnum) 971 for c in features.findall('command'): 972 self.generateFeature(c.get('name'), 'command', self.cmddict, 973 self.gen.genCmd) 974 # 975 # apiGen(genOpts) - generate interface for specified versions 976 # genOpts - GeneratorOptions object with parameters used 977 # by the Generator object. 978 def apiGen(self, genOpts): 979 """Generate interfaces for the specified API type and range of versions""" 980 # 981 self.gen.logMsg('diag', '*******************************************') 982 self.gen.logMsg('diag', ' Registry.apiGen file:', genOpts.filename, 983 'api:', genOpts.apiname, 984 'profile:', genOpts.profile) 985 self.gen.logMsg('diag', '*******************************************') 986 # 987 self.genOpts = genOpts 988 # Reset required/declared flags for all features 989 self.apiReset() 990 # 991 # Compile regexps used to select versions & extensions 992 regVersions = re.compile(self.genOpts.versions) 993 regEmitVersions = re.compile(self.genOpts.emitversions) 994 regAddExtensions = re.compile(self.genOpts.addExtensions) 995 regRemoveExtensions = re.compile(self.genOpts.removeExtensions) 996 # 997 # Get all matching API versions & add to list of FeatureInfo 998 features = [] 999 apiMatch = False 1000 for key in self.apidict: 1001 fi = self.apidict[key] 1002 api = fi.elem.get('api') 1003 if (api == self.genOpts.apiname): 1004 apiMatch = True 1005 if (regVersions.match(fi.number)): 1006 # Matches API & version #s being generated. Mark for 1007 # emission and add to the features[] list . 1008 # @@ Could use 'declared' instead of 'emit'? 1009 fi.emit = (regEmitVersions.match(fi.number) != None) 1010 features.append(fi) 1011 if (not fi.emit): 1012 self.gen.logMsg('diag', '*** NOT tagging feature api =', api, 1013 'name =', fi.name, 'number =', fi.number, 1014 'for emission (does not match emitversions pattern)') 1015 else: 1016 self.gen.logMsg('diag', '*** NOT including feature api =', api, 1017 'name =', fi.name, 'number =', fi.number, 1018 '(does not match requested versions)') 1019 else: 1020 self.gen.logMsg('diag', '*** NOT including feature api =', api, 1021 'name =', fi.name, 1022 '(does not match requested API)') 1023 if (not apiMatch): 1024 self.gen.logMsg('warn', '*** No matching API versions found!') 1025 # 1026 # Get all matching extensions & add to the list. 1027 # Start with extensions tagged with 'api' pattern matching the API 1028 # being generated. Add extensions matching the pattern specified in 1029 # regExtensions, then remove extensions matching the pattern 1030 # specified in regRemoveExtensions 1031 for key in self.extdict: 1032 ei = self.extdict[key] 1033 extName = ei.name 1034 include = False 1035 # 1036 # Include extension if defaultExtensions is not None and if the 1037 # 'supported' attribute matches defaultExtensions. The regexp in 1038 # 'supported' must exactly match defaultExtensions, so bracket 1039 # it with ^(pat)$. 1040 pat = '^(' + ei.elem.get('supported') + ')$' 1041 if (self.genOpts.defaultExtensions and 1042 re.match(pat, self.genOpts.defaultExtensions)): 1043 self.gen.logMsg('diag', '*** Including extension', 1044 extName, "(defaultExtensions matches the 'supported' attribute)") 1045 include = True 1046 # 1047 # Include additional extensions if the extension name matches 1048 # the regexp specified in the generator options. This allows 1049 # forcing extensions into an interface even if they're not 1050 # tagged appropriately in the registry. 1051 if (regAddExtensions.match(extName) != None): 1052 self.gen.logMsg('diag', '*** Including extension', 1053 extName, '(matches explicitly requested extensions to add)') 1054 include = True 1055 # Remove extensions if the name matches the regexp specified 1056 # in generator options. This allows forcing removal of 1057 # extensions from an interface even if they're tagged that 1058 # way in the registry. 1059 if (regRemoveExtensions.match(extName) != None): 1060 self.gen.logMsg('diag', '*** Removing extension', 1061 extName, '(matches explicitly requested extensions to remove)') 1062 include = False 1063 # 1064 # If the extension is to be included, add it to the 1065 # extension features list. 1066 if (include): 1067 ei.emit = True 1068 features.append(ei) 1069 else: 1070 self.gen.logMsg('diag', '*** NOT including extension', 1071 extName, '(does not match api attribute or explicitly requested extensions)') 1072 # 1073 # Sort the extension features list, if a sort procedure is defined 1074 if (self.genOpts.sortProcedure): 1075 self.genOpts.sortProcedure(features) 1076 # 1077 # Pass 1: loop over requested API versions and extensions tagging 1078 # types/commands/features as required (in an <require> block) or no 1079 # longer required (in an <exclude> block). It is possible to remove 1080 # a feature in one version and restore it later by requiring it in 1081 # a later version. 1082 # If a profile other than 'None' is being generated, it must 1083 # match the profile attribute (if any) of the <require> and 1084 # <remove> tags. 1085 self.gen.logMsg('diag', '*** PASS 1: TAG FEATURES ********************************************') 1086 for f in features: 1087 self.gen.logMsg('diag', '*** PASS 1: Tagging required and removed features for', 1088 f.name) 1089 self.requireAndRemoveFeatures(f.elem, self.genOpts.apiname, self.genOpts.profile) 1090 # 1091 # Pass 2: loop over specified API versions and extensions printing 1092 # declarations for required things which haven't already been 1093 # generated. 1094 self.gen.logMsg('diag', '*** PASS 2: GENERATE INTERFACES FOR FEATURES ************************') 1095 self.gen.beginFile(self.genOpts) 1096 for f in features: 1097 self.gen.logMsg('diag', '*** PASS 2: Generating interface for', 1098 f.name) 1099 emit = self.emitFeatures = f.emit 1100 if (not emit): 1101 self.gen.logMsg('diag', '*** PASS 2: NOT declaring feature', 1102 f.elem.get('name'), 'because it is not tagged for emission') 1103 # Generate the interface (or just tag its elements as having been 1104 # emitted, if they haven't been). 1105 self.gen.beginFeature(f.elem, emit) 1106 self.generateRequiredInterface(f.elem) 1107 self.gen.endFeature() 1108 self.gen.endFile() 1109 # 1110 # apiReset - use between apiGen() calls to reset internal state 1111 # 1112 def apiReset(self): 1113 """Reset type/enum/command dictionaries before generating another API""" 1114 for type in self.typedict: 1115 self.typedict[type].resetState() 1116 for enum in self.enumdict: 1117 self.enumdict[enum].resetState() 1118 for cmd in self.cmddict: 1119 self.cmddict[cmd].resetState() 1120 for cmd in self.apidict: 1121 self.apidict[cmd].resetState() 1122 # 1123 # validateGroups - check that group= attributes match actual groups 1124 # 1125 def validateGroups(self): 1126 """Validate group= attributes on <param> and <proto> tags""" 1127 # Keep track of group names not in <group> tags 1128 badGroup = {} 1129 self.gen.logMsg('diag', '*** VALIDATING GROUP ATTRIBUTES ***') 1130 for cmd in self.reg.findall('commands/command'): 1131 proto = cmd.find('proto') 1132 funcname = cmd.find('proto/name').text 1133 if ('group' in proto.attrib.keys()): 1134 group = proto.get('group') 1135 # self.gen.logMsg('diag', '*** Command ', funcname, ' has return group ', group) 1136 if (group not in self.groupdict.keys()): 1137 # self.gen.logMsg('diag', '*** Command ', funcname, ' has UNKNOWN return group ', group) 1138 if (group not in badGroup.keys()): 1139 badGroup[group] = 1 1140 else: 1141 badGroup[group] = badGroup[group] + 1 1142 for param in cmd.findall('param'): 1143 pname = param.find('name') 1144 if (pname != None): 1145 pname = pname.text 1146 else: 1147 pname = type.get('name') 1148 if ('group' in param.attrib.keys()): 1149 group = param.get('group') 1150 if (group not in self.groupdict.keys()): 1151 # self.gen.logMsg('diag', '*** Command ', funcname, ' param ', pname, ' has UNKNOWN group ', group) 1152 if (group not in badGroup.keys()): 1153 badGroup[group] = 1 1154 else: 1155 badGroup[group] = badGroup[group] + 1 1156 if (len(badGroup.keys()) > 0): 1157 self.gen.logMsg('diag', '*** SUMMARY OF UNRECOGNIZED GROUPS ***') 1158 for key in sorted(badGroup.keys()): 1159 self.gen.logMsg('diag', ' ', key, ' occurred ', badGroup[key], ' times') 1160