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