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 """Sanity check enumerated values. 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 += "static const {} {} = {};\n".format(flagTypeName, name, strVal) 497 498 # Postfix 499 500 return ("bitmask", body) 501 502 def buildEnumCDecl_Enum(self, expand, groupinfo, groupName): 503 """Generate the C declaration for an enumerated type""" 504 groupElem = groupinfo.elem 505 506 # Break the group name into prefix and suffix portions for range 507 # enum generation 508 expandName = re.sub(r'([0-9a-z_])([A-Z0-9])', r'\1_\2', groupName).upper() 509 expandPrefix = expandName 510 expandSuffix = '' 511 expandSuffixMatch = re.search(r'[A-Z][A-Z]+$', groupName) 512 if expandSuffixMatch: 513 expandSuffix = '_' + expandSuffixMatch.group() 514 # Strip off the suffix from the prefix 515 expandPrefix = expandName.rsplit(expandSuffix, 1)[0] 516 517 # Prefix 518 body = ["typedef enum %s {" % groupName] 519 520 # @@ Should use the type="bitmask" attribute instead 521 isEnum = ('FLAG_BITS' not in expandPrefix) 522 523 # Allowable range for a C enum - which is that of a signed 32-bit integer 524 maxValidValue = 2**(32 - 1) - 1 525 minValidValue = (maxValidValue * -1) - 1 526 527 528 # Get a list of nested 'enum' tags. 529 enums = groupElem.findall('enum') 530 531 # Check for and report duplicates, and return a list with them 532 # removed. 533 enums = self.checkDuplicateEnums(enums) 534 535 # Loop over the nested 'enum' tags. Keep track of the minimum and 536 # maximum numeric values, if they can be determined; but only for 537 # core API enumerants, not extension enumerants. This is inferred 538 # by looking for 'extends' attributes. 539 minName = None 540 541 # Accumulate non-numeric enumerant values separately and append 542 # them following the numeric values, to allow for aliases. 543 # NOTE: this doesn't do a topological sort yet, so aliases of 544 # aliases can still get in the wrong order. 545 aliasText = [] 546 547 for elem in enums: 548 # Convert the value to an integer and use that to track min/max. 549 # Values of form -(number) are accepted but nothing more complex. 550 # Should catch exceptions here for more complex constructs. Not yet. 551 (numVal, strVal) = self.enumToValue(elem, True) 552 name = elem.get('name') 553 554 # Extension enumerants are only included if they are required 555 if self.isEnumRequired(elem): 556 decl = " {} = {},".format(name, strVal) 557 if numVal is not None: 558 body.append(decl) 559 else: 560 aliasText.append(decl) 561 562 # Range check for the enum value 563 if numVal is not None and (numVal > maxValidValue or numVal < minValidValue): 564 self.logMsg('error', 'Allowable range for C enum types is [', minValidValue, ',', maxValidValue, '], but', name, 'has a value outside of this (', strVal, ')\n') 565 exit(1) 566 567 568 # Don't track min/max for non-numbers (numVal is None) 569 if isEnum and numVal is not None and elem.get('extends') is None: 570 if minName is None: 571 minName = maxName = name 572 minValue = maxValue = numVal 573 elif numVal < minValue: 574 minName = name 575 minValue = numVal 576 elif numVal > maxValue: 577 maxName = name 578 maxValue = numVal 579 580 # Now append the non-numeric enumerant values 581 body.extend(aliasText) 582 583 # Generate min/max value tokens - legacy use case. 584 if isEnum and expand: 585 body.extend((" {}_BEGIN_RANGE{} = {},".format(expandPrefix, expandSuffix, minName), 586 " {}_END_RANGE{} = {},".format( 587 expandPrefix, expandSuffix, maxName), 588 " {}_RANGE_SIZE{} = ({} - {} + 1),".format(expandPrefix, expandSuffix, maxName, minName))) 589 590 # Generate a range-padding value to ensure the enum is 32 bits, but 591 # only in code generators, so it doesn't appear in documentation 592 if (self.genOpts.codeGenerator or 593 self.conventions.generate_max_enum_in_docs): 594 body.append(" {}_MAX_ENUM{} = 0x7FFFFFFF".format( 595 expandPrefix, expandSuffix)) 596 597 # Postfix 598 body.append("} %s;" % groupName) 599 600 # Determine appropriate section for this declaration 601 if groupElem.get('type') == 'bitmask': 602 section = 'bitmask' 603 else: 604 section = 'group' 605 606 return (section, '\n'.join(body)) 607 608 def makeDir(self, path): 609 """Create a directory, if not already done. 610 611 Generally called from derived generators creating hierarchies.""" 612 self.logMsg('diag', 'OutputGenerator::makeDir(' + path + ')') 613 if path not in self.madeDirs: 614 # This can get race conditions with multiple writers, see 615 # https://stackoverflow.com/questions/273192/ 616 if not os.path.exists(path): 617 os.makedirs(path) 618 self.madeDirs[path] = None 619 620 def beginFile(self, genOpts): 621 """Start a new interface file 622 623 - genOpts - GeneratorOptions controlling what's generated and how""" 624 self.genOpts = genOpts 625 self.should_insert_may_alias_macro = \ 626 self.genOpts.conventions.should_insert_may_alias_macro(self.genOpts) 627 628 # Try to import the API dictionary, api.py, if it exists. Nothing in 629 # api.py cannot be extracted directly from the XML, and in the 630 # future we should do that. 631 if self.genOpts.genpath is not None: 632 try: 633 sys.path.insert(0, self.genOpts.genpath) 634 import api 635 self.apidict = api 636 except ImportError: 637 self.apidict = None 638 639 self.conventions = genOpts.conventions 640 641 # Open a temporary file for accumulating output. 642 if self.genOpts.filename is not None: 643 self.outFile = tempfile.NamedTemporaryFile(mode='w', encoding='utf-8', newline='\n', delete=False) 644 else: 645 self.outFile = sys.stdout 646 647 def endFile(self): 648 if self.errFile: 649 self.errFile.flush() 650 if self.warnFile: 651 self.warnFile.flush() 652 if self.diagFile: 653 self.diagFile.flush() 654 self.outFile.flush() 655 if self.outFile != sys.stdout and self.outFile != sys.stderr: 656 self.outFile.close() 657 658 # On successfully generating output, move the temporary file to the 659 # target file. 660 if self.genOpts.filename is not None: 661 if sys.platform == 'win32': 662 directory = Path(self.genOpts.directory) 663 if not Path.exists(directory): 664 os.makedirs(directory) 665 shutil.copy(self.outFile.name, self.genOpts.directory + '/' + self.genOpts.filename) 666 os.remove(self.outFile.name) 667 self.genOpts = None 668 669 def beginFeature(self, interface, emit): 670 """Write interface for a feature and tag generated features as having been done. 671 672 - interface - element for the `<version>` / `<extension>` to generate 673 - emit - actually write to the header only when True""" 674 self.emit = emit 675 self.featureName = interface.get('name') 676 # If there's an additional 'protect' attribute in the feature, save it 677 self.featureExtraProtect = interface.get('protect') 678 679 def endFeature(self): 680 """Finish an interface file, closing it when done. 681 682 Derived classes responsible for emitting feature""" 683 self.featureName = None 684 self.featureExtraProtect = None 685 686 def validateFeature(self, featureType, featureName): 687 """Validate we're generating something only inside a `<feature>` tag""" 688 if self.featureName is None: 689 raise UserWarning('Attempt to generate', featureType, 690 featureName, 'when not in feature') 691 692 def genType(self, typeinfo, name, alias): 693 """Generate interface for a type 694 695 - typeinfo - TypeInfo for a type 696 697 Extend to generate as desired in your derived class.""" 698 self.validateFeature('type', name) 699 700 def genStruct(self, typeinfo, typeName, alias): 701 """Generate interface for a C "struct" type. 702 703 - typeinfo - TypeInfo for a type interpreted as a struct 704 705 Extend to generate as desired in your derived class.""" 706 self.validateFeature('struct', typeName) 707 708 # The mixed-mode <member> tags may contain no-op <comment> tags. 709 # It is convenient to remove them here where all output generators 710 # will benefit. 711 for member in typeinfo.elem.findall('.//member'): 712 for comment in member.findall('comment'): 713 member.remove(comment) 714 715 def genGroup(self, groupinfo, groupName, alias): 716 """Generate interface for a group of enums (C "enum") 717 718 - groupinfo - GroupInfo for a group. 719 720 Extend to generate as desired in your derived class.""" 721 722 self.validateFeature('group', groupName) 723 724 def genEnum(self, enuminfo, typeName, alias): 725 """Generate interface for an enum (constant). 726 727 - enuminfo - EnumInfo for an enum 728 - name - enum name 729 730 Extend to generate as desired in your derived class.""" 731 self.validateFeature('enum', typeName) 732 733 def genCmd(self, cmd, cmdinfo, alias): 734 """Generate interface for a command. 735 736 - cmdinfo - CmdInfo for a command 737 738 Extend to generate as desired in your derived class.""" 739 self.validateFeature('command', cmdinfo) 740 741 def makeProtoName(self, name, tail): 742 """Turn a `<proto>` `<name>` into C-language prototype 743 and typedef declarations for that name. 744 745 - name - contents of `<name>` tag 746 - tail - whatever text follows that tag in the Element""" 747 return self.genOpts.apientry + name + tail 748 749 def makeTypedefName(self, name, tail): 750 """Make the function-pointer typedef name for a command.""" 751 return '(' + self.genOpts.apientryp + 'PFN_' + name + tail + ')' 752 753 def makeCParamDecl(self, param, aligncol): 754 """Return a string which is an indented, formatted 755 declaration for a `<param>` or `<member>` block (e.g. function parameter 756 or structure/union member). 757 758 - param - Element (`<param>` or `<member>`) to format 759 - aligncol - if non-zero, attempt to align the nested `<name>` element 760 at this column""" 761 indent = ' ' 762 paramdecl = indent + noneStr(param.text) 763 for elem in param: 764 text = noneStr(elem.text) 765 tail = noneStr(elem.tail) 766 767 if self.should_insert_may_alias_macro and self.genOpts.conventions.is_voidpointer_alias(elem.tag, text, tail): 768 # OpenXR-specific macro insertion - but not in apiinc for the spec 769 tail = self.genOpts.conventions.make_voidpointer_alias(tail) 770 if elem.tag == 'name' and aligncol > 0: 771 self.logMsg('diag', 'Aligning parameter', elem.text, 'to column', self.genOpts.alignFuncParam) 772 # Align at specified column, if possible 773 paramdecl = paramdecl.rstrip() 774 oldLen = len(paramdecl) 775 # This works around a problem where very long type names - 776 # longer than the alignment column - would run into the tail 777 # text. 778 paramdecl = paramdecl.ljust(aligncol - 1) + ' ' 779 newLen = len(paramdecl) 780 self.logMsg('diag', 'Adjust length of parameter decl from', oldLen, 'to', newLen, ':', paramdecl) 781 paramdecl += text + tail 782 if aligncol == 0: 783 # Squeeze out multiple spaces other than the indentation 784 paramdecl = indent + ' '.join(paramdecl.split()) 785 return paramdecl 786 787 def getCParamTypeLength(self, param): 788 """Return the length of the type field is an indented, formatted 789 declaration for a `<param>` or `<member>` block (e.g. function parameter 790 or structure/union member). 791 792 - param - Element (`<param>` or `<member>`) to identify""" 793 794 # Allow for missing <name> tag 795 newLen = 0 796 paramdecl = ' ' + noneStr(param.text) 797 for elem in param: 798 text = noneStr(elem.text) 799 tail = noneStr(elem.tail) 800 801 if self.should_insert_may_alias_macro and self.genOpts.conventions.is_voidpointer_alias(elem.tag, text, tail): 802 # OpenXR-specific macro insertion 803 tail = self.genOpts.conventions.make_voidpointer_alias(tail) 804 if elem.tag == 'name': 805 # Align at specified column, if possible 806 newLen = len(paramdecl.rstrip()) 807 self.logMsg('diag', 'Identifying length of', elem.text, 'as', newLen) 808 paramdecl += text + tail 809 810 return newLen 811 812 def getMaxCParamTypeLength(self, info): 813 """Return the length of the longest type field for a member/parameter. 814 815 - info - TypeInfo or CommandInfo. 816 """ 817 lengths = (self.getCParamTypeLength(member) 818 for member in info.getMembers()) 819 return max(lengths) 820 821 def getHandleParent(self, typename): 822 """Get the parent of a handle object.""" 823 info = self.registry.typedict.get(typename) 824 if info is None: 825 return None 826 827 elem = info.elem 828 if elem is not None: 829 return elem.get('parent') 830 831 return None 832 833 def iterateHandleAncestors(self, typename): 834 """Iterate through the ancestors of a handle type.""" 835 current = self.getHandleParent(typename) 836 while current is not None: 837 yield current 838 current = self.getHandleParent(current) 839 840 def getHandleAncestors(self, typename): 841 """Get the ancestors of a handle object.""" 842 return list(self.iterateHandleAncestors(typename)) 843 844 def getTypeCategory(self, typename): 845 """Get the category of a type.""" 846 info = self.registry.typedict.get(typename) 847 if info is None: 848 return None 849 850 elem = info.elem 851 if elem is not None: 852 return elem.get('category') 853 return None 854 855 def isStructAlwaysValid(self, structname): 856 """Try to do check if a structure is always considered valid (i.e. there's no rules to its acceptance).""" 857 # A conventions object is required for this call. 858 if not self.conventions: 859 raise RuntimeError("To use isStructAlwaysValid, be sure your options include a Conventions object.") 860 861 if self.conventions.type_always_valid(structname): 862 return True 863 864 category = self.getTypeCategory(structname) 865 if self.conventions.category_requires_validation(category): 866 return False 867 868 info = self.registry.typedict.get(structname) 869 assert(info is not None) 870 871 members = info.getMembers() 872 873 for member in members: 874 member_name = getElemName(member) 875 if member_name in (self.conventions.structtype_member_name, 876 self.conventions.nextpointer_member_name): 877 return False 878 879 if member.get('noautovalidity'): 880 return False 881 882 member_type = getElemType(member) 883 884 if member_type in ('void', 'char') or self.paramIsArray(member) or self.paramIsPointer(member): 885 return False 886 887 if self.conventions.type_always_valid(member_type): 888 continue 889 890 member_category = self.getTypeCategory(member_type) 891 892 if self.conventions.category_requires_validation(member_category): 893 return False 894 895 if member_category in ('struct', 'union'): 896 if self.isStructAlwaysValid(member_type) is False: 897 return False 898 899 return True 900 901 def isEnumRequired(self, elem): 902 """Return True if this `<enum>` element is 903 required, False otherwise 904 905 - elem - `<enum>` element to test""" 906 required = elem.get('required') is not None 907 self.logMsg('diag', 'isEnumRequired:', elem.get('name'), 908 '->', required) 909 return required 910 911 # @@@ This code is overridden by equivalent code now run in 912 # @@@ Registry.generateFeature 913 914 required = False 915 916 extname = elem.get('extname') 917 if extname is not None: 918 # 'supported' attribute was injected when the <enum> element was 919 # moved into the <enums> group in Registry.parseTree() 920 if self.genOpts.defaultExtensions == elem.get('supported'): 921 required = True 922 elif re.match(self.genOpts.addExtensions, extname) is not None: 923 required = True 924 elif elem.get('version') is not None: 925 required = re.match(self.genOpts.emitversions, elem.get('version')) is not None 926 else: 927 required = True 928 929 return required 930 931 def makeCDecls(self, cmd): 932 """Return C prototype and function pointer typedef for a 933 `<command>` Element, as a two-element list of strings. 934 935 - cmd - Element containing a `<command>` tag""" 936 proto = cmd.find('proto') 937 params = cmd.findall('param') 938 # Begin accumulating prototype and typedef strings 939 pdecl = self.genOpts.apicall 940 tdecl = 'typedef ' 941 942 # Insert the function return type/name. 943 # For prototypes, add APIENTRY macro before the name 944 # For typedefs, add (APIENTRY *<name>) around the name and 945 # use the PFN_cmdnameproc naming convention. 946 # Done by walking the tree for <proto> element by element. 947 # etree has elem.text followed by (elem[i], elem[i].tail) 948 # for each child element and any following text 949 # Leading text 950 pdecl += noneStr(proto.text) 951 tdecl += noneStr(proto.text) 952 # For each child element, if it's a <name> wrap in appropriate 953 # declaration. Otherwise append its contents and tail contents. 954 for elem in proto: 955 text = noneStr(elem.text) 956 tail = noneStr(elem.tail) 957 if elem.tag == 'name': 958 pdecl += self.makeProtoName(text, tail) 959 tdecl += self.makeTypedefName(text, tail) 960 else: 961 pdecl += text + tail 962 tdecl += text + tail 963 964 if self.genOpts.alignFuncParam == 0: 965 # Squeeze out multiple spaces - there is no indentation 966 pdecl = ' '.join(pdecl.split()) 967 tdecl = ' '.join(tdecl.split()) 968 969 # Now add the parameter declaration list, which is identical 970 # for prototypes and typedefs. Concatenate all the text from 971 # a <param> node without the tags. No tree walking required 972 # since all tags are ignored. 973 # Uses: self.indentFuncProto 974 # self.indentFuncPointer 975 # self.alignFuncParam 976 n = len(params) 977 # Indented parameters 978 if n > 0: 979 indentdecl = '(\n' 980 indentdecl += ',\n'.join(self.makeCParamDecl(p, self.genOpts.alignFuncParam) 981 for p in params) 982 indentdecl += ');' 983 else: 984 indentdecl = '(void);' 985 # Non-indented parameters 986 paramdecl = '(' 987 if n > 0: 988 paramnames = (''.join(t for t in p.itertext()) 989 for p in params) 990 paramdecl += ', '.join(paramnames) 991 else: 992 paramdecl += 'void' 993 paramdecl += ");" 994 return [pdecl + indentdecl, tdecl + paramdecl] 995 996 def newline(self): 997 """Print a newline to the output file (utility function)""" 998 write('', file=self.outFile) 999 1000 def setRegistry(self, registry): 1001 self.registry = registry 1002