1#!/usr/bin/python3 -i 2# 3# Copyright 2013-2023 The Khronos Group Inc. 4# Copyright 2023-2024 Google Inc. 5# 6# SPDX-License-Identifier: Apache-2.0 7"""Base class for source/header/doc generators, as well as some utility functions.""" 8 9from __future__ import unicode_literals 10 11import io 12import os 13import re 14import shutil 15import sys 16import tempfile 17try: 18 from pathlib import Path 19except ImportError: 20 from pathlib2 import Path # type: ignore 21 22CATEGORIES_REQUIRING_VALIDATION = set(('handle', 23 'enum', 24 'bitmask', 25 'basetype', 26 None)) 27 28# These are basic C types pulled in via openxr_platform_defines.h 29TYPES_KNOWN_ALWAYS_VALID = set(('char', 30 'float', 31 'int8_t', 'uint8_t', 32 'int16_t', 'uint16_t', 33 'int32_t', 'uint32_t', 34 'int64_t', 'uint64_t', 35 'size_t', 36 'intptr_t', 'uintptr_t', 37 'int', 38 )) 39 40def getElemName(elem, default=None): 41 """Get the name associated with an element, either a name child or name attribute.""" 42 name_elem = elem.find('name') 43 if name_elem is not None: 44 return name_elem.text 45 # Fallback if there is no child. 46 return elem.get('name', default) 47 48 49def getElemType(elem, default=None): 50 """Get the type associated with an element, either a type child or type attribute.""" 51 type_elem = elem.find('type') 52 if type_elem is not None: 53 return type_elem.text 54 # Fallback if there is no child. 55 return elem.get('type', default) 56 57def write(*args, **kwargs): 58 file = kwargs.pop('file', sys.stdout) 59 end = kwargs.pop('end', '\n') 60 file.write(' '.join(str(arg) for arg in args)) 61 file.write(end) 62 63def category_requires_validation(category): 64 """Return True if the given type 'category' always requires validation. 65 66 Defaults to a reasonable implementation. 67 68 May override.""" 69 return category in CATEGORIES_REQUIRING_VALIDATION 70 71def type_always_valid(typename): 72 """Return True if the given type name is always valid (never requires validation). 73 74 This is for things like integers. 75 76 Defaults to a reasonable implementation. 77 78 May override.""" 79 return typename in TYPES_KNOWN_ALWAYS_VALID 80 81def noneStr(s): 82 """Return string argument, or "" if argument is None. 83 84 Used in converting etree Elements into text. 85 s - string to convert""" 86 if s: 87 return s 88 return "" 89 90def regSortCategoryKey(feature): 91 """Sort key for regSortFeatures. 92 Sorts by category of the feature name string: 93 94 - Core API features (those defined with a `<feature>` tag) 95 - (sort VKSC after VK - this is Vulkan-specific) 96 - ARB/KHR/OES (Khronos extensions) 97 - other (EXT/vendor extensions)""" 98 99 if feature.elem.tag == 'feature': 100 if feature.name.startswith('VKSC'): 101 return 0.5 102 else: 103 return 0 104 if (feature.category == 'ARB' 105 or feature.category == 'KHR' 106 or feature.category == 'OES'): 107 return 1 108 109 return 2 110 111 112def regSortOrderKey(feature): 113 """Sort key for regSortFeatures - key is the sortorder attribute.""" 114 115 return feature.sortorder 116 117 118def regSortNameKey(feature): 119 """Sort key for regSortFeatures - key is the extension name.""" 120 121 return feature.name 122 123 124def regSortFeatureVersionKey(feature): 125 """Sort key for regSortFeatures - key is the feature version. 126 `<extension>` elements all have version number 0.""" 127 128 return float(feature.versionNumber) 129 130 131def regSortExtensionNumberKey(feature): 132 """Sort key for regSortFeatures - key is the extension number. 133 `<feature>` elements all have extension number 0.""" 134 135 return int(feature.number) 136 137 138def regSortFeatures(featureList): 139 """Default sort procedure for features. 140 141 - Sorts by explicit sort order (default 0) relative to other features 142 - then by feature category ('feature' or 'extension'), 143 - then by version number (for features) 144 - then by extension number (for extensions)""" 145 featureList.sort(key=regSortExtensionNumberKey) 146 featureList.sort(key=regSortFeatureVersionKey) 147 featureList.sort(key=regSortCategoryKey) 148 featureList.sort(key=regSortOrderKey) 149 150 151class MissingGeneratorOptionsError(RuntimeError): 152 """Error raised when a Generator tries to do something that requires GeneratorOptions but it is None.""" 153 154 def __init__(self, msg=None): 155 full_msg = 'Missing generator options object self.genOpts' 156 if msg: 157 full_msg += ': ' + msg 158 super().__init__(full_msg) 159 160 161class MissingRegistryError(RuntimeError): 162 """Error raised when a Generator tries to do something that requires a Registry object but it is None.""" 163 164 def __init__(self, msg=None): 165 full_msg = 'Missing Registry object self.registry' 166 if msg: 167 full_msg += ': ' + msg 168 super().__init__(full_msg) 169 170class GeneratorOptions: 171 """Base class for options used during header/documentation production. 172 173 These options are target language independent, and used by 174 Registry.apiGen() and by base OutputGenerator objects.""" 175 176 def __init__(self, 177 filename=None, 178 directory='.', 179 versions='.*', 180 emitversions='.*', 181 addExtensions=None, 182 emitExtensions=None, 183 sortProcedure=regSortFeatures, 184 ): 185 """Constructor. 186 187 Arguments: 188 189 an object that implements ConventionsBase 190 - filename - basename of file to generate, or None to write to stdout. 191 - directory - directory in which to generate filename 192 - versions - regex matching API versions to process interfaces for. 193 Normally `'.*'` or `'[0-9][.][0-9]'` to match all defined versions. 194 - emitversions - regex matching API versions to actually emit 195 interfaces for (though all requested versions are considered 196 when deciding which interfaces to generate). For GL 4.3 glext.h, 197 this might be `'1[.][2-5]|[2-4][.][0-9]'`. 198 - addExtensions - regex matching names of additional extensions 199 to include. Defaults to None. 200 - emitExtensions - regex matching names of extensions to actually emit 201 interfaces for (though all requested versions are considered when 202 deciding which interfaces to generate). Defaults to None. 203 - sortProcedure - takes a list of FeatureInfo objects and sorts 204 them in place to a preferred order in the generated output. 205 206 Default is 207 - core API versions 208 - Khronos (ARB/KHR/OES) extensions 209 - All other extensions 210 - By core API version number or extension number in each group. 211 212 The regex patterns can be None or empty, in which case they match 213 nothing.""" 214 215 self.filename = filename 216 "basename of file to generate, or None to write to stdout." 217 218 self.directory = directory 219 "directory in which to generate filename" 220 221 self.versions = self.emptyRegex(versions) 222 """regex matching API versions to process interfaces for. 223 Normally `'.*'` or `'[0-9][.][0-9]'` to match all defined versions.""" 224 225 self.emitversions = self.emptyRegex(emitversions) 226 """regex matching API versions to actually emit 227 interfaces for (though all requested versions are considered 228 when deciding which interfaces to generate). For GL 4.3 glext.h, 229 this might be `'1[.][2-5]|[2-4][.][0-9]'`.""" 230 231 self.addExtensions = self.emptyRegex(addExtensions) 232 """regex matching names of additional extensions 233 to include. Defaults to None.""" 234 235 self.emitExtensions = self.emptyRegex(emitExtensions) 236 """regex matching names of extensions to actually emit 237 interfaces for (though all requested versions are considered when 238 deciding which interfaces to generate).""" 239 240 self.sortProcedure = sortProcedure 241 """takes a list of FeatureInfo objects and sorts 242 them in place to a preferred order in the generated output. 243 Default is core API versions, ARB/KHR/OES extensions, all 244 other extensions, alphabetically within each group.""" 245 246 self.registry = None 247 """Populated later with the registry object.""" 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 __init__(self, errFile=sys.stderr, warnFile=sys.stderr, diagFile=sys.stdout): 277 """Constructor 278 279 - errFile, warnFile, diagFile - file handles to write errors, 280 warnings, diagnostics to. May be None to not write.""" 281 self.outFile = None 282 self.errFile = errFile 283 self.warnFile = warnFile 284 self.diagFile = diagFile 285 # Internal state 286 self.featureName = None 287 """The current feature name being generated.""" 288 289 self.featureType = None 290 """The current feature type being generated.""" 291 292 self.genOpts = None 293 """The GeneratorOptions subclass instance.""" 294 295 self.registry = None 296 """The specification registry object.""" 297 298 self.featureDictionary = {} 299 """The dictionary of dictionaries of API features.""" 300 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 enumToValue(self, elem, needsNum, bitwidth = 32, 311 forceSuffix = False, parent_for_alias_dereference=None): 312 """Parse and convert an `<enum>` tag into a value. 313 314 - elem - <enum> Element 315 - needsNum - generate a numeric representation of the element value 316 - bitwidth - size of the numeric representation in bits (32 or 64) 317 - forceSuffix - if True, always use a 'U' / 'ULL' suffix on integers 318 - parent_for_alias_dereference - if not None, an Element containing 319 the parent of elem, used to look for elements this is an alias of 320 321 Returns a list: 322 323 - first element - integer representation of the value, or None 324 if needsNum is False. The value must be a legal number 325 if needsNum is True. 326 - second element - string representation of the value 327 328 There are several possible representations of values. 329 330 - A 'value' attribute simply contains the value. 331 - A 'bitpos' attribute defines a value by specifying the bit 332 position which is set in that value. 333 - An 'offset','extbase','extends' triplet specifies a value 334 as an offset to a base value defined by the specified 335 'extbase' extension name, which is then cast to the 336 typename specified by 'extends'. This requires probing 337 the registry database, and imbeds knowledge of the 338 API extension enum scheme in this function. 339 - An 'alias' attribute contains the name of another enum 340 which this is an alias of. The other enum must be 341 declared first when emitting this enum.""" 342 if self.genOpts is None: 343 raise MissingGeneratorOptionsError() 344 345 name = elem.get('name') 346 numVal = None 347 if 'value' in elem.keys(): 348 value = elem.get('value') 349 # print('About to translate value =', value, 'type =', type(value)) 350 if needsNum: 351 numVal = int(value, 0) 352 # If there is a non-integer, numeric 'type' attribute (e.g. 'u' or 353 # 'ull'), append it to the string value. 354 # t = enuminfo.elem.get('type') 355 # if t is not None and t != '' and t != 'i' and t != 's': 356 # value += enuminfo.type 357 if forceSuffix: 358 if bitwidth == 64: 359 value = value + 'ULL' 360 else: 361 value = value + 'U' 362 return [numVal, value] 363 if 'bitpos' in elem.keys(): 364 value = elem.get('bitpos') 365 bitpos = int(value, 0) 366 numVal = 1 << bitpos 367 value = '0x%08x' % numVal 368 if bitwidth == 64 or bitpos >= 32: 369 value = value + 'ULL' 370 elif forceSuffix: 371 value = value + 'U' 372 return [numVal, value] 373 if 'offset' in elem.keys(): 374 # Obtain values in the mapping from the attributes 375 enumNegative = False 376 offset = int(elem.get('offset'), 0) 377 extnumber = int(elem.get('extnumber'), 0) 378 extends = elem.get('extends') 379 if 'dir' in elem.keys(): 380 enumNegative = True 381 # Now determine the actual enumerant value, as defined 382 # in the "Layers and Extensions" appendix of the spec. 383 numVal = self.extBase + (extnumber - 1) * self.extBlockSize + offset 384 if enumNegative: 385 numVal *= -1 386 value = '%d' % numVal 387 # More logic needed! 388 return [numVal, value] 389 if 'alias' in elem.keys(): 390 alias_of = elem.get('alias') 391 if parent_for_alias_dereference is None: 392 return (None, alias_of) 393 siblings = parent_for_alias_dereference.findall('enum') 394 for sib in siblings: 395 sib_name = sib.get('name') 396 if sib_name == alias_of: 397 return self.enumToValue(sib, needsNum) 398 raise RuntimeError("Could not find the aliased enum value") 399 return [None, None] 400 401 def buildConstantCDecl(self, enuminfo, name, alias): 402 """Generate the C declaration for a constant (a single <enum> 403 value). 404 405 <enum> tags may specify their values in several ways, but are 406 usually just integers or floating-point numbers.""" 407 408 (_, strVal) = self.enumToValue(enuminfo.elem, False) 409 410 if enuminfo.elem.get('type') and not alias: 411 # Generate e.g.: #define x (~0ULL) 412 typeStr = enuminfo.elem.get('type'); 413 invert = '~' in strVal 414 paren = '(' in strVal 415 number = strVal.strip("()~UL") 416 if typeStr != "float": 417 if typeStr == "uint64_t": 418 number += 'ULL' 419 else: 420 number += 'U' 421 strVal = "~" if invert else "" 422 strVal += number 423 if paren: 424 strVal = "(" + strVal + ")"; 425 body = '#define ' + name.ljust(33) + ' ' + strVal; 426 else: 427 body = '#define ' + name.ljust(33) + ' ' + strVal 428 429 return body 430 431 def beginFile(self, genOpts): 432 """Start a new interface file 433 434 - genOpts - GeneratorOptions controlling what is generated and how""" 435 436 self.genOpts = genOpts 437 if self.genOpts is None: 438 raise MissingGeneratorOptionsError() 439 440 # Open a temporary file for accumulating output. 441 if self.genOpts.filename is not None: 442 self.outFile = tempfile.NamedTemporaryFile(mode='w', encoding='utf-8', newline='\n', delete=False) 443 else: 444 self.outFile = sys.stdout 445 446 def endFile(self): 447 if self.errFile: 448 self.errFile.flush() 449 if self.warnFile: 450 self.warnFile.flush() 451 if self.diagFile: 452 self.diagFile.flush() 453 if self.outFile: 454 self.outFile.flush() 455 if self.outFile != sys.stdout and self.outFile != sys.stderr: 456 self.outFile.close() 457 458 if self.genOpts is None: 459 raise MissingGeneratorOptionsError() 460 461 # On successfully generating output, move the temporary file to the 462 # target file. 463 if self.genOpts.filename is not None: 464 if sys.platform == 'win32': 465 directory = Path(self.genOpts.directory) 466 if not Path.exists(directory): 467 os.makedirs(directory) 468 shutil.copy(self.outFile.name, self.genOpts.directory + '/' + self.genOpts.filename) 469 os.remove(self.outFile.name) 470 self.genOpts = None 471 472 def beginFeature(self, interface, emit): 473 """Write interface for a feature and tag generated features as having been done. 474 475 - interface - element for the `<version>` / `<extension>` to generate 476 - emit - actually write to the header only when True""" 477 self.emit = emit 478 self.featureName = interface.get('name') 479 self.featureType = interface.get('type') 480 481 def endFeature(self): 482 """Finish an interface file, closing it when done. 483 484 Derived classes responsible for emitting feature""" 485 self.featureName = None 486 self.featureType = None 487 488 def validateFeature(self, featureType, featureName): 489 """Validate we are generating something only inside a `<feature>` tag""" 490 if self.featureName is None: 491 raise UserWarning('Attempt to generate', featureType, 492 featureName, 'when not in feature') 493 494 def genType(self, typeinfo, name, alias): 495 """Generate interface for a type 496 497 - typeinfo - TypeInfo for a type 498 499 Extend to generate as desired in your derived class.""" 500 self.validateFeature('type', name) 501 502 def genStruct(self, typeinfo, typeName, alias): 503 """Generate interface for a C "struct" type. 504 505 - typeinfo - TypeInfo for a type interpreted as a struct 506 507 Extend to generate as desired in your derived class.""" 508 self.validateFeature('struct', typeName) 509 510 # The mixed-mode <member> tags may contain no-op <comment> tags. 511 # It is convenient to remove them here where all output generators 512 # will benefit. 513 for member in typeinfo.elem.findall('.//member'): 514 for comment in member.findall('comment'): 515 member.remove(comment) 516 517 def genGroup(self, groupinfo, groupName, alias): 518 """Generate interface for a group of enums (C "enum") 519 520 - groupinfo - GroupInfo for a group. 521 522 Extend to generate as desired in your derived class.""" 523 524 self.validateFeature('group', groupName) 525 526 def genEnum(self, enuminfo, typeName, alias): 527 """Generate interface for an enum (constant). 528 529 - enuminfo - EnumInfo for an enum 530 - name - enum name 531 532 Extend to generate as desired in your derived class.""" 533 self.validateFeature('enum', typeName) 534 535 def genCmd(self, cmd, cmdinfo, alias): 536 """Generate interface for a command. 537 538 - cmdinfo - CmdInfo for a command 539 540 Extend to generate as desired in your derived class.""" 541 self.validateFeature('command', cmdinfo) 542 543 def makeProtoName(self, name, tail): 544 """Turn a `<proto>` `<name>` into C-language prototype 545 and typedef declarations for that name. 546 547 - name - contents of `<name>` tag 548 - tail - whatever text follows that tag in the Element""" 549 if self.genOpts is None: 550 raise MissingGeneratorOptionsError() 551 return self.genOpts.apientry + name + tail 552 553 def makeTypedefName(self, name, tail): 554 """Make the function-pointer typedef name for a command.""" 555 if self.genOpts is None: 556 raise MissingGeneratorOptionsError() 557 return '(' + self.genOpts.apientryp + 'PFN_' + name + tail + ')' 558 559 def makeCParamDecl(self, param, aligncol): 560 """Return a string which is an indented, formatted 561 declaration for a `<param>` or `<member>` block (e.g. function parameter 562 or structure/union member). 563 564 - param - Element (`<param>` or `<member>`) to format 565 - aligncol - if non-zero, attempt to align the nested `<name>` element 566 at this column""" 567 if self.genOpts is None: 568 raise MissingGeneratorOptionsError() 569 indent = ' ' 570 paramdecl = indent 571 prefix = noneStr(param.text) 572 573 for elem in param: 574 text = noneStr(elem.text) 575 tail = noneStr(elem.tail) 576 577 if elem.tag == 'name' and aligncol > 0: 578 # Align at specified column, if possible 579 paramdecl = paramdecl.rstrip() 580 oldLen = len(paramdecl) 581 # This works around a problem where very long type names - 582 # longer than the alignment column - would run into the tail 583 # text. 584 paramdecl = paramdecl.ljust(aligncol - 1) + ' ' 585 newLen = len(paramdecl) 586 587 paramdecl += prefix + text + tail 588 589 # Clear prefix for subsequent iterations 590 prefix = '' 591 592 paramdecl = paramdecl + prefix 593 594 if aligncol == 0: 595 # Squeeze out multiple spaces other than the indentation 596 paramdecl = indent + ' '.join(paramdecl.split()) 597 return paramdecl 598 599 def getCParamTypeLength(self, param): 600 """Return the length of the type field is an indented, formatted 601 declaration for a `<param>` or `<member>` block (e.g. function parameter 602 or structure/union member). 603 604 - param - Element (`<param>` or `<member>`) to identify""" 605 if self.genOpts is None: 606 raise MissingGeneratorOptionsError() 607 608 # Allow for missing <name> tag 609 newLen = 0 610 paramdecl = ' ' + noneStr(param.text) 611 for elem in param: 612 text = noneStr(elem.text) 613 tail = noneStr(elem.tail) 614 615 if elem.tag == 'name': 616 # Align at specified column, if possible 617 newLen = len(paramdecl.rstrip()) 618 paramdecl += text + tail 619 620 return newLen 621 622 def getMaxCParamTypeLength(self, info): 623 """Return the length of the longest type field for a member/parameter. 624 - info - TypeInfo or CommandInfo. 625 """ 626 lengths = (self.getCParamTypeLength(member) 627 for member in info.getMembers()) 628 return max(lengths) 629 630 def getTypeCategory(self, typename): 631 """Get the category of a type.""" 632 if self.registry is None: 633 raise MissingRegistryError() 634 635 info = self.registry.typedict.get(typename) 636 if info is None: 637 return None 638 639 elem = info.elem 640 if elem is not None: 641 return elem.get('category') 642 return None 643 644 def isStructAlwaysValid(self, structname): 645 """Try to do check if a structure is always considered valid (i.e. there is no rules to its acceptance).""" 646 # A conventions object is required for this call. 647 if self.registry is None: 648 raise MissingRegistryError() 649 650 if type_always_valid(structname): 651 return True 652 653 category = self.getTypeCategory(structname) 654 if category_requires_validation(category): 655 return False 656 657 info = self.registry.typedict.get(structname) 658 members = info.getMembers() 659 660 for member in members: 661 member_name = getElemName(member) 662 if member_name in ('sType', 'pNext'): 663 return False 664 665 if member.get('noautovalidity'): 666 return False 667 668 member_type = getElemType(member) 669 670 if member_type in ('void', 'char') or self.paramIsArray(member) or self.paramIsPointer(member): 671 return False 672 673 if type_always_valid(member_type): 674 continue 675 676 member_category = self.getTypeCategory(member_type) 677 678 if category_requires_validation(member_category): 679 return False 680 681 if member_category in ('struct', 'union'): 682 if self.isStructAlwaysValid(member_type) is False: 683 return False 684 685 return True 686 687 def paramIsArray(self, param): 688 """Check if the parameter passed in is a pointer to an array. 689 690 param the XML information for the param 691 """ 692 return param.get('len') is not None 693 694 def paramIsPointer(self, param): 695 """Check if the parameter passed in is a pointer. 696 697 param the XML information for the param 698 """ 699 tail = param.find('type').tail 700 return tail is not None and '*' in tail 701 702 def isEnumRequired(self, elem): 703 """Return True if this `<enum>` element is 704 required, False otherwise 705 706 - elem - `<enum>` element to test""" 707 required = elem.get('required') is not None 708 return required 709 710 # @@@ This code is overridden by equivalent code now run in 711 # @@@ Registry.generateFeature 712 713 required = False 714 715 extname = elem.get('extname') 716 if extname is not None: 717 # 'supported' attribute was injected when the <enum> element was 718 # moved into the <enums> group in Registry.parseTree() 719 if 'vulkan' == elem.get('supported'): 720 required = True 721 elif re.match(self.genOpts.addExtensions, extname) is not None: 722 required = True 723 elif elem.get('version') is not None: 724 required = re.match(self.genOpts.emitversions, elem.get('version')) is not None 725 else: 726 required = True 727 728 return required 729 730 def makeCDecls(self, cmd): 731 """Return C prototype and function pointer typedef for a 732 `<command>` Element, as a two-element list of strings. 733 734 - cmd - Element containing a `<command>` tag""" 735 if self.genOpts is None: 736 raise MissingGeneratorOptionsError() 737 proto = cmd.find('proto') 738 params = cmd.findall('param') 739 # Begin accumulating prototype and typedef strings 740 pdecl = 'VKAPI_ATTR ' 741 tdecl = 'typedef ' 742 743 # Insert the function return type/name. 744 # For prototypes, add APIENTRY macro before the name 745 # For typedefs, add (APIENTRY *<name>) around the name and 746 # use the PFN_cmdnameproc naming convention. 747 # Done by walking the tree for <proto> element by element. 748 # etree has elem.text followed by (elem[i], elem[i].tail) 749 # for each child element and any following text 750 # Leading text 751 pdecl += noneStr(proto.text) 752 tdecl += noneStr(proto.text) 753 # For each child element, if it is a <name> wrap in appropriate 754 # declaration. Otherwise append its contents and tail contents. 755 for elem in proto: 756 text = noneStr(elem.text) 757 tail = noneStr(elem.tail) 758 if elem.tag == 'name': 759 pdecl += self.makeProtoName(text, tail) 760 tdecl += self.makeTypedefName(text, tail) 761 else: 762 pdecl += text + tail 763 tdecl += text + tail 764 765 if self.genOpts.alignFuncParam == 0: 766 # Squeeze out multiple spaces - there is no indentation 767 pdecl = ' '.join(pdecl.split()) 768 tdecl = ' '.join(tdecl.split()) 769 770 # Now add the parameter declaration list, which is identical 771 # for prototypes and typedefs. Concatenate all the text from 772 # a <param> node without the tags. No tree walking required 773 # since all tags are ignored. 774 # Uses: self.indentFuncProto 775 # self.indentFuncPointer 776 # self.alignFuncParam 777 n = len(params) 778 # Indented parameters 779 if n > 0: 780 indentdecl = '(\n' 781 indentdecl += ',\n'.join(self.makeCParamDecl(p, self.genOpts.alignFuncParam) 782 for p in params) 783 indentdecl += ');' 784 else: 785 indentdecl = '(void);' 786 # Non-indented parameters 787 paramdecl = '(' 788 if n > 0: 789 paramnames = [] 790 paramnames = (''.join(t for t in p.itertext()) 791 for p in params) 792 paramdecl += ', '.join(paramnames) 793 else: 794 paramdecl += 'void' 795 paramdecl += ");" 796 return [pdecl + indentdecl, tdecl + paramdecl] 797 798 def newline(self): 799 """Print a newline to the output file (utility function)""" 800 write('', file=self.outFile) 801 802 def setRegistry(self, registry): 803 self.registry = registry 804