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