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