validitygenerator.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 import re 18 import sys 19 from collections import OrderedDict, namedtuple 20 from functools import reduce 21 22 from generator import OutputGenerator, write 23 24 class UnhandledCaseError(RuntimeError): 25 def __init__(self): 26 super().__init__('Got a case in the validity generator that we have not explicitly handled.') 27 28 def _genericIterateIntersection(a, b): 29 """Iterate through all elements in a that are also in b. 30 31 Somewhat like a set's intersection(), 32 but not type-specific so it can work with OrderedDicts, etc. 33 It also returns a generator instead of a set, 34 so you can pick what container type you'd like, 35 if any. 36 """ 37 return (x for x in a if x in b) 38 39 40 def _make_ordered_dict(gen): 41 """Make an ordered dict (with None as the values) from a generator.""" 42 return OrderedDict(((x, None) for x in gen)) 43 44 45 def _orderedDictIntersection(a, b): 46 return _make_ordered_dict(_genericIterateIntersection(a, b)) 47 48 49 def _genericIsDisjoint(a, b): 50 """Return true if nothing in a is also in b. 51 52 Like a set's is_disjoint(), 53 but not type-specific so it can work with OrderedDicts, etc. 54 """ 55 for _ in _genericIterateIntersection(a, b): 56 return False 57 # if we never enter the loop... 58 return True 59 60 class LengthEntry: 61 """An entry in a (comma-separated) len attribute""" 62 63 def __init__(self, val): 64 self.other_param_name = None 65 self.null_terminated = False 66 self.number = None 67 if val == 'null-terminated': 68 self.null_terminated = True 69 return 70 71 if val.isdigit(): 72 self.number = int(val) 73 return 74 75 # Must be another param name. 76 self.other_param_name = val 77 78 @staticmethod 79 def parse_len_from_param(param): 80 """Get a list of LengthEntry.""" 81 return [LengthEntry(elt) for elt in param.get('len').split(',')] 82 83 84 def _getElemName(elem, default=None): 85 """Get the name associated with an element, either a name child or name attribute.""" 86 name_elem = elem.find('name') 87 if name_elem is not None: 88 return name_elem.text 89 # Fallback if there is no child. 90 return elem.get('name', default) 91 92 def _getElemType(elem, default=None): 93 """Get the type associated with an element, either a type child or type attribute.""" 94 type_elem = elem.find('type') 95 if type_elem is not None: 96 return type_elem.text 97 # Fallback if there is no child. 98 return elem.get('type', default) 99 100 def _findNamedElem(elems, name): 101 """Traverse a collection of elements with 'name' nodes or attributes, looking for and returning one with the right name. 102 103 NOTE: Many places where this is used might be better served by changing to a dictionary. 104 """ 105 for elem in elems: 106 if _getElemName(elem) == name: 107 return elem 108 return None 109 110 111 def _findNamedObject(collection, name): 112 """Traverse a collection of elements with 'name' attributes, looking for and returning one with the right name. 113 114 NOTE: Many places where this is used might be better served by changing to a dictionary. 115 """ 116 for elt in collection: 117 if elt.name == name: 118 return elt 119 return None 120 121 122 class ValidityCollection: 123 """Combines validity for a single entity.""" 124 125 def __init__(self, entity, conventions): 126 self.entity = entity 127 self.conventions = conventions 128 self.lines = [] 129 130 def possiblyAddExtensionRequirement(self, entity_data, entity_preface): 131 """Add an extension-related validity statement if required. 132 133 entity_preface is a string that goes between "must be enabled prior to " 134 and the name of the entity, and normally ends in a macro. 135 For instance, might be "calling flink:" for a function. 136 """ 137 if entity_data.extensionname: 138 msg = 'The {} extension must: be enabled prior to {}{}'.format( 139 self.conventions.formatExtension(entity_data.extensionname), entity_preface, self.entity) 140 self.addValidityEntry(msg, 'extension', 'notenabled') 141 142 def addValidityEntry(self, msg, *args): 143 """Add a validity entry, optionally with a VUID anchor. 144 145 If any trailing arguments are supplied, 146 an anchor is generated by concatenating them with dashes 147 at the end of the VUID anchor name. 148 """ 149 parts = ['*'] 150 if args: 151 parts.append('[[{}]]'.format( 152 '-'.join(['VUID', self.entity] + list(args)))) 153 parts.append(msg) 154 self.lines.append(' '.join(parts)) 155 156 def addText(self, msg): 157 """Add already formatted validity text.""" 158 if not msg: 159 return 160 msg = msg.rstrip() 161 if not msg: 162 return 163 self.lines.append(msg) 164 165 @property 166 def text(self): 167 """Access validity statements as a single string.""" 168 if not self.lines: 169 return None 170 return '\n'.join(self.lines) + '\n' 171 172 173 class ValidityOutputGenerator(OutputGenerator): 174 """ValidityOutputGenerator - subclass of OutputGenerator. 175 176 Generates AsciiDoc includes of valid usage information, for reference 177 pages and the Vulkan specification. Similar to DocOutputGenerator. 178 179 ---- methods ---- 180 ValidityOutputGenerator(errFile, warnFile, diagFile) - args as for 181 OutputGenerator. Defines additional internal state. 182 ---- methods overriding base class ---- 183 beginFile(genOpts) 184 endFile() 185 beginFeature(interface, emit) 186 endFeature() 187 genCmd(cmdinfo) 188 """ 189 190 def __init__(self, 191 errFile = sys.stderr, 192 warnFile = sys.stderr, 193 diagFile = sys.stdout): 194 OutputGenerator.__init__(self, errFile, warnFile, diagFile) 195 196 @property 197 def null(self): 198 """Preferred spelling of NULL. 199 200 Delegates to the object implementing ConventionsBase. 201 """ 202 return self.conventions.null 203 204 @property 205 def structtype_member_name(self): 206 """Return name of the structure type member. 207 208 Delegates to the object implementing ConventionsBase. 209 """ 210 return self.conventions.structtype_member_name 211 212 @property 213 def nextpointer_member_name(self): 214 """Return name of the structure pointer chain member. 215 216 Delegates to the object implementing ConventionsBase. 217 """ 218 return self.conventions.nextpointer_member_name 219 220 def makeProseList(self, elements, connective='and'): 221 """Make a (comma-separated) list for use in prose. 222 223 Adds a connective (by default, 'and') 224 before the last element if there are more than 1. 225 226 Delegates to the object implementing ConventionsBase. 227 """ 228 return self.conventions.makeProseList(elements, connective) 229 230 def makeValidityCollection(self, entity_name): 231 """Create a ValidityCollection object, passing along our Conventions.""" 232 return ValidityCollection(entity_name, self.conventions) 233 234 def beginFile(self, genOpts): 235 if not genOpts.conventions: 236 raise RuntimeError('Must specify conventions object to generator options') 237 self.conventions = genOpts.conventions 238 # Vulkan says 'must: be a valid pointer' a lot, OpenXR just says 239 # 'must: be a pointer'. 240 self.valid_pointer_text = ' '.join((self.conventions.valid_pointer_prefix, 'pointer')) 241 OutputGenerator.beginFile(self, genOpts) 242 243 def endFile(self): 244 OutputGenerator.endFile(self) 245 246 def beginFeature(self, interface, emit): 247 # Start processing in superclass 248 OutputGenerator.beginFeature(self, interface, emit) 249 self.currentExtension = interface.get('name') 250 251 def endFeature(self): 252 # Finish processing in superclass 253 OutputGenerator.endFeature(self) 254 255 @property 256 def struct_macro(self): 257 """Get the appropriate format macro for a structure.""" 258 # delegate to conventions 259 return self.conventions.struct_macro 260 261 def makeStructName(self, name): 262 """Prepend the appropriate format macro for a structure to a structure type name.""" 263 # delegate to conventions 264 return self.conventions.makeStructName(name) 265 266 def makeParameterName(self, name): 267 """Prepend the appropriate format macro for a parameter/member to a parameter name.""" 268 return 'pname:' + name 269 270 def makeBaseTypeName(self, name): 271 """Prepend the appropriate format macro for a 'base type' to a type name.""" 272 return 'basetype:' + name 273 274 def makeEnumerationName(self, name): 275 """Prepend the appropriate format macro for an enumeration type to a enum type name.""" 276 return 'elink:' + name 277 278 def makeFuncPointerName(self, name): 279 """Prepend the appropriate format macro for a function pointer type to a type name.""" 280 return 'tlink:' + name 281 282 def makeExternalTypeName(self, name): 283 """Prepend the appropriate format macro for an external type like uint32_t to a type name.""" 284 # delegate to conventions 285 return self.conventions.makeExternalTypeName(name) 286 287 def makeEnumerantName(self, name): 288 """Prepend the appropriate format macro for an enumerate (value) to a enum value name.""" 289 return 'ename:' + name 290 291 def makeAnchor(self, blockname, pname, category): 292 """Create a unique namespaced Valid Usage anchor name. 293 294 blockname - command or structure 295 pname - parameter or member (may be None) 296 category - distinct implicit VU type 297 """ 298 # For debugging 299 # return '* ' 300 if pname is not None: 301 return '* [[VUID-%s-%s-%s]] ' % (blockname, pname, category) 302 303 return '* [[VUID-%s-%s]] ' % (blockname, category) 304 305 def writeInclude(self, directory, basename, validity, threadsafety, commandpropertiesentry, successcodes, errorcodes): 306 """Generate an include file. 307 308 directory - subdirectory to put file in (relative pathname) 309 basename - base name of the file 310 contents - contents of the file (Asciidoc boilerplate aside) 311 """ 312 # Create subdirectory, if needed 313 directory = self.genOpts.directory + '/' + directory 314 self.makeDir(directory) 315 316 # Create validity file 317 filename = directory + '/' + basename + '.txt' 318 self.logMsg('diag', '# Generating include file:', filename) 319 320 fp = open(filename, 'w', encoding='utf-8') 321 # Asciidoc anchor 322 write(self.conventions.warning_comment, file=fp) 323 324 # Valid Usage 325 if validity: 326 write('.Valid Usage (Implicit)', file=fp) 327 write('****', file=fp) 328 write(validity, file=fp, end='') 329 write('****', file=fp) 330 write('', file=fp) 331 332 # Host Synchronization 333 if threadsafety: 334 write('.Host Synchronization', file=fp) 335 write('****', file=fp) 336 write(threadsafety, file=fp, end='') 337 write('****', file=fp) 338 write('', file=fp) 339 340 # Command Properties - contained within a block, to avoid table numbering 341 if commandpropertiesentry: 342 write('.Command Properties', file=fp) 343 write('****', file=fp) 344 write('[options="header", width="100%"]', file=fp) 345 write('|====', file=fp) 346 write('|<<VkCommandBufferLevel,Command Buffer Levels>>|<<vkCmdBeginRenderPass,Render Pass Scope>>|<<VkQueueFlagBits,Supported Queue Types>>|<<synchronization-pipeline-stages-types,Pipeline Type>>', file=fp) 347 write(commandpropertiesentry, file=fp) 348 write('|====', file=fp) 349 write('****', file=fp) 350 write('', file=fp) 351 352 # Success Codes - contained within a block, to avoid table numbering 353 has_success = (successcodes is not None and len(successcodes) > 0) 354 has_errors = (errorcodes is not None and len(errorcodes) > 0) 355 if has_success or has_errors: 356 write('.Return Codes', file=fp) 357 write('****', file=fp) 358 if has_success: 359 write('ifndef::doctype-manpage[]', file=fp) 360 write('<<fundamentals-successcodes,Success>>::', file=fp) 361 write('endif::doctype-manpage[]', file=fp) 362 write('ifdef::doctype-manpage[]', file=fp) 363 write('On success, this command returns::', file=fp) 364 write('endif::doctype-manpage[]', file=fp) 365 write(successcodes, file=fp) 366 if has_errors: 367 write('ifndef::doctype-manpage[]', file=fp) 368 write('<<fundamentals-errorcodes,Failure>>::', file=fp) 369 write('endif::doctype-manpage[]', file=fp) 370 write('ifdef::doctype-manpage[]', file=fp) 371 write('On failure, this command returns::', file=fp) 372 write('endif::doctype-manpage[]', file=fp) 373 write(errorcodes, file=fp) 374 write('****', file=fp) 375 write('', file=fp) 376 377 fp.close() 378 379 def paramIsPointer(self, param): 380 """Check if the parameter passed in is a pointer.""" 381 tail = param.find('type').tail 382 return tail is not None and '*' in tail 383 384 def paramIsStaticArray(self, param): 385 """Check if the parameter passed in is a static array.""" 386 tail = param.find('name').tail 387 return tail and tail[0] == '[' 388 389 def staticArrayLength(self, param): 390 """Get the length of a parameter that's been identified as a static array.""" 391 paramenumsize = param.find('enum') 392 if paramenumsize is not None: 393 return paramenumsize.text 394 395 return param.find('name').tail[1:-1] 396 397 def paramIsArray(self, param): 398 """Check if the parameter passed in is a pointer to an array.""" 399 return param.get('len') is not None 400 401 def getHandleParent(self, typename): 402 """Get the parent of a handle object.""" 403 types = self.registry.tree.findall("types/type") 404 elem = _findNamedElem(types, typename) 405 if elem: 406 return elem.get('parent') 407 408 return None 409 410 def iterateHandleAncestors(self, typename): 411 """Iterate through the ancestors of a handle type.""" 412 current = self.getHandleParent(typename) 413 while current is not None: 414 yield current 415 current = self.getHandleParent(current) 416 417 def getHandleAncestors(self, typename): 418 """Get the ancestors of a handle object.""" 419 ancestors = [] 420 current = typename 421 while True: 422 current = self.getHandleParent(current) 423 if current is None: 424 return ancestors 425 ancestors.append(current) 426 427 def getHandleDispatchableAncestors(self, typename): 428 """Get the ancestors of a handle object.""" 429 ancestors = [] 430 current = typename 431 while True: 432 current = self.getHandleParent(current) 433 if current is None: 434 return ancestors 435 if self.isHandleTypeDispatchable(current): 436 ancestors.append(current) 437 438 def isHandleTypeDispatchable(self, handlename): 439 """Check if a parent object is dispatchable or not.""" 440 handle = self.registry.tree.find("types/type/[name='" + handlename + "'][@category='handle']") 441 if handle is not None and _getElemType(handle) == 'VK_DEFINE_HANDLE': 442 return True 443 else: 444 return False 445 446 def isHandleOptional(self, param, params): 447 # Simple, if it's optional, return true 448 if param.get('optional') is not None: 449 return True 450 451 # If no validity is being generated, it usually means that validity is complex and not absolute, so let's say yes. 452 if param.get('noautovalidity') is not None: 453 return True 454 455 # If the parameter is an array and we haven't already returned, find out if any of the len parameters are optional 456 if self.paramIsArray(param): 457 for length in LengthEntry.parse_len_from_param(param): 458 if not length.other_param_name: 459 # don't care about constants or "null-terminated" 460 continue 461 462 other_param = _findNamedElem(params, length.other_param_name) 463 if other_param is None: 464 self.logMsg('warn', length.other_param_name, 'is listed as a length for parameter', param, 'but no such parameter exists') 465 if other_param and other_param.get('optional'): 466 return True 467 468 return False 469 470 def getTypeCategory(self, typename): 471 """Get the category of a type.""" 472 types = self.registry.tree.findall("types/type") 473 elem = _findNamedElem(types, typename) 474 if elem is not None: 475 return elem.get('category') 476 return None 477 478 def makeAsciiDocPreChunk(self, blockname, param, params): 479 """Make a chunk of text for the end of a parameter if it is an array.""" 480 param_name = _getElemName(param) 481 paramtype = _getElemType(param) 482 483 # General pre-amble. Check optionality and add stuff. 484 asciidoc = self.makeAnchor(blockname, param_name, 'parameter') 485 optionallengths = [] 486 487 if self.paramIsStaticArray(param): 488 asciidoc += 'Any given element of ' 489 490 elif self.paramIsArray(param): 491 # Find all the parameters that are called out as optional, so we can document that they might be zero, and the array may be ignored 492 lengths = LengthEntry.parse_len_from_param(param) 493 for length in lengths: 494 if not length.other_param_name: 495 # don't care about constants or "null-terminated" 496 continue 497 other_param = _findNamedElem(params, length.other_param_name) 498 if other_param is not None and other_param.get('optional') is not None: 499 if self.paramIsPointer(other_param): 500 optionallengths.append( 501 'the value referenced by ' + self.makeParameterName(length.other_param_name)) 502 else: 503 optionallengths.append( 504 self.makeParameterName(length.other_param_name)) 505 506 # Document that these arrays may be ignored if any of the length values are 0 507 if optionallengths or param.get('optional') is not None: 508 asciidoc += 'If ' 509 if optionallengths: 510 # TODO switch to commented line, but this introduces cosmetic changes. 511 #asciidoc += self.makeProseList(optionallengths, 'or') 512 asciidoc += ', or '.join(optionallengths) 513 if len(optionallengths) == 1: 514 asciidoc += ' is ' 515 else: 516 asciidoc += ' are ' 517 518 asciidoc += 'not `0`, ' 519 520 521 if optionallengths and param.get('optional') is not None: 522 asciidoc += 'and ' 523 if param.get('optional') is not None: 524 asciidoc += self.makeParameterName(param_name) 525 asciidoc += ' is not `NULL`, ' 526 527 elif param.get('optional'): 528 # Don't generate this stub for bitflags 529 if self.getTypeCategory(paramtype) != 'bitmask': 530 if param.get('optional').split(',')[0] == 'true': 531 asciidoc += 'If ' 532 asciidoc += self.makeParameterName(param_name) 533 asciidoc += ' is not ' 534 if self.paramIsArray(param) or self.paramIsPointer(param) or self.isHandleTypeDispatchable(paramtype): 535 asciidoc += self.null 536 elif self.getTypeCategory(paramtype) == 'handle': 537 asciidoc += 'dlink:' + self.conventions.api_prefix + 'NULL_HANDLE' 538 else: 539 asciidoc += '`0`' 540 541 asciidoc += ', ' 542 543 return asciidoc 544 545 def createValidationLineForParameterIntroChunk(self, blockname, param, params, typetext): 546 """Make the generic asciidoc line chunk portion used for all parameters. 547 548 May return an empty string if nothing to validate. 549 """ 550 551 asciidoc = '' 552 param_name = _getElemName(param) 553 paramtype = _getElemType(param) 554 555 asciidoc += self.makeAsciiDocPreChunk(blockname, param, params) 556 557 asciidoc += self.makeParameterName(param_name) 558 asciidoc += ' must: be ' 559 560 if self.paramIsArray(param): 561 # Arrays. These are hard to get right, apparently 562 563 lengths = param.get('len').split(',') 564 565 if lengths[0] == 'null-terminated': 566 asciidoc += 'a null-terminated ' 567 elif lengths[0] == '1': 568 asciidoc += 'a ' + self.valid_pointer_text + ' to ' 569 else: 570 asciidoc += 'a ' + self.valid_pointer_text + ' to an array of ' 571 572 # Handle equations, which are currently denoted with latex 573 if 'latexmath:' in lengths[0]: 574 asciidoc += lengths[0] 575 else: 576 asciidoc += self.makeParameterName(lengths[0]) 577 asciidoc += ' ' 578 579 for length in lengths[1:]: 580 if length == 'null-terminated': 581 # This should always be the last thing. 582 # If it ever isn't for some bizarre reason, then this will need some massaging. 583 asciidoc += 'null-terminated ' 584 elif length == '1': 585 asciidoc += self.valid_pointer_text + 's to ' 586 else: 587 asciidoc += self.valid_pointer_text + 's to arrays of ' 588 # Handle equations, which are currently denoted with latex 589 if 'latexmath:' in length: 590 asciidoc += length 591 else: 592 asciidoc += self.makeParameterName(length) 593 asciidoc += ' ' 594 595 # Void pointers don't actually point at anything - remove the word "to" 596 if paramtype == 'void': 597 if lengths[-1] == '1': 598 if len(lengths) > 1: 599 asciidoc = asciidoc[:-5] # Take care of the extra s added by the post array chunk function. #HACK# 600 else: 601 asciidoc = asciidoc[:-4] 602 else: 603 # An array of void values is a byte array. 604 asciidoc += 'byte' 605 606 elif paramtype == 'char': 607 # A null terminated array of chars is a string 608 if lengths[-1] == 'null-terminated': 609 asciidoc += 'UTF-8 string' 610 else: 611 # Else it's just a bunch of chars 612 asciidoc += 'char value' 613 elif param.text is not None: 614 # If a value is "const" that means it won't get modified, so it must be valid going into the function. 615 if 'const' in param.text: 616 typecategory = self.getTypeCategory(paramtype) 617 if (typecategory not in ('struct', 'union', 'basetype') and typecategory is not None) \ 618 or not self.isStructAlwaysValid(blockname, paramtype): 619 asciidoc += 'valid ' 620 621 asciidoc += typetext 622 623 # pluralize 624 if len(lengths) > 1 or (lengths[0] != '1' and lengths[0] != 'null-terminated'): 625 asciidoc += 's' 626 627 elif self.paramIsPointer(param): 628 # Handle pointers - which are really special case arrays (i.e. they don't have a length) 629 #TODO should do something here if someone ever uses some intricate comma-separated `optional` 630 pointercount = param.find('type').tail.count('*') 631 632 # Treat void* as an int 633 if paramtype == 'void': 634 pointercount -= 1 635 636 # Could be multi-level pointers (e.g. ppData - pointer to a pointer). Handle that. 637 asciidoc += 'a ' 638 asciidoc += (self.valid_pointer_text + ' to a ') * pointercount 639 640 # Handle void* and pointers to it 641 if paramtype == 'void': 642 # If there is only void*, it is just optional int - we don't need any language. 643 if pointercount == 0 and param.get('optional') is not None: 644 return '' # early return 645 646 if param.get('optional') is None or param.get('optional').split(',')[pointercount]: 647 # The last void* is just optional int (e.g. to be filled by the impl.) 648 typetext = 'pointer value' 649 650 # If a value is "const" that means it won't get modified, so it must be valid going into the function. 651 if param.text is not None and paramtype != 'void': 652 if 'const' in param.text: 653 asciidoc += 'valid ' 654 655 asciidoc += typetext 656 657 else: 658 # Non-pointer, non-optional things must be valid 659 asciidoc += 'a valid ' 660 asciidoc += typetext 661 662 if asciidoc != '': 663 asciidoc += '\n' 664 665 # Add additional line for non-optional bitmasks 666 isOutputParam = self.paramIsPointer(param) and not (param.text and 'const' in param.text) 667 if self.getTypeCategory(paramtype) == 'bitmask' and not isOutputParam: 668 isMandatory = param.get('optional') is None #TODO does not really handle if someone tries something like optional="true,false" 669 if isMandatory: 670 asciidoc += self.makeAnchor(blockname, param_name, 'requiredbitmask') 671 if self.paramIsArray(param): 672 asciidoc += 'Each element of ' 673 asciidoc += 'pname:' 674 asciidoc += param_name 675 asciidoc += ' must: not be `0`' 676 asciidoc += '\n' 677 678 return asciidoc 679 680 def makeAsciiDocLineForParameter(self, blockname, param, params, typetext): 681 if param.get('noautovalidity') is not None: 682 return '' 683 asciidoc = self.createValidationLineForParameterIntroChunk(blockname, param, params, typetext) 684 685 return asciidoc 686 687 def isStructAlwaysValid(self, blockname, structname): 688 """Try to do check if a structure is always considered valid (i.e. there's no rules to its acceptance).""" 689 690 struct = self.registry.tree.find("types/type[@name='" + structname + "']") 691 692 if struct.get('returnedonly'): 693 return True 694 695 params = struct.findall('member') 696 697 for param in params: 698 param_name = _getElemName(param) 699 paramtype = _getElemType(param) 700 typecategory = self.getTypeCategory(paramtype) 701 702 if param_name in (self.structtype_member_name, self.nextpointer_member_name): 703 return False 704 705 if param.get('noautovalidity'): 706 return False 707 708 if paramtype in ('void', 'char') or self.paramIsArray(param) or self.paramIsPointer(param): 709 if self.makeAsciiDocLineForParameter(blockname, param, params, '') != '': 710 return False 711 elif typecategory in ('handle', 'enum', 'bitmask'): 712 return False 713 elif typecategory in ('struct', 'union'): 714 if self.isStructAlwaysValid(blockname, paramtype) is False: 715 return False 716 717 return True 718 719 def fix_an(self, text): 720 """Fix 'a' vs 'an' in a string""" 721 return re.sub(r' a ([a-z]+:)?([aAeEiIoOxX]\w+\b)(?!:)', r' an \1\2', text) 722 723 def createValidationLineForParameter(self, blockname, param, params, typecategory): 724 """Make an entire asciidoc line for a given parameter.""" 725 asciidoc = '' 726 param_name = _getElemName(param) 727 paramtype = _getElemType(param) 728 type_name = paramtype 729 730 is_array = self.paramIsArray(param) 731 is_pointer = self.paramIsPointer(param) 732 needs_recursive_validity = (is_array or 733 is_pointer or 734 not self.isStructAlwaysValid(blockname, type_name)) 735 typetext = None 736 if type_name in ('void', 'char'): 737 # Chars and void are special cases - needs care inside the generator functions 738 # A null-terminated char array is a string, else it's chars. 739 # An array of void values is a byte array, a void pointer is just a pointer to nothing in particular 740 typetext = '' 741 742 elif typecategory == 'bitmask': 743 bitsname = type_name.replace('Flags', 'FlagBits') 744 if self.registry.tree.find("enums[@name='" + bitsname + "']") is None: 745 # Empty bit mask: presumably just a placeholder (or only in an extension not enabled for this build) 746 asciidoc += self.makeAnchor(blockname, 747 param_name, 'zerobitmask') 748 asciidoc += self.makeParameterName(param_name) 749 asciidoc += ' must: be `0`' 750 asciidoc += '\n' 751 else: 752 const_in_text = param.text is not None and 'const' in param.text 753 754 if is_array: 755 if const_in_text: 756 # input an array of bitmask values 757 template = 'combinations of {bitsname} value' 758 else: 759 template = '{paramtype} value' 760 elif is_pointer: 761 if const_in_text: 762 template = 'combination of {bitsname} values' 763 else: 764 template = '{paramtype} value' 765 else: 766 template = 'combination of {bitsname} values' 767 768 # The above few cases all use makeEnumerationName, just with different context. 769 typetext = template.format(bitsname=self.makeEnumerationName(bitsname), paramtype=self.makeEnumerationName(type_name)) 770 771 elif typecategory == 'handle': 772 typetext = '{} handle'.format(self.makeStructName(type_name)) 773 774 elif typecategory == 'enum': 775 typetext = '{} value'.format(self.makeEnumerationName(type_name)) 776 777 elif typecategory == 'funcpointer': 778 typetext = '{} value'.format(self.makeFuncPointerName(type_name)) 779 780 elif typecategory == 'struct': 781 if needs_recursive_validity: 782 typetext = '{} structure'.format( 783 self.makeStructName(type_name)) 784 785 else: 786 # TODO right now just skipping generating a line here. 787 # I think this was intentional in general, but maybe not in that particular case. 788 #raise UnhandledCaseError() 789 pass 790 791 elif typecategory == 'union': 792 if needs_recursive_validity: 793 typetext = '{} union'.format(self.makeStructName(type_name)) 794 else: 795 # TODO right now just skipping generating a line here. 796 # I think this was intentional in general: 797 # we do hit this in Vulkan. 798 pass 799 800 elif self.paramIsArray(param) or self.paramIsPointer(param): 801 typetext = '{} value'.format(self.makeBaseTypeName(paramtype)) 802 803 elif typecategory is None: 804 # "a valid uint32_t value" doesn't make much sense. 805 pass 806 807 # If any of the above conditions matched and set typetext, 808 # we call using it. 809 if typetext is not None: 810 asciidoc += self.makeAsciiDocLineForParameter( 811 blockname, param, params, typetext) 812 return self.fix_an(asciidoc) 813 814 def makeAsciiDocHandleParent(self, blockname, param, params): 815 """Make an asciidoc validity entry for a handle's parent object. 816 817 Creates 'parent' VUID. 818 """ 819 asciidoc = '' 820 param_name = _getElemName(param) 821 paramtype = _getElemType(param) 822 823 # Deal with handle parents 824 handleparent = self.getHandleParent(paramtype) 825 if handleparent is None: 826 return asciidoc 827 828 otherparam = None 829 for p in params: 830 if _getElemType(p) == handleparent: 831 otherparam = p 832 break 833 if otherparam is None: 834 return asciidoc 835 836 parentreference = _getElemName(otherparam) 837 asciidoc += self.makeAnchor(blockname, param_name, 'parent') 838 839 if self.isHandleOptional(param, params): 840 if self.paramIsArray(param): 841 asciidoc += 'Each element of ' 842 asciidoc += self.makeParameterName(param_name) 843 asciidoc += ' that is a valid handle' 844 else: 845 asciidoc += 'If ' 846 asciidoc += self.makeParameterName(param_name) 847 asciidoc += ' is a valid handle, it' 848 else: 849 if self.paramIsArray(param): 850 asciidoc += 'Each element of ' 851 asciidoc += self.makeParameterName(param_name) 852 asciidoc += ' must: have been created, allocated, or retrieved from ' 853 asciidoc += self.makeParameterName(parentreference) 854 855 asciidoc += '\n' 856 return asciidoc 857 858 def makeAsciiDocHandlesCommonAncestor(self, blockname, handles, params): 859 """Make an asciidoc validity entry for a common ancestors between handles. 860 861 Only handles parent validity for signatures taking multiple handles 862 any ancestors also being supplied to this function. 863 (e.g. "Each of x, y, and z must: come from the same slink:ParentHandle") 864 See self.makeAsciiDocHandleParent() for instances where the parent 865 handle is named and also passed. 866 867 Creates 'commonparent' VUID. 868 """ 869 asciidoc = '' 870 871 if len(handles) > 1: 872 ancestormap = {} 873 anyoptional = False 874 # Find all the ancestors 875 for param in handles: 876 paramtype = _getElemType(param) 877 878 if not self.paramIsPointer(param) or (param.text and 'const' in param.text): 879 ancestors = self.getHandleDispatchableAncestors(paramtype) 880 881 ancestormap[param] = ancestors 882 883 anyoptional |= self.isHandleOptional(param, params) 884 885 # Remove redundant ancestor lists 886 for param in handles: 887 paramtype = _getElemType(param) 888 889 removals = [] 890 for ancestors in ancestormap.items(): 891 if paramtype in ancestors[1]: 892 removals.append(ancestors[0]) 893 894 if removals != []: 895 for removal in removals: 896 del(ancestormap[removal]) 897 898 # Intersect 899 900 if len(ancestormap.values()) > 1: 901 current = list(ancestormap.values())[0] 902 for ancestors in list(ancestormap.values())[1:]: 903 current = [val for val in current if val in ancestors] 904 905 if len(current) > 0: 906 commonancestor = current[0] 907 908 if len(ancestormap.keys()) > 1: 909 910 asciidoc += self.makeAnchor(blockname, None, 'commonparent') 911 912 parametertexts = [] 913 for param in ancestormap.keys(): 914 param_name = _getElemName(param) 915 parametertext = self.makeParameterName(param_name) 916 if self.paramIsArray(param): 917 parametertext = 'the elements of ' + parametertext 918 parametertexts.append(parametertext) 919 920 parametertexts.sort() 921 922 if len(parametertexts) > 2: 923 asciidoc += 'Each of ' 924 else: 925 asciidoc += 'Both of ' 926 927 asciidoc += ", ".join(parametertexts[:-1]) 928 asciidoc += ', and ' 929 asciidoc += parametertexts[-1] 930 if anyoptional is True: 931 asciidoc += ' that are valid handles' 932 asciidoc += ' must: have been created, allocated, or retrieved from the same ' 933 asciidoc += self.makeStructName(commonancestor) 934 asciidoc += '\n' 935 936 return asciidoc 937 def makeStructureTypeFromName(self, structname): 938 """Create text for a structure type name, like ename:VK_STRUCTURE_TYPE_CREATE_INSTANCE_INFO""" 939 return self.makeEnumerantName(self.conventions.generate_structure_type_from_name(structname)) 940 941 def makeStructureType(self, structname, param): 942 """Generate an asciidoc validity line for the type value of a struct.""" 943 param_name = _getElemName(param) 944 945 asciidoc = self.makeAnchor(structname, param_name, self.structtype_member_name) 946 asciidoc += self.makeParameterName(param_name) 947 asciidoc += ' must: be ' 948 949 values = param.get('values') 950 if values: 951 # Extract each enumerant value. They could be validated in the 952 # same fashion as validextensionstructs in 953 # makeStructureExtensionPointer, although that's not relevant in 954 # the current extension struct model. 955 asciidoc += self.makeProseList(( self.makeEnumerantName(v) for v in values.split(',')), 'or') 956 elif 'Base' in structname: 957 # This type doesn't even have any values for its type, 958 # and it seems like it might be a base struct that we'd expect to lack its own type, 959 # so omit the entire statement 960 return '' 961 else: 962 self.logMsg('warn', 'No values were marked-up for the structure type member of', structname, 'so making one up!') 963 asciidoc += self.makeStructureTypeFromName(structname) 964 965 asciidoc += '\n' 966 967 return asciidoc 968 969 def makeStructureExtensionPointer(self, blockname, param): 970 """Generate an asciidoc validity line for the pointer chain member value of a struct.""" 971 param_name = _getElemName(param) 972 973 if param.get('validextensionstructs') is not None: 974 self.logMsg('warn', blockname, 'validextensionstructs is deprecated/removed', '\n') 975 976 asciidoc = self.makeAnchor(blockname, param_name, self.nextpointer_member_name) 977 validextensionstructs = self.registry.validextensionstructs.get(blockname) 978 extensionstructs = [] 979 980 if validextensionstructs is not None: 981 # Check each structure name and skip it if not required by the 982 # generator. This allows tagging extension structs in the XML 983 # that are only included in validity when needed for the spec 984 # being targeted. 985 for struct in validextensionstructs: 986 # Unpleasantly breaks encapsulation. Should be a method in the registry class 987 type = self.registry.lookupElementInfo(struct, self.registry.typedict) 988 if type is None: 989 self.logMsg('warn', 'makeStructureExtensionPointer: struct', struct, 'is in a validextensionstructs= attribute but is not in the registry') 990 elif type.required: 991 extensionstructs.append('slink:' + struct) 992 else: 993 self.logMsg('diag', 'makeStructureExtensionPointer: struct', struct, 'IS NOT required') 994 995 if not extensionstructs: 996 asciidoc += self.makeParameterName(param_name) 997 asciidoc += ' must: be ' 998 asciidoc += self.null 999 elif len(extensionstructs) == 1: 1000 asciidoc += self.makeParameterName(param_name) 1001 asciidoc += ' must: be ' 1002 asciidoc += self.null 1003 asciidoc +=' or a pointer to a valid instance of ' 1004 asciidoc += extensionstructs[0] 1005 else: 1006 asciidoc += 'Each ' 1007 asciidoc += self.makeParameterName(param_name) 1008 asciidoc += ' member of any structure (including this one) in the ' 1009 asciidoc += 'pname:' + self.nextpointer_member_name 1010 asciidoc += ' chain must: be either ' 1011 asciidoc += self.null 1012 asciidoc += ' or a pointer to a valid instance of ' 1013 1014 asciidoc += self.makeProseList(extensionstructs, 'or') 1015 asciidoc += '\n' 1016 1017 asciidoc += self.makeAnchor(blockname, self.structtype_member_name, 'unique') 1018 asciidoc += 'Each pname:' + self.structtype_member_name + ' member in the ' 1019 asciidoc += 'pname:' + self.nextpointer_member_name 1020 asciidoc += ' chain must: be unique' 1021 1022 asciidoc += '\n' 1023 1024 return asciidoc 1025 1026 def makeValidUsageStatementsReturnedOnly(self, cmd, blockname, params): 1027 """Generate all the valid usage information for a given struct 1028 that's only ever filled out by the implementation other than the 1029 structure type and pointer chain members. 1030 """ 1031 1032 # Start the asciidoc block for this 1033 asciidoc = '' 1034 1035 for param in params: 1036 param_name = _getElemName(param) 1037 paramtype = _getElemType(param) 1038 1039 # Valid usage ID tags (VUID) are generated for various 1040 # conditions based on the name of the block (structure or 1041 # command), name of the element (member or parameter), and type 1042 # of VU statement. 1043 1044 if param.get('noautovalidity') is None: 1045 # Generate language to independently validate a parameter 1046 if self.conventions.is_structure_type_member(paramtype, param_name): 1047 asciidoc += self.makeStructureType(blockname, param) 1048 elif self.conventions.is_nextpointer_member(paramtype, param_name) and cmd.get('structextends') is None: 1049 asciidoc += self.makeStructureExtensionPointer(blockname, param) 1050 1051 # In case there's nothing to report, return None 1052 if asciidoc == '': 1053 return None 1054 1055 return asciidoc 1056 1057 def makeValidUsageStatements(self, cmd, blockname, params): 1058 """Generate all the valid usage information for a given struct or command.""" 1059 # Start the asciidoc block for this 1060 asciidoc = '' 1061 1062 handles = [] 1063 arraylengths = set() 1064 for param in params: 1065 param_name = _getElemName(param) 1066 paramtype = _getElemType(param) 1067 1068 # Valid usage ID tags (VUID) are generated for various 1069 # conditions based on the name of the block (structure or 1070 # command), name of the element (member or parameter), and type 1071 # of VU statement. 1072 1073 # Get the type's category 1074 typecategory = self.getTypeCategory(paramtype) 1075 1076 if param.get('noautovalidity') is None: 1077 # Generate language to independently validate a parameter 1078 if self.conventions.is_structure_type_member(paramtype, param_name): 1079 asciidoc += self.makeStructureType(blockname, param) 1080 elif self.conventions.is_nextpointer_member(paramtype, param_name): 1081 if cmd.get('structextends') is None: 1082 asciidoc += self.makeStructureExtensionPointer(blockname, param) 1083 else: 1084 asciidoc += self.createValidationLineForParameter(blockname, param, params, typecategory) 1085 1086 # Ensure that any parenting is properly validated, and list that a handle was found 1087 if typecategory == 'handle': 1088 handles.append(param) 1089 1090 # Get the array length for this parameter 1091 arraylength = param.get('len') 1092 if arraylength is not None: 1093 arraylengths.update(set(arraylength.split(','))) 1094 1095 # For any vkQueue* functions, there might be queue type data 1096 if 'vkQueue' in blockname: 1097 # The queue type must be valid 1098 queuetypes = cmd.get('queues') 1099 if queuetypes: 1100 queuebits = [] 1101 for queuetype in re.findall(r'([^,]+)', queuetypes): 1102 queuebits.append(queuetype.replace('_',' ')) 1103 1104 asciidoc += self.makeAnchor(blockname, None, 'queuetype') 1105 asciidoc += 'The pname:queue must: support ' 1106 if len(queuebits) == 1: 1107 asciidoc += queuebits[0] 1108 else: 1109 asciidoc += (', ').join(queuebits[:-1]) 1110 asciidoc += ', or ' 1111 asciidoc += queuebits[-1] 1112 asciidoc += ' operations' 1113 asciidoc += '\n' 1114 1115 if 'vkCmd' in blockname: 1116 # The commandBuffer parameter must be being recorded 1117 asciidoc += self.makeAnchor(blockname, 'commandBuffer', 'recording') 1118 asciidoc += 'pname:commandBuffer must: be in the <<commandbuffers-lifecycle, recording state>>' 1119 asciidoc += '\n' 1120 1121 # 1122 # Start of valid queue type validation - command pool must have been 1123 # allocated against a queue with at least one of the valid queue types 1124 asciidoc += self.makeAnchor(blockname, 'commandBuffer', 'cmdpool') 1125 1126 # 1127 # This test for vkCmdFillBuffer is a hack, since we have no path 1128 # to conditionally have queues enabled or disabled by an extension. 1129 # As the VU stuff is all moving out (hopefully soon), this hack solves the issue for now 1130 if blockname == 'vkCmdFillBuffer': 1131 if 'VK_KHR_maintenance1' in self.registry.requiredextensions: 1132 asciidoc += 'The sname:VkCommandPool that pname:commandBuffer was allocated from must: support transfer, graphics or compute operations\n' 1133 else: 1134 asciidoc += 'The sname:VkCommandPool that pname:commandBuffer was allocated from must: support graphics or compute operations\n' 1135 else: 1136 # The queue type must be valid 1137 queuetypes = cmd.get('queues') 1138 queuebits = [] 1139 for queuetype in re.findall(r'([^,]+)', queuetypes): 1140 queuebits.append(queuetype.replace('_',' ')) 1141 1142 asciidoc += 'The sname:VkCommandPool that pname:commandBuffer was allocated from must: support ' 1143 if len(queuebits) == 1: 1144 asciidoc += queuebits[0] 1145 else: 1146 asciidoc += (', ').join(queuebits[:-1]) 1147 asciidoc += ', or ' 1148 asciidoc += queuebits[-1] 1149 asciidoc += ' operations' 1150 asciidoc += '\n' 1151 1152 # Must be called inside/outside a renderpass appropriately 1153 renderpass = cmd.get('renderpass') 1154 1155 if renderpass != 'both': 1156 asciidoc += self.makeAnchor(blockname, None, 'renderpass') 1157 asciidoc += 'This command must: only be called ' 1158 asciidoc += renderpass 1159 asciidoc += ' of a render pass instance' 1160 asciidoc += '\n' 1161 1162 # Must be in the right level command buffer 1163 cmdbufferlevel = cmd.get('cmdbufferlevel') 1164 1165 if cmdbufferlevel != 'primary,secondary': 1166 asciidoc += self.makeAnchor(blockname, None, 'bufferlevel') 1167 asciidoc += 'pname:commandBuffer must: be a ' 1168 asciidoc += cmdbufferlevel 1169 asciidoc += ' sname:VkCommandBuffer' 1170 asciidoc += '\n' 1171 1172 # Any non-optional arraylengths should specify they must be greater than 0 1173 non_optional_array_lengths = ((param, _getElemName(param)) 1174 for param in params 1175 if _getElemName(param) in arraylengths and not param.get('optional')) 1176 1177 for param, param_name in non_optional_array_lengths: 1178 # Get all the array dependencies 1179 arrays = cmd.findall( 1180 "param/[@len='{}'][@optional='true']".format(param_name)) 1181 1182 # Get all the optional array dependencies, including those not generating validity for some reason 1183 optionalarrays = arrays + \ 1184 cmd.findall( 1185 "param/[@len='{}'][@noautovalidity='true']".format(param_name)) 1186 1187 # If arraylength can ever be not a legal part of an 1188 # asciidoc anchor name, this will need to be altered. 1189 asciidoc += self.makeAnchor(blockname, param_name, 'arraylength') 1190 1191 # Allow lengths to be arbitrary if all their dependents are optional 1192 if optionalarrays and len(optionalarrays) == len(arrays): 1193 asciidoc += 'If ' 1194 if len(optionalarrays) > 1: 1195 asciidoc += 'any of ' 1196 1197 # TODO uncomment following statement once cosmetic changes OK 1198 # asciidoc += self.makeProseList((self.makeParameterName(_getElemName(array)) 1199 # for array in optionalarrays), 1200 # 'or') 1201 1202 if len(optionalarrays) > 1: 1203 # TODO remove following statement once cosmetic changes OK 1204 asciidoc += self.makeProseList((self.makeParameterName(_getElemName(array)) 1205 for array in optionalarrays), 1206 'or') 1207 asciidoc += ' are ' 1208 else: 1209 # TODO remove following statement once cosmetic changes OK 1210 asciidoc += ', '.join((self.makeParameterName(_getElemName(array)) 1211 for array in optionalarrays)) 1212 asciidoc += ' is ' 1213 1214 asciidoc += 'not ' + self.null + ', ' 1215 1216 if self.paramIsPointer(param): 1217 asciidoc += 'the value referenced by ' 1218 1219 elif self.paramIsPointer(param): 1220 asciidoc += 'The value referenced by ' 1221 1222 asciidoc += self.makeParameterName(param_name) 1223 asciidoc += ' must: be greater than `0`' 1224 asciidoc += '\n' 1225 1226 # Find the parents of all objects referenced in this command 1227 for param in handles: 1228 paramtype = _getElemType(param) 1229 # Don't detect a parent for return values! 1230 if not self.paramIsPointer(param) or (param.text is not None and 'const' in param.text): 1231 1232 parent = self.getHandleParent(paramtype) 1233 1234 if parent is not None: 1235 asciidoc += self.makeAsciiDocHandleParent(blockname, param, params) 1236 1237 # Find the common ancestor of all objects referenced in this command 1238 asciidoc += self.makeAsciiDocHandlesCommonAncestor(blockname, handles, params) 1239 1240 # In case there's nothing to report, return None 1241 if asciidoc == '': 1242 return None 1243 # Delimit the asciidoc block 1244 return asciidoc 1245 1246 def makeThreadSafetyBlock(self, cmd, paramtext): 1247 """Generate C function pointer typedef for <command> Element""" 1248 paramdecl = '' 1249 1250 # Find and add any parameters that are thread unsafe 1251 explicitexternsyncparams = cmd.findall(paramtext + "[@externsync]") 1252 if explicitexternsyncparams is not None: 1253 for param in explicitexternsyncparams: 1254 externsyncattribs = param.get('externsync') 1255 param_name = _getElemName(param) 1256 for externsyncattrib in externsyncattribs.split(','): 1257 paramdecl += '* ' 1258 paramdecl += 'Host access to ' 1259 if externsyncattrib == 'true': 1260 if self.paramIsArray(param): 1261 paramdecl += 'each member of ' + \ 1262 self.makeParameterName(param_name) 1263 elif self.paramIsPointer(param): 1264 paramdecl += 'the object referenced by ' + \ 1265 self.makeParameterName(param_name) 1266 else: 1267 paramdecl += self.makeParameterName(param_name) 1268 else: 1269 paramdecl += 'pname:' 1270 paramdecl += externsyncattrib 1271 paramdecl += ' must: be externally synchronized\n' 1272 1273 # Vulkan-specific 1274 # For any vkCmd* functions, the command pool is externally synchronized 1275 if cmd.find('proto/name') is not None and 'vkCmd' in cmd.find('proto/name').text: 1276 paramdecl += '* ' 1277 paramdecl += 'Host access to the sname:VkCommandPool that pname:commandBuffer was allocated from must: be externally synchronized' 1278 paramdecl += '\n' 1279 1280 # Find and add any "implicit" parameters that are thread unsafe 1281 implicitexternsyncparams = cmd.find('implicitexternsyncparams') 1282 if implicitexternsyncparams is not None: 1283 for elem in implicitexternsyncparams: 1284 paramdecl += '* ' 1285 paramdecl += 'Host access to ' 1286 paramdecl += elem.text 1287 paramdecl += ' must: be externally synchronized\n' 1288 1289 if not paramdecl: 1290 return None 1291 return paramdecl 1292 1293 def makeCommandPropertiesTableEntry(self, cmd, name): 1294 1295 if 'vkCmd' in name: 1296 # Must be called inside/outside a renderpass appropriately 1297 cmdbufferlevel = cmd.get('cmdbufferlevel') 1298 cmdbufferlevel = (' + \n').join(cmdbufferlevel.title().split(',')) 1299 1300 renderpass = cmd.get('renderpass') 1301 renderpass = renderpass.capitalize() 1302 1303 # 1304 # This test for vkCmdFillBuffer is a hack, since we have no path 1305 # to conditionally have queues enabled or disabled by an extension. 1306 # As the VU stuff is all moving out (hopefully soon), this hack solves the issue for now 1307 if name == 'vkCmdFillBuffer': 1308 if 'VK_KHR_maintenance1' in self.registry.requiredextensions: 1309 queues = 'Transfer + \nGraphics + \nCompute' 1310 else: 1311 queues = 'Graphics + \nCompute' 1312 else: 1313 queues = cmd.get('queues') 1314 queues = (' + \n').join(queues.title().split(',')) 1315 1316 pipeline = cmd.get('pipeline') 1317 if pipeline: 1318 pipeline = pipeline.capitalize() 1319 else: 1320 pipeline = '' 1321 1322 return '|' + cmdbufferlevel + '|' + renderpass + '|' + queues + '|' + pipeline 1323 elif 'vkQueue' in name: 1324 # Must be called inside/outside a renderpass appropriately 1325 1326 queues = cmd.get('queues') 1327 if queues is None: 1328 queues = 'Any' 1329 else: 1330 queues = (' + \n').join(queues.upper().split(',')) 1331 1332 return '|-|-|' + queues + '|-' 1333 1334 return None 1335 1336 # Check each enumerant name in the enums list and remove it if not 1337 # required by the generator. This allows specifying success and error 1338 # codes for extensions that are only included in validity when needed 1339 # for the spec being targeted. 1340 def findRequiredEnums(self, enums): 1341 out = [] 1342 for enum in enums: 1343 # Unpleasantly breaks encapsulation. Should be a method in the registry class 1344 ei = self.registry.lookupElementInfo(enum, self.registry.enumdict) 1345 if ei is None: 1346 self.logMsg('warn', 'findRequiredEnums: enum', enum, 1347 'is in an attribute list but is not in the registry') 1348 elif ei.required: 1349 out.append(enum) 1350 else: 1351 self.logMsg('diag', 'findRequiredEnums: enum', enum, 'IS NOT required, skipping') 1352 return out 1353 1354 def makeReturnCodeList(self, attrib, cmd, name): 1355 """Return a list of possible return codes for a function. 1356 1357 attrib is either 'successcodes' or 'errorcodes'. 1358 """ 1359 return_lines = [] 1360 RETURN_CODE_FORMAT = '* ename:{}' 1361 1362 codes_attr = cmd.get(attrib) 1363 if codes_attr: 1364 codes = self.findRequiredEnums(codes_attr.split(',')) 1365 if codes: 1366 return_lines.extend((RETURN_CODE_FORMAT.format(code) 1367 for code in codes)) 1368 1369 applicable_ext_codes = (ext_code 1370 for ext_code in self.registry.commandextensionsuccesses 1371 if ext_code.command == name) 1372 for ext_code in applicable_ext_codes: 1373 line = RETURN_CODE_FORMAT.format(ext_code.value) 1374 if ext_code.extension: 1375 line += ' [only if {} is enabled]'.format( 1376 self.conventions.formatExtension(ext_code.extension)) 1377 1378 return_lines.append(line) 1379 if return_lines: 1380 return '\n'.join(return_lines) 1381 1382 return None 1383 1384 def makeSuccessCodes(self, cmd, name): 1385 return self.makeReturnCodeList('successcodes', cmd, name) 1386 1387 def makeErrorCodes(self, cmd, name): 1388 return self.makeReturnCodeList('errorcodes', cmd, name) 1389 1390 def genCmd(self, cmdinfo, name, alias): 1391 """Command generation.""" 1392 OutputGenerator.genCmd(self, cmdinfo, name, alias) 1393 1394 # @@@ (Jon) something needs to be done here to handle aliases, probably 1395 1396 # Get all the parameters 1397 params = cmdinfo.elem.findall('param') 1398 1399 validity = self.makeValidUsageStatements(cmdinfo.elem, name, params) 1400 1401 threadsafety = self.makeThreadSafetyBlock(cmdinfo.elem, 'param') 1402 # Vulkan-specific 1403 commandpropertiesentry = self.makeCommandPropertiesTableEntry(cmdinfo.elem, name) 1404 successcodes = self.makeSuccessCodes(cmdinfo.elem, name) 1405 errorcodes = self.makeErrorCodes(cmdinfo.elem, name) 1406 # OpenXR-specific 1407 # beginsstate = self.makeBeginState(cmdinfo.elem, name) 1408 # endsstate = self.makeEndState(cmdinfo.elem, name) 1409 # checksstatedata = self.makeCheckState(cmdinfo.elem, name) 1410 1411 self.writeInclude('protos', name, validity, threadsafety, commandpropertiesentry, successcodes, errorcodes) 1412 1413 def genStruct(self, typeinfo, typeName, alias): 1414 """Struct Generation.""" 1415 OutputGenerator.genStruct(self, typeinfo, typeName, alias) 1416 1417 # @@@ (Jon) something needs to be done here to handle aliases, probably 1418 1419 # Anything that's only ever returned can't be set by the user, so shouldn't have any validity information. 1420 params = [] 1421 validity = '' 1422 threadsafety = [] 1423 1424 if typeinfo.elem.get('returnedonly') is None: 1425 params = typeinfo.elem.findall('member') 1426 1427 generalvalidity = self.makeValidUsageStatements(typeinfo.elem, typeName, params) 1428 if generalvalidity: 1429 validity += generalvalidity 1430 threadsafety = self.makeThreadSafetyBlock(typeinfo.elem, 'member') 1431 1432 self.writeInclude('structs', typeName, validity, threadsafety, None, None, None) 1433 else: 1434 # Need to generate structure type and next pointer chain member validation 1435 params = typeinfo.elem.findall('member') 1436 1437 retvalidity = self.makeValidUsageStatementsReturnedOnly(typeinfo.elem, typeName, params) 1438 if retvalidity: 1439 validity += retvalidity 1440 1441 self.writeInclude('structs', typeName, validity, None, None, None, None) 1442 1443 def genGroup(self, groupinfo, groupName, alias): 1444 """Group (e.g. C "enum" type) generation. 1445 For the validity generator, this just tags individual enumerants 1446 as required or not. 1447 """ 1448 OutputGenerator.genGroup(self, groupinfo, groupName, alias) 1449 1450 # @@@ (Jon) something needs to be done here to handle aliases, probably 1451 1452 groupElem = groupinfo.elem 1453 1454 # Loop over the nested 'enum' tags. Keep track of the minimum and 1455 # maximum numeric values, if they can be determined; but only for 1456 # core API enumerants, not extension enumerants. This is inferred 1457 # by looking for 'extends' attributes. 1458 for elem in groupElem.findall('enum'): 1459 name = elem.get('name') 1460 ei = self.registry.lookupElementInfo(name, self.registry.enumdict) 1461 1462 # Tag enumerant as required or not 1463 ei.required = self.isEnumRequired(elem) 1464 1465 def genType(self, typeinfo, name, alias): 1466 """Type Generation.""" 1467 OutputGenerator.genType(self, typeinfo, name, alias) 1468 1469 # @@@ (Jon) something needs to be done here to handle aliases, probably 1470 1471 category = typeinfo.elem.get('category') 1472 if category in ('struct', 'union'): 1473 self.genStruct(typeinfo, name, alias)