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