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