1#!/usr/bin/python3 -i 2# 3# Copyright (c) 2013-2020 The Khronos Group Inc. 4# 5# SPDX-License-Identifier: Apache-2.0 6"""Base class for source/header/doc generators, as well as some utility functions.""" 7 8from __future__ import unicode_literals 9 10import io 11import os 12import pdb 13import re 14import shutil 15import sys 16import tempfile 17try: 18 from pathlib import Path 19except ImportError: 20 from pathlib2 import Path 21 22from spec_tools.util import getElemName, getElemType 23 24 25def write(*args, **kwargs): 26 file = kwargs.pop('file', sys.stdout) 27 end = kwargs.pop('end', '\n') 28 file.write(' '.join(str(arg) for arg in args)) 29 file.write(end) 30 31 32def noneStr(s): 33 """Return string argument, or "" if argument is None. 34 35 Used in converting etree Elements into text. 36 s - string to convert""" 37 if s: 38 return s 39 return "" 40 41 42def enquote(s): 43 """Return string argument with surrounding quotes, 44 for serialization into Python code.""" 45 if s: 46 return "'{}'".format(s) 47 return None 48 49 50def regSortCategoryKey(feature): 51 """Sort key for regSortFeatures. 52 Sorts by category of the feature name string: 53 54 - Core API features (those defined with a `<feature>` tag) 55 - ARB/KHR/OES (Khronos extensions) 56 - other (EXT/vendor extensions)""" 57 58 if feature.elem.tag == 'feature': 59 return 0 60 if (feature.category == 'ARB' 61 or feature.category == 'KHR' 62 or feature.category == 'OES'): 63 return 1 64 65 return 2 66 67 68def regSortOrderKey(feature): 69 """Sort key for regSortFeatures - key is the sortorder attribute.""" 70 71 # print("regSortOrderKey {} -> {}".format(feature.name, feature.sortorder)) 72 return feature.sortorder 73 74 75def regSortFeatureVersionKey(feature): 76 """Sort key for regSortFeatures - key is the feature version. 77 `<extension>` elements all have version number 0.""" 78 79 return float(feature.versionNumber) 80 81 82def regSortExtensionNumberKey(feature): 83 """Sort key for regSortFeatures - key is the extension number. 84 `<feature>` elements all have extension number 0.""" 85 86 return int(feature.number) 87 88 89def regSortFeatures(featureList): 90 """Default sort procedure for features. 91 92 - Sorts by explicit sort order (default 0) relative to other features 93 - then by feature category ('feature' or 'extension'), 94 - then by version number (for features) 95 - then by extension number (for extensions)""" 96 featureList.sort(key=regSortExtensionNumberKey) 97 featureList.sort(key=regSortFeatureVersionKey) 98 featureList.sort(key=regSortCategoryKey) 99 featureList.sort(key=regSortOrderKey) 100 101 102class GeneratorOptions: 103 """Base class for options used during header/documentation production. 104 105 These options are target language independent, and used by 106 Registry.apiGen() and by base OutputGenerator objects.""" 107 108 def __init__(self, 109 conventions=None, 110 filename=None, 111 directory='.', 112 genpath=None, 113 apiname=None, 114 profile=None, 115 versions='.*', 116 emitversions='.*', 117 defaultExtensions=None, 118 addExtensions=None, 119 removeExtensions=None, 120 emitExtensions=None, 121 emitSpirv=None, 122 reparentEnums=True, 123 sortProcedure=regSortFeatures): 124 """Constructor. 125 126 Arguments: 127 128 - conventions - may be mandatory for some generators: 129 an object that implements ConventionsBase 130 - filename - basename of file to generate, or None to write to stdout. 131 - directory - directory in which to generate files 132 - genpath - path to previously generated files, such as api.py 133 - apiname - string matching `<api>` 'apiname' attribute, e.g. 'gl'. 134 - profile - string specifying API profile , e.g. 'core', or None. 135 - versions - regex matching API versions to process interfaces for. 136 Normally `'.*'` or `'[0-9][.][0-9]'` to match all defined versions. 137 - emitversions - regex matching API versions to actually emit 138 interfaces for (though all requested versions are considered 139 when deciding which interfaces to generate). For GL 4.3 glext.h, 140 this might be `'1[.][2-5]|[2-4][.][0-9]'`. 141 - defaultExtensions - If not None, a string which must in its 142 entirety match the pattern in the "supported" attribute of 143 the `<extension>`. Defaults to None. Usually the same as apiname. 144 - addExtensions - regex matching names of additional extensions 145 to include. Defaults to None. 146 - removeExtensions - regex matching names of extensions to 147 remove (after defaultExtensions and addExtensions). Defaults 148 to None. 149 - emitExtensions - regex matching names of extensions to actually emit 150 interfaces for (though all requested versions are considered when 151 deciding which interfaces to generate). 152 to None. 153 - emitSpirv - regex matching names of extensions and capabilities 154 to actually emit interfaces for. 155 - reparentEnums - move <enum> elements which extend an enumerated 156 type from <feature> or <extension> elements to the target <enums> 157 element. This is required for almost all purposes, but the 158 InterfaceGenerator relies on the list of interfaces in the <feature> 159 or <extension> being complete. Defaults to True. 160 - sortProcedure - takes a list of FeatureInfo objects and sorts 161 them in place to a preferred order in the generated output. 162 Default is core API versions, ARB/KHR/OES extensions, all other 163 extensions, by core API version number or extension number in each 164 group. 165 166 The regex patterns can be None or empty, in which case they match 167 nothing.""" 168 self.conventions = conventions 169 """may be mandatory for some generators: 170 an object that implements ConventionsBase""" 171 172 self.filename = filename 173 "basename of file to generate, or None to write to stdout." 174 175 self.genpath = genpath 176 """path to previously generated files, such as api.py""" 177 178 self.directory = directory 179 "directory in which to generate filename" 180 181 self.apiname = apiname 182 "string matching `<api>` 'apiname' attribute, e.g. 'gl'." 183 184 self.profile = profile 185 "string specifying API profile , e.g. 'core', or None." 186 187 self.versions = self.emptyRegex(versions) 188 """regex matching API versions to process interfaces for. 189 Normally `'.*'` or `'[0-9][.][0-9]'` to match all defined versions.""" 190 191 self.emitversions = self.emptyRegex(emitversions) 192 """regex matching API versions to actually emit 193 interfaces for (though all requested versions are considered 194 when deciding which interfaces to generate). For GL 4.3 glext.h, 195 this might be `'1[.][2-5]|[2-4][.][0-9]'`.""" 196 197 self.defaultExtensions = defaultExtensions 198 """If not None, a string which must in its 199 entirety match the pattern in the "supported" attribute of 200 the `<extension>`. Defaults to None. Usually the same as apiname.""" 201 202 self.addExtensions = self.emptyRegex(addExtensions) 203 """regex matching names of additional extensions 204 to include. Defaults to None.""" 205 206 self.removeExtensions = self.emptyRegex(removeExtensions) 207 """regex matching names of extensions to 208 remove (after defaultExtensions and addExtensions). Defaults 209 to None.""" 210 211 self.emitExtensions = self.emptyRegex(emitExtensions) 212 """regex matching names of extensions to actually emit 213 interfaces for (though all requested versions are considered when 214 deciding which interfaces to generate).""" 215 216 self.emitSpirv = self.emptyRegex(emitSpirv) 217 """regex matching names of extensions and capabilities 218 to actually emit interfaces for.""" 219 220 self.reparentEnums = reparentEnums 221 """boolean specifying whether to remove <enum> elements from 222 <feature> or <extension> when extending an <enums> type.""" 223 224 self.sortProcedure = sortProcedure 225 """takes a list of FeatureInfo objects and sorts 226 them in place to a preferred order in the generated output. 227 Default is core API versions, ARB/KHR/OES extensions, all 228 other extensions, alphabetically within each group.""" 229 230 self.codeGenerator = False 231 """True if this generator makes compilable code""" 232 233 def emptyRegex(self, pat): 234 """Substitute a regular expression which matches no version 235 or extension names for None or the empty string.""" 236 if not pat: 237 return '_nomatch_^' 238 239 return pat 240 241 242class OutputGenerator: 243 """Generate specified API interfaces in a specific style, such as a C header. 244 245 Base class for generating API interfaces. 246 Manages basic logic, logging, and output file control. 247 Derived classes actually generate formatted output. 248 """ 249 250 # categoryToPath - map XML 'category' to include file directory name 251 categoryToPath = { 252 'bitmask': 'flags', 253 'enum': 'enums', 254 'funcpointer': 'funcpointers', 255 'handle': 'handles', 256 'define': 'defines', 257 'basetype': 'basetypes', 258 } 259 260 def __init__(self, errFile=sys.stderr, warnFile=sys.stderr, diagFile=sys.stdout): 261 """Constructor 262 263 - errFile, warnFile, diagFile - file handles to write errors, 264 warnings, diagnostics to. May be None to not write.""" 265 self.outFile = None 266 self.errFile = errFile 267 self.warnFile = warnFile 268 self.diagFile = diagFile 269 # Internal state 270 self.featureName = None 271 self.genOpts = None 272 self.registry = None 273 self.featureDictionary = {} 274 # Used for extension enum value generation 275 self.extBase = 1000000000 276 self.extBlockSize = 1000 277 self.madeDirs = {} 278 279 # API dictionary, which may be loaded by the beginFile method of 280 # derived generators. 281 self.apidict = None 282 283 def logMsg(self, level, *args): 284 """Write a message of different categories to different 285 destinations. 286 287 - `level` 288 - 'diag' (diagnostic, voluminous) 289 - 'warn' (warning) 290 - 'error' (fatal error - raises exception after logging) 291 292 - `*args` - print()-style arguments to direct to corresponding log""" 293 if level == 'error': 294 strfile = io.StringIO() 295 write('ERROR:', *args, file=strfile) 296 if self.errFile is not None: 297 write(strfile.getvalue(), file=self.errFile) 298 raise UserWarning(strfile.getvalue()) 299 elif level == 'warn': 300 if self.warnFile is not None: 301 write('WARNING:', *args, file=self.warnFile) 302 elif level == 'diag': 303 if self.diagFile is not None: 304 write('DIAG:', *args, file=self.diagFile) 305 else: 306 raise UserWarning( 307 '*** FATAL ERROR in Generator.logMsg: unknown level:' + level) 308 309 def enumToValue(self, elem, needsNum): 310 """Parse and convert an `<enum>` tag into a value. 311 312 Returns a list: 313 314 - first element - integer representation of the value, or None 315 if needsNum is False. The value must be a legal number 316 if needsNum is True. 317 - second element - string representation of the value 318 319 There are several possible representations of values. 320 321 - A 'value' attribute simply contains the value. 322 - A 'bitpos' attribute defines a value by specifying the bit 323 position which is set in that value. 324 - An 'offset','extbase','extends' triplet specifies a value 325 as an offset to a base value defined by the specified 326 'extbase' extension name, which is then cast to the 327 typename specified by 'extends'. This requires probing 328 the registry database, and imbeds knowledge of the 329 API extension enum scheme in this function. 330 - An 'alias' attribute contains the name of another enum 331 which this is an alias of. The other enum must be 332 declared first when emitting this enum.""" 333 name = elem.get('name') 334 numVal = None 335 if 'value' in elem.keys(): 336 value = elem.get('value') 337 # print('About to translate value =', value, 'type =', type(value)) 338 if needsNum: 339 numVal = int(value, 0) 340 # If there's a non-integer, numeric 'type' attribute (e.g. 'u' or 341 # 'ull'), append it to the string value. 342 # t = enuminfo.elem.get('type') 343 # if t is not None and t != '' and t != 'i' and t != 's': 344 # value += enuminfo.type 345 self.logMsg('diag', 'Enum', name, '-> value [', numVal, ',', value, ']') 346 return [numVal, value] 347 if 'bitpos' in elem.keys(): 348 value = elem.get('bitpos') 349 bitpos = int(value, 0) 350 numVal = 1 << bitpos 351 value = '0x%08x' % numVal 352 if bitpos >= 32: 353 value = value + 'ULL' 354 self.logMsg('diag', 'Enum', name, '-> bitpos [', numVal, ',', value, ']') 355 return [numVal, value] 356 if 'offset' in elem.keys(): 357 # Obtain values in the mapping from the attributes 358 enumNegative = False 359 offset = int(elem.get('offset'), 0) 360 extnumber = int(elem.get('extnumber'), 0) 361 extends = elem.get('extends') 362 if 'dir' in elem.keys(): 363 enumNegative = True 364 self.logMsg('diag', 'Enum', name, 'offset =', offset, 365 'extnumber =', extnumber, 'extends =', extends, 366 'enumNegative =', enumNegative) 367 # Now determine the actual enumerant value, as defined 368 # in the "Layers and Extensions" appendix of the spec. 369 numVal = self.extBase + (extnumber - 1) * self.extBlockSize + offset 370 if enumNegative: 371 numVal *= -1 372 value = '%d' % numVal 373 # More logic needed! 374 self.logMsg('diag', 'Enum', name, '-> offset [', numVal, ',', value, ']') 375 return [numVal, value] 376 if 'alias' in elem.keys(): 377 return [None, elem.get('alias')] 378 return [None, None] 379 380 def checkDuplicateEnums(self, enums): 381 """Check enumerated values for duplicates. 382 383 - enums - list of `<enum>` Elements 384 385 returns the list with duplicates stripped""" 386 # Dictionaries indexed by name and numeric value. 387 # Entries are [ Element, numVal, strVal ] matching name or value 388 389 nameMap = {} 390 valueMap = {} 391 392 stripped = [] 393 for elem in enums: 394 name = elem.get('name') 395 (numVal, strVal) = self.enumToValue(elem, True) 396 397 if name in nameMap: 398 # Duplicate name found; check values 399 (name2, numVal2, strVal2) = nameMap[name] 400 401 # Duplicate enum values for the same name are benign. This 402 # happens when defining the same enum conditionally in 403 # several extension blocks. 404 if (strVal2 == strVal or (numVal is not None 405 and numVal == numVal2)): 406 True 407 # self.logMsg('info', 'checkDuplicateEnums: Duplicate enum (' + name + 408 # ') found with the same value:' + strVal) 409 else: 410 self.logMsg('warn', 'checkDuplicateEnums: Duplicate enum (' + name 411 + ') found with different values:' + strVal 412 + ' and ' + strVal2) 413 414 # Don't add the duplicate to the returned list 415 continue 416 elif numVal in valueMap: 417 # Duplicate value found (such as an alias); report it, but 418 # still add this enum to the list. 419 (name2, numVal2, strVal2) = valueMap[numVal] 420 421 msg = 'Two enums found with the same value: {} = {} = {}'.format( 422 name, name2.get('name'), strVal) 423 self.logMsg('error', msg) 424 425 # Track this enum to detect followon duplicates 426 nameMap[name] = [elem, numVal, strVal] 427 if numVal is not None: 428 valueMap[numVal] = [elem, numVal, strVal] 429 430 # Add this enum to the list 431 stripped.append(elem) 432 433 # Return the list 434 return stripped 435 436 def buildEnumCDecl(self, expand, groupinfo, groupName): 437 """Generate the C declaration for an enum""" 438 groupElem = groupinfo.elem 439 440 # Determine the required bit width for the enum group. 441 # 32 is the default, which generates C enum types for the values. 442 bitwidth = 32 443 444 # If the constFlagBits preference is set, 64 is the default for bitmasks 445 if self.genOpts.conventions.constFlagBits and groupElem.get('type') == 'bitmask': 446 bitwidth = 64 447 448 # Check for an explicitly defined bitwidth, which will override any defaults. 449 if groupElem.get('bitwidth'): 450 try: 451 bitwidth = int(groupElem.get('bitwidth')) 452 except ValueError as ve: 453 self.logMsg('error', 'Invalid value for bitwidth attribute (', groupElem.get('bitwidth'), ') for ', groupName, ' - must be an integer value\n') 454 exit(1) 455 456 # Bitmask types support 64-bit flags, so have different handling 457 if groupElem.get('type') == 'bitmask': 458 459 # Validate the bitwidth and generate values appropriately 460 # Bitmask flags up to 64-bit are generated as static const uint64_t values 461 # Bitmask flags up to 32-bit are generated as C enum values 462 if bitwidth > 64: 463 self.logMsg('error', 'Invalid value for bitwidth attribute (', groupElem.get('bitwidth'), ') for bitmask type ', groupName, ' - must be less than or equal to 64\n') 464 exit(1) 465 elif bitwidth > 32: 466 return self.buildEnumCDecl_Bitmask(groupinfo, groupName) 467 else: 468 return self.buildEnumCDecl_Enum(expand, groupinfo, groupName) 469 else: 470 # Validate the bitwidth and generate values appropriately 471 # Enum group types up to 32-bit are generated as C enum values 472 if bitwidth > 32: 473 self.logMsg('error', 'Invalid value for bitwidth attribute (', groupElem.get('bitwidth'), ') for enum type ', groupName, ' - must be less than or equal to 32\n') 474 exit(1) 475 else: 476 return self.buildEnumCDecl_Enum(expand, groupinfo, groupName) 477 478 def buildEnumCDecl_Bitmask(self, groupinfo, groupName): 479 """Generate the C declaration for an "enum" that is actually a 480 set of flag bits""" 481 groupElem = groupinfo.elem 482 flagTypeName = groupinfo.flagType.elem.get('name') 483 484 # Prefix 485 body = "// Flag bits for " + flagTypeName + "\n" 486 487 # Maximum allowable value for a flag (unsigned 64-bit integer) 488 maxValidValue = 2**(64) - 1 489 minValidValue = 0 490 491 # Loop over the nested 'enum' tags. 492 for elem in groupElem.findall('enum'): 493 # Convert the value to an integer and use that to track min/max. 494 # Values of form -(number) are accepted but nothing more complex. 495 # Should catch exceptions here for more complex constructs. Not yet. 496 (numVal, strVal) = self.enumToValue(elem, True) 497 name = elem.get('name') 498 499 # Range check for the enum value 500 if numVal is not None and (numVal > maxValidValue or numVal < minValidValue): 501 self.logMsg('error', 'Allowable range for flag types in C is [', minValidValue, ',', maxValidValue, '], but', name, 'flag has a value outside of this (', strVal, ')\n') 502 exit(1) 503 504 body += self.genRequirements(name, mustBeFound = False) 505 body += "static const {} {} = {};\n".format(flagTypeName, name, strVal) 506 507 # Postfix 508 509 return ("bitmask", body) 510 511 def buildEnumCDecl_Enum(self, expand, groupinfo, groupName): 512 """Generate the C declaration for an enumerated type""" 513 groupElem = groupinfo.elem 514 515 # Break the group name into prefix and suffix portions for range 516 # enum generation 517 expandName = re.sub(r'([0-9a-z_])([A-Z0-9])', r'\1_\2', groupName).upper() 518 expandPrefix = expandName 519 expandSuffix = '' 520 expandSuffixMatch = re.search(r'[A-Z][A-Z]+$', groupName) 521 if expandSuffixMatch: 522 expandSuffix = '_' + expandSuffixMatch.group() 523 # Strip off the suffix from the prefix 524 expandPrefix = expandName.rsplit(expandSuffix, 1)[0] 525 526 # Prefix 527 body = ["typedef enum %s {" % groupName] 528 529 # @@ Should use the type="bitmask" attribute instead 530 isEnum = ('FLAG_BITS' not in expandPrefix) 531 532 # Allowable range for a C enum - which is that of a signed 32-bit integer 533 maxValidValue = 2**(32 - 1) - 1 534 minValidValue = (maxValidValue * -1) - 1 535 536 537 # Get a list of nested 'enum' tags. 538 enums = groupElem.findall('enum') 539 540 # Check for and report duplicates, and return a list with them 541 # removed. 542 enums = self.checkDuplicateEnums(enums) 543 544 # Loop over the nested 'enum' tags. Keep track of the minimum and 545 # maximum numeric values, if they can be determined; but only for 546 # core API enumerants, not extension enumerants. This is inferred 547 # by looking for 'extends' attributes. 548 minName = None 549 550 # Accumulate non-numeric enumerant values separately and append 551 # them following the numeric values, to allow for aliases. 552 # NOTE: this doesn't do a topological sort yet, so aliases of 553 # aliases can still get in the wrong order. 554 aliasText = [] 555 556 for elem in enums: 557 # Convert the value to an integer and use that to track min/max. 558 # Values of form -(number) are accepted but nothing more complex. 559 # Should catch exceptions here for more complex constructs. Not yet. 560 (numVal, strVal) = self.enumToValue(elem, True) 561 name = elem.get('name') 562 563 # Extension enumerants are only included if they are required 564 if self.isEnumRequired(elem): 565 # Indent requirements comment, if there is one 566 decl = self.genRequirements(name, mustBeFound = False) 567 if decl != '': 568 decl = ' ' + decl 569 decl += " {} = {},".format(name, strVal) 570 if numVal is not None: 571 body.append(decl) 572 else: 573 aliasText.append(decl) 574 575 # Range check for the enum value 576 if numVal is not None and (numVal > maxValidValue or numVal < minValidValue): 577 self.logMsg('error', 'Allowable range for C enum types is [', minValidValue, ',', maxValidValue, '], but', name, 'has a value outside of this (', strVal, ')\n') 578 exit(1) 579 580 581 # Don't track min/max for non-numbers (numVal is None) 582 if isEnum and numVal is not None and elem.get('extends') is None: 583 if minName is None: 584 minName = maxName = name 585 minValue = maxValue = numVal 586 elif numVal < minValue: 587 minName = name 588 minValue = numVal 589 elif numVal > maxValue: 590 maxName = name 591 maxValue = numVal 592 593 # Now append the non-numeric enumerant values 594 body.extend(aliasText) 595 596 # Generate min/max value tokens - legacy use case. 597 if isEnum and expand: 598 body.extend((" {}_BEGIN_RANGE{} = {},".format(expandPrefix, expandSuffix, minName), 599 " {}_END_RANGE{} = {},".format( 600 expandPrefix, expandSuffix, maxName), 601 " {}_RANGE_SIZE{} = ({} - {} + 1),".format(expandPrefix, expandSuffix, maxName, minName))) 602 603 # Generate a range-padding value to ensure the enum is 32 bits, but 604 # only in code generators, so it doesn't appear in documentation 605 if (self.genOpts.codeGenerator or 606 self.conventions.generate_max_enum_in_docs): 607 body.append(" {}_MAX_ENUM{} = 0x7FFFFFFF".format( 608 expandPrefix, expandSuffix)) 609 610 # Postfix 611 body.append("} %s;" % groupName) 612 613 # Determine appropriate section for this declaration 614 if groupElem.get('type') == 'bitmask': 615 section = 'bitmask' 616 else: 617 section = 'group' 618 619 return (section, '\n'.join(body)) 620 621 def makeDir(self, path): 622 """Create a directory, if not already done. 623 624 Generally called from derived generators creating hierarchies.""" 625 self.logMsg('diag', 'OutputGenerator::makeDir(' + path + ')') 626 if path not in self.madeDirs: 627 # This can get race conditions with multiple writers, see 628 # https://stackoverflow.com/questions/273192/ 629 if not os.path.exists(path): 630 os.makedirs(path) 631 self.madeDirs[path] = None 632 633 def beginFile(self, genOpts, suppress = False): 634 """Start a new interface file 635 636 - genOpts - GeneratorOptions controlling what's generated and how""" 637 self.suppress = suppress 638 self.genOpts = genOpts 639 self.should_insert_may_alias_macro = \ 640 self.genOpts.conventions.should_insert_may_alias_macro(self.genOpts) 641 642 # Try to import the API dictionary, api.py, if it exists. Nothing in 643 # api.py cannot be extracted directly from the XML, and in the 644 # future we should do that. 645 if self.genOpts.genpath is not None: 646 try: 647 sys.path.insert(0, self.genOpts.genpath) 648 import api 649 self.apidict = api 650 except ImportError: 651 self.apidict = None 652 653 self.conventions = genOpts.conventions 654 655 # Open a temporary file for accumulating output. 656 if self.genOpts.filename is not None and not self.suppress: 657 self.outFile = tempfile.NamedTemporaryFile(mode='w', encoding='utf-8', newline='\n', delete=False) 658 else: 659 self.outFile = sys.stdout 660 661 def endFile(self): 662 if self.errFile: 663 self.errFile.flush() 664 if self.warnFile: 665 self.warnFile.flush() 666 if self.diagFile: 667 self.diagFile.flush() 668 self.outFile.flush() 669 if self.outFile != sys.stdout and self.outFile != sys.stderr: 670 self.outFile.close() 671 672 # On successfully generating output, move the temporary file to the 673 # target file. 674 if self.genOpts.filename is not None: 675 if sys.platform == 'win32': 676 directory = Path(self.genOpts.directory) 677 if not Path.exists(directory): 678 os.makedirs(directory) 679 shutil.copy(self.outFile.name, self.genOpts.directory + '/' + self.genOpts.filename) 680 os.remove(self.outFile.name) 681 self.genOpts = None 682 683 def beginFeature(self, interface, emit): 684 """Write interface for a feature and tag generated features as having been done. 685 686 - interface - element for the `<version>` / `<extension>` to generate 687 - emit - actually write to the header only when True""" 688 self.emit = emit 689 self.featureName = interface.get('name') 690 # If there's an additional 'protect' attribute in the feature, save it 691 self.featureExtraProtect = interface.get('protect') 692 693 def endFeature(self): 694 """Finish an interface file, closing it when done. 695 696 Derived classes responsible for emitting feature""" 697 self.featureName = None 698 self.featureExtraProtect = None 699 700 def genRequirements(self, name, mustBeFound = True): 701 """Generate text showing what core versions and extensions introduce 702 an API. This exists in the base Generator class because it's used by 703 the shared enumerant-generating interfaces (buildEnumCDecl, etc.). 704 Here it returns an empty string for most generators, but can be 705 overridden by e.g. DocGenerator. 706 707 - name - name of the API 708 - mustBeFound - If True, when requirements for 'name' cannot be 709 determined, a warning comment is generated. 710 """ 711 712 return '' 713 714 def validateFeature(self, featureType, featureName): 715 """Validate we're generating something only inside a `<feature>` tag""" 716 if self.featureName is None: 717 raise UserWarning('Attempt to generate', featureType, 718 featureName, 'when not in feature') 719 720 def genType(self, typeinfo, name, alias): 721 """Generate interface for a type 722 723 - typeinfo - TypeInfo for a type 724 725 Extend to generate as desired in your derived class.""" 726 self.validateFeature('type', name) 727 728 def genStruct(self, typeinfo, typeName, alias): 729 """Generate interface for a C "struct" type. 730 731 - typeinfo - TypeInfo for a type interpreted as a struct 732 733 Extend to generate as desired in your derived class.""" 734 self.validateFeature('struct', typeName) 735 736 # The mixed-mode <member> tags may contain no-op <comment> tags. 737 # It is convenient to remove them here where all output generators 738 # will benefit. 739 for member in typeinfo.elem.findall('.//member'): 740 for comment in member.findall('comment'): 741 member.remove(comment) 742 743 def genGroup(self, groupinfo, groupName, alias): 744 """Generate interface for a group of enums (C "enum") 745 746 - groupinfo - GroupInfo for a group. 747 748 Extend to generate as desired in your derived class.""" 749 750 self.validateFeature('group', groupName) 751 752 def genEnum(self, enuminfo, typeName, alias): 753 """Generate interface for an enum (constant). 754 755 - enuminfo - EnumInfo for an enum 756 - name - enum name 757 758 Extend to generate as desired in your derived class.""" 759 self.validateFeature('enum', typeName) 760 761 def genCmd(self, cmd, cmdinfo, alias): 762 """Generate interface for a command. 763 764 - cmdinfo - CmdInfo for a command 765 766 Extend to generate as desired in your derived class.""" 767 self.validateFeature('command', cmdinfo) 768 769 def genSpirv(self, spirv, spirvinfo, alias): 770 """Generate interface for a spirv element. 771 772 - spirvinfo - SpirvInfo for a command 773 774 Extend to generate as desired in your derived class.""" 775 return 776 777 def makeProtoName(self, name, tail): 778 """Turn a `<proto>` `<name>` into C-language prototype 779 and typedef declarations for that name. 780 781 - name - contents of `<name>` tag 782 - tail - whatever text follows that tag in the Element""" 783 return self.genOpts.apientry + name + tail 784 785 def makeTypedefName(self, name, tail): 786 """Make the function-pointer typedef name for a command.""" 787 return '(' + self.genOpts.apientryp + 'PFN_' + name + tail + ')' 788 789 def makeCParamDecl(self, param, aligncol): 790 """Return a string which is an indented, formatted 791 declaration for a `<param>` or `<member>` block (e.g. function parameter 792 or structure/union member). 793 794 - param - Element (`<param>` or `<member>`) to format 795 - aligncol - if non-zero, attempt to align the nested `<name>` element 796 at this column""" 797 indent = ' ' 798 paramdecl = indent + noneStr(param.text) 799 for elem in param: 800 text = noneStr(elem.text) 801 tail = noneStr(elem.tail) 802 803 if self.should_insert_may_alias_macro and self.genOpts.conventions.is_voidpointer_alias(elem.tag, text, tail): 804 # OpenXR-specific macro insertion - but not in apiinc for the spec 805 tail = self.genOpts.conventions.make_voidpointer_alias(tail) 806 if elem.tag == 'name' and aligncol > 0: 807 self.logMsg('diag', 'Aligning parameter', elem.text, 'to column', self.genOpts.alignFuncParam) 808 # Align at specified column, if possible 809 paramdecl = paramdecl.rstrip() 810 oldLen = len(paramdecl) 811 # This works around a problem where very long type names - 812 # longer than the alignment column - would run into the tail 813 # text. 814 paramdecl = paramdecl.ljust(aligncol - 1) + ' ' 815 newLen = len(paramdecl) 816 self.logMsg('diag', 'Adjust length of parameter decl from', oldLen, 'to', newLen, ':', paramdecl) 817 paramdecl += text + tail 818 if aligncol == 0: 819 # Squeeze out multiple spaces other than the indentation 820 paramdecl = indent + ' '.join(paramdecl.split()) 821 return paramdecl 822 823 def getCParamTypeLength(self, param): 824 """Return the length of the type field is an indented, formatted 825 declaration for a `<param>` or `<member>` block (e.g. function parameter 826 or structure/union member). 827 828 - param - Element (`<param>` or `<member>`) to identify""" 829 830 # Allow for missing <name> tag 831 newLen = 0 832 paramdecl = ' ' + noneStr(param.text) 833 for elem in param: 834 text = noneStr(elem.text) 835 tail = noneStr(elem.tail) 836 837 if self.should_insert_may_alias_macro and self.genOpts.conventions.is_voidpointer_alias(elem.tag, text, tail): 838 # OpenXR-specific macro insertion 839 tail = self.genOpts.conventions.make_voidpointer_alias(tail) 840 if elem.tag == 'name': 841 # Align at specified column, if possible 842 newLen = len(paramdecl.rstrip()) 843 self.logMsg('diag', 'Identifying length of', elem.text, 'as', newLen) 844 paramdecl += text + tail 845 846 return newLen 847 848 def getMaxCParamTypeLength(self, info): 849 """Return the length of the longest type field for a member/parameter. 850 851 - info - TypeInfo or CommandInfo. 852 """ 853 lengths = (self.getCParamTypeLength(member) 854 for member in info.getMembers()) 855 return max(lengths) 856 857 def getHandleParent(self, typename): 858 """Get the parent of a handle object.""" 859 info = self.registry.typedict.get(typename) 860 if info is None: 861 return None 862 863 elem = info.elem 864 if elem is not None: 865 return elem.get('parent') 866 867 return None 868 869 def iterateHandleAncestors(self, typename): 870 """Iterate through the ancestors of a handle type.""" 871 current = self.getHandleParent(typename) 872 while current is not None: 873 yield current 874 current = self.getHandleParent(current) 875 876 def getHandleAncestors(self, typename): 877 """Get the ancestors of a handle object.""" 878 return list(self.iterateHandleAncestors(typename)) 879 880 def getTypeCategory(self, typename): 881 """Get the category of a type.""" 882 info = self.registry.typedict.get(typename) 883 if info is None: 884 return None 885 886 elem = info.elem 887 if elem is not None: 888 return elem.get('category') 889 return None 890 891 def isStructAlwaysValid(self, structname): 892 """Try to do check if a structure is always considered valid (i.e. there's no rules to its acceptance).""" 893 # A conventions object is required for this call. 894 if not self.conventions: 895 raise RuntimeError("To use isStructAlwaysValid, be sure your options include a Conventions object.") 896 897 if self.conventions.type_always_valid(structname): 898 return True 899 900 category = self.getTypeCategory(structname) 901 if self.conventions.category_requires_validation(category): 902 return False 903 904 info = self.registry.typedict.get(structname) 905 assert(info is not None) 906 907 members = info.getMembers() 908 909 for member in members: 910 member_name = getElemName(member) 911 if member_name in (self.conventions.structtype_member_name, 912 self.conventions.nextpointer_member_name): 913 return False 914 915 if member.get('noautovalidity'): 916 return False 917 918 member_type = getElemType(member) 919 920 if member_type in ('void', 'char') or self.paramIsArray(member) or self.paramIsPointer(member): 921 return False 922 923 if self.conventions.type_always_valid(member_type): 924 continue 925 926 member_category = self.getTypeCategory(member_type) 927 928 if self.conventions.category_requires_validation(member_category): 929 return False 930 931 if member_category in ('struct', 'union'): 932 if self.isStructAlwaysValid(member_type) is False: 933 return False 934 935 return True 936 937 def isEnumRequired(self, elem): 938 """Return True if this `<enum>` element is 939 required, False otherwise 940 941 - elem - `<enum>` element to test""" 942 required = elem.get('required') is not None 943 self.logMsg('diag', 'isEnumRequired:', elem.get('name'), 944 '->', required) 945 return required 946 947 # @@@ This code is overridden by equivalent code now run in 948 # @@@ Registry.generateFeature 949 950 required = False 951 952 extname = elem.get('extname') 953 if extname is not None: 954 # 'supported' attribute was injected when the <enum> element was 955 # moved into the <enums> group in Registry.parseTree() 956 if self.genOpts.defaultExtensions == elem.get('supported'): 957 required = True 958 elif re.match(self.genOpts.addExtensions, extname) is not None: 959 required = True 960 elif elem.get('version') is not None: 961 required = re.match(self.genOpts.emitversions, elem.get('version')) is not None 962 else: 963 required = True 964 965 return required 966 967 def makeCDecls(self, cmd): 968 """Return C prototype and function pointer typedef for a 969 `<command>` Element, as a two-element list of strings. 970 971 - cmd - Element containing a `<command>` tag""" 972 proto = cmd.find('proto') 973 params = cmd.findall('param') 974 # Begin accumulating prototype and typedef strings 975 pdecl = self.genOpts.apicall 976 tdecl = 'typedef ' 977 978 # Insert the function return type/name. 979 # For prototypes, add APIENTRY macro before the name 980 # For typedefs, add (APIENTRY *<name>) around the name and 981 # use the PFN_cmdnameproc naming convention. 982 # Done by walking the tree for <proto> element by element. 983 # etree has elem.text followed by (elem[i], elem[i].tail) 984 # for each child element and any following text 985 # Leading text 986 pdecl += noneStr(proto.text) 987 tdecl += noneStr(proto.text) 988 # For each child element, if it's a <name> wrap in appropriate 989 # declaration. Otherwise append its contents and tail contents. 990 for elem in proto: 991 text = noneStr(elem.text) 992 tail = noneStr(elem.tail) 993 if elem.tag == 'name': 994 pdecl += self.makeProtoName(text, tail) 995 tdecl += self.makeTypedefName(text, tail) 996 else: 997 pdecl += text + tail 998 tdecl += text + tail 999 1000 if self.genOpts.alignFuncParam == 0: 1001 # Squeeze out multiple spaces - there is no indentation 1002 pdecl = ' '.join(pdecl.split()) 1003 tdecl = ' '.join(tdecl.split()) 1004 1005 # Now add the parameter declaration list, which is identical 1006 # for prototypes and typedefs. Concatenate all the text from 1007 # a <param> node without the tags. No tree walking required 1008 # since all tags are ignored. 1009 # Uses: self.indentFuncProto 1010 # self.indentFuncPointer 1011 # self.alignFuncParam 1012 n = len(params) 1013 # Indented parameters 1014 if n > 0: 1015 indentdecl = '(\n' 1016 indentdecl += ',\n'.join(self.makeCParamDecl(p, self.genOpts.alignFuncParam) 1017 for p in params) 1018 indentdecl += ');' 1019 else: 1020 indentdecl = '(void);' 1021 # Non-indented parameters 1022 paramdecl = '(' 1023 if n > 0: 1024 paramnames = (''.join(t for t in p.itertext()) 1025 for p in params) 1026 paramdecl += ', '.join(paramnames) 1027 else: 1028 paramdecl += 'void' 1029 paramdecl += ");" 1030 return [pdecl + indentdecl, tdecl + paramdecl] 1031 1032 def newline(self): 1033 """Print a newline to the output file (utility function)""" 1034 write('', file=self.outFile) 1035 1036 def setRegistry(self, registry): 1037 self.registry = registry 1038