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 (numVal, strVal) = self.enumToValue(alias, True, bitwidth, True) 587 decl += "static const {} {} = {};\n".format(flagTypeName, name, strVal) 588 589 if numVal is not None: 590 body += decl 591 else: 592 aliasText += decl 593 594 if protect is not None: 595 body += '#endif\n' 596 597 # Now append the non-numeric enumerant values 598 body += aliasText 599 600 # Postfix 601 602 return ("bitmask", body) 603 604 def buildEnumCDecl_Enum(self, expand, groupinfo, groupName): 605 """Generate the C declaration for an enumerated type""" 606 groupElem = groupinfo.elem 607 608 # Break the group name into prefix and suffix portions for range 609 # enum generation 610 expandName = re.sub(r'([0-9]+|[a-z_])([A-Z0-9])', r'\1_\2', groupName).upper() 611 expandPrefix = expandName 612 expandSuffix = '' 613 expandSuffixMatch = re.search(r'[A-Z][A-Z]+$', groupName) 614 if expandSuffixMatch: 615 expandSuffix = '_' + expandSuffixMatch.group() 616 # Strip off the suffix from the prefix 617 expandPrefix = expandName.rsplit(expandSuffix, 1)[0] 618 619 # Prefix 620 body = ["typedef enum %s {" % groupName] 621 622 # @@ Should use the type="bitmask" attribute instead 623 isEnum = ('FLAG_BITS' not in expandPrefix) 624 625 # Allowable range for a C enum - which is that of a signed 32-bit integer 626 maxValidValue = 2**(32 - 1) - 1 627 minValidValue = (maxValidValue * -1) - 1 628 629 630 # Get a list of nested 'enum' tags. 631 enums = groupElem.findall('enum') 632 633 # Check for and report duplicates, and return a list with them 634 # removed. 635 enums = self.checkDuplicateEnums(enums) 636 637 # Loop over the nested 'enum' tags. Keep track of the minimum and 638 # maximum numeric values, if they can be determined; but only for 639 # core API enumerants, not extension enumerants. This is inferred 640 # by looking for 'extends' attributes. 641 minName = None 642 643 # Accumulate non-numeric enumerant values separately and append 644 # them following the numeric values, to allow for aliases. 645 # NOTE: this does not do a topological sort yet, so aliases of 646 # aliases can still get in the wrong order. 647 aliasText = [] 648 649 for elem in enums: 650 # Convert the value to an integer and use that to track min/max. 651 # Values of form -(number) are accepted but nothing more complex. 652 # Should catch exceptions here for more complex constructs. Not yet. 653 (numVal, strVal) = self.enumToValue(elem, True) 654 name = elem.get('name') 655 656 # Extension enumerants are only included if they are required 657 if self.isEnumRequired(elem): 658 decl = '' 659 660 protect = elem.get('protect') 661 if protect is not None: 662 decl += '#ifdef {}\n'.format(protect) 663 664 # Indent requirements comment, if there is one 665 requirements = self.genRequirements(name, mustBeFound = False) 666 if requirements != '': 667 requirements = ' ' + requirements 668 decl += requirements 669 decl += ' {} = {},'.format(name, strVal) 670 671 if protect is not None: 672 decl += '\n#endif' 673 674 if numVal is not None: 675 body.append(decl) 676 else: 677 aliasText.append(decl) 678 679 # Range check for the enum value 680 if numVal is not None and (numVal > maxValidValue or numVal < minValidValue): 681 self.logMsg('error', 'Allowable range for C enum types is [', minValidValue, ',', maxValidValue, '], but', name, 'has a value outside of this (', strVal, ')\n') 682 exit(1) 683 684 # Do not track min/max for non-numbers (numVal is None) 685 if isEnum and numVal is not None and elem.get('extends') is None: 686 if minName is None: 687 minName = maxName = name 688 minValue = maxValue = numVal 689 elif numVal < minValue: 690 minName = name 691 minValue = numVal 692 elif numVal > maxValue: 693 maxName = name 694 maxValue = numVal 695 696 # Now append the non-numeric enumerant values 697 body.extend(aliasText) 698 699 # Generate min/max value tokens - legacy use case. 700 if isEnum and expand: 701 body.extend((" {}_BEGIN_RANGE{} = {},".format(expandPrefix, expandSuffix, minName), 702 " {}_END_RANGE{} = {},".format( 703 expandPrefix, expandSuffix, maxName), 704 " {}_RANGE_SIZE{} = ({} - {} + 1),".format(expandPrefix, expandSuffix, maxName, minName))) 705 706 # Generate a range-padding value to ensure the enum is 32 bits, but 707 # only in code generators, so it does not appear in documentation 708 if (self.genOpts.codeGenerator or 709 self.conventions.generate_max_enum_in_docs): 710 body.append(" {}_MAX_ENUM{} = 0x7FFFFFFF".format( 711 expandPrefix, expandSuffix)) 712 713 # Postfix 714 body.append("} %s;" % groupName) 715 716 # Determine appropriate section for this declaration 717 if groupElem.get('type') == 'bitmask': 718 section = 'bitmask' 719 else: 720 section = 'group' 721 722 return (section, '\n'.join(body)) 723 724 def buildConstantCDecl(self, enuminfo, name, alias): 725 """Generate the C declaration for a constant (a single <enum> 726 value). 727 728 <enum> tags may specify their values in several ways, but are 729 usually just integers or floating-point numbers.""" 730 731 (_, strVal) = self.enumToValue(enuminfo.elem, False) 732 733 if self.misracppstyle() and enuminfo.elem.get('type') and not alias: 734 # Generate e.g.: static constexpr uint32_t x = ~static_cast<uint32_t>(1U); 735 # This appeases MISRA "underlying type" rules. 736 typeStr = enuminfo.elem.get('type'); 737 invert = '~' in strVal 738 number = strVal.strip("()~UL") 739 if typeStr != "float": 740 number += 'U' 741 strVal = "~" if invert else "" 742 strVal += "static_cast<" + typeStr + ">(" + number + ")" 743 body = 'static constexpr ' + typeStr.ljust(9) + name.ljust(33) + ' {' + strVal + '};' 744 elif enuminfo.elem.get('type') and not alias: 745 # Generate e.g.: #define x (~0ULL) 746 typeStr = enuminfo.elem.get('type'); 747 invert = '~' in strVal 748 paren = '(' in strVal 749 number = strVal.strip("()~UL") 750 if typeStr != "float": 751 if typeStr == "uint64_t": 752 number += 'ULL' 753 else: 754 number += 'U' 755 strVal = "~" if invert else "" 756 strVal += number 757 if paren: 758 strVal = "(" + strVal + ")"; 759 body = '#define ' + name.ljust(33) + ' ' + strVal; 760 else: 761 body = '#define ' + name.ljust(33) + ' ' + strVal 762 763 return body 764 765 def makeDir(self, path): 766 """Create a directory, if not already done. 767 768 Generally called from derived generators creating hierarchies.""" 769 self.logMsg('diag', 'OutputGenerator::makeDir(' + path + ')') 770 if path not in self.madeDirs: 771 # This can get race conditions with multiple writers, see 772 # https://stackoverflow.com/questions/273192/ 773 if not os.path.exists(path): 774 os.makedirs(path) 775 self.madeDirs[path] = None 776 777 def beginFile(self, genOpts): 778 """Start a new interface file 779 780 - genOpts - GeneratorOptions controlling what is generated and how""" 781 self.genOpts = genOpts 782 self.should_insert_may_alias_macro = \ 783 self.genOpts.conventions.should_insert_may_alias_macro(self.genOpts) 784 785 # Try to import the API dictionary, api.py, if it exists. Nothing in 786 # api.py cannot be extracted directly from the XML, and in the 787 # future we should do that. 788 if self.genOpts.genpath is not None: 789 try: 790 sys.path.insert(0, self.genOpts.genpath) 791 import api 792 self.apidict = api 793 except ImportError: 794 self.apidict = None 795 796 self.conventions = genOpts.conventions 797 798 # Open a temporary file for accumulating output. 799 if self.genOpts.filename is not None: 800 self.outFile = tempfile.NamedTemporaryFile(mode='w', encoding='utf-8', newline='\n', delete=False) 801 else: 802 self.outFile = sys.stdout 803 804 def endFile(self): 805 if self.errFile: 806 self.errFile.flush() 807 if self.warnFile: 808 self.warnFile.flush() 809 if self.diagFile: 810 self.diagFile.flush() 811 if self.outFile != sys.stdout and self.outFile != sys.stderr: 812 self.outFile.close() 813 814 # On successfully generating output, move the temporary file to the 815 # target file. 816 if self.genOpts.filename is not None: 817 if sys.platform == 'win32': 818 directory = Path(self.genOpts.directory) 819 if not Path.exists(directory): 820 os.makedirs(directory) 821 shutil.copy(self.outFile.name, self.genOpts.directory + '/' + self.genOpts.filename) 822 os.remove(self.outFile.name) 823 self.genOpts = None 824 825 def beginFeature(self, interface, emit): 826 """Write interface for a feature and tag generated features as having been done. 827 828 - interface - element for the `<version>` / `<extension>` to generate 829 - emit - actually write to the header only when True""" 830 self.emit = emit 831 self.featureName = interface.get('name') 832 # If there is an additional 'protect' attribute in the feature, save it 833 self.featureExtraProtect = interface.get('protect') 834 835 def endFeature(self): 836 """Finish an interface file, closing it when done. 837 838 Derived classes responsible for emitting feature""" 839 self.featureName = None 840 self.featureExtraProtect = None 841 842 def genRequirements(self, name, mustBeFound = True): 843 """Generate text showing what core versions and extensions introduce 844 an API. This exists in the base Generator class because it is used by 845 the shared enumerant-generating interfaces (buildEnumCDecl, etc.). 846 Here it returns an empty string for most generators, but can be 847 overridden by e.g. DocGenerator. 848 849 - name - name of the API 850 - mustBeFound - If True, when requirements for 'name' cannot be 851 determined, a warning comment is generated. 852 """ 853 854 return '' 855 856 def validateFeature(self, featureType, featureName): 857 """Validate we are generating something only inside a `<feature>` tag""" 858 if self.featureName is None: 859 raise UserWarning('Attempt to generate', featureType, 860 featureName, 'when not in feature') 861 862 def genType(self, typeinfo, name, alias): 863 """Generate interface for a type 864 865 - typeinfo - TypeInfo for a type 866 867 Extend to generate as desired in your derived class.""" 868 self.validateFeature('type', name) 869 870 def genStruct(self, typeinfo, typeName, alias): 871 """Generate interface for a C "struct" type. 872 873 - typeinfo - TypeInfo for a type interpreted as a struct 874 875 Extend to generate as desired in your derived class.""" 876 self.validateFeature('struct', typeName) 877 878 # The mixed-mode <member> tags may contain no-op <comment> tags. 879 # It is convenient to remove them here where all output generators 880 # will benefit. 881 for member in typeinfo.elem.findall('.//member'): 882 for comment in member.findall('comment'): 883 member.remove(comment) 884 885 def genGroup(self, groupinfo, groupName, alias): 886 """Generate interface for a group of enums (C "enum") 887 888 - groupinfo - GroupInfo for a group. 889 890 Extend to generate as desired in your derived class.""" 891 892 self.validateFeature('group', groupName) 893 894 def genEnum(self, enuminfo, typeName, alias): 895 """Generate interface for an enum (constant). 896 897 - enuminfo - EnumInfo for an enum 898 - name - enum name 899 900 Extend to generate as desired in your derived class.""" 901 self.validateFeature('enum', typeName) 902 903 def genCmd(self, cmd, cmdinfo, alias): 904 """Generate interface for a command. 905 906 - cmdinfo - CmdInfo for a command 907 908 Extend to generate as desired in your derived class.""" 909 self.validateFeature('command', cmdinfo) 910 911 def genSpirv(self, spirv, spirvinfo, alias): 912 """Generate interface for a spirv element. 913 914 - spirvinfo - SpirvInfo for a command 915 916 Extend to generate as desired in your derived class.""" 917 return 918 919 def genFormat(self, format, formatinfo, alias): 920 """Generate interface for a format element. 921 922 - formatinfo - FormatInfo 923 924 Extend to generate as desired in your derived class.""" 925 return 926 927 def makeProtoName(self, name, tail): 928 """Turn a `<proto>` `<name>` into C-language prototype 929 and typedef declarations for that name. 930 931 - name - contents of `<name>` tag 932 - tail - whatever text follows that tag in the Element""" 933 return self.genOpts.apientry + name + tail 934 935 def makeTypedefName(self, name, tail): 936 """Make the function-pointer typedef name for a command.""" 937 return '(' + self.genOpts.apientryp + 'PFN_' + name + tail + ')' 938 939 def makeCParamDecl(self, param, aligncol): 940 """Return a string which is an indented, formatted 941 declaration for a `<param>` or `<member>` block (e.g. function parameter 942 or structure/union member). 943 944 - param - Element (`<param>` or `<member>`) to format 945 - aligncol - if non-zero, attempt to align the nested `<name>` element 946 at this column""" 947 indent = ' ' 948 paramdecl = indent 949 prefix = noneStr(param.text) 950 951 for elem in param: 952 text = noneStr(elem.text) 953 tail = noneStr(elem.tail) 954 955 if self.should_insert_may_alias_macro and self.genOpts.conventions.is_voidpointer_alias(elem.tag, text, tail): 956 # OpenXR-specific macro insertion - but not in apiinc for the spec 957 tail = self.genOpts.conventions.make_voidpointer_alias(tail) 958 if elem.tag == 'name' and aligncol > 0: 959 self.logMsg('diag', 'Aligning parameter', elem.text, 'to column', self.genOpts.alignFuncParam) 960 # Align at specified column, if possible 961 paramdecl = paramdecl.rstrip() 962 oldLen = len(paramdecl) 963 # This works around a problem where very long type names - 964 # longer than the alignment column - would run into the tail 965 # text. 966 paramdecl = paramdecl.ljust(aligncol - 1) + ' ' 967 newLen = len(paramdecl) 968 self.logMsg('diag', 'Adjust length of parameter decl from', oldLen, 'to', newLen, ':', paramdecl) 969 970 if (self.misracppstyle() and prefix.find('const ') != -1): 971 # Change pointer type order from e.g. "const void *" to "void const *". 972 # If the string starts with 'const', reorder it to be after the first type. 973 paramdecl += prefix.replace('const ', '') + text + ' const' + tail 974 else: 975 paramdecl += prefix + text + tail 976 977 # Clear prefix for subsequent iterations 978 prefix = '' 979 980 paramdecl = paramdecl + prefix 981 982 if aligncol == 0: 983 # Squeeze out multiple spaces other than the indentation 984 paramdecl = indent + ' '.join(paramdecl.split()) 985 return paramdecl 986 987 def getCParamTypeLength(self, param): 988 """Return the length of the type field is an indented, formatted 989 declaration for a `<param>` or `<member>` block (e.g. function parameter 990 or structure/union member). 991 992 - param - Element (`<param>` or `<member>`) to identify""" 993 994 # Allow for missing <name> tag 995 newLen = 0 996 paramdecl = ' ' + noneStr(param.text) 997 for elem in param: 998 text = noneStr(elem.text) 999 tail = noneStr(elem.tail) 1000 1001 if self.should_insert_may_alias_macro and self.genOpts.conventions.is_voidpointer_alias(elem.tag, text, tail): 1002 # OpenXR-specific macro insertion 1003 tail = self.genOpts.conventions.make_voidpointer_alias(tail) 1004 if elem.tag == 'name': 1005 # Align at specified column, if possible 1006 newLen = len(paramdecl.rstrip()) 1007 self.logMsg('diag', 'Identifying length of', elem.text, 'as', newLen) 1008 paramdecl += text + tail 1009 1010 return newLen 1011 1012 def getMaxCParamTypeLength(self, info): 1013 """Return the length of the longest type field for a member/parameter. 1014 1015 - info - TypeInfo or CommandInfo. 1016 """ 1017 lengths = (self.getCParamTypeLength(member) 1018 for member in info.getMembers()) 1019 return max(lengths) 1020 1021 def getHandleParent(self, typename): 1022 """Get the parent of a handle object.""" 1023 info = self.registry.typedict.get(typename) 1024 if info is None: 1025 return None 1026 1027 elem = info.elem 1028 if elem is not None: 1029 return elem.get('parent') 1030 1031 return None 1032 1033 def iterateHandleAncestors(self, typename): 1034 """Iterate through the ancestors of a handle type.""" 1035 current = self.getHandleParent(typename) 1036 while current is not None: 1037 yield current 1038 current = self.getHandleParent(current) 1039 1040 def getHandleAncestors(self, typename): 1041 """Get the ancestors of a handle object.""" 1042 return list(self.iterateHandleAncestors(typename)) 1043 1044 def getTypeCategory(self, typename): 1045 """Get the category of a type.""" 1046 info = self.registry.typedict.get(typename) 1047 if info is None: 1048 return None 1049 1050 elem = info.elem 1051 if elem is not None: 1052 return elem.get('category') 1053 return None 1054 1055 def isStructAlwaysValid(self, structname): 1056 """Try to do check if a structure is always considered valid (i.e. there is no rules to its acceptance).""" 1057 # A conventions object is required for this call. 1058 if not self.conventions: 1059 raise RuntimeError("To use isStructAlwaysValid, be sure your options include a Conventions object.") 1060 1061 if self.conventions.type_always_valid(structname): 1062 return True 1063 1064 category = self.getTypeCategory(structname) 1065 if self.conventions.category_requires_validation(category): 1066 return False 1067 1068 info = self.registry.typedict.get(structname) 1069 if info is None: 1070 self.logMsg('error', f'isStructAlwaysValid({structname}) - structure not found in typedict') 1071 1072 members = info.getMembers() 1073 1074 for member in members: 1075 member_name = getElemName(member) 1076 if member_name in (self.conventions.structtype_member_name, 1077 self.conventions.nextpointer_member_name): 1078 return False 1079 1080 if member.get('noautovalidity'): 1081 return False 1082 1083 member_type = getElemType(member) 1084 1085 if member_type in ('void', 'char') or self.paramIsArray(member) or self.paramIsPointer(member): 1086 return False 1087 1088 if self.conventions.type_always_valid(member_type): 1089 continue 1090 1091 member_category = self.getTypeCategory(member_type) 1092 1093 if self.conventions.category_requires_validation(member_category): 1094 return False 1095 1096 if member_category in ('struct', 'union'): 1097 if self.isStructAlwaysValid(member_type) is False: 1098 return False 1099 1100 return True 1101 1102 def isEnumRequired(self, elem): 1103 """Return True if this `<enum>` element is 1104 required, False otherwise 1105 1106 - elem - `<enum>` element to test""" 1107 required = elem.get('required') is not None 1108 self.logMsg('diag', 'isEnumRequired:', elem.get('name'), 1109 '->', required) 1110 return required 1111 1112 # @@@ This code is overridden by equivalent code now run in 1113 # @@@ Registry.generateFeature 1114 1115 required = False 1116 1117 extname = elem.get('extname') 1118 if extname is not None: 1119 # 'supported' attribute was injected when the <enum> element was 1120 # moved into the <enums> group in Registry.parseTree() 1121 if self.genOpts.defaultExtensions == elem.get('supported'): 1122 required = True 1123 elif re.match(self.genOpts.addExtensions, extname) is not None: 1124 required = True 1125 elif elem.get('version') is not None: 1126 required = re.match(self.genOpts.emitversions, elem.get('version')) is not None 1127 else: 1128 required = True 1129 1130 return required 1131 1132 def makeCDecls(self, cmd): 1133 """Return C prototype and function pointer typedef for a 1134 `<command>` Element, as a two-element list of strings. 1135 1136 - cmd - Element containing a `<command>` tag""" 1137 proto = cmd.find('proto') 1138 params = cmd.findall('param') 1139 # Begin accumulating prototype and typedef strings 1140 pdecl = self.genOpts.apicall 1141 tdecl = 'typedef ' 1142 1143 # Insert the function return type/name. 1144 # For prototypes, add APIENTRY macro before the name 1145 # For typedefs, add (APIENTRY *<name>) around the name and 1146 # use the PFN_cmdnameproc naming convention. 1147 # Done by walking the tree for <proto> element by element. 1148 # etree has elem.text followed by (elem[i], elem[i].tail) 1149 # for each child element and any following text 1150 # Leading text 1151 pdecl += noneStr(proto.text) 1152 tdecl += noneStr(proto.text) 1153 # For each child element, if it is a <name> wrap in appropriate 1154 # declaration. Otherwise append its contents and tail contents. 1155 for elem in proto: 1156 text = noneStr(elem.text) 1157 tail = noneStr(elem.tail) 1158 if elem.tag == 'name': 1159 pdecl += self.makeProtoName(text, tail) 1160 tdecl += self.makeTypedefName(text, tail) 1161 else: 1162 pdecl += text + tail 1163 tdecl += text + tail 1164 1165 if self.genOpts.alignFuncParam == 0: 1166 # Squeeze out multiple spaces - there is no indentation 1167 pdecl = ' '.join(pdecl.split()) 1168 tdecl = ' '.join(tdecl.split()) 1169 1170 # Now add the parameter declaration list, which is identical 1171 # for prototypes and typedefs. Concatenate all the text from 1172 # a <param> node without the tags. No tree walking required 1173 # since all tags are ignored. 1174 # Uses: self.indentFuncProto 1175 # self.indentFuncPointer 1176 # self.alignFuncParam 1177 n = len(params) 1178 # Indented parameters 1179 if n > 0: 1180 indentdecl = '(\n' 1181 indentdecl += ',\n'.join(self.makeCParamDecl(p, self.genOpts.alignFuncParam) 1182 for p in params) 1183 indentdecl += ');' 1184 else: 1185 indentdecl = '(void);' 1186 # Non-indented parameters 1187 paramdecl = '(' 1188 if n > 0: 1189 paramnames = [] 1190 if self.misracppstyle(): 1191 for p in params: 1192 param = '' 1193 firstIter = True; 1194 for t in p.itertext(): 1195 if (firstIter): 1196 prefix = t 1197 firstIter = False 1198 else: 1199 # Change pointer type order from e.g. "const void *" to "void const *". 1200 # If the string starts with 'const', reorder it to be after the first type. 1201 if (prefix.find('const ') != -1): 1202 param += prefix.replace('const ', '') + t + ' const ' 1203 else: 1204 param += prefix + t 1205 # Clear prefix for subsequent iterations 1206 prefix = '' 1207 paramnames.append(param); 1208 else: 1209 paramnames = (''.join(t for t in p.itertext()) 1210 for p in params) 1211 paramdecl += ', '.join(paramnames) 1212 else: 1213 paramdecl += 'void' 1214 paramdecl += ");" 1215 return [pdecl + indentdecl, tdecl + paramdecl] 1216 1217 def newline(self): 1218 """Print a newline to the output file (utility function)""" 1219 write('', file=self.outFile) 1220 1221 def setRegistry(self, registry): 1222 self.registry = registry 1223