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