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