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