• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 #!/usr/bin/python3 -i
2 import os,re,sys
3 from collections import namedtuple
4 from lxml import etree
5 
6 def 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
15 def 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.
23 def 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!
35 def 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.
47 def 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"
52 def 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.
57 def 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)
64 def 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.
97 class 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
160 class 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 #
223 class 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
287 class 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
361 class 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 #
440 class 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)
733 class 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)
973 class 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)
1138 class 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)
1273 class 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)
2166 class 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)
2349 class 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)
2699 class 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