1#!/usr/bin/python3 -i
2import os,re,sys
3from collections import namedtuple
4from lxml import etree
5
6def write( *args, **kwargs ):
7    file = kwargs.pop('file',sys.stdout)
8    end = kwargs.pop( 'end','\n')
9    file.write( ' '.join([str(arg) for arg in args]) )
10    file.write( end )
11
12# noneStr - returns string argument, or "" if argument is None.
13# Used in converting lxml Elements into text.
14#   str - string to convert
15def noneStr(str):
16    if (str):
17        return str
18    else:
19        return ""
20
21# enquote - returns string argument with surrounding quotes,
22#   for serialization into Python code.
23def enquote(str):
24    if (str):
25        return "'" + str + "'"
26    else:
27        return None
28
29# Primary sort key for regSortFeatures.
30# Sorts by category of the feature name string:
31#   Core API features (those defined with a <feature> tag)
32#   ARB/KHR/OES (Khronos extensions)
33#   other       (EXT/vendor extensions)
34# This will need changing for Vulkan!
35def regSortCategoryKey(feature):
36    if (feature.elem.tag == 'feature'):
37        return 0
38    elif (feature.category == 'ARB' or
39          feature.category == 'KHR' or
40          feature.category == 'OES'):
41        return 1
42    else:
43        return 2
44
45# Secondary sort key for regSortFeatures.
46# Sorts by extension name.
47def regSortNameKey(feature):
48    return feature.name
49
50# Second sort key for regSortFeatures.
51# Sorts by feature version. <extension> elements all have version number "0"
52def regSortFeatureVersionKey(feature):
53    return float(feature.version)
54
55# Tertiary sort key for regSortFeatures.
56# Sorts by extension number. <feature> elements all have extension number 0.
57def regSortExtensionNumberKey(feature):
58    return int(feature.number)
59
60# regSortFeatures - default sort procedure for features.
61# Sorts by primary key of feature category ('feature' or 'extension')
62#   then by version number (for features)
63#   then by extension number (for extensions)
64def regSortFeatures(featureList):
65    featureList.sort(key = regSortExtensionNumberKey)
66    featureList.sort(key = regSortFeatureVersionKey)
67    featureList.sort(key = regSortCategoryKey)
68
69# GeneratorOptions - base class for options used during header production
70# These options are target language independent, and used by
71# Registry.apiGen() and by base OutputGenerator objects.
72#
73# Members
74#   filename - name of file to generate, or None to write to stdout.
75#   apiname - string matching <api> 'apiname' attribute, e.g. 'gl'.
76#   profile - string specifying API profile , e.g. 'core', or None.
77#   versions - regex matching API versions to process interfaces for.
78#     Normally '.*' or '[0-9]\.[0-9]' to match all defined versions.
79#   emitversions - regex matching API versions to actually emit
80#    interfaces for (though all requested versions are considered
81#    when deciding which interfaces to generate). For GL 4.3 glext.h,
82#     this might be '1\.[2-5]|[2-4]\.[0-9]'.
83#   defaultExtensions - If not None, a string which must in its
84#     entirety match the pattern in the "supported" attribute of
85#     the <extension>. Defaults to None. Usually the same as apiname.
86#   addExtensions - regex matching names of additional extensions
87#     to include. Defaults to None.
88#   removeExtensions - regex matching names of extensions to
89#     remove (after defaultExtensions and addExtensions). Defaults
90#     to None.
91#   sortProcedure - takes a list of FeatureInfo objects and sorts
92#     them in place to a preferred order in the generated output.
93#     Default is core API versions, ARB/KHR/OES extensions, all
94#     other extensions, alphabetically within each group.
95# The regex patterns can be None or empty, in which case they match
96#   nothing.
97class GeneratorOptions:
98    """Represents options during header production from an API registry"""
99    def __init__(self,
100                 filename = None,
101                 apiname = None,
102                 profile = None,
103                 versions = '.*',
104                 emitversions = '.*',
105                 defaultExtensions = None,
106                 addExtensions = None,
107                 removeExtensions = None,
108                 sortProcedure = regSortFeatures):
109        self.filename          = filename
110        self.apiname           = apiname
111        self.profile           = profile
112        self.versions          = self.emptyRegex(versions)
113        self.emitversions      = self.emptyRegex(emitversions)
114        self.defaultExtensions = defaultExtensions
115        self.addExtensions     = self.emptyRegex(addExtensions)
116        self.removeExtensions  = self.emptyRegex(removeExtensions)
117        self.sortProcedure     = sortProcedure
118    #
119    # Substitute a regular expression which matches no version
120    # or extension names for None or the empty string.
121    def emptyRegex(self,pat):
122        if (pat == None or pat == ''):
123            return '_nomatch_^'
124        else:
125            return pat
126
127# CGeneratorOptions - subclass of GeneratorOptions.
128#
129# Adds options used by COutputGenerator objects during C language header
130# generation.
131#
132# Additional members
133#   prefixText - list of strings to prefix generated header with
134#     (usually a copyright statement + calling convention macros).
135#   protectFile - True if multiple inclusion protection should be
136#     generated (based on the filename) around the entire header.
137#   protectFeature - True if #ifndef..#endif protection should be
138#     generated around a feature interface in the header file.
139#   genFuncPointers - True if function pointer typedefs should be
140#     generated
141#   protectProto - If conditional protection should be generated
142#     around prototype declarations, set to either '#ifdef'
143#     to require opt-in (#ifdef protectProtoStr) or '#ifndef'
144#     to require opt-out (#ifndef protectProtoStr). Otherwise
145#     set to None.
146#   protectProtoStr - #ifdef/#ifndef symbol to use around prototype
147#     declarations, if protectProto is set
148#   apicall - string to use for the function declaration prefix,
149#     such as APICALL on Windows.
150#   apientry - string to use for the calling convention macro,
151#     in typedefs, such as APIENTRY.
152#   apientryp - string to use for the calling convention macro
153#     in function pointer typedefs, such as APIENTRYP.
154#   indentFuncProto - True if prototype declarations should put each
155#     parameter on a separate line
156#   indentFuncPointer - True if typedefed function pointers should put each
157#     parameter on a separate line
158#   alignFuncParam - if nonzero and parameters are being put on a
159#     separate line, align parameter names at the specified column
160class CGeneratorOptions(GeneratorOptions):
161    """Represents options during C interface generation for headers"""
162    def __init__(self,
163                 filename = None,
164                 apiname = None,
165                 profile = None,
166                 versions = '.*',
167                 emitversions = '.*',
168                 defaultExtensions = None,
169                 addExtensions = None,
170                 removeExtensions = None,
171                 sortProcedure = regSortFeatures,
172                 prefixText = "",
173                 genFuncPointers = True,
174                 protectFile = True,
175                 protectFeature = True,
176                 protectProto = None,
177                 protectProtoStr = None,
178                 apicall = '',
179                 apientry = '',
180                 apientryp = '',
181                 indentFuncProto = True,
182                 indentFuncPointer = False,
183                 alignFuncParam = 0):
184        GeneratorOptions.__init__(self, filename, apiname, profile,
185                                  versions, emitversions, defaultExtensions,
186                                  addExtensions, removeExtensions, sortProcedure)
187        self.prefixText      = prefixText
188        self.genFuncPointers = genFuncPointers
189        self.protectFile     = protectFile
190        self.protectFeature  = protectFeature
191        self.protectProto    = protectProto
192        self.protectProtoStr = protectProtoStr
193        self.apicall         = apicall
194        self.apientry        = apientry
195        self.apientryp       = apientryp
196        self.indentFuncProto = indentFuncProto
197        self.indentFuncPointer = indentFuncPointer
198        self.alignFuncParam  = alignFuncParam
199
200# DocGeneratorOptions - subclass of GeneratorOptions.
201#
202# Shares many members with CGeneratorOptions, since
203# both are writing C-style declarations:
204#
205#   prefixText - list of strings to prefix generated header with
206#     (usually a copyright statement + calling convention macros).
207#   apicall - string to use for the function declaration prefix,
208#     such as APICALL on Windows.
209#   apientry - string to use for the calling convention macro,
210#     in typedefs, such as APIENTRY.
211#   apientryp - string to use for the calling convention macro
212#     in function pointer typedefs, such as APIENTRYP.
213#   genDirectory - directory into which to generate include files
214#   indentFuncProto - True if prototype declarations should put each
215#     parameter on a separate line
216#   indentFuncPointer - True if typedefed function pointers should put each
217#     parameter on a separate line
218#   alignFuncParam - if nonzero and parameters are being put on a
219#     separate line, align parameter names at the specified column
220#
221# Additional members:
222#
223class DocGeneratorOptions(GeneratorOptions):
224    """Represents options during C interface generation for Asciidoc"""
225    def __init__(self,
226                 filename = None,
227                 apiname = None,
228                 profile = None,
229                 versions = '.*',
230                 emitversions = '.*',
231                 defaultExtensions = None,
232                 addExtensions = None,
233                 removeExtensions = None,
234                 sortProcedure = regSortFeatures,
235                 prefixText = "",
236                 apicall = '',
237                 apientry = '',
238                 apientryp = '',
239                 genDirectory = 'gen',
240                 indentFuncProto = True,
241                 indentFuncPointer = False,
242                 alignFuncParam = 0,
243                 expandEnumerants = True):
244        GeneratorOptions.__init__(self, filename, apiname, profile,
245                                  versions, emitversions, defaultExtensions,
246                                  addExtensions, removeExtensions, sortProcedure)
247        self.prefixText      = prefixText
248        self.apicall         = apicall
249        self.apientry        = apientry
250        self.apientryp       = apientryp
251        self.genDirectory    = genDirectory
252        self.indentFuncProto = indentFuncProto
253        self.indentFuncPointer = indentFuncPointer
254        self.alignFuncParam  = alignFuncParam
255        self.expandEnumerants = expandEnumerants
256
257# ThreadGeneratorOptions - subclass of GeneratorOptions.
258#
259# Adds options used by COutputGenerator objects during C language header
260# generation.
261#
262# Additional members
263#   prefixText - list of strings to prefix generated header with
264#     (usually a copyright statement + calling convention macros).
265#   protectFile - True if multiple inclusion protection should be
266#     generated (based on the filename) around the entire header.
267#   protectFeature - True if #ifndef..#endif protection should be
268#     generated around a feature interface in the header file.
269#   genFuncPointers - True if function pointer typedefs should be
270#     generated
271#   protectProto - True if #ifdef..#endif protection should be
272#     generated around prototype declarations
273#   protectProtoStr - #ifdef symbol to use around prototype
274#     declarations, if protected
275#   apicall - string to use for the function declaration prefix,
276#     such as APICALL on Windows.
277#   apientry - string to use for the calling convention macro,
278#     in typedefs, such as APIENTRY.
279#   apientryp - string to use for the calling convention macro
280#     in function pointer typedefs, such as APIENTRYP.
281#   indentFuncProto - True if prototype declarations should put each
282#     parameter on a separate line
283#   indentFuncPointer - True if typedefed function pointers should put each
284#     parameter on a separate line
285#   alignFuncParam - if nonzero and parameters are being put on a
286#     separate line, align parameter names at the specified column
287class ThreadGeneratorOptions(GeneratorOptions):
288    """Represents options during C interface generation for headers"""
289    def __init__(self,
290                 filename = None,
291                 apiname = None,
292                 profile = None,
293                 versions = '.*',
294                 emitversions = '.*',
295                 defaultExtensions = None,
296                 addExtensions = None,
297                 removeExtensions = None,
298                 sortProcedure = regSortFeatures,
299                 prefixText = "",
300                 genFuncPointers = True,
301                 protectFile = True,
302                 protectFeature = True,
303                 protectProto = True,
304                 protectProtoStr = True,
305                 apicall = '',
306                 apientry = '',
307                 apientryp = '',
308                 indentFuncProto = True,
309                 indentFuncPointer = False,
310                 alignFuncParam = 0):
311        GeneratorOptions.__init__(self, filename, apiname, profile,
312                                  versions, emitversions, defaultExtensions,
313                                  addExtensions, removeExtensions, sortProcedure)
314        self.prefixText      = prefixText
315        self.genFuncPointers = genFuncPointers
316        self.protectFile     = protectFile
317        self.protectFeature  = protectFeature
318        self.protectProto    = protectProto
319        self.protectProtoStr = protectProtoStr
320        self.apicall         = apicall
321        self.apientry        = apientry
322        self.apientryp       = apientryp
323        self.indentFuncProto = indentFuncProto
324        self.indentFuncPointer = indentFuncPointer
325        self.alignFuncParam  = alignFuncParam
326
327
328# ParamCheckerGeneratorOptions - subclass of GeneratorOptions.
329#
330# Adds options used by ParamCheckerOutputGenerator objects during parameter validation
331# generation.
332#
333# Additional members
334#   prefixText - list of strings to prefix generated header with
335#     (usually a copyright statement + calling convention macros).
336#   protectFile - True if multiple inclusion protection should be
337#     generated (based on the filename) around the entire header.
338#   protectFeature - True if #ifndef..#endif protection should be
339#     generated around a feature interface in the header file.
340#   genFuncPointers - True if function pointer typedefs should be
341#     generated
342#   protectProto - If conditional protection should be generated
343#     around prototype declarations, set to either '#ifdef'
344#     to require opt-in (#ifdef protectProtoStr) or '#ifndef'
345#     to require opt-out (#ifndef protectProtoStr). Otherwise
346#     set to None.
347#   protectProtoStr - #ifdef/#ifndef symbol to use around prototype
348#     declarations, if protectProto is set
349#   apicall - string to use for the function declaration prefix,
350#     such as APICALL on Windows.
351#   apientry - string to use for the calling convention macro,
352#     in typedefs, such as APIENTRY.
353#   apientryp - string to use for the calling convention macro
354#     in function pointer typedefs, such as APIENTRYP.
355#   indentFuncProto - True if prototype declarations should put each
356#     parameter on a separate line
357#   indentFuncPointer - True if typedefed function pointers should put each
358#     parameter on a separate line
359#   alignFuncParam - if nonzero and parameters are being put on a
360#     separate line, align parameter names at the specified column
361class ParamCheckerGeneratorOptions(GeneratorOptions):
362    """Represents options during C interface generation for headers"""
363    def __init__(self,
364                 filename = None,
365                 apiname = None,
366                 profile = None,
367                 versions = '.*',
368                 emitversions = '.*',
369                 defaultExtensions = None,
370                 addExtensions = None,
371                 removeExtensions = None,
372                 sortProcedure = regSortFeatures,
373                 prefixText = "",
374                 genFuncPointers = True,
375                 protectFile = True,
376                 protectFeature = True,
377                 protectProto = None,
378                 protectProtoStr = None,
379                 apicall = '',
380                 apientry = '',
381                 apientryp = '',
382                 indentFuncProto = True,
383                 indentFuncPointer = False,
384                 alignFuncParam = 0):
385        GeneratorOptions.__init__(self, filename, apiname, profile,
386                                  versions, emitversions, defaultExtensions,
387                                  addExtensions, removeExtensions, sortProcedure)
388        self.prefixText      = prefixText
389        self.genFuncPointers = genFuncPointers
390        self.protectFile     = protectFile
391        self.protectFeature  = protectFeature
392        self.protectProto    = protectProto
393        self.protectProtoStr = protectProtoStr
394        self.apicall         = apicall
395        self.apientry        = apientry
396        self.apientryp       = apientryp
397        self.indentFuncProto = indentFuncProto
398        self.indentFuncPointer = indentFuncPointer
399        self.alignFuncParam  = alignFuncParam
400
401
402# OutputGenerator - base class for generating API interfaces.
403# Manages basic logic, logging, and output file control
404# Derived classes actually generate formatted output.
405#
406# ---- methods ----
407# OutputGenerator(errFile, warnFile, diagFile)
408#   errFile, warnFile, diagFile - file handles to write errors,
409#     warnings, diagnostics to. May be None to not write.
410# logMsg(level, *args) - log messages of different categories
411#   level - 'error', 'warn', or 'diag'. 'error' will also
412#     raise a UserWarning exception
413#   *args - print()-style arguments
414# setExtMap(map) - specify a dictionary map from extension names to
415#   numbers, used in creating values for extension enumerants.
416# beginFile(genOpts) - start a new interface file
417#   genOpts - GeneratorOptions controlling what's generated and how
418# endFile() - finish an interface file, closing it when done
419# beginFeature(interface, emit) - write interface for a feature
420# and tag generated features as having been done.
421#   interface - element for the <version> / <extension> to generate
422#   emit - actually write to the header only when True
423# endFeature() - finish an interface.
424# genType(typeinfo,name) - generate interface for a type
425#   typeinfo - TypeInfo for a type
426# genStruct(typeinfo,name) - generate interface for a C "struct" type.
427#   typeinfo - TypeInfo for a type interpreted as a struct
428# genGroup(groupinfo,name) - generate interface for a group of enums (C "enum")
429#   groupinfo - GroupInfo for a group
430# genEnum(enuminfo, name) - generate interface for an enum (constant)
431#   enuminfo - EnumInfo for an enum
432#   name - enum name
433# genCmd(cmdinfo) - generate interface for a command
434#   cmdinfo - CmdInfo for a command
435# makeCDecls(cmd) - return C prototype and function pointer typedef for a
436#     <command> Element, as a list of two strings
437#   cmd - Element for the <command>
438# newline() - print a newline to the output file (utility function)
439#
440class OutputGenerator:
441    """Generate specified API interfaces in a specific style, such as a C header"""
442    def __init__(self,
443                 errFile = sys.stderr,
444                 warnFile = sys.stderr,
445                 diagFile = sys.stdout):
446        self.outFile = None
447        self.errFile = errFile
448        self.warnFile = warnFile
449        self.diagFile = diagFile
450        # Internal state
451        self.featureName = None
452        self.genOpts = None
453        self.registry = None
454        # Used for extension enum value generation
455        self.extBase      = 1000000000
456        self.extBlockSize = 1000
457    #
458    # logMsg - write a message of different categories to different
459    #   destinations.
460    # level -
461    #   'diag' (diagnostic, voluminous)
462    #   'warn' (warning)
463    #   'error' (fatal error - raises exception after logging)
464    # *args - print()-style arguments to direct to corresponding log
465    def logMsg(self, level, *args):
466        """Log a message at the given level. Can be ignored or log to a file"""
467        if (level == 'error'):
468            strfile = io.StringIO()
469            write('ERROR:', *args, file=strfile)
470            if (self.errFile != None):
471                write(strfile.getvalue(), file=self.errFile)
472            raise UserWarning(strfile.getvalue())
473        elif (level == 'warn'):
474            if (self.warnFile != None):
475                write('WARNING:', *args, file=self.warnFile)
476        elif (level == 'diag'):
477            if (self.diagFile != None):
478                write('DIAG:', *args, file=self.diagFile)
479        else:
480            raise UserWarning(
481                '*** FATAL ERROR in Generator.logMsg: unknown level:' + level)
482    #
483    # enumToValue - parses and converts an <enum> tag into a value.
484    # Returns a list
485    #   first element - integer representation of the value, or None
486    #       if needsNum is False. The value must be a legal number
487    #       if needsNum is True.
488    #   second element - string representation of the value
489    # There are several possible representations of values.
490    #   A 'value' attribute simply contains the value.
491    #   A 'bitpos' attribute defines a value by specifying the bit
492    #       position which is set in that value.
493    #   A 'offset','extbase','extends' triplet specifies a value
494    #       as an offset to a base value defined by the specified
495    #       'extbase' extension name, which is then cast to the
496    #       typename specified by 'extends'. This requires probing
497    #       the registry database, and imbeds knowledge of the
498    #       Vulkan extension enum scheme in this function.
499    def enumToValue(self, elem, needsNum):
500        name = elem.get('name')
501        numVal = None
502        if ('value' in elem.keys()):
503            value = elem.get('value')
504            # print('About to translate value =', value, 'type =', type(value))
505            if (needsNum):
506                numVal = int(value, 0)
507            # If there's a non-integer, numeric 'type' attribute (e.g. 'u' or
508            # 'ull'), append it to the string value.
509            # t = enuminfo.elem.get('type')
510            # if (t != None and t != '' and t != 'i' and t != 's'):
511            #     value += enuminfo.type
512            self.logMsg('diag', 'Enum', name, '-> value [', numVal, ',', value, ']')
513            return [numVal, value]
514        if ('bitpos' in elem.keys()):
515            value = elem.get('bitpos')
516            numVal = int(value, 0)
517            numVal = 1 << numVal
518            value = '0x%08x' % numVal
519            self.logMsg('diag', 'Enum', name, '-> bitpos [', numVal, ',', value, ']')
520            return [numVal, value]
521        if ('offset' in elem.keys()):
522            # Obtain values in the mapping from the attributes
523            enumNegative = False
524            offset = int(elem.get('offset'),0)
525            extnumber = int(elem.get('extnumber'),0)
526            extends = elem.get('extends')
527            if ('dir' in elem.keys()):
528                enumNegative = True
529            self.logMsg('diag', 'Enum', name, 'offset =', offset,
530                'extnumber =', extnumber, 'extends =', extends,
531                'enumNegative =', enumNegative)
532            # Now determine the actual enumerant value, as defined
533            # in the "Layers and Extensions" appendix of the spec.
534            numVal = self.extBase + (extnumber - 1) * self.extBlockSize + offset
535            if (enumNegative):
536                numVal = -numVal
537            value = '%d' % numVal
538            # More logic needed!
539            self.logMsg('diag', 'Enum', name, '-> offset [', numVal, ',', value, ']')
540            return [numVal, value]
541        return [None, None]
542    #
543    def beginFile(self, genOpts):
544        self.genOpts = genOpts
545        #
546        # Open specified output file. Not done in constructor since a
547        # Generator can be used without writing to a file.
548        if (self.genOpts.filename != None):
549            self.outFile = open(self.genOpts.filename, 'w')
550        else:
551            self.outFile = sys.stdout
552    def endFile(self):
553        self.errFile and self.errFile.flush()
554        self.warnFile and self.warnFile.flush()
555        self.diagFile and self.diagFile.flush()
556        self.outFile.flush()
557        if (self.outFile != sys.stdout and self.outFile != sys.stderr):
558            self.outFile.close()
559        self.genOpts = None
560    #
561    def beginFeature(self, interface, emit):
562        self.emit = emit
563        self.featureName = interface.get('name')
564        # If there's an additional 'protect' attribute in the feature, save it
565        self.featureExtraProtect = interface.get('protect')
566    def endFeature(self):
567        # Derived classes responsible for emitting feature
568        self.featureName = None
569        self.featureExtraProtect = None
570    # Utility method to validate we're generating something only inside a
571    # <feature> tag
572    def validateFeature(self, featureType, featureName):
573        if (self.featureName == None):
574            raise UserWarning('Attempt to generate', featureType, name,
575                    'when not in feature')
576    #
577    # Type generation
578    def genType(self, typeinfo, name):
579        self.validateFeature('type', name)
580    #
581    # Struct (e.g. C "struct" type) generation
582    def genStruct(self, typeinfo, name):
583        self.validateFeature('struct', name)
584    #
585    # Group (e.g. C "enum" type) generation
586    def genGroup(self, groupinfo, name):
587        self.validateFeature('group', name)
588    #
589    # Enumerant (really, constant) generation
590    def genEnum(self, enuminfo, name):
591        self.validateFeature('enum', name)
592    #
593    # Command generation
594    def genCmd(self, cmd, name):
595        self.validateFeature('command', name)
596    #
597    # Utility functions - turn a <proto> <name> into C-language prototype
598    # and typedef declarations for that name.
599    # name - contents of <name> tag
600    # tail - whatever text follows that tag in the Element
601    def makeProtoName(self, name, tail):
602        return self.genOpts.apientry + name + tail
603    def makeTypedefName(self, name, tail):
604       return '(' + self.genOpts.apientryp + 'PFN_' + name + tail + ')'
605    #
606    # makeCParamDecl - return a string which is an indented, formatted
607    # declaration for a <param> or <member> block (e.g. function parameter
608    # or structure/union member).
609    # param - Element (<param> or <member>) to format
610    # aligncol - if non-zero, attempt to align the nested <name> element
611    #   at this column
612    def makeCParamDecl(self, param, aligncol):
613        paramdecl = '    ' + noneStr(param.text)
614        for elem in param:
615            text = noneStr(elem.text)
616            tail = noneStr(elem.tail)
617            if (elem.tag == 'name' and aligncol > 0):
618                self.logMsg('diag', 'Aligning parameter', elem.text, 'to column', self.genOpts.alignFuncParam)
619                # Align at specified column, if possible
620                paramdecl = paramdecl.rstrip()
621                oldLen = len(paramdecl)
622                paramdecl = paramdecl.ljust(aligncol)
623                newLen = len(paramdecl)
624                self.logMsg('diag', 'Adjust length of parameter decl from', oldLen, 'to', newLen, ':', paramdecl)
625            paramdecl += text + tail
626        return paramdecl
627    #
628    # getCParamTypeLength - return the length of the type field is an indented, formatted
629    # declaration for a <param> or <member> block (e.g. function parameter
630    # or structure/union member).
631    # param - Element (<param> or <member>) to identify
632    def getCParamTypeLength(self, param):
633        paramdecl = '    ' + noneStr(param.text)
634        for elem in param:
635            text = noneStr(elem.text)
636            tail = noneStr(elem.tail)
637            if (elem.tag == 'name'):
638                # Align at specified column, if possible
639                newLen = len(paramdecl.rstrip())
640                self.logMsg('diag', 'Identifying length of', elem.text, 'as', newLen)
641            paramdecl += text + tail
642        return newLen
643    #
644    # makeCDecls - return C prototype and function pointer typedef for a
645    #   command, as a two-element list of strings.
646    # cmd - Element containing a <command> tag
647    def makeCDecls(self, cmd):
648        """Generate C function pointer typedef for <command> Element"""
649        proto = cmd.find('proto')
650        params = cmd.findall('param')
651        # Begin accumulating prototype and typedef strings
652        pdecl = self.genOpts.apicall
653        tdecl = 'typedef '
654        #
655        # Insert the function return type/name.
656        # For prototypes, add APIENTRY macro before the name
657        # For typedefs, add (APIENTRY *<name>) around the name and
658        #   use the PFN_cmdnameproc naming convention.
659        # Done by walking the tree for <proto> element by element.
660        # lxml.etree has elem.text followed by (elem[i], elem[i].tail)
661        #   for each child element and any following text
662        # Leading text
663        pdecl += noneStr(proto.text)
664        tdecl += noneStr(proto.text)
665        # For each child element, if it's a <name> wrap in appropriate
666        # declaration. Otherwise append its contents and tail contents.
667        for elem in proto:
668            text = noneStr(elem.text)
669            tail = noneStr(elem.tail)
670            if (elem.tag == 'name'):
671                pdecl += self.makeProtoName(text, tail)
672                tdecl += self.makeTypedefName(text, tail)
673            else:
674                pdecl += text + tail
675                tdecl += text + tail
676        # Now add the parameter declaration list, which is identical
677        # for prototypes and typedefs. Concatenate all the text from
678        # a <param> node without the tags. No tree walking required
679        # since all tags are ignored.
680        # Uses: self.indentFuncProto
681        # self.indentFuncPointer
682        # self.alignFuncParam
683        # Might be able to doubly-nest the joins, e.g.
684        #   ','.join(('_'.join([l[i] for i in range(0,len(l))])
685        n = len(params)
686        # Indented parameters
687        if n > 0:
688            indentdecl = '(\n'
689            for i in range(0,n):
690                paramdecl = self.makeCParamDecl(params[i], self.genOpts.alignFuncParam)
691                if (i < n - 1):
692                    paramdecl += ',\n'
693                else:
694                    paramdecl += ');'
695                indentdecl += paramdecl
696        else:
697            indentdecl = '(void);'
698        # Non-indented parameters
699        paramdecl = '('
700        if n > 0:
701            for i in range(0,n):
702                paramdecl += ''.join([t for t in params[i].itertext()])
703                if (i < n - 1):
704                    paramdecl += ', '
705        else:
706            paramdecl += 'void'
707        paramdecl += ");";
708        return [ pdecl + indentdecl, tdecl + paramdecl ]
709    #
710    def newline(self):
711        write('', file=self.outFile)
712
713    def setRegistry(self, registry):
714        self.registry = registry
715        #
716
717# COutputGenerator - subclass of OutputGenerator.
718# Generates C-language API interfaces.
719#
720# ---- methods ----
721# COutputGenerator(errFile, warnFile, diagFile) - args as for
722#   OutputGenerator. Defines additional internal state.
723# ---- methods overriding base class ----
724# beginFile(genOpts)
725# endFile()
726# beginFeature(interface, emit)
727# endFeature()
728# genType(typeinfo,name)
729# genStruct(typeinfo,name)
730# genGroup(groupinfo,name)
731# genEnum(enuminfo, name)
732# genCmd(cmdinfo)
733class COutputGenerator(OutputGenerator):
734    """Generate specified API interfaces in a specific style, such as a C header"""
735    # This is an ordered list of sections in the header file.
736    TYPE_SECTIONS = ['include', 'define', 'basetype', 'handle', 'enum',
737                     'group', 'bitmask', 'funcpointer', 'struct']
738    ALL_SECTIONS = TYPE_SECTIONS + ['commandPointer', 'command']
739    def __init__(self,
740                 errFile = sys.stderr,
741                 warnFile = sys.stderr,
742                 diagFile = sys.stdout):
743        OutputGenerator.__init__(self, errFile, warnFile, diagFile)
744        # Internal state - accumulators for different inner block text
745        self.sections = dict([(section, []) for section in self.ALL_SECTIONS])
746    #
747    def beginFile(self, genOpts):
748        OutputGenerator.beginFile(self, genOpts)
749        # C-specific
750        #
751        # Multiple inclusion protection & C++ wrappers.
752        if (genOpts.protectFile and self.genOpts.filename):
753            headerSym = '__' + re.sub('\.h', '_h_', os.path.basename(self.genOpts.filename))
754            write('#ifndef', headerSym, file=self.outFile)
755            write('#define', headerSym, '1', file=self.outFile)
756            self.newline()
757        write('#ifdef __cplusplus', file=self.outFile)
758        write('extern "C" {', file=self.outFile)
759        write('#endif', file=self.outFile)
760        self.newline()
761        #
762        # User-supplied prefix text, if any (list of strings)
763        if (genOpts.prefixText):
764            for s in genOpts.prefixText:
765                write(s, file=self.outFile)
766        #
767        # Some boilerplate describing what was generated - this
768        # will probably be removed later since the extensions
769        # pattern may be very long.
770        # write('/* Generated C header for:', file=self.outFile)
771        # write(' * API:', genOpts.apiname, file=self.outFile)
772        # if (genOpts.profile):
773        #     write(' * Profile:', genOpts.profile, file=self.outFile)
774        # write(' * Versions considered:', genOpts.versions, file=self.outFile)
775        # write(' * Versions emitted:', genOpts.emitversions, file=self.outFile)
776        # write(' * Default extensions included:', genOpts.defaultExtensions, file=self.outFile)
777        # write(' * Additional extensions included:', genOpts.addExtensions, file=self.outFile)
778        # write(' * Extensions removed:', genOpts.removeExtensions, file=self.outFile)
779        # write(' */', file=self.outFile)
780    def endFile(self):
781        # C-specific
782        # Finish C++ wrapper and multiple inclusion protection
783        self.newline()
784        write('#ifdef __cplusplus', file=self.outFile)
785        write('}', file=self.outFile)
786        write('#endif', file=self.outFile)
787        if (self.genOpts.protectFile and self.genOpts.filename):
788            self.newline()
789            write('#endif', file=self.outFile)
790        # Finish processing in superclass
791        OutputGenerator.endFile(self)
792    def beginFeature(self, interface, emit):
793        # Start processing in superclass
794        OutputGenerator.beginFeature(self, interface, emit)
795        # C-specific
796        # Accumulate includes, defines, types, enums, function pointer typedefs,
797        # end function prototypes separately for this feature. They're only
798        # printed in endFeature().
799        self.sections = dict([(section, []) for section in self.ALL_SECTIONS])
800    def endFeature(self):
801        # C-specific
802        # Actually write the interface to the output file.
803        if (self.emit):
804            self.newline()
805            if (self.genOpts.protectFeature):
806                write('#ifndef', self.featureName, file=self.outFile)
807            # If type declarations are needed by other features based on
808            # this one, it may be necessary to suppress the ExtraProtect,
809            # or move it below the 'for section...' loop.
810            if (self.featureExtraProtect != None):
811                write('#ifdef', self.featureExtraProtect, file=self.outFile)
812            write('#define', self.featureName, '1', file=self.outFile)
813            for section in self.TYPE_SECTIONS:
814                contents = self.sections[section]
815                if contents:
816                    write('\n'.join(contents), file=self.outFile)
817                    self.newline()
818            if (self.genOpts.genFuncPointers and self.sections['commandPointer']):
819                write('\n'.join(self.sections['commandPointer']), file=self.outFile)
820                self.newline()
821            if (self.sections['command']):
822                if (self.genOpts.protectProto):
823                    write(self.genOpts.protectProto,
824                          self.genOpts.protectProtoStr, file=self.outFile)
825                write('\n'.join(self.sections['command']), end='', file=self.outFile)
826                if (self.genOpts.protectProto):
827                    write('#endif', file=self.outFile)
828                else:
829                    self.newline()
830            if (self.featureExtraProtect != None):
831                write('#endif /*', self.featureExtraProtect, '*/', file=self.outFile)
832            if (self.genOpts.protectFeature):
833                write('#endif /*', self.featureName, '*/', file=self.outFile)
834        # Finish processing in superclass
835        OutputGenerator.endFeature(self)
836    #
837    # Append a definition to the specified section
838    def appendSection(self, section, text):
839        # self.sections[section].append('SECTION: ' + section + '\n')
840        self.sections[section].append(text)
841    #
842    # Type generation
843    def genType(self, typeinfo, name):
844        OutputGenerator.genType(self, typeinfo, name)
845        typeElem = typeinfo.elem
846        # If the type is a struct type, traverse the imbedded <member> tags
847        # generating a structure. Otherwise, emit the tag text.
848        category = typeElem.get('category')
849        if (category == 'struct' or category == 'union'):
850            self.genStruct(typeinfo, name)
851        else:
852            # Replace <apientry /> tags with an APIENTRY-style string
853            # (from self.genOpts). Copy other text through unchanged.
854            # If the resulting text is an empty string, don't emit it.
855            s = noneStr(typeElem.text)
856            for elem in typeElem:
857                if (elem.tag == 'apientry'):
858                    s += self.genOpts.apientry + noneStr(elem.tail)
859                else:
860                    s += noneStr(elem.text) + noneStr(elem.tail)
861            if s:
862                # Add extra newline after multi-line entries.
863                if '\n' in s:
864                    s += '\n'
865                self.appendSection(category, s)
866    #
867    # Struct (e.g. C "struct" type) generation.
868    # This is a special case of the <type> tag where the contents are
869    # interpreted as a set of <member> tags instead of freeform C
870    # C type declarations. The <member> tags are just like <param>
871    # tags - they are a declaration of a struct or union member.
872    # Only simple member declarations are supported (no nested
873    # structs etc.)
874    def genStruct(self, typeinfo, typeName):
875        OutputGenerator.genStruct(self, typeinfo, typeName)
876        body = 'typedef ' + typeinfo.elem.get('category') + ' ' + typeName + ' {\n'
877        # paramdecl = self.makeCParamDecl(typeinfo.elem, self.genOpts.alignFuncParam)
878        targetLen = 0;
879        for member in typeinfo.elem.findall('.//member'):
880            targetLen = max(targetLen, self.getCParamTypeLength(member))
881        for member in typeinfo.elem.findall('.//member'):
882            body += self.makeCParamDecl(member, targetLen + 4)
883            body += ';\n'
884        body += '} ' + typeName + ';\n'
885        self.appendSection('struct', body)
886    #
887    # Group (e.g. C "enum" type) generation.
888    # These are concatenated together with other types.
889    def genGroup(self, groupinfo, groupName):
890        OutputGenerator.genGroup(self, groupinfo, groupName)
891        groupElem = groupinfo.elem
892        # See if this group needs min/max/num/padding at end
893        expand = 'expand' in groupElem.keys()
894        if (expand):
895            expandPrefix = groupElem.get('expand')
896        # Prefix
897        body = "\ntypedef enum " + groupName + " {\n"
898
899        # Loop over the nested 'enum' tags. Keep track of the minimum and
900        # maximum numeric values, if they can be determined; but only for
901        # core API enumerants, not extension enumerants. This is inferred
902        # by looking for 'extends' attributes.
903        minName = None
904        for elem in groupElem.findall('enum'):
905            # Convert the value to an integer and use that to track min/max.
906            # Values of form -(number) are accepted but nothing more complex.
907            # Should catch exceptions here for more complex constructs. Not yet.
908            (numVal,strVal) = self.enumToValue(elem, True)
909            name = elem.get('name')
910            body += "    " + name + " = " + strVal + ",\n"
911            if (expand and elem.get('extends') is None):
912                if (minName == None):
913                    minName = maxName = name
914                    minValue = maxValue = numVal
915                elif (numVal < minValue):
916                    minName = name
917                    minValue = numVal
918                elif (numVal > maxValue):
919                    maxName = name
920                    maxValue = numVal
921        # Generate min/max value tokens and a range-padding enum. Need some
922        # additional padding to generate correct names...
923        if (expand):
924            body += "    " + expandPrefix + "_BEGIN_RANGE = " + minName + ",\n"
925            body += "    " + expandPrefix + "_END_RANGE = " + maxName + ",\n"
926            body += "    " + expandPrefix + "_RANGE_SIZE = (" + maxName + " - " + minName + " + 1),\n"
927            body += "    " + expandPrefix + "_MAX_ENUM = 0x7FFFFFFF\n"
928        # Postfix
929        body += "} " + groupName + ";"
930        if groupElem.get('type') == 'bitmask':
931            section = 'bitmask'
932        else:
933            section = 'group'
934        self.appendSection(section, body)
935    # Enumerant generation
936    # <enum> tags may specify their values in several ways, but are usually
937    # just integers.
938    def genEnum(self, enuminfo, name):
939        OutputGenerator.genEnum(self, enuminfo, name)
940        (numVal,strVal) = self.enumToValue(enuminfo.elem, False)
941        body = '#define ' + name.ljust(33) + ' ' + strVal
942        self.appendSection('enum', body)
943    #
944    # Command generation
945    def genCmd(self, cmdinfo, name):
946        OutputGenerator.genCmd(self, cmdinfo, name)
947        #
948        decls = self.makeCDecls(cmdinfo.elem)
949        self.appendSection('command', decls[0] + '\n')
950        if (self.genOpts.genFuncPointers):
951            self.appendSection('commandPointer', decls[1])
952
953# DocOutputGenerator - subclass of OutputGenerator.
954# Generates AsciiDoc includes with C-language API interfaces, for reference
955# pages and the Vulkan specification. Similar to COutputGenerator, but
956# each interface is written into a different file as determined by the
957# options, only actual C types are emitted, and none of the boilerplate
958# preprocessor code is emitted.
959#
960# ---- methods ----
961# DocOutputGenerator(errFile, warnFile, diagFile) - args as for
962#   OutputGenerator. Defines additional internal state.
963# ---- methods overriding base class ----
964# beginFile(genOpts)
965# endFile()
966# beginFeature(interface, emit)
967# endFeature()
968# genType(typeinfo,name)
969# genStruct(typeinfo,name)
970# genGroup(groupinfo,name)
971# genEnum(enuminfo, name)
972# genCmd(cmdinfo)
973class DocOutputGenerator(OutputGenerator):
974    """Generate specified API interfaces in a specific style, such as a C header"""
975    def __init__(self,
976                 errFile = sys.stderr,
977                 warnFile = sys.stderr,
978                 diagFile = sys.stdout):
979        OutputGenerator.__init__(self, errFile, warnFile, diagFile)
980    #
981    def beginFile(self, genOpts):
982        OutputGenerator.beginFile(self, genOpts)
983    def endFile(self):
984        OutputGenerator.endFile(self)
985    def beginFeature(self, interface, emit):
986        # Start processing in superclass
987        OutputGenerator.beginFeature(self, interface, emit)
988    def endFeature(self):
989        # Finish processing in superclass
990        OutputGenerator.endFeature(self)
991    #
992    # Generate an include file
993    #
994    # directory - subdirectory to put file in
995    # basename - base name of the file
996    # contents - contents of the file (Asciidoc boilerplate aside)
997    def writeInclude(self, directory, basename, contents):
998        # Create file
999        filename = self.genOpts.genDirectory + '/' + directory + '/' + basename + '.txt'
1000        self.logMsg('diag', '# Generating include file:', filename)
1001        fp = open(filename, 'w')
1002        # Asciidoc anchor
1003        write('[[{0},{0}]]'.format(basename), file=fp)
1004        write('["source","{basebackend@docbook:c++:cpp}",title=""]', file=fp)
1005        write('------------------------------------------------------------------------------', file=fp)
1006        write(contents, file=fp)
1007        write('------------------------------------------------------------------------------', file=fp)
1008        fp.close()
1009    #
1010    # Type generation
1011    def genType(self, typeinfo, name):
1012        OutputGenerator.genType(self, typeinfo, name)
1013        typeElem = typeinfo.elem
1014        # If the type is a struct type, traverse the imbedded <member> tags
1015        # generating a structure. Otherwise, emit the tag text.
1016        category = typeElem.get('category')
1017        if (category == 'struct' or category == 'union'):
1018            self.genStruct(typeinfo, name)
1019        else:
1020            # Replace <apientry /> tags with an APIENTRY-style string
1021            # (from self.genOpts). Copy other text through unchanged.
1022            # If the resulting text is an empty string, don't emit it.
1023            s = noneStr(typeElem.text)
1024            for elem in typeElem:
1025                if (elem.tag == 'apientry'):
1026                    s += self.genOpts.apientry + noneStr(elem.tail)
1027                else:
1028                    s += noneStr(elem.text) + noneStr(elem.tail)
1029            if (len(s) > 0):
1030                if (category == 'bitmask'):
1031                    self.writeInclude('flags', name, s + '\n')
1032                elif (category == 'enum'):
1033                    self.writeInclude('enums', name, s + '\n')
1034                elif (category == 'funcpointer'):
1035                    self.writeInclude('funcpointers', name, s+ '\n')
1036                else:
1037                    self.logMsg('diag', '# NOT writing include file for type:',
1038                        name, 'category: ', category)
1039            else:
1040                self.logMsg('diag', '# NOT writing empty include file for type', name)
1041    #
1042    # Struct (e.g. C "struct" type) generation.
1043    # This is a special case of the <type> tag where the contents are
1044    # interpreted as a set of <member> tags instead of freeform C
1045    # C type declarations. The <member> tags are just like <param>
1046    # tags - they are a declaration of a struct or union member.
1047    # Only simple member declarations are supported (no nested
1048    # structs etc.)
1049    def genStruct(self, typeinfo, typeName):
1050        OutputGenerator.genStruct(self, typeinfo, typeName)
1051        s = 'typedef ' + typeinfo.elem.get('category') + ' ' + typeName + ' {\n'
1052        # paramdecl = self.makeCParamDecl(typeinfo.elem, self.genOpts.alignFuncParam)
1053        targetLen = 0;
1054        for member in typeinfo.elem.findall('.//member'):
1055            targetLen = max(targetLen, self.getCParamTypeLength(member))
1056        for member in typeinfo.elem.findall('.//member'):
1057            s += self.makeCParamDecl(member, targetLen + 4)
1058            s += ';\n'
1059        s += '} ' + typeName + ';'
1060        self.writeInclude('structs', typeName, s)
1061    #
1062    # Group (e.g. C "enum" type) generation.
1063    # These are concatenated together with other types.
1064    def genGroup(self, groupinfo, groupName):
1065        OutputGenerator.genGroup(self, groupinfo, groupName)
1066        groupElem = groupinfo.elem
1067        # See if this group needs min/max/num/padding at end
1068        expand = self.genOpts.expandEnumerants and ('expand' in groupElem.keys())
1069        if (expand):
1070            expandPrefix = groupElem.get('expand')
1071        # Prefix
1072        s = "typedef enum " + groupName + " {\n"
1073
1074        # Loop over the nested 'enum' tags. Keep track of the minimum and
1075        # maximum numeric values, if they can be determined.
1076        minName = None
1077        for elem in groupElem.findall('enum'):
1078            # Convert the value to an integer and use that to track min/max.
1079            # Values of form -(number) are accepted but nothing more complex.
1080            # Should catch exceptions here for more complex constructs. Not yet.
1081            (numVal,strVal) = self.enumToValue(elem, True)
1082            name = elem.get('name')
1083            s += "    " + name + " = " + strVal + ",\n"
1084            if (expand and elem.get('extends') is None):
1085                if (minName == None):
1086                    minName = maxName = name
1087                    minValue = maxValue = numVal
1088                elif (numVal < minValue):
1089                    minName = name
1090                    minValue = numVal
1091                elif (numVal > maxValue):
1092                    maxName = name
1093                    maxValue = numVal
1094        # Generate min/max value tokens and a range-padding enum. Need some
1095        # additional padding to generate correct names...
1096        if (expand):
1097            s += "\n"
1098            s += "    " + expandPrefix + "_BEGIN_RANGE = " + minName + ",\n"
1099            s += "    " + expandPrefix + "_END_RANGE = " + maxName + ",\n"
1100            s += "    " + expandPrefix + "_NUM = (" + maxName + " - " + minName + " + 1),\n"
1101            s += "    " + expandPrefix + "_MAX_ENUM = 0x7FFFFFFF\n"
1102        # Postfix
1103        s += "} " + groupName + ";"
1104        self.writeInclude('enums', groupName, s)
1105    # Enumerant generation
1106    # <enum> tags may specify their values in several ways, but are usually
1107    # just integers.
1108    def genEnum(self, enuminfo, name):
1109        OutputGenerator.genEnum(self, enuminfo, name)
1110        (numVal,strVal) = self.enumToValue(enuminfo.elem, False)
1111        s = '#define ' + name.ljust(33) + ' ' + strVal
1112        self.logMsg('diag', '# NOT writing compile-time constant', name)
1113        # self.writeInclude('consts', name, s)
1114    #
1115    # Command generation
1116    def genCmd(self, cmdinfo, name):
1117        OutputGenerator.genCmd(self, cmdinfo, name)
1118        #
1119        decls = self.makeCDecls(cmdinfo.elem)
1120        self.writeInclude('protos', name, decls[0])
1121
1122# PyOutputGenerator - subclass of OutputGenerator.
1123# Generates Python data structures describing API names.
1124# Similar to DocOutputGenerator, but writes a single
1125# file.
1126#
1127# ---- methods ----
1128# PyOutputGenerator(errFile, warnFile, diagFile) - args as for
1129#   OutputGenerator. Defines additional internal state.
1130# ---- methods overriding base class ----
1131# beginFile(genOpts)
1132# endFile()
1133# genType(typeinfo,name)
1134# genStruct(typeinfo,name)
1135# genGroup(groupinfo,name)
1136# genEnum(enuminfo, name)
1137# genCmd(cmdinfo)
1138class PyOutputGenerator(OutputGenerator):
1139    """Generate specified API interfaces in a specific style, such as a C header"""
1140    def __init__(self,
1141                 errFile = sys.stderr,
1142                 warnFile = sys.stderr,
1143                 diagFile = sys.stdout):
1144        OutputGenerator.__init__(self, errFile, warnFile, diagFile)
1145    #
1146    def beginFile(self, genOpts):
1147        OutputGenerator.beginFile(self, genOpts)
1148        for dict in [ 'flags', 'enums', 'structs', 'consts', 'enums',
1149          'consts', 'protos', 'funcpointers' ]:
1150            write(dict, '= {}', file=self.outFile)
1151    def endFile(self):
1152        OutputGenerator.endFile(self)
1153    #
1154    # Add a name from the interface
1155    #
1156    # dict - type of name (see beginFile above)
1157    # name - name to add
1158    # value - A serializable Python value for the name
1159    def addName(self, dict, name, value=None):
1160        write(dict + "['" + name + "'] = ", value, file=self.outFile)
1161    #
1162    # Type generation
1163    # For 'struct' or 'union' types, defer to genStruct() to
1164    #   add to the dictionary.
1165    # For 'bitmask' types, add the type name to the 'flags' dictionary,
1166    #   with the value being the corresponding 'enums' name defining
1167    #   the acceptable flag bits.
1168    # For 'enum' types, add the type name to the 'enums' dictionary,
1169    #   with the value being '@STOPHERE@' (because this case seems
1170    #   never to happen).
1171    # For 'funcpointer' types, add the type name to the 'funcpointers'
1172    #   dictionary.
1173    # For 'handle' and 'define' types, add the handle or #define name
1174    #   to the 'struct' dictionary, because that's how the spec sources
1175    #   tag these types even though they aren't structs.
1176    def genType(self, typeinfo, name):
1177        OutputGenerator.genType(self, typeinfo, name)
1178        typeElem = typeinfo.elem
1179        # If the type is a struct type, traverse the imbedded <member> tags
1180        # generating a structure. Otherwise, emit the tag text.
1181        category = typeElem.get('category')
1182        if (category == 'struct' or category == 'union'):
1183            self.genStruct(typeinfo, name)
1184        else:
1185            # Extract the type name
1186            # (from self.genOpts). Copy other text through unchanged.
1187            # If the resulting text is an empty string, don't emit it.
1188            count = len(noneStr(typeElem.text))
1189            for elem in typeElem:
1190                count += len(noneStr(elem.text)) + len(noneStr(elem.tail))
1191            if (count > 0):
1192                if (category == 'bitmask'):
1193                    requiredEnum = typeElem.get('requires')
1194                    self.addName('flags', name, enquote(requiredEnum))
1195                elif (category == 'enum'):
1196                    # This case never seems to come up!
1197                    # @enums   C 'enum' name           Dictionary of enumerant names
1198                    self.addName('enums', name, enquote('@STOPHERE@'))
1199                elif (category == 'funcpointer'):
1200                    self.addName('funcpointers', name, None)
1201                elif (category == 'handle' or category == 'define'):
1202                    self.addName('structs', name, None)
1203                else:
1204                    write('# Unprocessed type:', name, 'category:', category, file=self.outFile)
1205            else:
1206                write('# Unprocessed type:', name, file=self.outFile)
1207    #
1208    # Struct (e.g. C "struct" type) generation.
1209    #
1210    # Add the struct name to the 'structs' dictionary, with the
1211    # value being an ordered list of the struct member names.
1212    def genStruct(self, typeinfo, typeName):
1213        OutputGenerator.genStruct(self, typeinfo, typeName)
1214
1215        members = [member.text for member in typeinfo.elem.findall('.//member/name')]
1216        self.addName('structs', typeName, members)
1217    #
1218    # Group (e.g. C "enum" type) generation.
1219    # These are concatenated together with other types.
1220    #
1221    # Add the enum type name to the 'enums' dictionary, with
1222    #   the value being an ordered list of the enumerant names.
1223    # Add each enumerant name to the 'consts' dictionary, with
1224    #   the value being the enum type the enumerant is part of.
1225    def genGroup(self, groupinfo, groupName):
1226        OutputGenerator.genGroup(self, groupinfo, groupName)
1227        groupElem = groupinfo.elem
1228
1229        # @enums   C 'enum' name           Dictionary of enumerant names
1230        # @consts  C enumerant/const name  Name of corresponding 'enums' key
1231
1232        # Loop over the nested 'enum' tags. Keep track of the minimum and
1233        # maximum numeric values, if they can be determined.
1234        enumerants = [elem.get('name') for elem in groupElem.findall('enum')]
1235        for name in enumerants:
1236            self.addName('consts', name, enquote(groupName))
1237        self.addName('enums', groupName, enumerants)
1238    # Enumerant generation (compile-time constants)
1239    #
1240    # Add the constant name to the 'consts' dictionary, with the
1241    #   value being None to indicate that the constant isn't
1242    #   an enumeration value.
1243    def genEnum(self, enuminfo, name):
1244        OutputGenerator.genEnum(self, enuminfo, name)
1245
1246        # @consts  C enumerant/const name  Name of corresponding 'enums' key
1247
1248        self.addName('consts', name, None)
1249    #
1250    # Command generation
1251    #
1252    # Add the command name to the 'protos' dictionary, with the
1253    #   value being an ordered list of the parameter names.
1254    def genCmd(self, cmdinfo, name):
1255        OutputGenerator.genCmd(self, cmdinfo, name)
1256
1257        params = [param.text for param in cmdinfo.elem.findall('param/name')]
1258        self.addName('protos', name, params)
1259
1260# ValidityOutputGenerator - subclass of OutputGenerator.
1261# Generates AsciiDoc includes of valid usage information, for reference
1262# pages and the Vulkan specification. Similar to DocOutputGenerator.
1263#
1264# ---- methods ----
1265# ValidityOutputGenerator(errFile, warnFile, diagFile) - args as for
1266#   OutputGenerator. Defines additional internal state.
1267# ---- methods overriding base class ----
1268# beginFile(genOpts)
1269# endFile()
1270# beginFeature(interface, emit)
1271# endFeature()
1272# genCmd(cmdinfo)
1273class ValidityOutputGenerator(OutputGenerator):
1274    """Generate specified API interfaces in a specific style, such as a C header"""
1275    def __init__(self,
1276                 errFile = sys.stderr,
1277                 warnFile = sys.stderr,
1278                 diagFile = sys.stdout):
1279        OutputGenerator.__init__(self, errFile, warnFile, diagFile)
1280
1281    def beginFile(self, genOpts):
1282        OutputGenerator.beginFile(self, genOpts)
1283    def endFile(self):
1284        OutputGenerator.endFile(self)
1285    def beginFeature(self, interface, emit):
1286        # Start processing in superclass
1287        OutputGenerator.beginFeature(self, interface, emit)
1288    def endFeature(self):
1289        # Finish processing in superclass
1290        OutputGenerator.endFeature(self)
1291
1292    def makeParameterName(self, name):
1293        return 'pname:' + name
1294
1295    def makeStructName(self, name):
1296        return 'sname:' + name
1297
1298    def makeBaseTypeName(self, name):
1299        return 'basetype:' + name
1300
1301    def makeEnumerationName(self, name):
1302        return 'elink:' + name
1303
1304    def makeEnumerantName(self, name):
1305        return 'ename:' + name
1306
1307    def makeFLink(self, name):
1308        return 'flink:' + name
1309
1310    #
1311    # Generate an include file
1312    #
1313    # directory - subdirectory to put file in
1314    # basename - base name of the file
1315    # contents - contents of the file (Asciidoc boilerplate aside)
1316    def writeInclude(self, directory, basename, validity, threadsafety, commandpropertiesentry, successcodes, errorcodes):
1317        # Create file
1318        filename = self.genOpts.genDirectory + '/' + directory + '/' + basename + '.txt'
1319        self.logMsg('diag', '# Generating include file:', filename)
1320        fp = open(filename, 'w')
1321        # Asciidoc anchor
1322
1323        # Valid Usage
1324        if validity is not None:
1325            write('.Valid Usage', file=fp)
1326            write('*' * 80, file=fp)
1327            write(validity, file=fp, end='')
1328            write('*' * 80, file=fp)
1329            write('', file=fp)
1330
1331        # Host Synchronization
1332        if threadsafety is not None:
1333            write('.Host Synchronization', file=fp)
1334            write('*' * 80, file=fp)
1335            write(threadsafety, file=fp, end='')
1336            write('*' * 80, file=fp)
1337            write('', file=fp)
1338
1339        # Command Properties - contained within a block, to avoid table numbering
1340        if commandpropertiesentry is not None:
1341            write('.Command Properties', file=fp)
1342            write('*' * 80, file=fp)
1343            write('[options="header", width="100%"]', file=fp)
1344            write('|=====================', file=fp)
1345            write('|Command Buffer Levels|Render Pass Scope|Supported Queue Types', file=fp)
1346            write(commandpropertiesentry, file=fp)
1347            write('|=====================', file=fp)
1348            write('*' * 80, file=fp)
1349            write('', file=fp)
1350
1351        # Success Codes - contained within a block, to avoid table numbering
1352        if successcodes is not None or errorcodes is not None:
1353            write('.Return Codes', file=fp)
1354            write('*' * 80, file=fp)
1355            if successcodes is not None:
1356                write('<<fundamentals-successcodes,Success>>::', file=fp)
1357                write(successcodes, file=fp)
1358            if errorcodes is not None:
1359                write('<<fundamentals-errorcodes,Failure>>::', file=fp)
1360                write(errorcodes, file=fp)
1361            write('*' * 80, file=fp)
1362            write('', file=fp)
1363
1364        fp.close()
1365
1366    #
1367    # Check if the parameter passed in is a pointer
1368    def paramIsPointer(self, param):
1369        ispointer = False
1370        paramtype = param.find('type')
1371        if paramtype.tail is not None and '*' in paramtype.tail:
1372            ispointer = True
1373
1374        return ispointer
1375
1376    #
1377    # Check if the parameter passed in is a static array
1378    def paramIsStaticArray(self, param):
1379        if param.find('name').tail is not None:
1380            if param.find('name').tail[0] == '[':
1381                return True
1382
1383    #
1384    # Get the length of a parameter that's been identified as a static array
1385    def staticArrayLength(self, param):
1386        paramname = param.find('name')
1387        paramenumsize = param.find('enum')
1388
1389        if paramenumsize is not None:
1390            return paramenumsize.text
1391        else:
1392            return paramname.tail[1:-1]
1393
1394    #
1395    # Check if the parameter passed in is a pointer to an array
1396    def paramIsArray(self, param):
1397        return param.attrib.get('len') is not None
1398
1399    #
1400    # Get the parent of a handle object
1401    def getHandleParent(self, typename):
1402        types = self.registry.findall("types/type")
1403        for elem in types:
1404            if (elem.find("name") is not None and elem.find('name').text == typename) or elem.attrib.get('name') == typename:
1405                return elem.attrib.get('parent')
1406
1407    #
1408    # Check if a parent object is dispatchable or not
1409    def isHandleTypeDispatchable(self, handlename):
1410        handle = self.registry.find("types/type/[name='" + handlename + "'][@category='handle']")
1411        if handle is not None and handle.find('type').text == 'VK_DEFINE_HANDLE':
1412            return True
1413        else:
1414            return False
1415
1416    def isHandleOptional(self, param, params):
1417
1418        # See if the handle is optional
1419        isOptional = False
1420
1421        # Simple, if it's optional, return true
1422        if param.attrib.get('optional') is not None:
1423            return True
1424
1425        # If no validity is being generated, it usually means that validity is complex and not absolute, so let's say yes.
1426        if param.attrib.get('noautovalidity') is not None:
1427            return True
1428
1429        # If the parameter is an array and we haven't already returned, find out if any of the len parameters are optional
1430        if self.paramIsArray(param):
1431            lengths = param.attrib.get('len').split(',')
1432            for length in lengths:
1433                if (length) != 'null-terminated' and (length) != '1':
1434                    for otherparam in params:
1435                        if otherparam.find('name').text == length:
1436                            if otherparam.attrib.get('optional') is not None:
1437                                return True
1438
1439        return False
1440    #
1441    # Get the category of a type
1442    def getTypeCategory(self, typename):
1443        types = self.registry.findall("types/type")
1444        for elem in types:
1445            if (elem.find("name") is not None and elem.find('name').text == typename) or elem.attrib.get('name') == typename:
1446                return elem.attrib.get('category')
1447
1448    #
1449    # Make a chunk of text for the end of a parameter if it is an array
1450    def makeAsciiDocPreChunk(self, param, params):
1451        paramname = param.find('name')
1452        paramtype = param.find('type')
1453
1454        # General pre-amble. Check optionality and add stuff.
1455        asciidoc = '* '
1456
1457        if self.paramIsStaticArray(param):
1458            asciidoc += 'Any given element of '
1459
1460        elif self.paramIsArray(param):
1461            lengths = param.attrib.get('len').split(',')
1462
1463            # 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
1464            optionallengths = []
1465            for length in lengths:
1466                if (length) != 'null-terminated' and (length) != '1':
1467                    for otherparam in params:
1468                        if otherparam.find('name').text == length:
1469                            if otherparam.attrib.get('optional') is not None:
1470                                if self.paramIsPointer(otherparam):
1471                                    optionallengths.append('the value referenced by ' + self.makeParameterName(length))
1472                                else:
1473                                    optionallengths.append(self.makeParameterName(length))
1474
1475            # Document that these arrays may be ignored if any of the length values are 0
1476            if len(optionallengths) != 0 or param.attrib.get('optional') is not None:
1477                asciidoc += 'If '
1478
1479
1480                if len(optionallengths) != 0:
1481                    if len(optionallengths) == 1:
1482
1483                        asciidoc += optionallengths[0]
1484                        asciidoc += ' is '
1485
1486                    else:
1487                        asciidoc += ' or '.join(optionallengths)
1488                        asciidoc += ' are '
1489
1490                    asciidoc += 'not `0`, '
1491
1492                if len(optionallengths) != 0 and param.attrib.get('optional') is not None:
1493                    asciidoc += 'and '
1494
1495                if param.attrib.get('optional') is not None:
1496                    asciidoc += self.makeParameterName(paramname.text)
1497                    asciidoc += ' is not `NULL`, '
1498
1499        elif param.attrib.get('optional') is not None:
1500            # Don't generate this stub for bitflags
1501            if self.getTypeCategory(paramtype.text) != 'bitmask':
1502                if param.attrib.get('optional').split(',')[0] == 'true':
1503                    asciidoc += 'If '
1504                    asciidoc += self.makeParameterName(paramname.text)
1505                    asciidoc += ' is not '
1506                    if self.paramIsArray(param) or self.paramIsPointer(param) or self.isHandleTypeDispatchable(paramtype.text):
1507                        asciidoc += '`NULL`'
1508                    elif self.getTypeCategory(paramtype.text) == 'handle':
1509                        asciidoc += 'sname:VK_NULL_HANDLE'
1510                    else:
1511                        asciidoc += '`0`'
1512
1513                    asciidoc += ', '
1514
1515        return asciidoc
1516
1517    #
1518    # Make the generic asciidoc line chunk portion used for all parameters.
1519    # May return an empty string if nothing to validate.
1520    def createValidationLineForParameterIntroChunk(self, param, params, typetext):
1521        asciidoc = ''
1522        paramname = param.find('name')
1523        paramtype = param.find('type')
1524
1525        asciidoc += self.makeAsciiDocPreChunk(param, params)
1526
1527        asciidoc += self.makeParameterName(paramname.text)
1528        asciidoc += ' must: be '
1529
1530        if self.paramIsArray(param):
1531            # Arrays. These are hard to get right, apparently
1532
1533            lengths = param.attrib.get('len').split(',')
1534
1535            if (lengths[0]) == 'null-terminated':
1536                asciidoc += 'a null-terminated '
1537            elif (lengths[0]) == '1':
1538                asciidoc += 'a pointer to '
1539            else:
1540                asciidoc += 'a pointer to an array of '
1541
1542                # Handle equations, which are currently denoted with latex
1543                if 'latexmath:' in lengths[0]:
1544                    asciidoc += lengths[0]
1545                else:
1546                    asciidoc += self.makeParameterName(lengths[0])
1547                asciidoc += ' '
1548
1549            for length in lengths[1:]:
1550                if (length) == 'null-terminated': # This should always be the last thing. If it ever isn't for some bizarre reason, then this will need some massaging.
1551                    asciidoc += 'null-terminated '
1552                elif (length) == '1':
1553                    asciidoc += 'pointers to '
1554                else:
1555                    asciidoc += 'pointers to arrays of '
1556                    # Handle equations, which are currently denoted with latex
1557                    if 'latex:' in length:
1558                        asciidoc += length
1559                    else:
1560                        asciidoc += self.makeParameterName(length)
1561                    asciidoc += ' '
1562
1563            # Void pointers don't actually point at anything - remove the word "to"
1564            if paramtype.text == 'void':
1565                if lengths[-1] == '1':
1566                    if len(lengths) > 1:
1567                        asciidoc = asciidoc[:-5]    # Take care of the extra s added by the post array chunk function. #HACK#
1568                    else:
1569                        asciidoc = asciidoc[:-4]
1570                else:
1571                    # An array of void values is a byte array.
1572                    asciidoc += 'byte'
1573
1574            elif paramtype.text == 'char':
1575                # A null terminated array of chars is a string
1576                if lengths[-1] == 'null-terminated':
1577                    asciidoc += 'string'
1578                else:
1579                    # Else it's just a bunch of chars
1580                    asciidoc += 'char value'
1581            elif param.text is not None:
1582                # If a value is "const" that means it won't get modified, so it must be valid going into the function.
1583                if 'const' in param.text:
1584                    typecategory = self.getTypeCategory(paramtype.text)
1585                    if (typecategory != 'struct' and typecategory != 'union' and typecategory != 'basetype' and typecategory is not None) or not self.isStructAlwaysValid(paramtype.text):
1586                        asciidoc += 'valid '
1587
1588            asciidoc += typetext
1589
1590            # pluralize
1591            if len(lengths) > 1 or (lengths[0] != '1' and lengths[0] != 'null-terminated'):
1592                asciidoc += 's'
1593
1594        elif self.paramIsPointer(param):
1595            # Handle pointers - which are really special case arrays (i.e. they don't have a length)
1596            pointercount = paramtype.tail.count('*')
1597
1598            # Could be multi-level pointers (e.g. ppData - pointer to a pointer). Handle that.
1599            for i in range(0, pointercount):
1600                asciidoc += 'a pointer to '
1601
1602            if paramtype.text == 'void':
1603                # If there's only one pointer, it's optional, and it doesn't point at anything in particular - we don't need any language.
1604                if pointercount == 1 and param.attrib.get('optional') is not None:
1605                    return '' # early return
1606                else:
1607                    # Pointer to nothing in particular - delete the " to " portion
1608                    asciidoc = asciidoc[:-4]
1609            else:
1610                # Add an article for English semantic win
1611                asciidoc += 'a '
1612
1613            # If a value is "const" that means it won't get modified, so it must be valid going into the function.
1614            if param.text is not None and paramtype.text != 'void':
1615                if 'const' in param.text:
1616                    asciidoc += 'valid '
1617
1618            asciidoc += typetext
1619
1620        else:
1621            # Non-pointer, non-optional things must be valid
1622            asciidoc += 'a valid '
1623            asciidoc += typetext
1624
1625        if asciidoc != '':
1626            asciidoc += '\n'
1627
1628            # Add additional line for non-optional bitmasks
1629            if self.getTypeCategory(paramtype.text) == 'bitmask':
1630                if param.attrib.get('optional') is None:
1631                    asciidoc += '* '
1632                    if self.paramIsArray(param):
1633                        asciidoc += 'Each element of '
1634                    asciidoc += 'pname:'
1635                    asciidoc += paramname.text
1636                    asciidoc += ' mustnot: be `0`'
1637                    asciidoc += '\n'
1638
1639        return asciidoc
1640
1641    def makeAsciiDocLineForParameter(self, param, params, typetext):
1642        if param.attrib.get('noautovalidity') is not None:
1643            return ''
1644        asciidoc  = self.createValidationLineForParameterIntroChunk(param, params, typetext)
1645
1646        return asciidoc
1647
1648    # Try to do check if a structure is always considered valid (i.e. there's no rules to its acceptance)
1649    def isStructAlwaysValid(self, structname):
1650
1651        struct = self.registry.find("types/type[@name='" + structname + "']")
1652
1653        params = struct.findall('member')
1654        validity = struct.find('validity')
1655
1656        if validity is not None:
1657            return False
1658
1659        for param in params:
1660            paramname = param.find('name')
1661            paramtype = param.find('type')
1662            typecategory = self.getTypeCategory(paramtype.text)
1663
1664            if paramname.text == 'pNext':
1665                return False
1666
1667            if paramname.text == 'sType':
1668                return False
1669
1670            if paramtype.text == 'void' or paramtype.text == 'char' or self.paramIsArray(param) or self.paramIsPointer(param):
1671                if self.makeAsciiDocLineForParameter(param, params, '') != '':
1672                    return False
1673            elif typecategory == 'handle' or typecategory == 'enum' or typecategory == 'bitmask' or param.attrib.get('returnedonly') == 'true':
1674                return False
1675            elif typecategory == 'struct' or typecategory == 'union':
1676                if self.isStructAlwaysValid(paramtype.text) is False:
1677                    return False
1678
1679        return True
1680
1681    #
1682    # Make an entire asciidoc line for a given parameter
1683    def createValidationLineForParameter(self, param, params, typecategory):
1684        asciidoc = ''
1685        paramname = param.find('name')
1686        paramtype = param.find('type')
1687
1688        if paramtype.text == 'void' or paramtype.text == 'char':
1689            # Chars and void are special cases - needs care inside the generator functions
1690            # A null-terminated char array is a string, else it's chars.
1691            # An array of void values is a byte array, a void pointer is just a pointer to nothing in particular
1692            asciidoc += self.makeAsciiDocLineForParameter(param, params, '')
1693        elif typecategory == 'bitmask':
1694            bitsname = paramtype.text.replace('Flags', 'FlagBits')
1695            if self.registry.find("enums[@name='" + bitsname + "']") is None:
1696                asciidoc += '* '
1697                asciidoc += self.makeParameterName(paramname.text)
1698                asciidoc += ' must: be `0`'
1699                asciidoc += '\n'
1700            else:
1701                if self.paramIsArray(param):
1702                    asciidoc += self.makeAsciiDocLineForParameter(param, params, 'combinations of ' + self.makeEnumerationName(bitsname) + ' value')
1703                else:
1704                    asciidoc += self.makeAsciiDocLineForParameter(param, params, 'combination of ' + self.makeEnumerationName(bitsname) + ' values')
1705        elif typecategory == 'handle':
1706            asciidoc += self.makeAsciiDocLineForParameter(param, params, self.makeStructName(paramtype.text) + ' handle')
1707        elif typecategory == 'enum':
1708            asciidoc += self.makeAsciiDocLineForParameter(param, params, self.makeEnumerationName(paramtype.text) + ' value')
1709        elif typecategory == 'struct':
1710            if (self.paramIsArray(param) or self.paramIsPointer(param)) or not self.isStructAlwaysValid(paramtype.text):
1711                asciidoc += self.makeAsciiDocLineForParameter(param, params, self.makeStructName(paramtype.text) + ' structure')
1712        elif typecategory == 'union':
1713            if (self.paramIsArray(param) or self.paramIsPointer(param)) or not self.isStructAlwaysValid(paramtype.text):
1714                asciidoc += self.makeAsciiDocLineForParameter(param, params, self.makeStructName(paramtype.text) + ' union')
1715        elif self.paramIsArray(param) or self.paramIsPointer(param):
1716            asciidoc += self.makeAsciiDocLineForParameter(param, params, self.makeBaseTypeName(paramtype.text) + ' value')
1717
1718        return asciidoc
1719
1720    #
1721    # Make an asciidoc validity entry for a handle's parent object
1722    def makeAsciiDocHandleParent(self, param, params):
1723        asciidoc = ''
1724        paramname = param.find('name')
1725        paramtype = param.find('type')
1726
1727        # Deal with handle parents
1728        handleparent = self.getHandleParent(paramtype.text)
1729        if handleparent is not None:
1730            parentreference = None
1731            for otherparam in params:
1732                if otherparam.find('type').text == handleparent:
1733                    parentreference = otherparam.find('name').text
1734            if parentreference is not None:
1735                asciidoc += '* '
1736
1737                if self.isHandleOptional(param, params):
1738                    if self.paramIsArray(param):
1739                        asciidoc += 'Each element of '
1740                        asciidoc += self.makeParameterName(paramname.text)
1741                        asciidoc += ' that is a valid handle'
1742                    else:
1743                        asciidoc += 'If '
1744                        asciidoc += self.makeParameterName(paramname.text)
1745                        asciidoc += ' is a valid handle, it'
1746                else:
1747                    if self.paramIsArray(param):
1748                        asciidoc += 'Each element of '
1749                    asciidoc += self.makeParameterName(paramname.text)
1750                asciidoc += ' must: have been created, allocated or retrieved from '
1751                asciidoc += self.makeParameterName(parentreference)
1752
1753                asciidoc += '\n'
1754        return asciidoc
1755
1756    #
1757    # Generate an asciidoc validity line for the sType value of a struct
1758    def makeStructureType(self, blockname, param):
1759        asciidoc = '* '
1760        paramname = param.find('name')
1761        paramtype = param.find('type')
1762
1763        asciidoc += self.makeParameterName(paramname.text)
1764        asciidoc += ' must: be '
1765
1766        structuretype = ''
1767        for elem in re.findall(r'(([A-Z][a-z]+)|([A-Z][A-Z]+))', blockname):
1768            if elem[0] == 'Vk':
1769                structuretype += 'VK_STRUCTURE_TYPE_'
1770            else:
1771                structuretype += elem[0].upper()
1772                structuretype += '_'
1773
1774        asciidoc += self.makeEnumerantName(structuretype[:-1])
1775        asciidoc += '\n'
1776
1777        return asciidoc
1778
1779    #
1780    # Generate an asciidoc validity line for the pNext value of a struct
1781    def makeStructureExtensionPointer(self, param):
1782        asciidoc = '* '
1783        paramname = param.find('name')
1784        paramtype = param.find('type')
1785
1786        asciidoc += self.makeParameterName(paramname.text)
1787
1788        validextensionstructs = param.attrib.get('validextensionstructs')
1789        if validextensionstructs is None:
1790            asciidoc += ' must: be `NULL`'
1791        else:
1792            extensionstructs = validextensionstructs.split(',')
1793            asciidoc += ' must: point to one of ' + extensionstructs[:-1].join(', ') + ' or ' + extensionstructs[-1] + 'if the extension that introduced them is enabled '
1794
1795        asciidoc += '\n'
1796
1797        return asciidoc
1798
1799    #
1800    # Generate all the valid usage information for a given struct or command
1801    def makeValidUsageStatements(self, cmd, blockname, params, usages):
1802        # Start the asciidoc block for this
1803        asciidoc = ''
1804
1805        handles = []
1806        anyparentedhandlesoptional = False
1807        parentdictionary = {}
1808        arraylengths = set()
1809        for param in params:
1810            paramname = param.find('name')
1811            paramtype = param.find('type')
1812
1813            # Get the type's category
1814            typecategory = self.getTypeCategory(paramtype.text)
1815
1816            # Generate language to independently validate a parameter
1817            if paramtype.text == 'VkStructureType' and paramname.text == 'sType':
1818                asciidoc += self.makeStructureType(blockname, param)
1819            elif paramtype.text == 'void' and paramname.text == 'pNext':
1820                asciidoc += self.makeStructureExtensionPointer(param)
1821            else:
1822                asciidoc += self.createValidationLineForParameter(param, params, typecategory)
1823
1824            # Ensure that any parenting is properly validated, and list that a handle was found
1825            if typecategory == 'handle':
1826                # Don't detect a parent for return values!
1827                if not self.paramIsPointer(param) or (param.text is not None and 'const' in param.text):
1828                    parent = self.getHandleParent(paramtype.text)
1829                    if parent is not None:
1830                        handles.append(param)
1831
1832                        # If any param is optional, it affects the output
1833                        if self.isHandleOptional(param, params):
1834                            anyparentedhandlesoptional = True
1835
1836                        # Find the first dispatchable parent
1837                        ancestor = parent
1838                        while ancestor is not None and not self.isHandleTypeDispatchable(ancestor):
1839                            ancestor = self.getHandleParent(ancestor)
1840
1841                        # If one was found, add this parameter to the parent dictionary
1842                        if ancestor is not None:
1843                            if ancestor not in parentdictionary:
1844                                parentdictionary[ancestor] = []
1845
1846                            if self.paramIsArray(param):
1847                                parentdictionary[ancestor].append('the elements of ' + self.makeParameterName(paramname.text))
1848                            else:
1849                                parentdictionary[ancestor].append(self.makeParameterName(paramname.text))
1850
1851            # Get the array length for this parameter
1852            arraylength = param.attrib.get('len')
1853            if arraylength is not None:
1854                for onelength in arraylength.split(','):
1855                    arraylengths.add(onelength)
1856
1857        # For any vkQueue* functions, there might be queue type data
1858        if 'vkQueue' in blockname:
1859            # The queue type must be valid
1860            queuetypes = cmd.attrib.get('queues')
1861            if queuetypes is not None:
1862                queuebits = []
1863                for queuetype in re.findall(r'([^,]+)', queuetypes):
1864                    queuebits.append(queuetype.replace('_',' '))
1865
1866                asciidoc += '* '
1867                asciidoc += 'The pname:queue must: support '
1868                if len(queuebits) == 1:
1869                    asciidoc += queuebits[0]
1870                else:
1871                    asciidoc += (', ').join(queuebits[:-1])
1872                    asciidoc += ' or '
1873                    asciidoc += queuebits[-1]
1874                asciidoc += ' operations'
1875                asciidoc += '\n'
1876
1877        if 'vkCmd' in blockname:
1878            # The commandBuffer parameter must be being recorded
1879            asciidoc += '* '
1880            asciidoc += 'pname:commandBuffer must: be in the recording state'
1881            asciidoc += '\n'
1882
1883            # The queue type must be valid
1884            queuetypes = cmd.attrib.get('queues')
1885            queuebits = []
1886            for queuetype in re.findall(r'([^,]+)', queuetypes):
1887                queuebits.append(queuetype.replace('_',' '))
1888
1889            asciidoc += '* '
1890            asciidoc += 'The sname:VkCommandPool that pname:commandBuffer was allocated from must: support '
1891            if len(queuebits) == 1:
1892                asciidoc += queuebits[0]
1893            else:
1894                asciidoc += (', ').join(queuebits[:-1])
1895                asciidoc += ' or '
1896                asciidoc += queuebits[-1]
1897            asciidoc += ' operations'
1898            asciidoc += '\n'
1899
1900            # Must be called inside/outside a renderpass appropriately
1901            renderpass = cmd.attrib.get('renderpass')
1902
1903            if renderpass != 'both':
1904                asciidoc += '* This command must: only be called '
1905                asciidoc += renderpass
1906                asciidoc += ' of a render pass instance'
1907                asciidoc += '\n'
1908
1909            # Must be in the right level command buffer
1910            cmdbufferlevel = cmd.attrib.get('cmdbufferlevel')
1911
1912            if cmdbufferlevel != 'primary,secondary':
1913                asciidoc += '* pname:commandBuffer must: be a '
1914                asciidoc += cmdbufferlevel
1915                asciidoc += ' sname:VkCommandBuffer'
1916                asciidoc += '\n'
1917
1918        # Any non-optional arraylengths should specify they must be greater than 0
1919        for param in params:
1920            paramname = param.find('name')
1921
1922            for arraylength in arraylengths:
1923                if paramname.text == arraylength and param.attrib.get('optional') is None:
1924                    # Get all the array dependencies
1925                    arrays = cmd.findall("param/[@len='" + arraylength + "'][@optional='true']")
1926
1927                    # Get all the optional array dependencies, including those not generating validity for some reason
1928                    optionalarrays = cmd.findall("param/[@len='" + arraylength + "'][@optional='true']")
1929                    optionalarrays.extend(cmd.findall("param/[@len='" + arraylength + "'][@noautovalidity='true']"))
1930
1931                    asciidoc += '* '
1932
1933                    # Allow lengths to be arbitrary if all their dependents are optional
1934                    if len(optionalarrays) == len(arrays) and len(optionalarrays) != 0:
1935                        asciidoc += 'If '
1936                        if len(optionalarrays) > 1:
1937                            asciidoc += 'any of '
1938
1939                        for array in optionalarrays[:-1]:
1940                            asciidoc += self.makeParameterName(optionalarrays.find('name').text)
1941                            asciidoc += ', '
1942
1943                        if len(optionalarrays) > 1:
1944                            asciidoc += 'and '
1945                            asciidoc += self.makeParameterName(optionalarrays[-1].find('name').text)
1946                            asciidoc += ' are '
1947                        else:
1948                            asciidoc += self.makeParameterName(optionalarrays[-1].find('name').text)
1949                            asciidoc += ' is '
1950
1951                        asciidoc += 'not `NULL`, '
1952
1953                        if self.paramIsPointer(param):
1954                            asciidoc += 'the value referenced by '
1955                        else:
1956                            asciidoc += 'the value of '
1957
1958                    elif self.paramIsPointer(param):
1959                        asciidoc += 'The value referenced by '
1960                    else:
1961                        asciidoc += 'The value of '
1962
1963                    asciidoc += self.makeParameterName(arraylength)
1964                    asciidoc += ' must: be greater than `0`'
1965                    asciidoc += '\n'
1966
1967        # Find the parents of all objects referenced in this command
1968        for param in handles:
1969            asciidoc += self.makeAsciiDocHandleParent(param, params)
1970
1971        # Find the common ancestors of objects
1972        noancestorscount = 0
1973        while noancestorscount < len(parentdictionary):
1974            noancestorscount = 0
1975            oldparentdictionary = parentdictionary.copy()
1976            for parent in oldparentdictionary.items():
1977                ancestor = self.getHandleParent(parent[0])
1978
1979                while ancestor is not None and ancestor not in parentdictionary:
1980                    ancestor = self.getHandleParent(ancestor)
1981
1982                if ancestor is not None:
1983                    parentdictionary[ancestor] += parentdictionary.pop(parent[0])
1984                else:
1985                    # No ancestors possible - so count it up
1986                    noancestorscount += 1
1987
1988        # Add validation language about common ancestors
1989        for parent in parentdictionary.items():
1990            if len(parent[1]) > 1:
1991                parentlanguage = '* '
1992
1993                parentlanguage += 'Each of '
1994                parentlanguage += ", ".join(parent[1][:-1])
1995                parentlanguage += ' and '
1996                parentlanguage += parent[1][-1]
1997                if anyparentedhandlesoptional is True:
1998                    parentlanguage += ' that are valid handles'
1999                parentlanguage += ' must: have been created, allocated or retrieved from the same '
2000                parentlanguage += self.makeStructName(parent[0])
2001                parentlanguage += '\n'
2002
2003                # Capitalize and add to the main language
2004                asciidoc += parentlanguage
2005
2006        # Add in any plain-text validation language that's in the xml
2007        for usage in usages:
2008            asciidoc += '* '
2009            asciidoc += usage.text
2010            asciidoc += '\n'
2011
2012        # In case there's nothing to report, return None
2013        if asciidoc == '':
2014            return None
2015        # Delimit the asciidoc block
2016        return asciidoc
2017
2018    def makeThreadSafetyBlock(self, cmd, paramtext):
2019        """Generate C function pointer typedef for <command> Element"""
2020        paramdecl = ''
2021
2022        # For any vkCmd* functions, the commandBuffer parameter must be being recorded
2023        if cmd.find('proto/name') is not None and 'vkCmd' in cmd.find('proto/name'):
2024            paramdecl += '* '
2025            paramdecl += 'The sname:VkCommandPool that pname:commandBuffer was created from'
2026            paramdecl += '\n'
2027
2028        # Find and add any parameters that are thread unsafe
2029        explicitexternsyncparams = cmd.findall(paramtext + "[@externsync]")
2030        if (explicitexternsyncparams is not None):
2031            for param in explicitexternsyncparams:
2032                externsyncattribs = param.attrib.get('externsync')
2033                paramname = param.find('name')
2034                for externsyncattrib in externsyncattribs.split(','):
2035                    paramdecl += '* '
2036                    paramdecl += 'Host access to '
2037                    if externsyncattrib == 'true':
2038                        if self.paramIsArray(param):
2039                            paramdecl += 'each member of ' + self.makeParameterName(paramname.text)
2040                        elif self.paramIsPointer(param):
2041                            paramdecl += 'the object referenced by ' + self.makeParameterName(paramname.text)
2042                        else:
2043                            paramdecl += self.makeParameterName(paramname.text)
2044                    else:
2045                        paramdecl += 'pname:'
2046                        paramdecl += externsyncattrib
2047                    paramdecl += ' must: be externally synchronized\n'
2048
2049        # Find and add any "implicit" parameters that are thread unsafe
2050        implicitexternsyncparams = cmd.find('implicitexternsyncparams')
2051        if (implicitexternsyncparams is not None):
2052            for elem in implicitexternsyncparams:
2053                paramdecl += '* '
2054                paramdecl += 'Host access to '
2055                paramdecl += elem.text
2056                paramdecl += ' must: be externally synchronized\n'
2057
2058        if (paramdecl == ''):
2059            return None
2060        else:
2061            return paramdecl
2062
2063    def makeCommandPropertiesTableEntry(self, cmd, name):
2064
2065        if 'vkCmd' in name:
2066            # Must be called inside/outside a renderpass appropriately
2067            cmdbufferlevel = cmd.attrib.get('cmdbufferlevel')
2068            cmdbufferlevel = (' + \n').join(cmdbufferlevel.title().split(','))
2069
2070            renderpass = cmd.attrib.get('renderpass')
2071            renderpass = renderpass.capitalize()
2072
2073            queues = cmd.attrib.get('queues')
2074            queues = (' + \n').join(queues.upper().split(','))
2075
2076            return '|' + cmdbufferlevel + '|' + renderpass + '|' + queues
2077        elif 'vkQueue' in name:
2078            # Must be called inside/outside a renderpass appropriately
2079
2080            queues = cmd.attrib.get('queues')
2081            if queues is None:
2082                queues = 'Any'
2083            else:
2084                queues = (' + \n').join(queues.upper().split(','))
2085
2086            return '|-|-|' + queues
2087
2088        return None
2089
2090    def makeSuccessCodes(self, cmd, name):
2091
2092        successcodes = cmd.attrib.get('successcodes')
2093        if successcodes is not None:
2094
2095            successcodeentry = ''
2096            successcodes = successcodes.split(',')
2097            return '* ' + '\n* '.join(successcodes)
2098
2099        return None
2100
2101    def makeErrorCodes(self, cmd, name):
2102
2103        errorcodes = cmd.attrib.get('errorcodes')
2104        if errorcodes is not None:
2105
2106            errorcodeentry = ''
2107            errorcodes = errorcodes.split(',')
2108            return '* ' + '\n* '.join(errorcodes)
2109
2110        return None
2111
2112    #
2113    # Command generation
2114    def genCmd(self, cmdinfo, name):
2115        OutputGenerator.genCmd(self, cmdinfo, name)
2116        #
2117        # Get all thh parameters
2118        params = cmdinfo.elem.findall('param')
2119        usages = cmdinfo.elem.findall('validity/usage')
2120
2121        validity = self.makeValidUsageStatements(cmdinfo.elem, name, params, usages)
2122        threadsafety = self.makeThreadSafetyBlock(cmdinfo.elem, 'param')
2123        commandpropertiesentry = self.makeCommandPropertiesTableEntry(cmdinfo.elem, name)
2124        successcodes = self.makeSuccessCodes(cmdinfo.elem, name)
2125        errorcodes = self.makeErrorCodes(cmdinfo.elem, name)
2126
2127        self.writeInclude('validity/protos', name, validity, threadsafety, commandpropertiesentry, successcodes, errorcodes)
2128
2129    #
2130    # Struct Generation
2131    def genStruct(self, typeinfo, typename):
2132        OutputGenerator.genStruct(self, typeinfo, typename)
2133
2134        # Anything that's only ever returned can't be set by the user, so shouldn't have any validity information.
2135        if typeinfo.elem.attrib.get('returnedonly') is None:
2136            params = typeinfo.elem.findall('member')
2137            usages = typeinfo.elem.findall('validity/usage')
2138
2139            validity = self.makeValidUsageStatements(typeinfo.elem, typename, params, usages)
2140            threadsafety = self.makeThreadSafetyBlock(typeinfo.elem, 'member')
2141
2142            self.writeInclude('validity/structs', typename, validity, threadsafety, None, None, None)
2143        else:
2144            # Still generate files for return only structs, in case this state changes later
2145            self.writeInclude('validity/structs', typename, None, None, None, None, None)
2146
2147    #
2148    # Type Generation
2149    def genType(self, typeinfo, typename):
2150        OutputGenerator.genType(self, typeinfo, typename)
2151
2152        category = typeinfo.elem.get('category')
2153        if (category == 'struct' or category == 'union'):
2154            self.genStruct(typeinfo, typename)
2155
2156# HostSynchronizationOutputGenerator - subclass of OutputGenerator.
2157# Generates AsciiDoc includes of the externsync parameter table for the
2158# fundamentals chapter of the Vulkan specification. Similar to
2159# DocOutputGenerator.
2160#
2161# ---- methods ----
2162# HostSynchronizationOutputGenerator(errFile, warnFile, diagFile) - args as for
2163#   OutputGenerator. Defines additional internal state.
2164# ---- methods overriding base class ----
2165# genCmd(cmdinfo)
2166class HostSynchronizationOutputGenerator(OutputGenerator):
2167    # Generate Host Synchronized Parameters in a table at the top of the spec
2168    def __init__(self,
2169                 errFile = sys.stderr,
2170                 warnFile = sys.stderr,
2171                 diagFile = sys.stdout):
2172        OutputGenerator.__init__(self, errFile, warnFile, diagFile)
2173
2174    threadsafety = {'parameters': '', 'parameterlists': '', 'implicit': ''}
2175
2176    def makeParameterName(self, name):
2177        return 'pname:' + name
2178
2179    def makeFLink(self, name):
2180        return 'flink:' + name
2181
2182    #
2183    # Generate an include file
2184    #
2185    # directory - subdirectory to put file in
2186    # basename - base name of the file
2187    # contents - contents of the file (Asciidoc boilerplate aside)
2188    def writeInclude(self):
2189
2190        if self.threadsafety['parameters'] is not None:
2191            # Create file
2192            filename = self.genOpts.genDirectory + '/' + self.genOpts.filename + '/parameters.txt'
2193            self.logMsg('diag', '# Generating include file:', filename)
2194            fp = open(filename, 'w')
2195
2196            # Host Synchronization
2197            write('.Externally Synchronized Parameters', file=fp)
2198            write('*' * 80, file=fp)
2199            write(self.threadsafety['parameters'], file=fp, end='')
2200            write('*' * 80, file=fp)
2201            write('', file=fp)
2202
2203        if self.threadsafety['parameterlists'] is not None:
2204            # Create file
2205            filename = self.genOpts.genDirectory + '/' + self.genOpts.filename + '/parameterlists.txt'
2206            self.logMsg('diag', '# Generating include file:', filename)
2207            fp = open(filename, 'w')
2208
2209            # Host Synchronization
2210            write('.Externally Synchronized Parameter Lists', file=fp)
2211            write('*' * 80, file=fp)
2212            write(self.threadsafety['parameterlists'], file=fp, end='')
2213            write('*' * 80, file=fp)
2214            write('', file=fp)
2215
2216        if self.threadsafety['implicit'] is not None:
2217            # Create file
2218            filename = self.genOpts.genDirectory + '/' + self.genOpts.filename + '/implicit.txt'
2219            self.logMsg('diag', '# Generating include file:', filename)
2220            fp = open(filename, 'w')
2221
2222            # Host Synchronization
2223            write('.Implicit Externally Synchronized Parameters', file=fp)
2224            write('*' * 80, file=fp)
2225            write(self.threadsafety['implicit'], file=fp, end='')
2226            write('*' * 80, file=fp)
2227            write('', file=fp)
2228
2229        fp.close()
2230
2231    #
2232    # Check if the parameter passed in is a pointer to an array
2233    def paramIsArray(self, param):
2234        return param.attrib.get('len') is not None
2235
2236    # Check if the parameter passed in is a pointer
2237    def paramIsPointer(self, param):
2238        ispointer = False
2239        paramtype = param.find('type')
2240        if paramtype.tail is not None and '*' in paramtype.tail:
2241            ispointer = True
2242
2243        return ispointer
2244
2245    # Turn the "name[].member[]" notation into plain English.
2246    def makeThreadDereferenceHumanReadable(self, dereference):
2247        matches = re.findall(r"[\w]+[^\w]*",dereference)
2248        stringval = ''
2249        for match in reversed(matches):
2250            if '->' in match or '.' in match:
2251                stringval += 'member of '
2252            if '[]' in match:
2253                stringval += 'each element of '
2254
2255            stringval += 'the '
2256            stringval += self.makeParameterName(re.findall(r"[\w]+",match)[0])
2257            stringval += ' '
2258
2259        stringval += 'parameter'
2260
2261        return stringval[0].upper() + stringval[1:]
2262
2263    def makeThreadSafetyBlocks(self, cmd, paramtext):
2264        protoname = cmd.find('proto/name').text
2265
2266        # Find and add any parameters that are thread unsafe
2267        explicitexternsyncparams = cmd.findall(paramtext + "[@externsync]")
2268        if (explicitexternsyncparams is not None):
2269            for param in explicitexternsyncparams:
2270                externsyncattribs = param.attrib.get('externsync')
2271                paramname = param.find('name')
2272                for externsyncattrib in externsyncattribs.split(','):
2273
2274                    tempstring = '* '
2275                    if externsyncattrib == 'true':
2276                        if self.paramIsArray(param):
2277                            tempstring += 'Each element of the '
2278                        elif self.paramIsPointer(param):
2279                            tempstring += 'The object referenced by the '
2280                        else:
2281                            tempstring += 'The '
2282
2283                        tempstring += self.makeParameterName(paramname.text)
2284                        tempstring += ' parameter'
2285
2286                    else:
2287                        tempstring += self.makeThreadDereferenceHumanReadable(externsyncattrib)
2288
2289                    tempstring += ' in '
2290                    tempstring += self.makeFLink(protoname)
2291                    tempstring += '\n'
2292
2293
2294                    if ' element of ' in tempstring:
2295                        self.threadsafety['parameterlists'] += tempstring
2296                    else:
2297                        self.threadsafety['parameters'] += tempstring
2298
2299
2300        # Find and add any "implicit" parameters that are thread unsafe
2301        implicitexternsyncparams = cmd.find('implicitexternsyncparams')
2302        if (implicitexternsyncparams is not None):
2303            for elem in implicitexternsyncparams:
2304                self.threadsafety['implicit'] += '* '
2305                self.threadsafety['implicit'] += elem.text[0].upper()
2306                self.threadsafety['implicit'] += elem.text[1:]
2307                self.threadsafety['implicit'] += ' in '
2308                self.threadsafety['implicit'] += self.makeFLink(protoname)
2309                self.threadsafety['implicit'] += '\n'
2310
2311
2312        # For any vkCmd* functions, the commandBuffer parameter must be being recorded
2313        if protoname is not None and 'vkCmd' in protoname:
2314            self.threadsafety['implicit'] += '* '
2315            self.threadsafety['implicit'] += 'The sname:VkCommandPool that pname:commandBuffer was allocated from, in '
2316            self.threadsafety['implicit'] += self.makeFLink(protoname)
2317
2318            self.threadsafety['implicit'] += '\n'
2319
2320    #
2321    # Command generation
2322    def genCmd(self, cmdinfo, name):
2323        OutputGenerator.genCmd(self, cmdinfo, name)
2324        #
2325        # Get all thh parameters
2326        params = cmdinfo.elem.findall('param')
2327        usages = cmdinfo.elem.findall('validity/usage')
2328
2329        self.makeThreadSafetyBlocks(cmdinfo.elem, 'param')
2330
2331        self.writeInclude()
2332
2333# ThreadOutputGenerator - subclass of OutputGenerator.
2334# Generates Thread checking framework
2335#
2336# ---- methods ----
2337# ThreadOutputGenerator(errFile, warnFile, diagFile) - args as for
2338#   OutputGenerator. Defines additional internal state.
2339# ---- methods overriding base class ----
2340# beginFile(genOpts)
2341# endFile()
2342# beginFeature(interface, emit)
2343# endFeature()
2344# genType(typeinfo,name)
2345# genStruct(typeinfo,name)
2346# genGroup(groupinfo,name)
2347# genEnum(enuminfo, name)
2348# genCmd(cmdinfo)
2349class ThreadOutputGenerator(OutputGenerator):
2350    """Generate specified API interfaces in a specific style, such as a C header"""
2351    # This is an ordered list of sections in the header file.
2352    TYPE_SECTIONS = ['include', 'define', 'basetype', 'handle', 'enum',
2353                     'group', 'bitmask', 'funcpointer', 'struct']
2354    ALL_SECTIONS = TYPE_SECTIONS + ['command']
2355    def __init__(self,
2356                 errFile = sys.stderr,
2357                 warnFile = sys.stderr,
2358                 diagFile = sys.stdout):
2359        OutputGenerator.__init__(self, errFile, warnFile, diagFile)
2360        # Internal state - accumulators for different inner block text
2361        self.sections = dict([(section, []) for section in self.ALL_SECTIONS])
2362        self.intercepts = []
2363
2364    # Check if the parameter passed in is a pointer to an array
2365    def paramIsArray(self, param):
2366        return param.attrib.get('len') is not None
2367
2368    # Check if the parameter passed in is a pointer
2369    def paramIsPointer(self, param):
2370        ispointer = False
2371        for elem in param:
2372            #write('paramIsPointer '+elem.text, file=sys.stderr)
2373            #write('elem.tag '+elem.tag, file=sys.stderr)
2374            #if (elem.tail is None):
2375            #    write('elem.tail is None', file=sys.stderr)
2376            #else:
2377            #    write('elem.tail '+elem.tail, file=sys.stderr)
2378            if ((elem.tag is not 'type') and (elem.tail is not None)) and '*' in elem.tail:
2379                ispointer = True
2380            #    write('is pointer', file=sys.stderr)
2381        return ispointer
2382    def makeThreadUseBlock(self, cmd, functionprefix):
2383        """Generate C function pointer typedef for <command> Element"""
2384        paramdecl = ''
2385        thread_check_dispatchable_objects = [
2386            "VkCommandBuffer",
2387            "VkDevice",
2388            "VkInstance",
2389            "VkQueue",
2390        ]
2391        thread_check_nondispatchable_objects = [
2392            "VkBuffer",
2393            "VkBufferView",
2394            "VkCommandPool",
2395            "VkDescriptorPool",
2396            "VkDescriptorSetLayout",
2397            "VkDeviceMemory",
2398            "VkEvent",
2399            "VkFence",
2400            "VkFramebuffer",
2401            "VkImage",
2402            "VkImageView",
2403            "VkPipeline",
2404            "VkPipelineCache",
2405            "VkPipelineLayout",
2406            "VkQueryPool",
2407            "VkRenderPass",
2408            "VkSampler",
2409            "VkSemaphore",
2410            "VkShaderModule",
2411        ]
2412
2413        # Find and add any parameters that are thread unsafe
2414        params = cmd.findall('param')
2415        for param in params:
2416            paramname = param.find('name')
2417            if False: # self.paramIsPointer(param):
2418                paramdecl += '    // not watching use of pointer ' + paramname.text + '\n'
2419            else:
2420                externsync = param.attrib.get('externsync')
2421                if externsync == 'true':
2422                    if self.paramIsArray(param):
2423                        paramdecl += '    for (int index=0;index<' + param.attrib.get('len') + ';index++) {\n'
2424                        paramdecl += '        ' + functionprefix + 'WriteObject(my_data, ' + paramname.text + '[index]);\n'
2425                        paramdecl += '    }\n'
2426                    else:
2427                        paramdecl += '    ' + functionprefix + 'WriteObject(my_data, ' + paramname.text + ');\n'
2428                elif (param.attrib.get('externsync')):
2429                    if self.paramIsArray(param):
2430                        # Externsync can list pointers to arrays of members to synchronize
2431                        paramdecl += '    for (int index=0;index<' + param.attrib.get('len') + ';index++) {\n'
2432                        for member in externsync.split(","):
2433                            # Replace first empty [] in member name with index
2434                            element = member.replace('[]','[index]',1)
2435                            if '[]' in element:
2436                                # Replace any second empty [] in element name with
2437                                # inner array index based on mapping array names like
2438                                # "pSomeThings[]" to "someThingCount" array size.
2439                                # This could be more robust by mapping a param member
2440                                # name to a struct type and "len" attribute.
2441                                limit = element[0:element.find('s[]')] + 'Count'
2442                                dotp = limit.rfind('.p')
2443                                limit = limit[0:dotp+1] + limit[dotp+2:dotp+3].lower() + limit[dotp+3:]
2444                                paramdecl += '        for(int index2=0;index2<'+limit+';index2++)'
2445                                element = element.replace('[]','[index2]')
2446                            paramdecl += '        ' + functionprefix + 'WriteObject(my_data, ' + element + ');\n'
2447                        paramdecl += '    }\n'
2448                    else:
2449                        # externsync can list members to synchronize
2450                        for member in externsync.split(","):
2451                            paramdecl += '    ' + functionprefix + 'WriteObject(my_data, ' + member + ');\n'
2452                else:
2453                    paramtype = param.find('type')
2454                    if paramtype is not None:
2455                        paramtype = paramtype.text
2456                    else:
2457                        paramtype = 'None'
2458                    if paramtype in thread_check_dispatchable_objects or paramtype in thread_check_nondispatchable_objects:
2459                        if self.paramIsArray(param) and ('pPipelines' != paramname.text):
2460                            paramdecl += '    for (int index=0;index<' + param.attrib.get('len') + ';index++) {\n'
2461                            paramdecl += '        ' + functionprefix + 'ReadObject(my_data, ' + paramname.text + '[index]);\n'
2462                            paramdecl += '    }\n'
2463                        elif not self.paramIsPointer(param):
2464                            # Pointer params are often being created.
2465                            # They are not being read from.
2466                            paramdecl += '    ' + functionprefix + 'ReadObject(my_data, ' + paramname.text + ');\n'
2467        explicitexternsyncparams = cmd.findall("param[@externsync]")
2468        if (explicitexternsyncparams is not None):
2469            for param in explicitexternsyncparams:
2470                externsyncattrib = param.attrib.get('externsync')
2471                paramname = param.find('name')
2472                paramdecl += '// Host access to '
2473                if externsyncattrib == 'true':
2474                    if self.paramIsArray(param):
2475                        paramdecl += 'each member of ' + paramname.text
2476                    elif self.paramIsPointer(param):
2477                        paramdecl += 'the object referenced by ' + paramname.text
2478                    else:
2479                        paramdecl += paramname.text
2480                else:
2481                    paramdecl += externsyncattrib
2482                paramdecl += ' must be externally synchronized\n'
2483
2484        # Find and add any "implicit" parameters that are thread unsafe
2485        implicitexternsyncparams = cmd.find('implicitexternsyncparams')
2486        if (implicitexternsyncparams is not None):
2487            for elem in implicitexternsyncparams:
2488                paramdecl += '    // '
2489                paramdecl += elem.text
2490                paramdecl += ' must be externally synchronized between host accesses\n'
2491
2492        if (paramdecl == ''):
2493            return None
2494        else:
2495            return paramdecl
2496    def beginFile(self, genOpts):
2497        OutputGenerator.beginFile(self, genOpts)
2498        # C-specific
2499        #
2500        # Multiple inclusion protection & C++ wrappers.
2501        if (genOpts.protectFile and self.genOpts.filename):
2502            headerSym = '__' + re.sub('\.h', '_h_', os.path.basename(self.genOpts.filename))
2503            write('#ifndef', headerSym, file=self.outFile)
2504            write('#define', headerSym, '1', file=self.outFile)
2505            self.newline()
2506        write('#ifdef __cplusplus', file=self.outFile)
2507        write('extern "C" {', file=self.outFile)
2508        write('#endif', file=self.outFile)
2509        self.newline()
2510        #
2511        # User-supplied prefix text, if any (list of strings)
2512        if (genOpts.prefixText):
2513            for s in genOpts.prefixText:
2514                write(s, file=self.outFile)
2515    def endFile(self):
2516        # C-specific
2517        # Finish C++ wrapper and multiple inclusion protection
2518        self.newline()
2519        # record intercepted procedures
2520        write('// intercepts', file=self.outFile)
2521        write('struct { const char* name; PFN_vkVoidFunction pFunc;} procmap[] = {', file=self.outFile)
2522        write('\n'.join(self.intercepts), file=self.outFile)
2523        write('};\n', file=self.outFile)
2524        self.newline()
2525        write('#ifdef __cplusplus', file=self.outFile)
2526        write('}', file=self.outFile)
2527        write('#endif', file=self.outFile)
2528        if (self.genOpts.protectFile and self.genOpts.filename):
2529            self.newline()
2530            write('#endif', file=self.outFile)
2531        # Finish processing in superclass
2532        OutputGenerator.endFile(self)
2533    def beginFeature(self, interface, emit):
2534        #write('// starting beginFeature', file=self.outFile)
2535        # Start processing in superclass
2536        OutputGenerator.beginFeature(self, interface, emit)
2537        # C-specific
2538        # Accumulate includes, defines, types, enums, function pointer typedefs,
2539        # end function prototypes separately for this feature. They're only
2540        # printed in endFeature().
2541        self.sections = dict([(section, []) for section in self.ALL_SECTIONS])
2542        #write('// ending beginFeature', file=self.outFile)
2543    def endFeature(self):
2544        # C-specific
2545        # Actually write the interface to the output file.
2546        #write('// starting endFeature', file=self.outFile)
2547        if (self.emit):
2548            self.newline()
2549            if (self.genOpts.protectFeature):
2550                write('#ifndef', self.featureName, file=self.outFile)
2551            # If type declarations are needed by other features based on
2552            # this one, it may be necessary to suppress the ExtraProtect,
2553            # or move it below the 'for section...' loop.
2554            #write('// endFeature looking at self.featureExtraProtect', file=self.outFile)
2555            if (self.featureExtraProtect != None):
2556                write('#ifdef', self.featureExtraProtect, file=self.outFile)
2557            #write('#define', self.featureName, '1', file=self.outFile)
2558            for section in self.TYPE_SECTIONS:
2559                #write('// endFeature writing section'+section, file=self.outFile)
2560                contents = self.sections[section]
2561                if contents:
2562                    write('\n'.join(contents), file=self.outFile)
2563                    self.newline()
2564            #write('// endFeature looking at self.sections[command]', file=self.outFile)
2565            if (self.sections['command']):
2566                write('\n'.join(self.sections['command']), end='', file=self.outFile)
2567                self.newline()
2568            if (self.featureExtraProtect != None):
2569                write('#endif /*', self.featureExtraProtect, '*/', file=self.outFile)
2570            if (self.genOpts.protectFeature):
2571                write('#endif /*', self.featureName, '*/', file=self.outFile)
2572        # Finish processing in superclass
2573        OutputGenerator.endFeature(self)
2574        #write('// ending endFeature', file=self.outFile)
2575    #
2576    # Append a definition to the specified section
2577    def appendSection(self, section, text):
2578        # self.sections[section].append('SECTION: ' + section + '\n')
2579        self.sections[section].append(text)
2580    #
2581    # Type generation
2582    def genType(self, typeinfo, name):
2583        pass
2584    #
2585    # Struct (e.g. C "struct" type) generation.
2586    # This is a special case of the <type> tag where the contents are
2587    # interpreted as a set of <member> tags instead of freeform C
2588    # C type declarations. The <member> tags are just like <param>
2589    # tags - they are a declaration of a struct or union member.
2590    # Only simple member declarations are supported (no nested
2591    # structs etc.)
2592    def genStruct(self, typeinfo, typeName):
2593        OutputGenerator.genStruct(self, typeinfo, typeName)
2594        body = 'typedef ' + typeinfo.elem.get('category') + ' ' + typeName + ' {\n'
2595        # paramdecl = self.makeCParamDecl(typeinfo.elem, self.genOpts.alignFuncParam)
2596        for member in typeinfo.elem.findall('.//member'):
2597            body += self.makeCParamDecl(member, self.genOpts.alignFuncParam)
2598            body += ';\n'
2599        body += '} ' + typeName + ';\n'
2600        self.appendSection('struct', body)
2601    #
2602    # Group (e.g. C "enum" type) generation.
2603    # These are concatenated together with other types.
2604    def genGroup(self, groupinfo, groupName):
2605        pass
2606    # Enumerant generation
2607    # <enum> tags may specify their values in several ways, but are usually
2608    # just integers.
2609    def genEnum(self, enuminfo, name):
2610        pass
2611    #
2612    # Command generation
2613    def genCmd(self, cmdinfo, name):
2614        special_functions = [
2615            'vkGetDeviceProcAddr',
2616            'vkGetInstanceProcAddr',
2617            'vkCreateDevice',
2618            'vkDestroyDevice',
2619            'vkCreateInstance',
2620            'vkDestroyInstance',
2621            'vkEnumerateInstanceLayerProperties',
2622            'vkEnumerateInstanceExtensionProperties',
2623            'vkAllocateCommandBuffers',
2624            'vkFreeCommandBuffers',
2625            'vkCreateDebugReportCallbackEXT',
2626            'vkDestroyDebugReportCallbackEXT',
2627        ]
2628        if name in special_functions:
2629            self.intercepts += [ '    {"%s", reinterpret_cast<PFN_vkVoidFunction>(%s)},' % (name,name) ]
2630            return
2631        if "KHR" in name:
2632            self.appendSection('command', '// TODO - not wrapping KHR function ' + name)
2633            return
2634        # Determine first if this function needs to be intercepted
2635        startthreadsafety = self.makeThreadUseBlock(cmdinfo.elem, 'start')
2636        if startthreadsafety is None:
2637            return
2638        finishthreadsafety = self.makeThreadUseBlock(cmdinfo.elem, 'finish')
2639        # record that the function will be intercepted
2640        if (self.featureExtraProtect != None):
2641            self.intercepts += [ '#ifdef %s' % self.featureExtraProtect ]
2642        self.intercepts += [ '    {"%s", reinterpret_cast<PFN_vkVoidFunction>(%s)},' % (name,name) ]
2643        if (self.featureExtraProtect != None):
2644            self.intercepts += [ '#endif' ]
2645
2646        OutputGenerator.genCmd(self, cmdinfo, name)
2647        #
2648        decls = self.makeCDecls(cmdinfo.elem)
2649        self.appendSection('command', '')
2650        self.appendSection('command', decls[0][:-1])
2651        self.appendSection('command', '{')
2652        # setup common to call wrappers
2653        # first parameter is always dispatchable
2654        dispatchable_type = cmdinfo.elem.find('param/type').text
2655        dispatchable_name = cmdinfo.elem.find('param/name').text
2656        self.appendSection('command', '    dispatch_key key = get_dispatch_key('+dispatchable_name+');')
2657        self.appendSection('command', '    layer_data *my_data = get_my_data_ptr(key, layer_data_map);')
2658        if dispatchable_type in ["VkPhysicalDevice", "VkInstance"]:
2659            self.appendSection('command', '    VkLayerInstanceDispatchTable *pTable = my_data->instance_dispatch_table;')
2660        else:
2661            self.appendSection('command', '    VkLayerDispatchTable *pTable = my_data->device_dispatch_table;')
2662        # Declare result variable, if any.
2663        resulttype = cmdinfo.elem.find('proto/type')
2664        if (resulttype != None and resulttype.text == 'void'):
2665          resulttype = None
2666        if (resulttype != None):
2667            self.appendSection('command', '    ' + resulttype.text + ' result;')
2668            assignresult = 'result = '
2669        else:
2670            assignresult = ''
2671
2672        self.appendSection('command', str(startthreadsafety))
2673        params = cmdinfo.elem.findall('param/name')
2674        paramstext = ','.join([str(param.text) for param in params])
2675        API = cmdinfo.elem.attrib.get('name').replace('vk','pTable->',1)
2676        self.appendSection('command', '    ' + assignresult + API + '(' + paramstext + ');')
2677        self.appendSection('command', str(finishthreadsafety))
2678        # Return result variable, if any.
2679        if (resulttype != None):
2680            self.appendSection('command', '    return result;')
2681        self.appendSection('command', '}')
2682
2683# ParamCheckerOutputGenerator - subclass of OutputGenerator.
2684# Generates param checker layer code.
2685#
2686# ---- methods ----
2687# ParamCheckerOutputGenerator(errFile, warnFile, diagFile) - args as for
2688#   OutputGenerator. Defines additional internal state.
2689# ---- methods overriding base class ----
2690# beginFile(genOpts)
2691# endFile()
2692# beginFeature(interface, emit)
2693# endFeature()
2694# genType(typeinfo,name)
2695# genStruct(typeinfo,name)
2696# genGroup(groupinfo,name)
2697# genEnum(enuminfo, name)
2698# genCmd(cmdinfo)
2699class ParamCheckerOutputGenerator(OutputGenerator):
2700    """Generate ParamChecker code based on XML element attributes"""
2701    # This is an ordered list of sections in the header file.
2702    ALL_SECTIONS = ['command']
2703    def __init__(self,
2704                 errFile = sys.stderr,
2705                 warnFile = sys.stderr,
2706                 diagFile = sys.stdout):
2707        OutputGenerator.__init__(self, errFile, warnFile, diagFile)
2708        self.INDENT_SPACES = 4
2709        # Commands to ignore
2710        self.blacklist = [
2711            'vkGetInstanceProcAddr',
2712            'vkGetDeviceProcAddr',
2713            'vkEnumerateInstanceLayerProperties',
2714            'vkEnumerateInstanceExtensionsProperties',
2715            'vkEnumerateDeviceLayerProperties',
2716            'vkEnumerateDeviceExtensionsProperties',
2717            'vkCreateDebugReportCallbackEXT',
2718            'vkDebugReportMessageEXT']
2719        # Internal state - accumulators for different inner block text
2720        self.sections = dict([(section, []) for section in self.ALL_SECTIONS])
2721        self.structNames = []                             # List of Vulkan struct typenames
2722        self.stypes = []                                  # Values from the VkStructureType enumeration
2723        self.structTypes = dict()                         # Map of Vulkan struct typename to required VkStructureType
2724        self.commands = []                                # List of CommandData records for all Vulkan commands
2725        self.structMembers = []                           # List of StructMemberData records for all Vulkan structs
2726        self.validatedStructs = set()                     # Set of structs containing members that require validation
2727        # Named tuples to store struct and command data
2728        self.StructType = namedtuple('StructType', ['name', 'value'])
2729        self.CommandParam = namedtuple('CommandParam', ['type', 'name', 'ispointer', 'isstaticarray', 'isoptional', 'iscount', 'len', 'extstructs', 'cdecl'])
2730        self.CommandData = namedtuple('CommandData', ['name', 'params', 'cdecl'])
2731        self.StructMemberData = namedtuple('StructMemberData', ['name', 'members'])
2732    #
2733    def incIndent(self, indent):
2734        inc = ' ' * self.INDENT_SPACES
2735        if indent:
2736            return indent + inc
2737        return inc
2738    #
2739    def decIndent(self, indent):
2740        if indent and (len(indent) > self.INDENT_SPACES):
2741            return indent[:-self.INDENT_SPACES]
2742        return ''
2743    #
2744    def beginFile(self, genOpts):
2745        OutputGenerator.beginFile(self, genOpts)
2746        # C-specific
2747        #
2748        # User-supplied prefix text, if any (list of strings)
2749        if (genOpts.prefixText):
2750            for s in genOpts.prefixText:
2751                write(s, file=self.outFile)
2752        #
2753        # Multiple inclusion protection & C++ wrappers.
2754        if (genOpts.protectFile and self.genOpts.filename):
2755            headerSym = re.sub('\.h', '_H', os.path.basename(self.genOpts.filename)).upper()
2756            write('#ifndef', headerSym, file=self.outFile)
2757            write('#define', headerSym, '1', file=self.outFile)
2758            self.newline()
2759        #
2760        # Headers
2761        write('#include "vulkan/vulkan.h"', file=self.outFile)
2762        write('#include "vk_layer_extension_utils.h"', file=self.outFile)
2763        write('#include "parameter_validation_utils.h"', file=self.outFile)
2764        #
2765        # Macros
2766        self.newline()
2767        write('#ifndef UNUSED_PARAMETER', file=self.outFile)
2768        write('#define UNUSED_PARAMETER(x) (void)(x)', file=self.outFile)
2769        write('#endif // UNUSED_PARAMETER', file=self.outFile)
2770    def endFile(self):
2771        # C-specific
2772        # Finish C++ wrapper and multiple inclusion protection
2773        self.newline()
2774        if (self.genOpts.protectFile and self.genOpts.filename):
2775            self.newline()
2776            write('#endif', file=self.outFile)
2777        # Finish processing in superclass
2778        OutputGenerator.endFile(self)
2779    def beginFeature(self, interface, emit):
2780        # Start processing in superclass
2781        OutputGenerator.beginFeature(self, interface, emit)
2782        # C-specific
2783        # Accumulate includes, defines, types, enums, function pointer typedefs,
2784        # end function prototypes separately for this feature. They're only
2785        # printed in endFeature().
2786        self.sections = dict([(section, []) for section in self.ALL_SECTIONS])
2787        self.structNames = []
2788        self.stypes = []
2789        self.structTypes = dict()
2790        self.commands = []
2791        self.structMembers = []
2792        self.validatedStructs = set()
2793    def endFeature(self):
2794        # C-specific
2795        # Actually write the interface to the output file.
2796        if (self.emit):
2797            self.newline()
2798            # If type declarations are needed by other features based on
2799            # this one, it may be necessary to suppress the ExtraProtect,
2800            # or move it below the 'for section...' loop.
2801            if (self.featureExtraProtect != None):
2802                write('#ifdef', self.featureExtraProtect, file=self.outFile)
2803            # Generate the struct member checking code from the captured data
2804            self.prepareStructMemberData()
2805            self.processStructMemberData()
2806            # Generate the command parameter checking code from the captured data
2807            self.processCmdData()
2808            if (self.sections['command']):
2809                if (self.genOpts.protectProto):
2810                    write(self.genOpts.protectProto,
2811                          self.genOpts.protectProtoStr, file=self.outFile)
2812                write('\n'.join(self.sections['command']), end='', file=self.outFile)
2813            if (self.featureExtraProtect != None):
2814                write('#endif /*', self.featureExtraProtect, '*/', file=self.outFile)
2815            else:
2816                self.newline()
2817        # Finish processing in superclass
2818        OutputGenerator.endFeature(self)
2819    #
2820    # Append a definition to the specified section
2821    def appendSection(self, section, text):
2822        # self.sections[section].append('SECTION: ' + section + '\n')
2823        self.sections[section].append(text)
2824    #
2825    # Type generation
2826    def genType(self, typeinfo, name):
2827        OutputGenerator.genType(self, typeinfo, name)
2828        typeElem = typeinfo.elem
2829        # If the type is a struct type, traverse the imbedded <member> tags
2830        # generating a structure. Otherwise, emit the tag text.
2831        category = typeElem.get('category')
2832        if (category == 'struct' or category == 'union'):
2833            self.structNames.append(name)
2834            self.genStruct(typeinfo, name)
2835    #
2836    # Struct parameter check generation.
2837    # This is a special case of the <type> tag where the contents are
2838    # interpreted as a set of <member> tags instead of freeform C
2839    # C type declarations. The <member> tags are just like <param>
2840    # tags - they are a declaration of a struct or union member.
2841    # Only simple member declarations are supported (no nested
2842    # structs etc.)
2843    def genStruct(self, typeinfo, typeName):
2844        OutputGenerator.genStruct(self, typeinfo, typeName)
2845        members = typeinfo.elem.findall('.//member')
2846        #
2847        # Iterate over members once to get length parameters for arrays
2848        lens = set()
2849        for member in members:
2850            len = self.getLen(member)
2851            if len:
2852                lens.add(len)
2853        #
2854        # Generate member info
2855        membersInfo = []
2856        for member in members:
2857            # Get the member's type and name
2858            info = self.getTypeNameTuple(member)
2859            type = info[0]
2860            name = info[1]
2861            stypeValue = ''
2862            # Process VkStructureType
2863            if type == 'VkStructureType':
2864                # Extract the required struct type value from the comments
2865                # embedded in the original text defining the 'typeinfo' element
2866                rawXml = etree.tostring(typeinfo.elem).decode('ascii')
2867                result = re.search(r'VK_STRUCTURE_TYPE_\w+', rawXml)
2868                if result:
2869                    value = result.group(0)
2870                    # Make sure value is valid
2871                    #if value not in self.stypes:
2872                    #    print('WARNING: {} is not part of the VkStructureType enumeration [{}]'.format(value, typeName))
2873                else:
2874                    value = '<ERROR>'
2875                # Store the required type value
2876                self.structTypes[typeName] = self.StructType(name=name, value=value)
2877            #
2878            # Store pointer/array/string info
2879            # Check for parameter name in lens set
2880            iscount = False
2881            if name in lens:
2882                iscount = True
2883            # The pNext members are not tagged as optional, but are treated as
2884            # optional for parameter NULL checks.  Static array members
2885            # are also treated as optional to skip NULL pointer validation, as
2886            # they won't be NULL.
2887            isstaticarray = self.paramIsStaticArray(member)
2888            isoptional = False
2889            if self.paramIsOptional(member) or (name == 'pNext') or (isstaticarray):
2890                isoptional = True
2891            membersInfo.append(self.CommandParam(type=type, name=name,
2892                                                ispointer=self.paramIsPointer(member),
2893                                                isstaticarray=isstaticarray,
2894                                                isoptional=isoptional,
2895                                                iscount=iscount,
2896                                                len=self.getLen(member),
2897                                                extstructs=member.attrib.get('validextensionstructs') if name == 'pNext' else None,
2898                                                cdecl=self.makeCParamDecl(member, 0)))
2899        self.structMembers.append(self.StructMemberData(name=typeName, members=membersInfo))
2900    #
2901    # Capture group (e.g. C "enum" type) info to be used for
2902    # param check code generation.
2903    # These are concatenated together with other types.
2904    def genGroup(self, groupinfo, groupName):
2905        OutputGenerator.genGroup(self, groupinfo, groupName)
2906        if groupName == 'VkStructureType':
2907            groupElem = groupinfo.elem
2908            for elem in groupElem.findall('enum'):
2909                name = elem.get('name')
2910                self.stypes.append(name)
2911    #
2912    # Capture command parameter info to be used for param
2913    # check code generation.
2914    def genCmd(self, cmdinfo, name):
2915        OutputGenerator.genCmd(self, cmdinfo, name)
2916        if name not in self.blacklist:
2917            params = cmdinfo.elem.findall('param')
2918            # Get list of array lengths
2919            lens = set()
2920            for param in params:
2921                len = self.getLen(param)
2922                if len:
2923                    lens.add(len)
2924            # Get param info
2925            paramsInfo = []
2926            for param in params:
2927                paramInfo = self.getTypeNameTuple(param)
2928                # Check for parameter name in lens set
2929                iscount = False
2930                if paramInfo[1] in lens:
2931                    iscount = True
2932                paramsInfo.append(self.CommandParam(type=paramInfo[0], name=paramInfo[1],
2933                                                    ispointer=self.paramIsPointer(param),
2934                                                    isstaticarray=self.paramIsStaticArray(param),
2935                                                    isoptional=self.paramIsOptional(param),
2936                                                    iscount=iscount,
2937                                                    len=self.getLen(param),
2938                                                    extstructs=None,
2939                                                    cdecl=self.makeCParamDecl(param, 0)))
2940            self.commands.append(self.CommandData(name=name, params=paramsInfo, cdecl=self.makeCDecls(cmdinfo.elem)[0]))
2941    #
2942    # Check if the parameter passed in is a pointer
2943    def paramIsPointer(self, param):
2944        ispointer = 0
2945        paramtype = param.find('type')
2946        if (paramtype.tail is not None) and ('*' in paramtype.tail):
2947            ispointer = paramtype.tail.count('*')
2948        elif paramtype.text[:4] == 'PFN_':
2949            # Treat function pointer typedefs as a pointer to a single value
2950            ispointer = 1
2951        return ispointer
2952    #
2953    # Check if the parameter passed in is a static array
2954    def paramIsStaticArray(self, param):
2955        isstaticarray = 0
2956        paramname = param.find('name')
2957        if (paramname.tail is not None) and ('[' in paramname.tail):
2958            isstaticarray = paramname.tail.count('[')
2959        return isstaticarray
2960    #
2961    # Check if the parameter passed in is optional
2962    # Returns a list of Boolean values for comma separated len attributes (len='false,true')
2963    def paramIsOptional(self, param):
2964        # See if the handle is optional
2965        isoptional = False
2966        # Simple, if it's optional, return true
2967        optString = param.attrib.get('optional')
2968        if optString:
2969            if optString == 'true':
2970                isoptional = True
2971            elif ',' in optString:
2972                opts = []
2973                for opt in optString.split(','):
2974                    val = opt.strip()
2975                    if val == 'true':
2976                        opts.append(True)
2977                    elif val == 'false':
2978                        opts.append(False)
2979                    else:
2980                        print('Unrecognized len attribute value',val)
2981                isoptional = opts
2982        return isoptional
2983    #
2984    # Retrieve the value of the len tag
2985    def getLen(self, param):
2986        result = None
2987        len = param.attrib.get('len')
2988        if len and len != 'null-terminated':
2989            # For string arrays, 'len' can look like 'count,null-terminated',
2990            # indicating that we have a null terminated array of strings.  We
2991            # strip the null-terminated from the 'len' field and only return
2992            # the parameter specifying the string count
2993            if 'null-terminated' in len:
2994                result = len.split(',')[0]
2995            else:
2996                result = len
2997        return result
2998    #
2999    # Retrieve the type and name for a parameter
3000    def getTypeNameTuple(self, param):
3001        type = ''
3002        name = ''
3003        for elem in param:
3004            if elem.tag == 'type':
3005                type = noneStr(elem.text)
3006            elif elem.tag == 'name':
3007                name = noneStr(elem.text)
3008        return (type, name)
3009    #
3010    # Find a named parameter in a parameter list
3011    def getParamByName(self, params, name):
3012        for param in params:
3013            if param.name == name:
3014                return param
3015        return None
3016    #
3017    # Get the length paramater record for the specified parameter name
3018    def getLenParam(self, params, name):
3019        lenParam = None
3020        if name:
3021            if '->' in name:
3022                # The count is obtained by dereferencing a member of a struct parameter
3023                lenParam = self.CommandParam(name=name, iscount=True, ispointer=False, isoptional=False, type=None, len=None, isstaticarray=None, extstructs=None, cdecl=None)
3024            elif 'latexmath' in name:
3025                result = re.search('mathit\{(\w+)\}', name)
3026                lenParam = self.getParamByName(params, result.group(1))
3027            elif '/' in name:
3028                # Len specified as an equation such as dataSize/4
3029                lenParam = self.getParamByName(params, name.split('/')[0])
3030            else:
3031                lenParam = self.getParamByName(params, name)
3032        return lenParam
3033    #
3034    # Convert a vulkan.h command declaration into a parameter_validation.h definition
3035    def getCmdDef(self, cmd):
3036        #
3037        # Strip the trailing ';' and split into individual lines
3038        lines = cmd.cdecl[:-1].split('\n')
3039        # Replace Vulkan prototype
3040        lines[0] = 'static VkBool32 parameter_validation_' + cmd.name + '('
3041        # Replace the first argument with debug_report_data, when the first
3042        # argument is a handle (not vkCreateInstance)
3043        reportData = '    debug_report_data*'.ljust(self.genOpts.alignFuncParam) + 'report_data,'
3044        if cmd.name != 'vkCreateInstance':
3045            lines[1] = reportData
3046        else:
3047            lines.insert(1, reportData)
3048        return '\n'.join(lines)
3049    #
3050    # Generate the code to check for a NULL dereference before calling the
3051    # validation function
3052    def genCheckedLengthCall(self, indent, name, expr):
3053        count = name.count('->')
3054        if count:
3055            checkedExpr = ''
3056            localIndent = indent
3057            elements = name.split('->')
3058            # Open the if expression blocks
3059            for i in range(0, count):
3060                checkedExpr += localIndent + 'if ({} != NULL) {{\n'.format('->'.join(elements[0:i+1]))
3061                localIndent = self.incIndent(localIndent)
3062            # Add the validation expression
3063            checkedExpr += localIndent + expr
3064            # Close the if blocks
3065            for i in range(0, count):
3066                localIndent = self.decIndent(localIndent)
3067                checkedExpr += localIndent + '}\n'
3068            return checkedExpr
3069        # No if statements were required
3070        return indent + expr
3071    #
3072    # Generate the parameter checking code
3073    def genFuncBody(self, indent, name, values, valuePrefix, variablePrefix, structName):
3074        funcBody = ''
3075        unused = []
3076        for value in values:
3077            checkExpr = ''     # Code to check the current parameter
3078            #
3079            # Check for NULL pointers, ignore the inout count parameters that
3080            # will be validated with their associated array
3081            if (value.ispointer or value.isstaticarray) and not value.iscount:
3082                #
3083                # Generate the full name of the value, which will be printed in
3084                # the error message, by adding the variable prefix to the
3085                # value name
3086                valueDisplayName = '(std::string({}) + std::string("{}")).c_str()'.format(variablePrefix, value.name) if variablePrefix else '"{}"'.format(value.name)
3087                #
3088                # Parameters for function argument generation
3089                req = 'VK_TRUE'    # Paramerter can be NULL
3090                cpReq = 'VK_TRUE'  # Count pointer can be NULL
3091                cvReq = 'VK_TRUE'  # Count value can be 0
3092                lenParam = None
3093                #
3094                # Generate required/optional parameter strings for the pointer and count values
3095                if value.isoptional:
3096                    req = 'VK_FALSE'
3097                if value.len:
3098                    # The parameter is an array with an explicit count parameter
3099                    lenParam = self.getLenParam(values, value.len)
3100                    if not lenParam: print(value.len)
3101                    if lenParam.ispointer:
3102                        # Count parameters that are pointers are inout
3103                        if type(lenParam.isoptional) is list:
3104                            if lenParam.isoptional[0]:
3105                                cpReq = 'VK_FALSE'
3106                            if lenParam.isoptional[1]:
3107                                cvReq = 'VK_FALSE'
3108                        else:
3109                            if lenParam.isoptional:
3110                                cpReq = 'VK_FALSE'
3111                    else:
3112                        if lenParam.isoptional:
3113                            cvReq = 'VK_FALSE'
3114                #
3115                # If this is a pointer to a struct with an sType field, verify the type
3116                if value.type in self.structTypes:
3117                    stype = self.structTypes[value.type]
3118                    if lenParam:
3119                        # This is an array
3120                        if lenParam.ispointer:
3121                            # When the length parameter is a pointer, there is an extra Boolean parameter in the function call to indicate if it is required
3122                            checkExpr = 'skipCall |= validate_struct_type_array(report_data, {}, "{ln}", {dn}, "{sv}", {pf}{ln}, {pf}{vn}, {sv}, {}, {}, {});\n'.format(name, cpReq, cvReq, req, ln=lenParam.name, dn=valueDisplayName, vn=value.name, sv=stype.value, pf=valuePrefix)
3123                        else:
3124                            checkExpr = 'skipCall |= validate_struct_type_array(report_data, {}, "{ln}", {dn}, "{sv}", {pf}{ln}, {pf}{vn}, {sv}, {}, {});\n'.format(name, cvReq, req, ln=lenParam.name, dn=valueDisplayName, vn=value.name, sv=stype.value, pf=valuePrefix)
3125                    else:
3126                        checkExpr = 'skipCall |= validate_struct_type(report_data, {}, {}, "{sv}", {}{vn}, {sv}, {});\n'.format(name, valueDisplayName, valuePrefix, req, vn=value.name, sv=stype.value)
3127                elif value.name == 'pNext':
3128                    # We need to ignore VkDeviceCreateInfo and VkInstanceCreateInfo, as the loader manipulates them in a way that is not documented in vk.xml
3129                    if not structName in ['VkDeviceCreateInfo', 'VkInstanceCreateInfo']:
3130                        # Generate an array of acceptable VkStructureType values for pNext
3131                        extStructCount = 0
3132                        extStructVar = 'NULL'
3133                        extStructNames = 'NULL'
3134                        if value.extstructs:
3135                            structs = value.extstructs.split(',')
3136                            checkExpr = 'const VkStructureType allowedStructs[] = {' + ', '.join([self.structTypes[s].value for s in structs]) + '};\n' + indent
3137                            extStructCount = 'ARRAY_SIZE(allowedStructs)'
3138                            extStructVar = 'allowedStructs'
3139                            extStructNames = '"' + ', '.join(structs) + '"'
3140                        checkExpr += 'skipCall |= validate_struct_pnext(report_data, {}, {}, {}, {}{vn}, {}, {});\n'.format(name, valueDisplayName, extStructNames, valuePrefix, extStructCount, extStructVar, vn=value.name)
3141                else:
3142                    if lenParam:
3143                        # This is an array
3144                        if lenParam.ispointer:
3145                            # If count and array parameters are optional, there
3146                            # will be no validation
3147                            if req == 'VK_TRUE' or cpReq == 'VK_TRUE' or cvReq == 'VK_TRUE':
3148                                # When the length parameter is a pointer, there is an extra Boolean parameter in the function call to indicate if it is required
3149                                checkExpr = 'skipCall |= validate_array(report_data, {}, "{ln}", {dn}, {pf}{ln}, {pf}{vn}, {}, {}, {});\n'.format(name, cpReq, cvReq, req, ln=lenParam.name, dn=valueDisplayName, vn=value.name, pf=valuePrefix)
3150                        else:
3151                            # If count and array parameters are optional, there
3152                            # will be no validation
3153                            if req == 'VK_TRUE' or cvReq == 'VK_TRUE':
3154                                funcName = 'validate_array' if value.type != 'char' else 'validate_string_array'
3155                                checkExpr = 'skipCall |= {}(report_data, {}, "{ln}", {dn}, {pf}{ln}, {pf}{vn}, {}, {});\n'.format(funcName, name, cvReq, req, ln=lenParam.name, dn=valueDisplayName, vn=value.name, pf=valuePrefix)
3156                    elif not value.isoptional:
3157                        # Function pointers need a reinterpret_cast to void*
3158                        if value.type[:4] == 'PFN_':
3159                            checkExpr = 'skipCall |= validate_required_pointer(report_data, {}, {}, reinterpret_cast<const void*>({}{vn}));\n'.format(name, valueDisplayName, valuePrefix, vn=value.name)
3160                        else:
3161                            checkExpr = 'skipCall |= validate_required_pointer(report_data, {}, {}, {}{vn});\n'.format(name, valueDisplayName, valuePrefix, vn=value.name)
3162                #
3163                # If this is a pointer to a struct, see if it contains members
3164                # that need to be checked
3165                if value.type in self.validatedStructs:
3166                    if checkExpr:
3167                        checkExpr += '\n' + indent
3168                    #
3169                    # The name prefix used when reporting an error with a struct member (eg. the 'pCreateInfor->' in 'pCreateInfo->sType')
3170                    prefix = '(std::string({}) + std::string("{}->")).c_str()'.format(variablePrefix, value.name) if variablePrefix else '"{}->"'.format(value.name)
3171                    checkExpr += 'skipCall |= parameter_validation_{}(report_data, {}, {}, {}{});\n'.format(value.type, name, prefix, valuePrefix, value.name)
3172            elif value.type in self.validatedStructs:
3173                # The name prefix used when reporting an error with a struct member (eg. the 'pCreateInfor->' in 'pCreateInfo->sType')
3174                prefix = '(std::string({}) + std::string("{}.")).c_str()'.format(variablePrefix, value.name) if variablePrefix else '"{}."'.format(value.name)
3175                checkExpr += 'skipCall |= parameter_validation_{}(report_data, {}, {}, &({}{}));\n'.format(value.type, name, prefix, valuePrefix, value.name)
3176            #
3177            # Append the parameter check to the function body for the current command
3178            if checkExpr:
3179                funcBody += '\n'
3180                if lenParam and ('->' in lenParam.name):
3181                    # Add checks to ensure the validation call does not dereference a NULL pointer to obtain the count
3182                    funcBody += self.genCheckedLengthCall(indent, lenParam.name, checkExpr)
3183                else:
3184                    funcBody += indent + checkExpr
3185            elif not value.iscount:
3186                # The parameter is not checked (counts will be checked with
3187                # their associated array)
3188                unused.append(value.name)
3189        return funcBody, unused
3190    #
3191    # Post-process the collected struct member data to create a list of structs
3192    # with members that need to be validated
3193    def prepareStructMemberData(self):
3194        for struct in self.structMembers:
3195            for member in struct.members:
3196                if not member.iscount:
3197                    lenParam = self.getLenParam(struct.members, member.len)
3198                    # The sType needs to be validated
3199                    # An required array/count needs to be validated
3200                    # A required pointer needs to be validated
3201                    validated = False
3202                    if member.type in self.structTypes:
3203                        validated = True
3204                    elif member.ispointer and lenParam:  # This is an array
3205                        # Make sure len is not optional
3206                        if lenParam.ispointer:
3207                            if not lenParam.isoptional[0] or not lenParam.isoptional[1] or not member.isoptional:
3208                                validated = True
3209                        else:
3210                            if not lenParam.isoptional or not member.isoptional:
3211                                validated = True
3212                    elif member.ispointer and not member.isoptional:
3213                        validated = True
3214                    #
3215                    if validated:
3216                        self.validatedStructs.add(struct.name)
3217            # Second pass to check for struct members that are structs
3218            # requiring validation
3219            for member in struct.members:
3220                if member.type in self.validatedStructs:
3221                    self.validatedStructs.add(struct.name)
3222    #
3223    # Generate the struct member check code from the captured data
3224    def processStructMemberData(self):
3225        indent = self.incIndent(None)
3226        for struct in self.structMembers:
3227            # The string returned by genFuncBody will be nested in an if check
3228            # for a NULL pointer, so needs its indent incremented
3229            funcBody, unused = self.genFuncBody(self.incIndent(indent), 'pFuncName', struct.members, 'pStruct->', 'pVariableName', struct.name)
3230            if funcBody:
3231                cmdDef = 'static VkBool32 parameter_validation_{}(\n'.format(struct.name)
3232                cmdDef += '    debug_report_data*'.ljust(self.genOpts.alignFuncParam) + ' report_data,\n'
3233                cmdDef += '    const char*'.ljust(self.genOpts.alignFuncParam) + ' pFuncName,\n'
3234                cmdDef += '    const char*'.ljust(self.genOpts.alignFuncParam) + ' pVariableName,\n'
3235                cmdDef += '    const {}*'.format(struct.name).ljust(self.genOpts.alignFuncParam) + ' pStruct)\n'
3236                cmdDef += '{\n'
3237                cmdDef += indent + 'VkBool32 skipCall = VK_FALSE;\n'
3238                cmdDef += '\n'
3239                cmdDef += indent + 'if (pStruct != NULL) {'
3240                cmdDef += funcBody
3241                cmdDef += indent +'}\n'
3242                cmdDef += '\n'
3243                cmdDef += indent + 'return skipCall;\n'
3244                cmdDef += '}\n'
3245                self.appendSection('command', cmdDef)
3246    #
3247    # Generate the command param check code from the captured data
3248    def processCmdData(self):
3249        indent = self.incIndent(None)
3250        for command in self.commands:
3251            cmdBody, unused = self.genFuncBody(indent, '"{}"'.format(command.name), command.params, '', None, None)
3252            if cmdBody:
3253                cmdDef = self.getCmdDef(command) + '\n'
3254                cmdDef += '{\n'
3255                # Process unused parameters
3256                # Ignore the first dispatch handle parameter, which is not
3257                # processed by parameter_validation (except for vkCreateInstance, which
3258                # does not have a handle as its first parameter)
3259                startIndex = 1
3260                if command.name == 'vkCreateInstance':
3261                    startIndex = 0
3262                for name in unused[startIndex:]:
3263                    cmdDef += indent + 'UNUSED_PARAMETER({});\n'.format(name)
3264                if len(unused) > 1:
3265                    cmdDef += '\n'
3266                cmdDef += indent + 'VkBool32 skipCall = VK_FALSE;\n'
3267                cmdDef += cmdBody
3268                cmdDef += '\n'
3269                cmdDef += indent + 'return skipCall;\n'
3270                cmdDef += '}\n'
3271                self.appendSection('command', cmdDef)
3272