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