generator.py
1 #!/usr/bin/python3 -i 2 # 3 # Copyright (c) 2013-2019 The Khronos Group Inc. 4 # 5 # Licensed under the Apache License, Version 2.0 (the "License"); 6 # you may not use this file except in compliance with the License. 7 # You may obtain a copy of the License at 8 # 9 # http://www.apache.org/licenses/LICENSE-2.0 10 # 11 # Unless required by applicable law or agreed to in writing, software 12 # distributed under the License is distributed on an "AS IS" BASIS, 13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 # See the License for the specific language governing permissions and 15 # limitations under the License. 16 17 from __future__ import unicode_literals 18 19 import io 20 import os 21 import re 22 import pdb 23 import sys 24 from pathlib import Path 25 26 def write( *args, **kwargs ): 27 file = kwargs.pop('file',sys.stdout) 28 end = kwargs.pop('end','\n') 29 file.write(' '.join(str(arg) for arg in args)) 30 file.write(end) 31 32 # noneStr - returns string argument, or "" if argument is None. 33 # Used in converting etree Elements into text. 34 # s - string to convert 35 def noneStr(s): 36 if s: 37 return s 38 return "" 39 40 # enquote - returns string argument with surrounding quotes, 41 # for serialization into Python code. 42 def enquote(s): 43 if s: 44 return "'{}'".format(s) 45 return None 46 47 # Primary sort key for regSortFeatures. 48 # Sorts by category of the feature name string: 49 # Core API features (those defined with a <feature> tag) 50 # ARB/KHR/OES (Khronos extensions) 51 # other (EXT/vendor extensions) 52 # This will need changing for Vulkan! 53 def regSortCategoryKey(feature): 54 if feature.elem.tag == 'feature': 55 return 0 56 if (feature.category == 'ARB' or 57 feature.category == 'KHR' or 58 feature.category == 'OES'): 59 return 1 60 61 return 2 62 63 # Secondary sort key for regSortFeatures. 64 # Sorts by extension name. 65 def regSortNameKey(feature): 66 return feature.name 67 68 # Second sort key for regSortFeatures. 69 # Sorts by feature version. <extension> elements all have version number "0" 70 def regSortFeatureVersionKey(feature): 71 return float(feature.versionNumber) 72 73 # Tertiary sort key for regSortFeatures. 74 # Sorts by extension number. <feature> elements all have extension number 0. 75 def regSortExtensionNumberKey(feature): 76 return int(feature.number) 77 78 # regSortFeatures - default sort procedure for features. 79 # Sorts by primary key of feature category ('feature' or 'extension') 80 # then by version number (for features) 81 # then by extension number (for extensions) 82 def regSortFeatures(featureList): 83 featureList.sort(key = regSortExtensionNumberKey) 84 featureList.sort(key = regSortFeatureVersionKey) 85 featureList.sort(key = regSortCategoryKey) 86 87 # GeneratorOptions - base class for options used during header production 88 # These options are target language independent, and used by 89 # Registry.apiGen() and by base OutputGenerator objects. 90 # 91 # Members 92 # conventions - may be mandatory for some generators: 93 # an object that implements ConventionsBase 94 # filename - basename of file to generate, or None to write to stdout. 95 # directory - directory in which to generate filename 96 # apiname - string matching <api> 'apiname' attribute, e.g. 'gl'. 97 # profile - string specifying API profile , e.g. 'core', or None. 98 # versions - regex matching API versions to process interfaces for. 99 # Normally '.*' or '[0-9]\.[0-9]' to match all defined versions. 100 # emitversions - regex matching API versions to actually emit 101 # interfaces for (though all requested versions are considered 102 # when deciding which interfaces to generate). For GL 4.3 glext.h, 103 # this might be '1\.[2-5]|[2-4]\.[0-9]'. 104 # defaultExtensions - If not None, a string which must in its 105 # entirety match the pattern in the "supported" attribute of 106 # the <extension>. Defaults to None. Usually the same as apiname. 107 # addExtensions - regex matching names of additional extensions 108 # to include. Defaults to None. 109 # removeExtensions - regex matching names of extensions to 110 # remove (after defaultExtensions and addExtensions). Defaults 111 # to None. 112 # emitExtensions - regex matching names of extensions to actually emit 113 # interfaces for (though all requested versions are considered when 114 # deciding which interfaces to generate). 115 # sortProcedure - takes a list of FeatureInfo objects and sorts 116 # them in place to a preferred order in the generated output. 117 # Default is core API versions, ARB/KHR/OES extensions, all 118 # other extensions, alphabetically within each group. 119 # The regex patterns can be None or empty, in which case they match 120 # nothing. 121 class GeneratorOptions: 122 """Represents options during header production from an API registry""" 123 124 def __init__(self, 125 conventions = None, 126 filename = None, 127 directory = '.', 128 apiname = None, 129 profile = None, 130 versions = '.*', 131 emitversions = '.*', 132 defaultExtensions = None, 133 addExtensions = None, 134 removeExtensions = None, 135 emitExtensions = None, 136 sortProcedure = regSortFeatures): 137 self.conventions = conventions 138 self.filename = filename 139 self.directory = directory 140 self.apiname = apiname 141 self.profile = profile 142 self.versions = self.emptyRegex(versions) 143 self.emitversions = self.emptyRegex(emitversions) 144 self.defaultExtensions = defaultExtensions 145 self.addExtensions = self.emptyRegex(addExtensions) 146 self.removeExtensions = self.emptyRegex(removeExtensions) 147 self.emitExtensions = self.emptyRegex(emitExtensions) 148 self.sortProcedure = sortProcedure 149 150 # Substitute a regular expression which matches no version 151 # or extension names for None or the empty string. 152 def emptyRegex(self, pat): 153 if pat is None or pat == '': 154 return '_nomatch_^' 155 156 return pat 157 158 # OutputGenerator - base class for generating API interfaces. 159 # Manages basic logic, logging, and output file control 160 # Derived classes actually generate formatted output. 161 # 162 # ---- methods ---- 163 # OutputGenerator(errFile, warnFile, diagFile) 164 # errFile, warnFile, diagFile - file handles to write errors, 165 # warnings, diagnostics to. May be None to not write. 166 # logMsg(level, *args) - log messages of different categories 167 # level - 'error', 'warn', or 'diag'. 'error' will also 168 # raise a UserWarning exception 169 # *args - print()-style arguments 170 # setExtMap(map) - specify a dictionary map from extension names to 171 # numbers, used in creating values for extension enumerants. 172 # makeDir(directory) - create a directory, if not already done. 173 # Generally called from derived generators creating hierarchies. 174 # beginFile(genOpts) - start a new interface file 175 # genOpts - GeneratorOptions controlling what's generated and how 176 # endFile() - finish an interface file, closing it when done 177 # beginFeature(interface, emit) - write interface for a feature 178 # and tag generated features as having been done. 179 # interface - element for the <version> / <extension> to generate 180 # emit - actually write to the header only when True 181 # endFeature() - finish an interface. 182 # genType(typeinfo,name,alias) - generate interface for a type 183 # typeinfo - TypeInfo for a type 184 # genStruct(typeinfo,name,alias) - generate interface for a C "struct" type. 185 # typeinfo - TypeInfo for a type interpreted as a struct 186 # genGroup(groupinfo,name,alias) - generate interface for a group of enums (C "enum") 187 # groupinfo - GroupInfo for a group 188 # genEnum(enuminfo,name,alias) - generate interface for an enum (constant) 189 # enuminfo - EnumInfo for an enum 190 # name - enum name 191 # genCmd(cmdinfo,name,alias) - generate interface for a command 192 # cmdinfo - CmdInfo for a command 193 # isEnumRequired(enumElem) - return True if this <enum> element is required 194 # elem - <enum> element to test 195 # makeCDecls(cmd) - return C prototype and function pointer typedef for a 196 # <command> Element, as a list of two strings 197 # cmd - Element for the <command> 198 # newline() - print a newline to the output file (utility function) 199 # 200 class OutputGenerator: 201 """Generate specified API interfaces in a specific style, such as a C header""" 202 203 # categoryToPath - map XML 'category' to include file directory name 204 categoryToPath = { 205 'bitmask' : 'flags', 206 'enum' : 'enums', 207 'funcpointer' : 'funcpointers', 208 'handle' : 'handles', 209 'define' : 'defines', 210 'basetype' : 'basetypes', 211 } 212 213 # Constructor 214 def __init__(self, 215 errFile = sys.stderr, 216 warnFile = sys.stderr, 217 diagFile = sys.stdout): 218 self.outFile = None 219 self.errFile = errFile 220 self.warnFile = warnFile 221 self.diagFile = diagFile 222 # Internal state 223 self.featureName = None 224 self.genOpts = None 225 self.registry = None 226 # Used for extension enum value generation 227 self.extBase = 1000000000 228 self.extBlockSize = 1000 229 self.madeDirs = {} 230 231 # logMsg - write a message of different categories to different 232 # destinations. 233 # level - 234 # 'diag' (diagnostic, voluminous) 235 # 'warn' (warning) 236 # 'error' (fatal error - raises exception after logging) 237 # *args - print()-style arguments to direct to corresponding log 238 def logMsg(self, level, *args): 239 """Log a message at the given level. Can be ignored or log to a file""" 240 if level == 'error': 241 strfile = io.StringIO() 242 write('ERROR:', *args, file=strfile) 243 if self.errFile is not None: 244 write(strfile.getvalue(), file=self.errFile) 245 raise UserWarning(strfile.getvalue()) 246 elif level == 'warn': 247 if self.warnFile is not None: 248 write('WARNING:', *args, file=self.warnFile) 249 elif level == 'diag': 250 if self.diagFile is not None: 251 write('DIAG:', *args, file=self.diagFile) 252 else: 253 raise UserWarning( 254 '*** FATAL ERROR in Generator.logMsg: unknown level:' + level) 255 256 # enumToValue - parses and converts an <enum> tag into a value. 257 # Returns a list 258 # first element - integer representation of the value, or None 259 # if needsNum is False. The value must be a legal number 260 # if needsNum is True. 261 # second element - string representation of the value 262 # There are several possible representations of values. 263 # A 'value' attribute simply contains the value. 264 # A 'bitpos' attribute defines a value by specifying the bit 265 # position which is set in that value. 266 # A 'offset','extbase','extends' triplet specifies a value 267 # as an offset to a base value defined by the specified 268 # 'extbase' extension name, which is then cast to the 269 # typename specified by 'extends'. This requires probing 270 # the registry database, and imbeds knowledge of the 271 # API extension enum scheme in this function. 272 # A 'alias' attribute contains the name of another enum 273 # which this is an alias of. The other enum must be 274 # declared first when emitting this enum. 275 def enumToValue(self, elem, needsNum): 276 name = elem.get('name') 277 numVal = None 278 if 'value' in elem.keys(): 279 value = elem.get('value') 280 # print('About to translate value =', value, 'type =', type(value)) 281 if needsNum: 282 numVal = int(value, 0) 283 # If there's a non-integer, numeric 'type' attribute (e.g. 'u' or 284 # 'ull'), append it to the string value. 285 # t = enuminfo.elem.get('type') 286 # if t is not None and t != '' and t != 'i' and t != 's': 287 # value += enuminfo.type 288 self.logMsg('diag', 'Enum', name, '-> value [', numVal, ',', value, ']') 289 return [numVal, value] 290 if 'bitpos' in elem.keys(): 291 value = elem.get('bitpos') 292 bitpos = int(value, 0) 293 numVal = 1 << bitpos 294 value = '0x%08x' % numVal 295 if( bitpos >= 32 ): 296 value = value + 'ULL' 297 self.logMsg('diag', 'Enum', name, '-> bitpos [', numVal, ',', value, ']') 298 return [numVal, value] 299 if 'offset' in elem.keys(): 300 # Obtain values in the mapping from the attributes 301 enumNegative = False 302 offset = int(elem.get('offset'),0) 303 extnumber = int(elem.get('extnumber'),0) 304 extends = elem.get('extends') 305 if 'dir' in elem.keys(): 306 enumNegative = True 307 self.logMsg('diag', 'Enum', name, 'offset =', offset, 308 'extnumber =', extnumber, 'extends =', extends, 309 'enumNegative =', enumNegative) 310 # Now determine the actual enumerant value, as defined 311 # in the "Layers and Extensions" appendix of the spec. 312 numVal = self.extBase + (extnumber - 1) * self.extBlockSize + offset 313 if enumNegative: 314 numVal *= -1 315 value = '%d' % numVal 316 # More logic needed! 317 self.logMsg('diag', 'Enum', name, '-> offset [', numVal, ',', value, ']') 318 return [numVal, value] 319 if 'alias' in elem.keys(): 320 return [None, elem.get('alias')] 321 return [None, None] 322 323 # checkDuplicateEnums - sanity check for enumerated values 324 # enums - list of <enum> Elements 325 # returns the list with duplicates stripped 326 def checkDuplicateEnums(self, enums): 327 # Dictionaries indexed by name and numeric value. 328 # Entries are [ Element, numVal, strVal ] matching name or value 329 330 nameMap = {} 331 valueMap = {} 332 333 stripped = [] 334 for elem in enums: 335 name = elem.get('name') 336 (numVal, strVal) = self.enumToValue(elem, True) 337 338 if name in nameMap: 339 # Duplicate name found; check values 340 (name2, numVal2, strVal2) = nameMap[name] 341 342 # Duplicate enum values for the same name are benign. This 343 # happens when defining the same enum conditionally in 344 # several extension blocks. 345 if (strVal2 == strVal or (numVal is not None and 346 numVal == numVal2)): 347 True 348 # self.logMsg('info', 'checkDuplicateEnums: Duplicate enum (' + name + 349 # ') found with the same value:' + strVal) 350 else: 351 self.logMsg('warn', 'checkDuplicateEnums: Duplicate enum (' + name + 352 ') found with different values:' + strVal + 353 ' and ' + strVal2) 354 355 # Don't add the duplicate to the returned list 356 continue 357 elif numVal in valueMap: 358 # Duplicate value found (such as an alias); report it, but 359 # still add this enum to the list. 360 (name2, numVal2, strVal2) = valueMap[numVal] 361 362 try: 363 self.logMsg('warn', 'Two enums found with the same value: ' 364 + name + ' = ' + name2.get('name') + ' = ' + strVal) 365 except: 366 pdb.set_trace() 367 368 # Track this enum to detect followon duplicates 369 nameMap[name] = [ elem, numVal, strVal ] 370 if numVal is not None: 371 valueMap[numVal] = [ elem, numVal, strVal ] 372 373 # Add this enum to the list 374 stripped.append(elem) 375 376 # Return the list 377 return stripped 378 379 # buildEnumCDecl 380 # Generates the C declaration for an enum 381 def buildEnumCDecl(self, expand, groupinfo, groupName): 382 groupElem = groupinfo.elem 383 384 if self.genOpts.conventions.constFlagBits and groupElem.get('type') == 'bitmask': 385 return self.buildEnumCDecl_Bitmask( groupinfo, groupName) 386 else: 387 return self.buildEnumCDecl_Enum(expand, groupinfo, groupName) 388 389 # buildEnumCDecl_Bitmask 390 # Generates the C declaration for an "enum" that is actually a 391 # set of flag bits 392 def buildEnumCDecl_Bitmask(self, groupinfo, groupName): 393 groupElem = groupinfo.elem 394 flagTypeName = groupinfo.flagType.elem.get('name') 395 396 # Prefix 397 body = "// Flag bits for " + flagTypeName + "\n" 398 399 # Loop over the nested 'enum' tags. 400 for elem in groupElem.findall('enum'): 401 # Convert the value to an integer and use that to track min/max. 402 # Values of form -(number) are accepted but nothing more complex. 403 # Should catch exceptions here for more complex constructs. Not yet. 404 (_, strVal) = self.enumToValue(elem, True) 405 name = elem.get('name') 406 body += "static const " + flagTypeName + " " + name + " = " + strVal + ";\n" 407 408 # Postfix 409 410 return ("bitmask", body) 411 412 # Generates the C declaration for an enumerated type 413 def buildEnumCDecl_Enum(self, expand, groupinfo, groupName): 414 groupElem = groupinfo.elem 415 416 # Break the group name into prefix and suffix portions for range 417 # enum generation 418 expandName = re.sub(r'([0-9a-z_])([A-Z0-9])',r'\1_\2',groupName).upper() 419 expandPrefix = expandName 420 expandSuffix = '' 421 expandSuffixMatch = re.search(r'[A-Z][A-Z]+$',groupName) 422 if expandSuffixMatch: 423 expandSuffix = '_' + expandSuffixMatch.group() 424 # Strip off the suffix from the prefix 425 expandPrefix = expandName.rsplit(expandSuffix, 1)[0] 426 427 # Prefix 428 body = "typedef enum " + groupName + " {\n" 429 430 # @@ Should use the type="bitmask" attribute instead 431 isEnum = ('FLAG_BITS' not in expandPrefix) 432 433 # Get a list of nested 'enum' tags. 434 enums = groupElem.findall('enum') 435 436 # Check for and report duplicates, and return a list with them 437 # removed. 438 enums = self.checkDuplicateEnums(enums) 439 440 # Loop over the nested 'enum' tags. Keep track of the minimum and 441 # maximum numeric values, if they can be determined; but only for 442 # core API enumerants, not extension enumerants. This is inferred 443 # by looking for 'extends' attributes. 444 minName = None 445 446 # Accumulate non-numeric enumerant values separately and append 447 # them following the numeric values, to allow for aliases. 448 # NOTE: this doesn't do a topological sort yet, so aliases of 449 # aliases can still get in the wrong order. 450 aliasText = "" 451 452 for elem in enums: 453 # Convert the value to an integer and use that to track min/max. 454 # Values of form -(number) are accepted but nothing more complex. 455 # Should catch exceptions here for more complex constructs. Not yet. 456 (numVal,strVal) = self.enumToValue(elem, True) 457 name = elem.get('name') 458 459 # Extension enumerants are only included if they are required 460 if self.isEnumRequired(elem): 461 decl = " " + name + " = " + strVal + ",\n" 462 if numVal is not None: 463 body += decl 464 else: 465 aliasText += decl 466 467 # Don't track min/max for non-numbers (numVal is None) 468 if isEnum and numVal is not None and elem.get('extends') is None: 469 if minName is None: 470 minName = maxName = name 471 minValue = maxValue = numVal 472 elif numVal < minValue: 473 minName = name 474 minValue = numVal 475 elif numVal > maxValue: 476 maxName = name 477 maxValue = numVal 478 479 # Now append the non-numeric enumerant values 480 body += aliasText 481 482 # Generate min/max value tokens and a range-padding enum. Need some 483 # additional padding to generate correct names... 484 if isEnum and expand: 485 body += " " + expandPrefix + "_BEGIN_RANGE" + expandSuffix + " = " + minName + ",\n" 486 body += " " + expandPrefix + "_END_RANGE" + expandSuffix + " = " + maxName + ",\n" 487 body += " " + expandPrefix + "_RANGE_SIZE" + expandSuffix + " = (" + maxName + " - " + minName + " + 1),\n" 488 489 # Always generate this to make sure the enumerated type is 32 bits 490 body += " " + expandPrefix + "_MAX_ENUM" + expandSuffix + " = 0x7FFFFFFF\n" 491 492 # Postfix 493 body += "} " + groupName + ";" 494 495 # Determine appropriate section for this declaration 496 if groupElem.get('type') == 'bitmask': 497 section = 'bitmask' 498 else: 499 section = 'group' 500 501 return (section, body) 502 503 def makeDir(self, path): 504 self.logMsg('diag', 'OutputGenerator::makeDir(' + path + ')') 505 if path not in self.madeDirs: 506 # This can get race conditions with multiple writers, see 507 # https://stackoverflow.com/questions/273192/ 508 if not os.path.exists(path): 509 os.makedirs(path) 510 self.madeDirs[path] = None 511 512 def beginFile(self, genOpts): 513 self.genOpts = genOpts 514 515 # Open specified output file. Not done in constructor since a 516 # Generator can be used without writing to a file. 517 if self.genOpts.filename is not None: 518 if sys.platform == 'win32': 519 directory = Path(self.genOpts.directory) 520 if not Path.exists(directory): 521 os.makedirs(directory) 522 self.outFile = (directory / self.genOpts.filename).open('w', encoding='utf-8') 523 else: 524 filename = self.genOpts.directory + '/' + self.genOpts.filename 525 self.outFile = io.open(filename, 'w', encoding='utf-8') 526 else: 527 self.outFile = sys.stdout 528 529 def endFile(self): 530 if self.errFile: 531 self.errFile.flush() 532 if self.warnFile: 533 self.warnFile.flush() 534 if self.diagFile: 535 self.diagFile.flush() 536 self.outFile.flush() 537 if self.outFile != sys.stdout and self.outFile != sys.stderr: 538 self.outFile.close() 539 self.genOpts = None 540 541 def beginFeature(self, interface, emit): 542 self.emit = emit 543 self.featureName = interface.get('name') 544 # If there's an additional 'protect' attribute in the feature, save it 545 self.featureExtraProtect = interface.get('protect') 546 547 def endFeature(self): 548 # Derived classes responsible for emitting feature 549 self.featureName = None 550 self.featureExtraProtect = None 551 552 # Utility method to validate we're generating something only inside a 553 # <feature> tag 554 def validateFeature(self, featureType, featureName): 555 if self.featureName is None: 556 raise UserWarning('Attempt to generate', featureType, 557 featureName, 'when not in feature') 558 559 # Type generation 560 def genType(self, typeinfo, name, alias): 561 self.validateFeature('type', name) 562 563 # Struct (e.g. C "struct" type) generation 564 def genStruct(self, typeinfo, typeName, alias): 565 self.validateFeature('struct', typeName) 566 567 # The mixed-mode <member> tags may contain no-op <comment> tags. 568 # It is convenient to remove them here where all output generators 569 # will benefit. 570 for member in typeinfo.elem.findall('.//member'): 571 for comment in member.findall('comment'): 572 member.remove(comment) 573 574 # Group (e.g. C "enum" type) generation 575 def genGroup(self, groupinfo, groupName, alias): 576 self.validateFeature('group', groupName) 577 578 # Enumerant (really, constant) generation 579 def genEnum(self, enuminfo, typeName, alias): 580 self.validateFeature('enum', typeName) 581 582 # Command generation 583 def genCmd(self, cmd, cmdinfo, alias): 584 self.validateFeature('command', cmdinfo) 585 586 # Utility functions - turn a <proto> <name> into C-language prototype 587 # and typedef declarations for that name. 588 # name - contents of <name> tag 589 # tail - whatever text follows that tag in the Element 590 def makeProtoName(self, name, tail): 591 return self.genOpts.apientry + name + tail 592 593 def makeTypedefName(self, name, tail): 594 return '(' + self.genOpts.apientryp + 'PFN_' + name + tail + ')' 595 596 # makeCParamDecl - return a string which is an indented, formatted 597 # declaration for a <param> or <member> block (e.g. function parameter 598 # or structure/union member). 599 # param - Element (<param> or <member>) to format 600 # aligncol - if non-zero, attempt to align the nested <name> element 601 # at this column 602 def makeCParamDecl(self, param, aligncol): 603 paramdecl = ' ' + noneStr(param.text) 604 for elem in param: 605 text = noneStr(elem.text) 606 tail = noneStr(elem.tail) 607 608 if self.genOpts.conventions.is_voidpointer_alias(elem.tag, text, tail): 609 # OpenXR-specific macro insertion 610 tail = self.genOpts.conventions.make_voidpointer_alias(tail) 611 if elem.tag == 'name' and aligncol > 0: 612 self.logMsg('diag', 'Aligning parameter', elem.text, 'to column', self.genOpts.alignFuncParam) 613 # Align at specified column, if possible 614 paramdecl = paramdecl.rstrip() 615 oldLen = len(paramdecl) 616 # This works around a problem where very long type names - 617 # longer than the alignment column - would run into the tail 618 # text. 619 paramdecl = paramdecl.ljust(aligncol-1) + ' ' 620 newLen = len(paramdecl) 621 self.logMsg('diag', 'Adjust length of parameter decl from', oldLen, 'to', newLen, ':', paramdecl) 622 paramdecl += text + tail 623 return paramdecl 624 625 # getCParamTypeLength - return the length of the type field is an indented, formatted 626 # declaration for a <param> or <member> block (e.g. function parameter 627 # or structure/union member). 628 # param - Element (<param> or <member>) to identify 629 def getCParamTypeLength(self, param): 630 paramdecl = ' ' + noneStr(param.text) 631 for elem in param: 632 text = noneStr(elem.text) 633 tail = noneStr(elem.tail) 634 635 if self.genOpts.conventions.is_voidpointer_alias(elem.tag, text, tail): 636 # OpenXR-specific macro insertion 637 tail = self.genOpts.conventions.make_voidpointer_alias(tail) 638 if elem.tag == 'name': 639 # Align at specified column, if possible 640 newLen = len(paramdecl.rstrip()) 641 self.logMsg('diag', 'Identifying length of', elem.text, 'as', newLen) 642 paramdecl += text + tail 643 644 return newLen 645 646 # isEnumRequired(elem) - return True if this <enum> element is 647 # required, False otherwise 648 # elem - <enum> element to test 649 def isEnumRequired(self, elem): 650 required = elem.get('required') is not None 651 self.logMsg('diag', 'isEnumRequired:', elem.get('name'), 652 '->', required) 653 return required 654 655 #@@@ This code is overridden by equivalent code now run in 656 #@@@ Registry.generateFeature 657 658 required = False 659 660 extname = elem.get('extname') 661 if extname is not None: 662 # 'supported' attribute was injected when the <enum> element was 663 # moved into the <enums> group in Registry.parseTree() 664 if self.genOpts.defaultExtensions == elem.get('supported'): 665 required = True 666 elif re.match(self.genOpts.addExtensions, extname) is not None: 667 required = True 668 elif elem.get('version') is not None: 669 required = re.match(self.genOpts.emitversions, elem.get('version')) is not None 670 else: 671 required = True 672 673 return required 674 675 # makeCDecls - return C prototype and function pointer typedef for a 676 # command, as a two-element list of strings. 677 # cmd - Element containing a <command> tag 678 def makeCDecls(self, cmd): 679 """Generate C function pointer typedef for <command> Element""" 680 proto = cmd.find('proto') 681 params = cmd.findall('param') 682 # Begin accumulating prototype and typedef strings 683 pdecl = self.genOpts.apicall 684 tdecl = 'typedef ' 685 686 # Insert the function return type/name. 687 # For prototypes, add APIENTRY macro before the name 688 # For typedefs, add (APIENTRY *<name>) around the name and 689 # use the PFN_cmdnameproc naming convention. 690 # Done by walking the tree for <proto> element by element. 691 # etree has elem.text followed by (elem[i], elem[i].tail) 692 # for each child element and any following text 693 # Leading text 694 pdecl += noneStr(proto.text) 695 tdecl += noneStr(proto.text) 696 # For each child element, if it's a <name> wrap in appropriate 697 # declaration. Otherwise append its contents and tail contents. 698 for elem in proto: 699 text = noneStr(elem.text) 700 tail = noneStr(elem.tail) 701 if elem.tag == 'name': 702 pdecl += self.makeProtoName(text, tail) 703 tdecl += self.makeTypedefName(text, tail) 704 else: 705 pdecl += text + tail 706 tdecl += text + tail 707 # Now add the parameter declaration list, which is identical 708 # for prototypes and typedefs. Concatenate all the text from 709 # a <param> node without the tags. No tree walking required 710 # since all tags are ignored. 711 # Uses: self.indentFuncProto 712 # self.indentFuncPointer 713 # self.alignFuncParam 714 n = len(params) 715 # Indented parameters 716 if n > 0: 717 indentdecl = '(\n' 718 indentdecl += ',\n'.join(self.makeCParamDecl(p, self.genOpts.alignFuncParam) 719 for p in params) 720 indentdecl += ');' 721 else: 722 indentdecl = '(void);' 723 # Non-indented parameters 724 paramdecl = '(' 725 if n > 0: 726 paramnames = (''.join(t for t in p.itertext()) 727 for p in params) 728 paramdecl += ', '.join(paramnames) 729 else: 730 paramdecl += 'void' 731 paramdecl += ");" 732 return [ pdecl + indentdecl, tdecl + paramdecl ] 733 734 def newline(self): 735 write('', file=self.outFile) 736 737 def setRegistry(self, registry): 738 self.registry = registry