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