blob: b57298620de28a115712467bc164715ff9d91303 [file] [log] [blame]
Mike Stroyana451fa82016-01-07 15:35:37 -07001#!/usr/bin/python3 -i
2import os,re,sys
3
4def write( *args, **kwargs ):
5 file = kwargs.pop('file',sys.stdout)
6 end = kwargs.pop( 'end','\n')
7 file.write( ' '.join([str(arg) for arg in args]) )
8 file.write( end )
9
10# noneStr - returns string argument, or "" if argument is None.
11# Used in converting lxml Elements into text.
12# str - string to convert
13def noneStr(str):
14 if (str):
15 return str
16 else:
17 return ""
18
19# enquote - returns string argument with surrounding quotes,
20# for serialization into Python code.
21def enquote(str):
22 if (str):
23 return "'" + str + "'"
24 else:
25 return None
26
27# Primary sort key for regSortFeatures.
28# Sorts by category of the feature name string:
29# Core API features (those defined with a <feature> tag)
30# ARB/KHR/OES (Khronos extensions)
31# other (EXT/vendor extensions)
32# This will need changing for Vulkan!
33def regSortCategoryKey(feature):
34 if (feature.elem.tag == 'feature'):
35 return 0
36 elif (feature.category == 'ARB' or
37 feature.category == 'KHR' or
38 feature.category == 'OES'):
39 return 1
40 else:
41 return 2
42
43# Secondary sort key for regSortFeatures.
44# Sorts by extension name.
45def regSortNameKey(feature):
46 return feature.name
47
48# Second sort key for regSortFeatures.
49# Sorts by feature version. <extension> elements all have version number "0"
50def regSortFeatureVersionKey(feature):
51 return float(feature.version)
52
53# Tertiary sort key for regSortFeatures.
54# Sorts by extension number. <feature> elements all have extension number 0.
55def regSortExtensionNumberKey(feature):
56 return int(feature.number)
57
58# regSortFeatures - default sort procedure for features.
59# Sorts by primary key of feature category ('feature' or 'extension')
60# then by version number (for features)
61# then by extension number (for extensions)
62def regSortFeatures(featureList):
63 featureList.sort(key = regSortExtensionNumberKey)
64 featureList.sort(key = regSortFeatureVersionKey)
65 featureList.sort(key = regSortCategoryKey)
66
67# GeneratorOptions - base class for options used during header production
68# These options are target language independent, and used by
69# Registry.apiGen() and by base OutputGenerator objects.
70#
71# Members
72# filename - name of file to generate, or None to write to stdout.
73# apiname - string matching <api> 'apiname' attribute, e.g. 'gl'.
74# profile - string specifying API profile , e.g. 'core', or None.
75# versions - regex matching API versions to process interfaces for.
76# Normally '.*' or '[0-9]\.[0-9]' to match all defined versions.
77# emitversions - regex matching API versions to actually emit
78# interfaces for (though all requested versions are considered
79# when deciding which interfaces to generate). For GL 4.3 glext.h,
80# this might be '1\.[2-5]|[2-4]\.[0-9]'.
81# defaultExtensions - If not None, a string which must in its
82# entirety match the pattern in the "supported" attribute of
83# the <extension>. Defaults to None. Usually the same as apiname.
84# addExtensions - regex matching names of additional extensions
85# to include. Defaults to None.
86# removeExtensions - regex matching names of extensions to
87# remove (after defaultExtensions and addExtensions). Defaults
88# to None.
89# sortProcedure - takes a list of FeatureInfo objects and sorts
90# them in place to a preferred order in the generated output.
91# Default is core API versions, ARB/KHR/OES extensions, all
92# other extensions, alphabetically within each group.
93# The regex patterns can be None or empty, in which case they match
94# nothing.
95class GeneratorOptions:
96 """Represents options during header production from an API registry"""
97 def __init__(self,
98 filename = None,
99 apiname = None,
100 profile = None,
101 versions = '.*',
102 emitversions = '.*',
103 defaultExtensions = None,
104 addExtensions = None,
105 removeExtensions = None,
106 sortProcedure = regSortFeatures):
107 self.filename = filename
108 self.apiname = apiname
109 self.profile = profile
110 self.versions = self.emptyRegex(versions)
111 self.emitversions = self.emptyRegex(emitversions)
112 self.defaultExtensions = defaultExtensions
113 self.addExtensions = self.emptyRegex(addExtensions)
114 self.removeExtensions = self.emptyRegex(removeExtensions)
115 self.sortProcedure = sortProcedure
116 #
117 # Substitute a regular expression which matches no version
118 # or extension names for None or the empty string.
119 def emptyRegex(self,pat):
120 if (pat == None or pat == ''):
121 return '_nomatch_^'
122 else:
123 return pat
124
125# CGeneratorOptions - subclass of GeneratorOptions.
126#
127# Adds options used by COutputGenerator objects during C language header
128# generation.
129#
130# Additional members
131# prefixText - list of strings to prefix generated header with
132# (usually a copyright statement + calling convention macros).
133# protectFile - True if multiple inclusion protection should be
134# generated (based on the filename) around the entire header.
135# protectFeature - True if #ifndef..#endif protection should be
136# generated around a feature interface in the header file.
137# genFuncPointers - True if function pointer typedefs should be
138# generated
139# protectProto - If conditional protection should be generated
140# around prototype declarations, set to either '#ifdef'
141# to require opt-in (#ifdef protectProtoStr) or '#ifndef'
142# to require opt-out (#ifndef protectProtoStr). Otherwise
143# set to None.
144# protectProtoStr - #ifdef/#ifndef symbol to use around prototype
145# declarations, if protectProto is set
146# apicall - string to use for the function declaration prefix,
147# such as APICALL on Windows.
148# apientry - string to use for the calling convention macro,
149# in typedefs, such as APIENTRY.
150# apientryp - string to use for the calling convention macro
151# in function pointer typedefs, such as APIENTRYP.
152# indentFuncProto - True if prototype declarations should put each
153# parameter on a separate line
154# indentFuncPointer - True if typedefed function pointers should put each
155# parameter on a separate line
156# alignFuncParam - if nonzero and parameters are being put on a
157# separate line, align parameter names at the specified column
158class CGeneratorOptions(GeneratorOptions):
159 """Represents options during C interface generation for headers"""
160 def __init__(self,
161 filename = None,
162 apiname = None,
163 profile = None,
164 versions = '.*',
165 emitversions = '.*',
166 defaultExtensions = None,
167 addExtensions = None,
168 removeExtensions = None,
169 sortProcedure = regSortFeatures,
170 prefixText = "",
171 genFuncPointers = True,
172 protectFile = True,
173 protectFeature = True,
174 protectProto = None,
175 protectProtoStr = None,
176 apicall = '',
177 apientry = '',
178 apientryp = '',
179 indentFuncProto = True,
180 indentFuncPointer = False,
181 alignFuncParam = 0):
182 GeneratorOptions.__init__(self, filename, apiname, profile,
183 versions, emitversions, defaultExtensions,
184 addExtensions, removeExtensions, sortProcedure)
185 self.prefixText = prefixText
186 self.genFuncPointers = genFuncPointers
187 self.protectFile = protectFile
188 self.protectFeature = protectFeature
189 self.protectProto = protectProto
190 self.protectProtoStr = protectProtoStr
191 self.apicall = apicall
192 self.apientry = apientry
193 self.apientryp = apientryp
194 self.indentFuncProto = indentFuncProto
195 self.indentFuncPointer = indentFuncPointer
196 self.alignFuncParam = alignFuncParam
197
198# DocGeneratorOptions - subclass of GeneratorOptions.
199#
200# Shares many members with CGeneratorOptions, since
201# both are writing C-style declarations:
202#
203# prefixText - list of strings to prefix generated header with
204# (usually a copyright statement + calling convention macros).
205# apicall - string to use for the function declaration prefix,
206# such as APICALL on Windows.
207# apientry - string to use for the calling convention macro,
208# in typedefs, such as APIENTRY.
209# apientryp - string to use for the calling convention macro
210# in function pointer typedefs, such as APIENTRYP.
211# genDirectory - directory into which to generate include files
212# indentFuncProto - True if prototype declarations should put each
213# parameter on a separate line
214# indentFuncPointer - True if typedefed function pointers should put each
215# parameter on a separate line
216# alignFuncParam - if nonzero and parameters are being put on a
217# separate line, align parameter names at the specified column
218#
219# Additional members:
220#
221class DocGeneratorOptions(GeneratorOptions):
222 """Represents options during C interface generation for Asciidoc"""
223 def __init__(self,
224 filename = None,
225 apiname = None,
226 profile = None,
227 versions = '.*',
228 emitversions = '.*',
229 defaultExtensions = None,
230 addExtensions = None,
231 removeExtensions = None,
232 sortProcedure = regSortFeatures,
233 prefixText = "",
234 apicall = '',
235 apientry = '',
236 apientryp = '',
237 genDirectory = 'gen',
238 indentFuncProto = True,
239 indentFuncPointer = False,
240 alignFuncParam = 0,
241 expandEnumerants = True):
242 GeneratorOptions.__init__(self, filename, apiname, profile,
243 versions, emitversions, defaultExtensions,
244 addExtensions, removeExtensions, sortProcedure)
245 self.prefixText = prefixText
246 self.apicall = apicall
247 self.apientry = apientry
248 self.apientryp = apientryp
249 self.genDirectory = genDirectory
250 self.indentFuncProto = indentFuncProto
251 self.indentFuncPointer = indentFuncPointer
252 self.alignFuncParam = alignFuncParam
253 self.expandEnumerants = expandEnumerants
254
255# OutputGenerator - base class for generating API interfaces.
256# Manages basic logic, logging, and output file control
257# Derived classes actually generate formatted output.
258#
259# ---- methods ----
260# OutputGenerator(errFile, warnFile, diagFile)
261# errFile, warnFile, diagFile - file handles to write errors,
262# warnings, diagnostics to. May be None to not write.
263# logMsg(level, *args) - log messages of different categories
264# level - 'error', 'warn', or 'diag'. 'error' will also
265# raise a UserWarning exception
266# *args - print()-style arguments
267# setExtMap(map) - specify a dictionary map from extension names to
268# numbers, used in creating values for extension enumerants.
269# beginFile(genOpts) - start a new interface file
270# genOpts - GeneratorOptions controlling what's generated and how
271# endFile() - finish an interface file, closing it when done
272# beginFeature(interface, emit) - write interface for a feature
273# and tag generated features as having been done.
274# interface - element for the <version> / <extension> to generate
275# emit - actually write to the header only when True
276# endFeature() - finish an interface.
277# genType(typeinfo,name) - generate interface for a type
278# typeinfo - TypeInfo for a type
279# genStruct(typeinfo,name) - generate interface for a C "struct" type.
280# typeinfo - TypeInfo for a type interpreted as a struct
281# genGroup(groupinfo,name) - generate interface for a group of enums (C "enum")
282# groupinfo - GroupInfo for a group
283# genEnum(enuminfo, name) - generate interface for an enum (constant)
284# enuminfo - EnumInfo for an enum
285# name - enum name
286# genCmd(cmdinfo) - generate interface for a command
287# cmdinfo - CmdInfo for a command
288# makeCDecls(cmd) - return C prototype and function pointer typedef for a
289# <command> Element, as a list of two strings
290# cmd - Element for the <command>
291# newline() - print a newline to the output file (utility function)
292#
293class OutputGenerator:
294 """Generate specified API interfaces in a specific style, such as a C header"""
295 def __init__(self,
296 errFile = sys.stderr,
297 warnFile = sys.stderr,
298 diagFile = sys.stdout):
299 self.outFile = None
300 self.errFile = errFile
301 self.warnFile = warnFile
302 self.diagFile = diagFile
303 # Internal state
304 self.featureName = None
305 self.genOpts = None
306 self.registry = None
307 # Used for extension enum value generation
308 self.extBase = 1000000000
309 self.extBlockSize = 1000
310 #
311 # logMsg - write a message of different categories to different
312 # destinations.
313 # level -
314 # 'diag' (diagnostic, voluminous)
315 # 'warn' (warning)
316 # 'error' (fatal error - raises exception after logging)
317 # *args - print()-style arguments to direct to corresponding log
318 def logMsg(self, level, *args):
319 """Log a message at the given level. Can be ignored or log to a file"""
320 if (level == 'error'):
321 strfile = io.StringIO()
322 write('ERROR:', *args, file=strfile)
323 if (self.errFile != None):
324 write(strfile.getvalue(), file=self.errFile)
325 raise UserWarning(strfile.getvalue())
326 elif (level == 'warn'):
327 if (self.warnFile != None):
328 write('WARNING:', *args, file=self.warnFile)
329 elif (level == 'diag'):
330 if (self.diagFile != None):
331 write('DIAG:', *args, file=self.diagFile)
332 else:
333 raise UserWarning(
334 '*** FATAL ERROR in Generator.logMsg: unknown level:' + level)
335 #
336 # enumToValue - parses and converts an <enum> tag into a value.
337 # Returns a list
338 # first element - integer representation of the value, or None
339 # if needsNum is False. The value must be a legal number
340 # if needsNum is True.
341 # second element - string representation of the value
342 # There are several possible representations of values.
343 # A 'value' attribute simply contains the value.
344 # A 'bitpos' attribute defines a value by specifying the bit
345 # position which is set in that value.
346 # A 'offset','extbase','extends' triplet specifies a value
347 # as an offset to a base value defined by the specified
348 # 'extbase' extension name, which is then cast to the
349 # typename specified by 'extends'. This requires probing
350 # the registry database, and imbeds knowledge of the
351 # Vulkan extension enum scheme in this function.
352 def enumToValue(self, elem, needsNum):
353 name = elem.get('name')
354 numVal = None
355 if ('value' in elem.keys()):
356 value = elem.get('value')
357 # print('About to translate value =', value, 'type =', type(value))
358 if (needsNum):
359 numVal = int(value, 0)
360 # If there's a non-integer, numeric 'type' attribute (e.g. 'u' or
361 # 'ull'), append it to the string value.
362 # t = enuminfo.elem.get('type')
363 # if (t != None and t != '' and t != 'i' and t != 's'):
364 # value += enuminfo.type
365 self.logMsg('diag', 'Enum', name, '-> value [', numVal, ',', value, ']')
366 return [numVal, value]
367 if ('bitpos' in elem.keys()):
368 value = elem.get('bitpos')
369 numVal = int(value, 0)
370 numVal = 1 << numVal
371 value = '0x%08x' % numVal
372 self.logMsg('diag', 'Enum', name, '-> bitpos [', numVal, ',', value, ']')
373 return [numVal, value]
374 if ('offset' in elem.keys()):
375 # Obtain values in the mapping from the attributes
376 enumNegative = False
377 offset = int(elem.get('offset'),0)
378 extnumber = int(elem.get('extnumber'),0)
379 extends = elem.get('extends')
380 if ('dir' in elem.keys()):
381 enumNegative = True
382 self.logMsg('diag', 'Enum', name, 'offset =', offset,
383 'extnumber =', extnumber, 'extends =', extends,
384 'enumNegative =', enumNegative)
385 # Now determine the actual enumerant value, as defined
386 # in the "Layers and Extensions" appendix of the spec.
387 numVal = self.extBase + (extnumber - 1) * self.extBlockSize + offset
388 if (enumNegative):
389 numVal = -numVal
390 value = '%d' % numVal
391 # More logic needed!
392 self.logMsg('diag', 'Enum', name, '-> offset [', numVal, ',', value, ']')
393 return [numVal, value]
394 return [None, None]
395 #
396 def beginFile(self, genOpts):
397 self.genOpts = genOpts
398 #
399 # Open specified output file. Not done in constructor since a
400 # Generator can be used without writing to a file.
401 if (self.genOpts.filename != None):
402 self.outFile = open(self.genOpts.filename, 'w')
403 else:
404 self.outFile = sys.stdout
405 def endFile(self):
406 self.errFile and self.errFile.flush()
407 self.warnFile and self.warnFile.flush()
408 self.diagFile and self.diagFile.flush()
409 self.outFile.flush()
410 if (self.outFile != sys.stdout and self.outFile != sys.stderr):
411 self.outFile.close()
412 self.genOpts = None
413 #
414 def beginFeature(self, interface, emit):
415 self.emit = emit
416 self.featureName = interface.get('name')
417 # If there's an additional 'protect' attribute in the feature, save it
418 self.featureExtraProtect = interface.get('protect')
419 def endFeature(self):
420 # Derived classes responsible for emitting feature
421 self.featureName = None
422 self.featureExtraProtect = None
423 # Utility method to validate we're generating something only inside a
424 # <feature> tag
425 def validateFeature(self, featureType, featureName):
426 if (self.featureName == None):
427 raise UserWarning('Attempt to generate', featureType, name,
428 'when not in feature')
429 #
430 # Type generation
431 def genType(self, typeinfo, name):
432 self.validateFeature('type', name)
433 #
434 # Struct (e.g. C "struct" type) generation
435 def genStruct(self, typeinfo, name):
436 self.validateFeature('struct', name)
437 #
438 # Group (e.g. C "enum" type) generation
439 def genGroup(self, groupinfo, name):
440 self.validateFeature('group', name)
441 #
442 # Enumerant (really, constant) generation
443 def genEnum(self, enuminfo, name):
444 self.validateFeature('enum', name)
445 #
446 # Command generation
447 def genCmd(self, cmd, name):
448 self.validateFeature('command', name)
449 #
450 # Utility functions - turn a <proto> <name> into C-language prototype
451 # and typedef declarations for that name.
452 # name - contents of <name> tag
453 # tail - whatever text follows that tag in the Element
454 def makeProtoName(self, name, tail):
455 return self.genOpts.apientry + name + tail
456 def makeTypedefName(self, name, tail):
457 return '(' + self.genOpts.apientryp + 'PFN_' + name + tail + ')'
458 #
459 # makeCParamDecl - return a string which is an indented, formatted
460 # declaration for a <param> or <member> block (e.g. function parameter
461 # or structure/union member).
462 # param - Element (<param> or <member>) to format
463 # aligncol - if non-zero, attempt to align the nested <name> element
464 # at this column
465 def makeCParamDecl(self, param, aligncol):
466 paramdecl = ' ' + noneStr(param.text)
467 for elem in param:
468 text = noneStr(elem.text)
469 tail = noneStr(elem.tail)
470 if (elem.tag == 'name' and aligncol > 0):
471 self.logMsg('diag', 'Aligning parameter', elem.text, 'to column', self.genOpts.alignFuncParam)
472 # Align at specified column, if possible
473 paramdecl = paramdecl.rstrip()
474 oldLen = len(paramdecl)
475 paramdecl = paramdecl.ljust(aligncol)
476 newLen = len(paramdecl)
477 self.logMsg('diag', 'Adjust length of parameter decl from', oldLen, 'to', newLen, ':', paramdecl)
478 paramdecl += text + tail
479 return paramdecl
480 #
481 # getCParamTypeLength - return the length of the type field is an indented, formatted
482 # declaration for a <param> or <member> block (e.g. function parameter
483 # or structure/union member).
484 # param - Element (<param> or <member>) to identify
485 def getCParamTypeLength(self, param):
486 paramdecl = ' ' + noneStr(param.text)
487 for elem in param:
488 text = noneStr(elem.text)
489 tail = noneStr(elem.tail)
490 if (elem.tag == 'name'):
491 # Align at specified column, if possible
492 newLen = len(paramdecl.rstrip())
493 self.logMsg('diag', 'Identifying length of', elem.text, 'as', newLen)
494 paramdecl += text + tail
495 return newLen
496 #
497 # makeCDecls - return C prototype and function pointer typedef for a
498 # command, as a two-element list of strings.
499 # cmd - Element containing a <command> tag
500 def makeCDecls(self, cmd):
501 """Generate C function pointer typedef for <command> Element"""
502 proto = cmd.find('proto')
503 params = cmd.findall('param')
504 # Begin accumulating prototype and typedef strings
505 pdecl = self.genOpts.apicall
506 tdecl = 'typedef '
507 #
508 # Insert the function return type/name.
509 # For prototypes, add APIENTRY macro before the name
510 # For typedefs, add (APIENTRY *<name>) around the name and
511 # use the PFN_cmdnameproc naming convention.
512 # Done by walking the tree for <proto> element by element.
513 # lxml.etree has elem.text followed by (elem[i], elem[i].tail)
514 # for each child element and any following text
515 # Leading text
516 pdecl += noneStr(proto.text)
517 tdecl += noneStr(proto.text)
518 # For each child element, if it's a <name> wrap in appropriate
519 # declaration. Otherwise append its contents and tail contents.
520 for elem in proto:
521 text = noneStr(elem.text)
522 tail = noneStr(elem.tail)
523 if (elem.tag == 'name'):
524 pdecl += self.makeProtoName(text, tail)
525 tdecl += self.makeTypedefName(text, tail)
526 else:
527 pdecl += text + tail
528 tdecl += text + tail
529 # Now add the parameter declaration list, which is identical
530 # for prototypes and typedefs. Concatenate all the text from
531 # a <param> node without the tags. No tree walking required
532 # since all tags are ignored.
533 # Uses: self.indentFuncProto
534 # self.indentFuncPointer
535 # self.alignFuncParam
536 # Might be able to doubly-nest the joins, e.g.
537 # ','.join(('_'.join([l[i] for i in range(0,len(l))])
538 n = len(params)
539 # Indented parameters
540 if n > 0:
541 indentdecl = '(\n'
542 for i in range(0,n):
543 paramdecl = self.makeCParamDecl(params[i], self.genOpts.alignFuncParam)
544 if (i < n - 1):
545 paramdecl += ',\n'
546 else:
547 paramdecl += ');'
548 indentdecl += paramdecl
549 else:
550 indentdecl = '(void);'
551 # Non-indented parameters
552 paramdecl = '('
553 if n > 0:
554 for i in range(0,n):
555 paramdecl += ''.join([t for t in params[i].itertext()])
556 if (i < n - 1):
557 paramdecl += ', '
558 else:
559 paramdecl += 'void'
560 paramdecl += ");";
561 return [ pdecl + indentdecl, tdecl + paramdecl ]
562 #
563 def newline(self):
564 write('', file=self.outFile)
565
566 def setRegistry(self, registry):
567 self.registry = registry
568 #
569
570# COutputGenerator - subclass of OutputGenerator.
571# Generates C-language API interfaces.
572#
573# ---- methods ----
574# COutputGenerator(errFile, warnFile, diagFile) - args as for
575# OutputGenerator. Defines additional internal state.
576# ---- methods overriding base class ----
577# beginFile(genOpts)
578# endFile()
579# beginFeature(interface, emit)
580# endFeature()
581# genType(typeinfo,name)
582# genStruct(typeinfo,name)
583# genGroup(groupinfo,name)
584# genEnum(enuminfo, name)
585# genCmd(cmdinfo)
586class COutputGenerator(OutputGenerator):
587 """Generate specified API interfaces in a specific style, such as a C header"""
588 # This is an ordered list of sections in the header file.
589 TYPE_SECTIONS = ['include', 'define', 'basetype', 'handle', 'enum',
590 'group', 'bitmask', 'funcpointer', 'struct']
591 ALL_SECTIONS = TYPE_SECTIONS + ['commandPointer', 'command']
592 def __init__(self,
593 errFile = sys.stderr,
594 warnFile = sys.stderr,
595 diagFile = sys.stdout):
596 OutputGenerator.__init__(self, errFile, warnFile, diagFile)
597 # Internal state - accumulators for different inner block text
598 self.sections = dict([(section, []) for section in self.ALL_SECTIONS])
599 #
600 def beginFile(self, genOpts):
601 OutputGenerator.beginFile(self, genOpts)
602 # C-specific
603 #
604 # Multiple inclusion protection & C++ wrappers.
605 if (genOpts.protectFile and self.genOpts.filename):
606 headerSym = '__' + re.sub('\.h', '_h_', os.path.basename(self.genOpts.filename))
607 write('#ifndef', headerSym, file=self.outFile)
608 write('#define', headerSym, '1', file=self.outFile)
609 self.newline()
610 write('#ifdef __cplusplus', file=self.outFile)
611 write('extern "C" {', file=self.outFile)
612 write('#endif', file=self.outFile)
613 self.newline()
614 #
615 # User-supplied prefix text, if any (list of strings)
616 if (genOpts.prefixText):
617 for s in genOpts.prefixText:
618 write(s, file=self.outFile)
619 #
620 # Some boilerplate describing what was generated - this
621 # will probably be removed later since the extensions
622 # pattern may be very long.
623 # write('/* Generated C header for:', file=self.outFile)
624 # write(' * API:', genOpts.apiname, file=self.outFile)
625 # if (genOpts.profile):
626 # write(' * Profile:', genOpts.profile, file=self.outFile)
627 # write(' * Versions considered:', genOpts.versions, file=self.outFile)
628 # write(' * Versions emitted:', genOpts.emitversions, file=self.outFile)
629 # write(' * Default extensions included:', genOpts.defaultExtensions, file=self.outFile)
630 # write(' * Additional extensions included:', genOpts.addExtensions, file=self.outFile)
631 # write(' * Extensions removed:', genOpts.removeExtensions, file=self.outFile)
632 # write(' */', file=self.outFile)
633 def endFile(self):
634 # C-specific
635 # Finish C++ wrapper and multiple inclusion protection
636 self.newline()
637 write('#ifdef __cplusplus', file=self.outFile)
638 write('}', file=self.outFile)
639 write('#endif', file=self.outFile)
640 if (self.genOpts.protectFile and self.genOpts.filename):
641 self.newline()
642 write('#endif', file=self.outFile)
643 # Finish processing in superclass
644 OutputGenerator.endFile(self)
645 def beginFeature(self, interface, emit):
646 # Start processing in superclass
647 OutputGenerator.beginFeature(self, interface, emit)
648 # C-specific
649 # Accumulate includes, defines, types, enums, function pointer typedefs,
650 # end function prototypes separately for this feature. They're only
651 # printed in endFeature().
652 self.sections = dict([(section, []) for section in self.ALL_SECTIONS])
653 def endFeature(self):
654 # C-specific
655 # Actually write the interface to the output file.
656 if (self.emit):
657 self.newline()
658 if (self.genOpts.protectFeature):
659 write('#ifndef', self.featureName, file=self.outFile)
660 # If type declarations are needed by other features based on
661 # this one, it may be necessary to suppress the ExtraProtect,
662 # or move it below the 'for section...' loop.
663 if (self.featureExtraProtect != None):
664 write('#ifdef', self.featureExtraProtect, file=self.outFile)
665 write('#define', self.featureName, '1', file=self.outFile)
666 for section in self.TYPE_SECTIONS:
667 contents = self.sections[section]
668 if contents:
669 write('\n'.join(contents), file=self.outFile)
670 self.newline()
671 if (self.genOpts.genFuncPointers and self.sections['commandPointer']):
672 write('\n'.join(self.sections['commandPointer']), file=self.outFile)
673 self.newline()
674 if (self.sections['command']):
675 if (self.genOpts.protectProto):
676 write(self.genOpts.protectProto,
677 self.genOpts.protectProtoStr, file=self.outFile)
678 write('\n'.join(self.sections['command']), end='', file=self.outFile)
679 if (self.genOpts.protectProto):
680 write('#endif', file=self.outFile)
681 else:
682 self.newline()
683 if (self.featureExtraProtect != None):
684 write('#endif /*', self.featureExtraProtect, '*/', file=self.outFile)
685 if (self.genOpts.protectFeature):
686 write('#endif /*', self.featureName, '*/', file=self.outFile)
687 # Finish processing in superclass
688 OutputGenerator.endFeature(self)
689 #
690 # Append a definition to the specified section
691 def appendSection(self, section, text):
692 # self.sections[section].append('SECTION: ' + section + '\n')
693 self.sections[section].append(text)
694 #
695 # Type generation
696 def genType(self, typeinfo, name):
697 OutputGenerator.genType(self, typeinfo, name)
698 typeElem = typeinfo.elem
699 # If the type is a struct type, traverse the imbedded <member> tags
700 # generating a structure. Otherwise, emit the tag text.
701 category = typeElem.get('category')
702 if (category == 'struct' or category == 'union'):
703 self.genStruct(typeinfo, name)
704 else:
705 # Replace <apientry /> tags with an APIENTRY-style string
706 # (from self.genOpts). Copy other text through unchanged.
707 # If the resulting text is an empty string, don't emit it.
708 s = noneStr(typeElem.text)
709 for elem in typeElem:
710 if (elem.tag == 'apientry'):
711 s += self.genOpts.apientry + noneStr(elem.tail)
712 else:
713 s += noneStr(elem.text) + noneStr(elem.tail)
714 if s:
715 # Add extra newline after multi-line entries.
716 if '\n' in s:
717 s += '\n'
718 self.appendSection(category, s)
719 #
720 # Struct (e.g. C "struct" type) generation.
721 # This is a special case of the <type> tag where the contents are
722 # interpreted as a set of <member> tags instead of freeform C
723 # C type declarations. The <member> tags are just like <param>
724 # tags - they are a declaration of a struct or union member.
725 # Only simple member declarations are supported (no nested
726 # structs etc.)
727 def genStruct(self, typeinfo, typeName):
728 OutputGenerator.genStruct(self, typeinfo, typeName)
729 body = 'typedef ' + typeinfo.elem.get('category') + ' ' + typeName + ' {\n'
730 # paramdecl = self.makeCParamDecl(typeinfo.elem, self.genOpts.alignFuncParam)
731 targetLen = 0;
732 for member in typeinfo.elem.findall('.//member'):
733 targetLen = max(targetLen, self.getCParamTypeLength(member))
734 for member in typeinfo.elem.findall('.//member'):
735 body += self.makeCParamDecl(member, targetLen + 4)
736 body += ';\n'
737 body += '} ' + typeName + ';\n'
738 self.appendSection('struct', body)
739 #
740 # Group (e.g. C "enum" type) generation.
741 # These are concatenated together with other types.
742 def genGroup(self, groupinfo, groupName):
743 OutputGenerator.genGroup(self, groupinfo, groupName)
744 groupElem = groupinfo.elem
745 # See if this group needs min/max/num/padding at end
746 expand = 'expand' in groupElem.keys()
747 if (expand):
748 expandPrefix = groupElem.get('expand')
749 # Prefix
750 body = "\ntypedef enum " + groupName + " {\n"
751
752 # Loop over the nested 'enum' tags. Keep track of the minimum and
753 # maximum numeric values, if they can be determined; but only for
754 # core API enumerants, not extension enumerants. This is inferred
755 # by looking for 'extends' attributes.
756 minName = None
757 for elem in groupElem.findall('enum'):
758 # Convert the value to an integer and use that to track min/max.
759 # Values of form -(number) are accepted but nothing more complex.
760 # Should catch exceptions here for more complex constructs. Not yet.
761 (numVal,strVal) = self.enumToValue(elem, True)
762 name = elem.get('name')
763 body += " " + name + " = " + strVal + ",\n"
764 if (expand and elem.get('extends') is None):
765 if (minName == None):
766 minName = maxName = name
767 minValue = maxValue = numVal
768 elif (numVal < minValue):
769 minName = name
770 minValue = numVal
771 elif (numVal > maxValue):
772 maxName = name
773 maxValue = numVal
774 # Generate min/max value tokens and a range-padding enum. Need some
775 # additional padding to generate correct names...
776 if (expand):
777 body += " " + expandPrefix + "_BEGIN_RANGE = " + minName + ",\n"
778 body += " " + expandPrefix + "_END_RANGE = " + maxName + ",\n"
779 body += " " + expandPrefix + "_RANGE_SIZE = (" + maxName + " - " + minName + " + 1),\n"
780 body += " " + expandPrefix + "_MAX_ENUM = 0x7FFFFFFF\n"
781 # Postfix
782 body += "} " + groupName + ";"
783 if groupElem.get('type') == 'bitmask':
784 section = 'bitmask'
785 else:
786 section = 'group'
787 self.appendSection(section, body)
788 # Enumerant generation
789 # <enum> tags may specify their values in several ways, but are usually
790 # just integers.
791 def genEnum(self, enuminfo, name):
792 OutputGenerator.genEnum(self, enuminfo, name)
793 (numVal,strVal) = self.enumToValue(enuminfo.elem, False)
794 body = '#define ' + name.ljust(33) + ' ' + strVal
795 self.appendSection('enum', body)
796 #
797 # Command generation
798 def genCmd(self, cmdinfo, name):
799 OutputGenerator.genCmd(self, cmdinfo, name)
800 #
801 decls = self.makeCDecls(cmdinfo.elem)
802 self.appendSection('command', decls[0] + '\n')
803 if (self.genOpts.genFuncPointers):
804 self.appendSection('commandPointer', decls[1])
805
806# DocOutputGenerator - subclass of OutputGenerator.
807# Generates AsciiDoc includes with C-language API interfaces, for reference
808# pages and the Vulkan specification. Similar to COutputGenerator, but
809# each interface is written into a different file as determined by the
810# options, only actual C types are emitted, and none of the boilerplate
811# preprocessor code is emitted.
812#
813# ---- methods ----
814# DocOutputGenerator(errFile, warnFile, diagFile) - args as for
815# OutputGenerator. Defines additional internal state.
816# ---- methods overriding base class ----
817# beginFile(genOpts)
818# endFile()
819# beginFeature(interface, emit)
820# endFeature()
821# genType(typeinfo,name)
822# genStruct(typeinfo,name)
823# genGroup(groupinfo,name)
824# genEnum(enuminfo, name)
825# genCmd(cmdinfo)
826class DocOutputGenerator(OutputGenerator):
827 """Generate specified API interfaces in a specific style, such as a C header"""
828 def __init__(self,
829 errFile = sys.stderr,
830 warnFile = sys.stderr,
831 diagFile = sys.stdout):
832 OutputGenerator.__init__(self, errFile, warnFile, diagFile)
833 #
834 def beginFile(self, genOpts):
835 OutputGenerator.beginFile(self, genOpts)
836 def endFile(self):
837 OutputGenerator.endFile(self)
838 def beginFeature(self, interface, emit):
839 # Start processing in superclass
840 OutputGenerator.beginFeature(self, interface, emit)
841 def endFeature(self):
842 # Finish processing in superclass
843 OutputGenerator.endFeature(self)
844 #
845 # Generate an include file
846 #
847 # directory - subdirectory to put file in
848 # basename - base name of the file
849 # contents - contents of the file (Asciidoc boilerplate aside)
850 def writeInclude(self, directory, basename, contents):
851 # Create file
852 filename = self.genOpts.genDirectory + '/' + directory + '/' + basename + '.txt'
853 self.logMsg('diag', '# Generating include file:', filename)
854 fp = open(filename, 'w')
855 # Asciidoc anchor
856 write('[[{0},{0}]]'.format(basename), file=fp)
857 write('["source","{basebackend@docbook:c++:cpp}",title=""]', file=fp)
858 write('------------------------------------------------------------------------------', file=fp)
859 write(contents, file=fp)
860 write('------------------------------------------------------------------------------', file=fp)
861 fp.close()
862 #
863 # Type generation
864 def genType(self, typeinfo, name):
865 OutputGenerator.genType(self, typeinfo, name)
866 typeElem = typeinfo.elem
867 # If the type is a struct type, traverse the imbedded <member> tags
868 # generating a structure. Otherwise, emit the tag text.
869 category = typeElem.get('category')
870 if (category == 'struct' or category == 'union'):
871 self.genStruct(typeinfo, name)
872 else:
873 # Replace <apientry /> tags with an APIENTRY-style string
874 # (from self.genOpts). Copy other text through unchanged.
875 # If the resulting text is an empty string, don't emit it.
876 s = noneStr(typeElem.text)
877 for elem in typeElem:
878 if (elem.tag == 'apientry'):
879 s += self.genOpts.apientry + noneStr(elem.tail)
880 else:
881 s += noneStr(elem.text) + noneStr(elem.tail)
882 if (len(s) > 0):
883 if (category == 'bitmask'):
884 self.writeInclude('flags', name, s + '\n')
885 elif (category == 'enum'):
886 self.writeInclude('enums', name, s + '\n')
887 elif (category == 'funcpointer'):
888 self.writeInclude('funcpointers', name, s+ '\n')
889 else:
890 self.logMsg('diag', '# NOT writing include file for type:',
891 name, 'category: ', category)
892 else:
893 self.logMsg('diag', '# NOT writing empty include file for type', name)
894 #
895 # Struct (e.g. C "struct" type) generation.
896 # This is a special case of the <type> tag where the contents are
897 # interpreted as a set of <member> tags instead of freeform C
898 # C type declarations. The <member> tags are just like <param>
899 # tags - they are a declaration of a struct or union member.
900 # Only simple member declarations are supported (no nested
901 # structs etc.)
902 def genStruct(self, typeinfo, typeName):
903 OutputGenerator.genStruct(self, typeinfo, typeName)
904 s = 'typedef ' + typeinfo.elem.get('category') + ' ' + typeName + ' {\n'
905 # paramdecl = self.makeCParamDecl(typeinfo.elem, self.genOpts.alignFuncParam)
906 targetLen = 0;
907 for member in typeinfo.elem.findall('.//member'):
908 targetLen = max(targetLen, self.getCParamTypeLength(member))
909 for member in typeinfo.elem.findall('.//member'):
910 s += self.makeCParamDecl(member, targetLen + 4)
911 s += ';\n'
912 s += '} ' + typeName + ';'
913 self.writeInclude('structs', typeName, s)
914 #
915 # Group (e.g. C "enum" type) generation.
916 # These are concatenated together with other types.
917 def genGroup(self, groupinfo, groupName):
918 OutputGenerator.genGroup(self, groupinfo, groupName)
919 groupElem = groupinfo.elem
920 # See if this group needs min/max/num/padding at end
921 expand = self.genOpts.expandEnumerants and ('expand' in groupElem.keys())
922 if (expand):
923 expandPrefix = groupElem.get('expand')
924 # Prefix
925 s = "typedef enum " + groupName + " {\n"
926
927 # Loop over the nested 'enum' tags. Keep track of the minimum and
928 # maximum numeric values, if they can be determined.
929 minName = None
930 for elem in groupElem.findall('enum'):
931 # Convert the value to an integer and use that to track min/max.
932 # Values of form -(number) are accepted but nothing more complex.
933 # Should catch exceptions here for more complex constructs. Not yet.
934 (numVal,strVal) = self.enumToValue(elem, True)
935 name = elem.get('name')
936 s += " " + name + " = " + strVal + ",\n"
937 if (expand and elem.get('extends') is None):
938 if (minName == None):
939 minName = maxName = name
940 minValue = maxValue = numVal
941 elif (numVal < minValue):
942 minName = name
943 minValue = numVal
944 elif (numVal > maxValue):
945 maxName = name
946 maxValue = numVal
947 # Generate min/max value tokens and a range-padding enum. Need some
948 # additional padding to generate correct names...
949 if (expand):
950 s += "\n"
951 s += " " + expandPrefix + "_BEGIN_RANGE = " + minName + ",\n"
952 s += " " + expandPrefix + "_END_RANGE = " + maxName + ",\n"
953 s += " " + expandPrefix + "_NUM = (" + maxName + " - " + minName + " + 1),\n"
954 s += " " + expandPrefix + "_MAX_ENUM = 0x7FFFFFFF\n"
955 # Postfix
956 s += "} " + groupName + ";"
957 self.writeInclude('enums', groupName, s)
958 # Enumerant generation
959 # <enum> tags may specify their values in several ways, but are usually
960 # just integers.
961 def genEnum(self, enuminfo, name):
962 OutputGenerator.genEnum(self, enuminfo, name)
963 (numVal,strVal) = self.enumToValue(enuminfo.elem, False)
964 s = '#define ' + name.ljust(33) + ' ' + strVal
965 self.logMsg('diag', '# NOT writing compile-time constant', name)
966 # self.writeInclude('consts', name, s)
967 #
968 # Command generation
969 def genCmd(self, cmdinfo, name):
970 OutputGenerator.genCmd(self, cmdinfo, name)
971 #
972 decls = self.makeCDecls(cmdinfo.elem)
973 self.writeInclude('protos', name, decls[0])
974
975# PyOutputGenerator - subclass of OutputGenerator.
976# Generates Python data structures describing API names.
977# Similar to DocOutputGenerator, but writes a single
978# file.
979#
980# ---- methods ----
981# PyOutputGenerator(errFile, warnFile, diagFile) - args as for
982# OutputGenerator. Defines additional internal state.
983# ---- methods overriding base class ----
984# beginFile(genOpts)
985# endFile()
986# genType(typeinfo,name)
987# genStruct(typeinfo,name)
988# genGroup(groupinfo,name)
989# genEnum(enuminfo, name)
990# genCmd(cmdinfo)
991class PyOutputGenerator(OutputGenerator):
992 """Generate specified API interfaces in a specific style, such as a C header"""
993 def __init__(self,
994 errFile = sys.stderr,
995 warnFile = sys.stderr,
996 diagFile = sys.stdout):
997 OutputGenerator.__init__(self, errFile, warnFile, diagFile)
998 #
999 def beginFile(self, genOpts):
1000 OutputGenerator.beginFile(self, genOpts)
1001 for dict in [ 'flags', 'enums', 'structs', 'consts', 'enums',
1002 'consts', 'protos', 'funcpointers' ]:
1003 write(dict, '= {}', file=self.outFile)
1004 def endFile(self):
1005 OutputGenerator.endFile(self)
1006 #
1007 # Add a name from the interface
1008 #
1009 # dict - type of name (see beginFile above)
1010 # name - name to add
1011 # value - A serializable Python value for the name
1012 def addName(self, dict, name, value=None):
1013 write(dict + "['" + name + "'] = ", value, file=self.outFile)
1014 #
1015 # Type generation
1016 # For 'struct' or 'union' types, defer to genStruct() to
1017 # add to the dictionary.
1018 # For 'bitmask' types, add the type name to the 'flags' dictionary,
1019 # with the value being the corresponding 'enums' name defining
1020 # the acceptable flag bits.
1021 # For 'enum' types, add the type name to the 'enums' dictionary,
1022 # with the value being '@STOPHERE@' (because this case seems
1023 # never to happen).
1024 # For 'funcpointer' types, add the type name to the 'funcpointers'
1025 # dictionary.
1026 # For 'handle' and 'define' types, add the handle or #define name
1027 # to the 'struct' dictionary, because that's how the spec sources
1028 # tag these types even though they aren't structs.
1029 def genType(self, typeinfo, name):
1030 OutputGenerator.genType(self, typeinfo, name)
1031 typeElem = typeinfo.elem
1032 # If the type is a struct type, traverse the imbedded <member> tags
1033 # generating a structure. Otherwise, emit the tag text.
1034 category = typeElem.get('category')
1035 if (category == 'struct' or category == 'union'):
1036 self.genStruct(typeinfo, name)
1037 else:
1038 # Extract the type name
1039 # (from self.genOpts). Copy other text through unchanged.
1040 # If the resulting text is an empty string, don't emit it.
1041 count = len(noneStr(typeElem.text))
1042 for elem in typeElem:
1043 count += len(noneStr(elem.text)) + len(noneStr(elem.tail))
1044 if (count > 0):
1045 if (category == 'bitmask'):
1046 requiredEnum = typeElem.get('requires')
1047 self.addName('flags', name, enquote(requiredEnum))
1048 elif (category == 'enum'):
1049 # This case never seems to come up!
1050 # @enums C 'enum' name Dictionary of enumerant names
1051 self.addName('enums', name, enquote('@STOPHERE@'))
1052 elif (category == 'funcpointer'):
1053 self.addName('funcpointers', name, None)
1054 elif (category == 'handle' or category == 'define'):
1055 self.addName('structs', name, None)
1056 else:
1057 write('# Unprocessed type:', name, 'category:', category, file=self.outFile)
1058 else:
1059 write('# Unprocessed type:', name, file=self.outFile)
1060 #
1061 # Struct (e.g. C "struct" type) generation.
1062 #
1063 # Add the struct name to the 'structs' dictionary, with the
1064 # value being an ordered list of the struct member names.
1065 def genStruct(self, typeinfo, typeName):
1066 OutputGenerator.genStruct(self, typeinfo, typeName)
1067
1068 members = [member.text for member in typeinfo.elem.findall('.//member/name')]
1069 self.addName('structs', typeName, members)
1070 #
1071 # Group (e.g. C "enum" type) generation.
1072 # These are concatenated together with other types.
1073 #
1074 # Add the enum type name to the 'enums' dictionary, with
1075 # the value being an ordered list of the enumerant names.
1076 # Add each enumerant name to the 'consts' dictionary, with
1077 # the value being the enum type the enumerant is part of.
1078 def genGroup(self, groupinfo, groupName):
1079 OutputGenerator.genGroup(self, groupinfo, groupName)
1080 groupElem = groupinfo.elem
1081
1082 # @enums C 'enum' name Dictionary of enumerant names
1083 # @consts C enumerant/const name Name of corresponding 'enums' key
1084
1085 # Loop over the nested 'enum' tags. Keep track of the minimum and
1086 # maximum numeric values, if they can be determined.
1087 enumerants = [elem.get('name') for elem in groupElem.findall('enum')]
1088 for name in enumerants:
1089 self.addName('consts', name, enquote(groupName))
1090 self.addName('enums', groupName, enumerants)
1091 # Enumerant generation (compile-time constants)
1092 #
1093 # Add the constant name to the 'consts' dictionary, with the
1094 # value being None to indicate that the constant isn't
1095 # an enumeration value.
1096 def genEnum(self, enuminfo, name):
1097 OutputGenerator.genEnum(self, enuminfo, name)
1098
1099 # @consts C enumerant/const name Name of corresponding 'enums' key
1100
1101 self.addName('consts', name, None)
1102 #
1103 # Command generation
1104 #
1105 # Add the command name to the 'protos' dictionary, with the
1106 # value being an ordered list of the parameter names.
1107 def genCmd(self, cmdinfo, name):
1108 OutputGenerator.genCmd(self, cmdinfo, name)
1109
1110 params = [param.text for param in cmdinfo.elem.findall('param/name')]
1111 self.addName('protos', name, params)
1112
1113# ValidityOutputGenerator - subclass of OutputGenerator.
1114# Generates AsciiDoc includes of valid usage information, for reference
1115# pages and the Vulkan specification. Similar to DocOutputGenerator.
1116#
1117# ---- methods ----
1118# ValidityOutputGenerator(errFile, warnFile, diagFile) - args as for
1119# OutputGenerator. Defines additional internal state.
1120# ---- methods overriding base class ----
1121# beginFile(genOpts)
1122# endFile()
1123# beginFeature(interface, emit)
1124# endFeature()
1125# genCmd(cmdinfo)
1126class ValidityOutputGenerator(OutputGenerator):
1127 """Generate specified API interfaces in a specific style, such as a C header"""
1128 def __init__(self,
1129 errFile = sys.stderr,
1130 warnFile = sys.stderr,
1131 diagFile = sys.stdout):
1132 OutputGenerator.__init__(self, errFile, warnFile, diagFile)
1133
1134 def beginFile(self, genOpts):
1135 OutputGenerator.beginFile(self, genOpts)
1136 def endFile(self):
1137 OutputGenerator.endFile(self)
1138 def beginFeature(self, interface, emit):
1139 # Start processing in superclass
1140 OutputGenerator.beginFeature(self, interface, emit)
1141 def endFeature(self):
1142 # Finish processing in superclass
1143 OutputGenerator.endFeature(self)
1144
1145 def makeParameterName(self, name):
1146 return 'pname:' + name
1147
1148 def makeStructName(self, name):
1149 return 'sname:' + name
1150
1151 def makeBaseTypeName(self, name):
1152 return 'basetype:' + name
1153
1154 def makeEnumerationName(self, name):
1155 return 'elink:' + name
1156
1157 def makeEnumerantName(self, name):
1158 return 'ename:' + name
1159
1160 def makeFLink(self, name):
1161 return 'flink:' + name
1162
1163 #
1164 # Generate an include file
1165 #
1166 # directory - subdirectory to put file in
1167 # basename - base name of the file
1168 # contents - contents of the file (Asciidoc boilerplate aside)
1169 def writeInclude(self, directory, basename, validity, threadsafety, commandpropertiesentry, successcodes, errorcodes):
1170 # Create file
1171 filename = self.genOpts.genDirectory + '/' + directory + '/' + basename + '.txt'
1172 self.logMsg('diag', '# Generating include file:', filename)
1173 fp = open(filename, 'w')
1174 # Asciidoc anchor
1175
1176 # Valid Usage
1177 if validity is not None:
1178 write('.Valid Usage', file=fp)
1179 write('*' * 80, file=fp)
1180 write(validity, file=fp, end='')
1181 write('*' * 80, file=fp)
1182 write('', file=fp)
1183
1184 # Host Synchronization
1185 if threadsafety is not None:
1186 write('.Host Synchronization', file=fp)
1187 write('*' * 80, file=fp)
1188 write(threadsafety, file=fp, end='')
1189 write('*' * 80, file=fp)
1190 write('', file=fp)
1191
1192 # Command Properties - contained within a block, to avoid table numbering
1193 if commandpropertiesentry is not None:
1194 write('.Command Properties', file=fp)
1195 write('*' * 80, file=fp)
1196 write('[options="header", width="100%"]', file=fp)
1197 write('|=====================', file=fp)
1198 write('|Command Buffer Levels|Render Pass Scope|Supported Queue Types', file=fp)
1199 write(commandpropertiesentry, file=fp)
1200 write('|=====================', file=fp)
1201 write('*' * 80, file=fp)
1202 write('', file=fp)
1203
1204 # Success Codes - contained within a block, to avoid table numbering
1205 if successcodes is not None or errorcodes is not None:
1206 write('.Return Codes', file=fp)
1207 write('*' * 80, file=fp)
1208 if successcodes is not None:
1209 write('<<fundamentals-successcodes,Success>>::', file=fp)
1210 write(successcodes, file=fp)
1211 if errorcodes is not None:
1212 write('<<fundamentals-errorcodes,Failure>>::', file=fp)
1213 write(errorcodes, file=fp)
1214 write('*' * 80, file=fp)
1215 write('', file=fp)
1216
1217 fp.close()
1218
1219 #
1220 # Check if the parameter passed in is a pointer
1221 def paramIsPointer(self, param):
1222 ispointer = False
1223 paramtype = param.find('type')
1224 if paramtype.tail is not None and '*' in paramtype.tail:
1225 ispointer = True
1226
1227 return ispointer
1228
1229 #
1230 # Check if the parameter passed in is a static array
1231 def paramIsStaticArray(self, param):
1232 if param.find('name').tail is not None:
1233 if param.find('name').tail[0] == '[':
1234 return True
1235
1236 #
1237 # Get the length of a parameter that's been identified as a static array
1238 def staticArrayLength(self, param):
1239 paramname = param.find('name')
1240 paramenumsize = param.find('enum')
1241
1242 if paramenumsize is not None:
1243 return paramenumsize.text
1244 else:
1245 return paramname.tail[1:-1]
1246
1247 #
1248 # Check if the parameter passed in is a pointer to an array
1249 def paramIsArray(self, param):
1250 return param.attrib.get('len') is not None
1251
1252 #
1253 # Get the parent of a handle object
1254 def getHandleParent(self, typename):
1255 types = self.registry.findall("types/type")
1256 for elem in types:
1257 if (elem.find("name") is not None and elem.find('name').text == typename) or elem.attrib.get('name') == typename:
1258 return elem.attrib.get('parent')
1259
1260 #
1261 # Check if a parent object is dispatchable or not
1262 def isHandleTypeDispatchable(self, handlename):
1263 handle = self.registry.find("types/type/[name='" + handlename + "'][@category='handle']")
1264 if handle is not None and handle.find('type').text == 'VK_DEFINE_HANDLE':
1265 return True
1266 else:
1267 return False
1268
1269 def isHandleOptional(self, param, params):
1270
1271 # See if the handle is optional
1272 isOptional = False
1273
1274 # Simple, if it's optional, return true
1275 if param.attrib.get('optional') is not None:
1276 return True
1277
1278 # If no validity is being generated, it usually means that validity is complex and not absolute, so let's say yes.
1279 if param.attrib.get('noautovalidity') is not None:
1280 return True
1281
1282 # If the parameter is an array and we haven't already returned, find out if any of the len parameters are optional
1283 if self.paramIsArray(param):
1284 lengths = param.attrib.get('len').split(',')
1285 for length in lengths:
1286 if (length) != 'null-terminated' and (length) != '1':
1287 for otherparam in params:
1288 if otherparam.find('name').text == length:
1289 if otherparam.attrib.get('optional') is not None:
1290 return True
1291
1292 return False
1293 #
1294 # Get the category of a type
1295 def getTypeCategory(self, typename):
1296 types = self.registry.findall("types/type")
1297 for elem in types:
1298 if (elem.find("name") is not None and elem.find('name').text == typename) or elem.attrib.get('name') == typename:
1299 return elem.attrib.get('category')
1300
1301 #
1302 # Make a chunk of text for the end of a parameter if it is an array
1303 def makeAsciiDocPreChunk(self, param, params):
1304 paramname = param.find('name')
1305 paramtype = param.find('type')
1306
1307 # General pre-amble. Check optionality and add stuff.
1308 asciidoc = '* '
1309
1310 if self.paramIsStaticArray(param):
1311 asciidoc += 'Any given element of '
1312
1313 elif self.paramIsArray(param):
1314 lengths = param.attrib.get('len').split(',')
1315
1316 # 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
1317 optionallengths = []
1318 for length in lengths:
1319 if (length) != 'null-terminated' and (length) != '1':
1320 for otherparam in params:
1321 if otherparam.find('name').text == length:
1322 if otherparam.attrib.get('optional') is not None:
1323 if self.paramIsPointer(otherparam):
1324 optionallengths.append('the value referenced by ' + self.makeParameterName(length))
1325 else:
1326 optionallengths.append(self.makeParameterName(length))
1327
1328 # Document that these arrays may be ignored if any of the length values are 0
1329 if len(optionallengths) != 0 or param.attrib.get('optional') is not None:
1330 asciidoc += 'If '
1331
1332
1333 if len(optionallengths) != 0:
1334 if len(optionallengths) == 1:
1335
1336 asciidoc += optionallengths[0]
1337 asciidoc += ' is '
1338
1339 else:
1340 asciidoc += ' or '.join(optionallengths)
1341 asciidoc += ' are '
1342
1343 asciidoc += 'not `0`, '
1344
1345 if len(optionallengths) != 0 and param.attrib.get('optional') is not None:
1346 asciidoc += 'and '
1347
1348 if param.attrib.get('optional') is not None:
1349 asciidoc += self.makeParameterName(paramname.text)
1350 asciidoc += ' is not `NULL`, '
1351
1352 elif param.attrib.get('optional') is not None:
1353 # Don't generate this stub for bitflags
1354 if self.getTypeCategory(paramtype.text) != 'bitmask':
1355 if param.attrib.get('optional').split(',')[0] == 'true':
1356 asciidoc += 'If '
1357 asciidoc += self.makeParameterName(paramname.text)
1358 asciidoc += ' is not '
1359 if self.paramIsArray(param) or self.paramIsPointer(param) or self.isHandleTypeDispatchable(paramtype.text):
1360 asciidoc += '`NULL`'
1361 elif self.getTypeCategory(paramtype.text) == 'handle':
1362 asciidoc += 'sname:VK_NULL_HANDLE'
1363 else:
1364 asciidoc += '`0`'
1365
1366 asciidoc += ', '
1367
1368 return asciidoc
1369
1370 #
1371 # Make the generic asciidoc line chunk portion used for all parameters.
1372 # May return an empty string if nothing to validate.
1373 def createValidationLineForParameterIntroChunk(self, param, params, typetext):
1374 asciidoc = ''
1375 paramname = param.find('name')
1376 paramtype = param.find('type')
1377
1378 asciidoc += self.makeAsciiDocPreChunk(param, params)
1379
1380 asciidoc += self.makeParameterName(paramname.text)
1381 asciidoc += ' must: be '
1382
1383 if self.paramIsArray(param):
1384 # Arrays. These are hard to get right, apparently
1385
1386 lengths = param.attrib.get('len').split(',')
1387
1388 if (lengths[0]) == 'null-terminated':
1389 asciidoc += 'a null-terminated '
1390 elif (lengths[0]) == '1':
1391 asciidoc += 'a pointer to '
1392 else:
1393 asciidoc += 'a pointer to an array of '
1394
1395 # Handle equations, which are currently denoted with latex
1396 if 'latexmath:' in lengths[0]:
1397 asciidoc += lengths[0]
1398 else:
1399 asciidoc += self.makeParameterName(lengths[0])
1400 asciidoc += ' '
1401
1402 for length in lengths[1:]:
1403 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.
1404 asciidoc += 'null-terminated '
1405 elif (length) == '1':
1406 asciidoc += 'pointers to '
1407 else:
1408 asciidoc += 'pointers to arrays of '
1409 # Handle equations, which are currently denoted with latex
1410 if 'latex:' in length:
1411 asciidoc += length
1412 else:
1413 asciidoc += self.makeParameterName(length)
1414 asciidoc += ' '
1415
1416 # Void pointers don't actually point at anything - remove the word "to"
1417 if paramtype.text == 'void':
1418 if lengths[-1] == '1':
1419 if len(lengths) > 1:
1420 asciidoc = asciidoc[:-5] # Take care of the extra s added by the post array chunk function. #HACK#
1421 else:
1422 asciidoc = asciidoc[:-4]
1423 else:
1424 # An array of void values is a byte array.
1425 asciidoc += 'byte'
1426
1427 elif paramtype.text == 'char':
1428 # A null terminated array of chars is a string
1429 if lengths[-1] == 'null-terminated':
1430 asciidoc += 'string'
1431 else:
1432 # Else it's just a bunch of chars
1433 asciidoc += 'char value'
1434 elif param.text is not None:
1435 # If a value is "const" that means it won't get modified, so it must be valid going into the function.
1436 if 'const' in param.text:
1437 typecategory = self.getTypeCategory(paramtype.text)
1438 if (typecategory != 'struct' and typecategory != 'union' and typecategory != 'basetype' and typecategory is not None) or not self.isStructAlwaysValid(paramtype.text):
1439 asciidoc += 'valid '
1440
1441 asciidoc += typetext
1442
1443 # pluralize
1444 if len(lengths) > 1 or (lengths[0] != '1' and lengths[0] != 'null-terminated'):
1445 asciidoc += 's'
1446
1447 elif self.paramIsPointer(param):
1448 # Handle pointers - which are really special case arrays (i.e. they don't have a length)
1449 pointercount = paramtype.tail.count('*')
1450
1451 # Could be multi-level pointers (e.g. ppData - pointer to a pointer). Handle that.
1452 for i in range(0, pointercount):
1453 asciidoc += 'a pointer to '
1454
1455 if paramtype.text == 'void':
1456 # If there's only one pointer, it's optional, and it doesn't point at anything in particular - we don't need any language.
1457 if pointercount == 1 and param.attrib.get('optional') is not None:
1458 return '' # early return
1459 else:
1460 # Pointer to nothing in particular - delete the " to " portion
1461 asciidoc = asciidoc[:-4]
1462 else:
1463 # Add an article for English semantic win
1464 asciidoc += 'a '
1465
1466 # If a value is "const" that means it won't get modified, so it must be valid going into the function.
1467 if param.text is not None and paramtype.text != 'void':
1468 if 'const' in param.text:
1469 asciidoc += 'valid '
1470
1471 asciidoc += typetext
1472
1473 else:
1474 # Non-pointer, non-optional things must be valid
1475 asciidoc += 'a valid '
1476 asciidoc += typetext
1477
1478 if asciidoc != '':
1479 asciidoc += '\n'
1480
1481 # Add additional line for non-optional bitmasks
1482 if self.getTypeCategory(paramtype.text) == 'bitmask':
1483 if param.attrib.get('optional') is None:
1484 asciidoc += '* '
1485 if self.paramIsArray(param):
1486 asciidoc += 'Each element of '
1487 asciidoc += 'pname:'
1488 asciidoc += paramname.text
1489 asciidoc += ' mustnot: be `0`'
1490 asciidoc += '\n'
1491
1492 return asciidoc
1493
1494 def makeAsciiDocLineForParameter(self, param, params, typetext):
1495 if param.attrib.get('noautovalidity') is not None:
1496 return ''
1497 asciidoc = self.createValidationLineForParameterIntroChunk(param, params, typetext)
1498
1499 return asciidoc
1500
1501 # Try to do check if a structure is always considered valid (i.e. there's no rules to its acceptance)
1502 def isStructAlwaysValid(self, structname):
1503
1504 struct = self.registry.find("types/type[@name='" + structname + "']")
1505
1506 params = struct.findall('member')
1507 validity = struct.find('validity')
1508
1509 if validity is not None:
1510 return False
1511
1512 for param in params:
1513 paramname = param.find('name')
1514 paramtype = param.find('type')
1515 typecategory = self.getTypeCategory(paramtype.text)
1516
1517 if paramname.text == 'pNext':
1518 return False
1519
1520 if paramname.text == 'sType':
1521 return False
1522
1523 if paramtype.text == 'void' or paramtype.text == 'char' or self.paramIsArray(param) or self.paramIsPointer(param):
1524 if self.makeAsciiDocLineForParameter(param, params, '') != '':
1525 return False
1526 elif typecategory == 'handle' or typecategory == 'enum' or typecategory == 'bitmask' or param.attrib.get('returnedonly') == 'true':
1527 return False
1528 elif typecategory == 'struct' or typecategory == 'union':
1529 if self.isStructAlwaysValid(paramtype.text) is False:
1530 return False
1531
1532 return True
1533
1534 #
1535 # Make an entire asciidoc line for a given parameter
1536 def createValidationLineForParameter(self, param, params, typecategory):
1537 asciidoc = ''
1538 paramname = param.find('name')
1539 paramtype = param.find('type')
1540
1541 if paramtype.text == 'void' or paramtype.text == 'char':
1542 # Chars and void are special cases - needs care inside the generator functions
1543 # A null-terminated char array is a string, else it's chars.
1544 # An array of void values is a byte array, a void pointer is just a pointer to nothing in particular
1545 asciidoc += self.makeAsciiDocLineForParameter(param, params, '')
1546 elif typecategory == 'bitmask':
1547 bitsname = paramtype.text.replace('Flags', 'FlagBits')
1548 if self.registry.find("enums[@name='" + bitsname + "']") is None:
1549 asciidoc += '* '
1550 asciidoc += self.makeParameterName(paramname.text)
1551 asciidoc += ' must: be `0`'
1552 asciidoc += '\n'
1553 else:
1554 if self.paramIsArray(param):
1555 asciidoc += self.makeAsciiDocLineForParameter(param, params, 'combinations of ' + self.makeEnumerationName(bitsname) + ' value')
1556 else:
1557 asciidoc += self.makeAsciiDocLineForParameter(param, params, 'combination of ' + self.makeEnumerationName(bitsname) + ' values')
1558 elif typecategory == 'handle':
1559 asciidoc += self.makeAsciiDocLineForParameter(param, params, self.makeStructName(paramtype.text) + ' handle')
1560 elif typecategory == 'enum':
1561 asciidoc += self.makeAsciiDocLineForParameter(param, params, self.makeEnumerationName(paramtype.text) + ' value')
1562 elif typecategory == 'struct':
1563 if (self.paramIsArray(param) or self.paramIsPointer(param)) or not self.isStructAlwaysValid(paramtype.text):
1564 asciidoc += self.makeAsciiDocLineForParameter(param, params, self.makeStructName(paramtype.text) + ' structure')
1565 elif typecategory == 'union':
1566 if (self.paramIsArray(param) or self.paramIsPointer(param)) or not self.isStructAlwaysValid(paramtype.text):
1567 asciidoc += self.makeAsciiDocLineForParameter(param, params, self.makeStructName(paramtype.text) + ' union')
1568 elif self.paramIsArray(param) or self.paramIsPointer(param):
1569 asciidoc += self.makeAsciiDocLineForParameter(param, params, self.makeBaseTypeName(paramtype.text) + ' value')
1570
1571 return asciidoc
1572
1573 #
1574 # Make an asciidoc validity entry for a handle's parent object
1575 def makeAsciiDocHandleParent(self, param, params):
1576 asciidoc = ''
1577 paramname = param.find('name')
1578 paramtype = param.find('type')
1579
1580 # Deal with handle parents
1581 handleparent = self.getHandleParent(paramtype.text)
1582 if handleparent is not None:
1583 parentreference = None
1584 for otherparam in params:
1585 if otherparam.find('type').text == handleparent:
1586 parentreference = otherparam.find('name').text
1587 if parentreference is not None:
1588 asciidoc += '* '
1589
1590 if self.isHandleOptional(param, params):
1591 if self.paramIsArray(param):
1592 asciidoc += 'Each element of '
1593 asciidoc += self.makeParameterName(paramname.text)
1594 asciidoc += ' that is a valid handle'
1595 else:
1596 asciidoc += 'If '
1597 asciidoc += self.makeParameterName(paramname.text)
1598 asciidoc += ' is a valid handle, it'
1599 else:
1600 if self.paramIsArray(param):
1601 asciidoc += 'Each element of '
1602 asciidoc += self.makeParameterName(paramname.text)
1603 asciidoc += ' must: have been created, allocated or retrieved from '
1604 asciidoc += self.makeParameterName(parentreference)
1605
1606 asciidoc += '\n'
1607 return asciidoc
1608
1609 #
1610 # Generate an asciidoc validity line for the sType value of a struct
1611 def makeStructureType(self, blockname, param):
1612 asciidoc = '* '
1613 paramname = param.find('name')
1614 paramtype = param.find('type')
1615
1616 asciidoc += self.makeParameterName(paramname.text)
1617 asciidoc += ' must: be '
1618
1619 structuretype = ''
1620 for elem in re.findall(r'(([A-Z][a-z]+)|([A-Z][A-Z]+))', blockname):
1621 if elem[0] == 'Vk':
1622 structuretype += 'VK_STRUCTURE_TYPE_'
1623 else:
1624 structuretype += elem[0].upper()
1625 structuretype += '_'
1626
1627 asciidoc += self.makeEnumerantName(structuretype[:-1])
1628 asciidoc += '\n'
1629
1630 return asciidoc
1631
1632 #
1633 # Generate an asciidoc validity line for the pNext value of a struct
1634 def makeStructureExtensionPointer(self, param):
1635 asciidoc = '* '
1636 paramname = param.find('name')
1637 paramtype = param.find('type')
1638
1639 asciidoc += self.makeParameterName(paramname.text)
1640
1641 validextensionstructs = param.attrib.get('validextensionstructs')
1642 if validextensionstructs is None:
1643 asciidoc += ' must: be `NULL`'
1644 else:
1645 extensionstructs = validextensionstructs.split(',')
1646 asciidoc += ' must: point to one of ' + extensionstructs[:-1].join(', ') + ' or ' + extensionstructs[-1] + 'if the extension that introduced them is enabled '
1647
1648 asciidoc += '\n'
1649
1650 return asciidoc
1651
1652 #
1653 # Generate all the valid usage information for a given struct or command
1654 def makeValidUsageStatements(self, cmd, blockname, params, usages):
1655 # Start the asciidoc block for this
1656 asciidoc = ''
1657
1658 handles = []
1659 anyparentedhandlesoptional = False
1660 parentdictionary = {}
1661 arraylengths = set()
1662 for param in params:
1663 paramname = param.find('name')
1664 paramtype = param.find('type')
1665
1666 # Get the type's category
1667 typecategory = self.getTypeCategory(paramtype.text)
1668
1669 # Generate language to independently validate a parameter
1670 if paramtype.text == 'VkStructureType' and paramname.text == 'sType':
1671 asciidoc += self.makeStructureType(blockname, param)
1672 elif paramtype.text == 'void' and paramname.text == 'pNext':
1673 asciidoc += self.makeStructureExtensionPointer(param)
1674 else:
1675 asciidoc += self.createValidationLineForParameter(param, params, typecategory)
1676
1677 # Ensure that any parenting is properly validated, and list that a handle was found
1678 if typecategory == 'handle':
1679 # Don't detect a parent for return values!
1680 if not self.paramIsPointer(param) or (param.text is not None and 'const' in param.text):
1681 parent = self.getHandleParent(paramtype.text)
1682 if parent is not None:
1683 handles.append(param)
1684
1685 # If any param is optional, it affects the output
1686 if self.isHandleOptional(param, params):
1687 anyparentedhandlesoptional = True
1688
1689 # Find the first dispatchable parent
1690 ancestor = parent
1691 while ancestor is not None and not self.isHandleTypeDispatchable(ancestor):
1692 ancestor = self.getHandleParent(ancestor)
1693
1694 # If one was found, add this parameter to the parent dictionary
1695 if ancestor is not None:
1696 if ancestor not in parentdictionary:
1697 parentdictionary[ancestor] = []
1698
1699 if self.paramIsArray(param):
1700 parentdictionary[ancestor].append('the elements of ' + self.makeParameterName(paramname.text))
1701 else:
1702 parentdictionary[ancestor].append(self.makeParameterName(paramname.text))
1703
1704 # Get the array length for this parameter
1705 arraylength = param.attrib.get('len')
1706 if arraylength is not None:
1707 for onelength in arraylength.split(','):
1708 arraylengths.add(onelength)
1709
1710 # For any vkQueue* functions, there might be queue type data
1711 if 'vkQueue' in blockname:
1712 # The queue type must be valid
1713 queuetypes = cmd.attrib.get('queues')
1714 if queuetypes is not None:
1715 queuebits = []
1716 for queuetype in re.findall(r'([^,]+)', queuetypes):
1717 queuebits.append(queuetype.replace('_',' '))
1718
1719 asciidoc += '* '
1720 asciidoc += 'The pname:queue must: support '
1721 if len(queuebits) == 1:
1722 asciidoc += queuebits[0]
1723 else:
1724 asciidoc += (', ').join(queuebits[:-1])
1725 asciidoc += ' or '
1726 asciidoc += queuebits[-1]
1727 asciidoc += ' operations'
1728 asciidoc += '\n'
1729
1730 if 'vkCmd' in blockname:
1731 # The commandBuffer parameter must be being recorded
1732 asciidoc += '* '
1733 asciidoc += 'pname:commandBuffer must: be in the recording state'
1734 asciidoc += '\n'
1735
1736 # The queue type must be valid
1737 queuetypes = cmd.attrib.get('queues')
1738 queuebits = []
1739 for queuetype in re.findall(r'([^,]+)', queuetypes):
1740 queuebits.append(queuetype.replace('_',' '))
1741
1742 asciidoc += '* '
1743 asciidoc += 'The sname:VkCommandPool that pname:commandBuffer was allocated from must: support '
1744 if len(queuebits) == 1:
1745 asciidoc += queuebits[0]
1746 else:
1747 asciidoc += (', ').join(queuebits[:-1])
1748 asciidoc += ' or '
1749 asciidoc += queuebits[-1]
1750 asciidoc += ' operations'
1751 asciidoc += '\n'
1752
1753 # Must be called inside/outside a renderpass appropriately
1754 renderpass = cmd.attrib.get('renderpass')
1755
1756 if renderpass != 'both':
1757 asciidoc += '* This command must: only be called '
1758 asciidoc += renderpass
1759 asciidoc += ' of a render pass instance'
1760 asciidoc += '\n'
1761
1762 # Must be in the right level command buffer
1763 cmdbufferlevel = cmd.attrib.get('cmdbufferlevel')
1764
1765 if cmdbufferlevel != 'primary,secondary':
1766 asciidoc += '* pname:commandBuffer must: be a '
1767 asciidoc += cmdbufferlevel
1768 asciidoc += ' sname:VkCommandBuffer'
1769 asciidoc += '\n'
1770
1771 # Any non-optional arraylengths should specify they must be greater than 0
1772 for param in params:
1773 paramname = param.find('name')
1774
1775 for arraylength in arraylengths:
1776 if paramname.text == arraylength and param.attrib.get('optional') is None:
1777 # Get all the array dependencies
1778 arrays = cmd.findall("param/[@len='" + arraylength + "'][@optional='true']")
1779
1780 # Get all the optional array dependencies, including those not generating validity for some reason
1781 optionalarrays = cmd.findall("param/[@len='" + arraylength + "'][@optional='true']")
1782 optionalarrays.extend(cmd.findall("param/[@len='" + arraylength + "'][@noautovalidity='true']"))
1783
1784 asciidoc += '* '
1785
1786 # Allow lengths to be arbitrary if all their dependents are optional
1787 if len(optionalarrays) == len(arrays) and len(optionalarrays) != 0:
1788 asciidoc += 'If '
1789 if len(optionalarrays) > 1:
1790 asciidoc += 'any of '
1791
1792 for array in optionalarrays[:-1]:
1793 asciidoc += self.makeParameterName(optionalarrays.find('name').text)
1794 asciidoc += ', '
1795
1796 if len(optionalarrays) > 1:
1797 asciidoc += 'and '
1798 asciidoc += self.makeParameterName(optionalarrays[-1].find('name').text)
1799 asciidoc += ' are '
1800 else:
1801 asciidoc += self.makeParameterName(optionalarrays[-1].find('name').text)
1802 asciidoc += ' is '
1803
1804 asciidoc += 'not `NULL`, '
1805
1806 if self.paramIsPointer(param):
1807 asciidoc += 'the value referenced by '
1808 else:
1809 asciidoc += 'the value of '
1810
1811 elif self.paramIsPointer(param):
1812 asciidoc += 'The value referenced by '
1813 else:
1814 asciidoc += 'The value of '
1815
1816 asciidoc += self.makeParameterName(arraylength)
1817 asciidoc += ' must: be greater than `0`'
1818 asciidoc += '\n'
1819
1820 # Find the parents of all objects referenced in this command
1821 for param in handles:
1822 asciidoc += self.makeAsciiDocHandleParent(param, params)
1823
1824 # Find the common ancestors of objects
1825 noancestorscount = 0
1826 while noancestorscount < len(parentdictionary):
1827 noancestorscount = 0
1828 oldparentdictionary = parentdictionary.copy()
1829 for parent in oldparentdictionary.items():
1830 ancestor = self.getHandleParent(parent[0])
1831
1832 while ancestor is not None and ancestor not in parentdictionary:
1833 ancestor = self.getHandleParent(ancestor)
1834
1835 if ancestor is not None:
1836 parentdictionary[ancestor] += parentdictionary.pop(parent[0])
1837 else:
1838 # No ancestors possible - so count it up
1839 noancestorscount += 1
1840
1841 # Add validation language about common ancestors
1842 for parent in parentdictionary.items():
1843 if len(parent[1]) > 1:
1844 parentlanguage = '* '
1845
1846 parentlanguage += 'Each of '
1847 parentlanguage += ", ".join(parent[1][:-1])
1848 parentlanguage += ' and '
1849 parentlanguage += parent[1][-1]
1850 if anyparentedhandlesoptional is True:
1851 parentlanguage += ' that are valid handles'
1852 parentlanguage += ' must: have been created, allocated or retrieved from the same '
1853 parentlanguage += self.makeStructName(parent[0])
1854 parentlanguage += '\n'
1855
1856 # Capitalize and add to the main language
1857 asciidoc += parentlanguage
1858
1859 # Add in any plain-text validation language that's in the xml
1860 for usage in usages:
1861 asciidoc += '* '
1862 asciidoc += usage.text
1863 asciidoc += '\n'
1864
1865 # In case there's nothing to report, return None
1866 if asciidoc == '':
1867 return None
1868 # Delimit the asciidoc block
1869 return asciidoc
1870
1871 def makeThreadSafetyBlock(self, cmd, paramtext):
1872 """Generate C function pointer typedef for <command> Element"""
1873 paramdecl = ''
1874
1875 # For any vkCmd* functions, the commandBuffer parameter must be being recorded
1876 if cmd.find('proto/name') is not None and 'vkCmd' in cmd.find('proto/name'):
1877 paramdecl += '* '
1878 paramdecl += 'The sname:VkCommandPool that pname:commandBuffer was created from'
1879 paramdecl += '\n'
1880
1881 # Find and add any parameters that are thread unsafe
1882 explicitexternsyncparams = cmd.findall(paramtext + "[@externsync]")
1883 if (explicitexternsyncparams is not None):
1884 for param in explicitexternsyncparams:
1885 externsyncattribs = param.attrib.get('externsync')
1886 paramname = param.find('name')
1887 for externsyncattrib in externsyncattribs.split(','):
1888 paramdecl += '* '
1889 paramdecl += 'Host access to '
1890 if externsyncattrib == 'true':
1891 if self.paramIsArray(param):
1892 paramdecl += 'each member of ' + self.makeParameterName(paramname.text)
1893 elif self.paramIsPointer(param):
1894 paramdecl += 'the object referenced by ' + self.makeParameterName(paramname.text)
1895 else:
1896 paramdecl += self.makeParameterName(paramname.text)
1897 else:
1898 paramdecl += 'pname:'
1899 paramdecl += externsyncattrib
1900 paramdecl += ' must: be externally synchronized\n'
1901
1902 # Find and add any "implicit" parameters that are thread unsafe
1903 implicitexternsyncparams = cmd.find('implicitexternsyncparams')
1904 if (implicitexternsyncparams is not None):
1905 for elem in implicitexternsyncparams:
1906 paramdecl += '* '
1907 paramdecl += 'Host access to '
1908 paramdecl += elem.text
1909 paramdecl += ' must: be externally synchronized\n'
1910
1911 if (paramdecl == ''):
1912 return None
1913 else:
1914 return paramdecl
1915
1916 def makeCommandPropertiesTableEntry(self, cmd, name):
1917
1918 if 'vkCmd' in name:
1919 # Must be called inside/outside a renderpass appropriately
1920 cmdbufferlevel = cmd.attrib.get('cmdbufferlevel')
1921 cmdbufferlevel = (' + \n').join(cmdbufferlevel.title().split(','))
1922
1923 renderpass = cmd.attrib.get('renderpass')
1924 renderpass = renderpass.capitalize()
1925
1926 queues = cmd.attrib.get('queues')
1927 queues = (' + \n').join(queues.upper().split(','))
1928
1929 return '|' + cmdbufferlevel + '|' + renderpass + '|' + queues
1930 elif 'vkQueue' in name:
1931 # Must be called inside/outside a renderpass appropriately
1932
1933 queues = cmd.attrib.get('queues')
1934 if queues is None:
1935 queues = 'Any'
1936 else:
1937 queues = (' + \n').join(queues.upper().split(','))
1938
1939 return '|-|-|' + queues
1940
1941 return None
1942
1943 def makeSuccessCodes(self, cmd, name):
1944
1945 successcodes = cmd.attrib.get('successcodes')
1946 if successcodes is not None:
1947
1948 successcodeentry = ''
1949 successcodes = successcodes.split(',')
1950 return '* ' + '\n* '.join(successcodes)
1951
1952 return None
1953
1954 def makeErrorCodes(self, cmd, name):
1955
1956 errorcodes = cmd.attrib.get('errorcodes')
1957 if errorcodes is not None:
1958
1959 errorcodeentry = ''
1960 errorcodes = errorcodes.split(',')
1961 return '* ' + '\n* '.join(errorcodes)
1962
1963 return None
1964
1965 #
1966 # Command generation
1967 def genCmd(self, cmdinfo, name):
1968 OutputGenerator.genCmd(self, cmdinfo, name)
1969 #
1970 # Get all thh parameters
1971 params = cmdinfo.elem.findall('param')
1972 usages = cmdinfo.elem.findall('validity/usage')
1973
1974 validity = self.makeValidUsageStatements(cmdinfo.elem, name, params, usages)
1975 threadsafety = self.makeThreadSafetyBlock(cmdinfo.elem, 'param')
1976 commandpropertiesentry = self.makeCommandPropertiesTableEntry(cmdinfo.elem, name)
1977 successcodes = self.makeSuccessCodes(cmdinfo.elem, name)
1978 errorcodes = self.makeErrorCodes(cmdinfo.elem, name)
1979
1980 self.writeInclude('validity/protos', name, validity, threadsafety, commandpropertiesentry, successcodes, errorcodes)
1981
1982 #
1983 # Struct Generation
1984 def genStruct(self, typeinfo, typename):
1985 OutputGenerator.genStruct(self, typeinfo, typename)
1986
1987 # Anything that's only ever returned can't be set by the user, so shouldn't have any validity information.
1988 if typeinfo.elem.attrib.get('returnedonly') is None:
1989 params = typeinfo.elem.findall('member')
1990 usages = typeinfo.elem.findall('validity/usage')
1991
1992 validity = self.makeValidUsageStatements(typeinfo.elem, typename, params, usages)
1993 threadsafety = self.makeThreadSafetyBlock(typeinfo.elem, 'member')
1994
1995 self.writeInclude('validity/structs', typename, validity, threadsafety, None, None, None)
1996 else:
1997 # Still generate files for return only structs, in case this state changes later
1998 self.writeInclude('validity/structs', typename, None, None, None, None, None)
1999
2000 #
2001 # Type Generation
2002 def genType(self, typeinfo, typename):
2003 OutputGenerator.genType(self, typeinfo, typename)
2004
2005 category = typeinfo.elem.get('category')
2006 if (category == 'struct' or category == 'union'):
2007 self.genStruct(typeinfo, typename)
2008
2009# HostSynchronizationOutputGenerator - subclass of OutputGenerator.
2010# Generates AsciiDoc includes of the externsync parameter table for the
2011# fundamentals chapter of the Vulkan specification. Similar to
2012# DocOutputGenerator.
2013#
2014# ---- methods ----
2015# HostSynchronizationOutputGenerator(errFile, warnFile, diagFile) - args as for
2016# OutputGenerator. Defines additional internal state.
2017# ---- methods overriding base class ----
2018# genCmd(cmdinfo)
2019class HostSynchronizationOutputGenerator(OutputGenerator):
2020 # Generate Host Synchronized Parameters in a table at the top of the spec
2021 def __init__(self,
2022 errFile = sys.stderr,
2023 warnFile = sys.stderr,
2024 diagFile = sys.stdout):
2025 OutputGenerator.__init__(self, errFile, warnFile, diagFile)
2026
2027 threadsafety = {'parameters': '', 'parameterlists': '', 'implicit': ''}
2028
2029 def makeParameterName(self, name):
2030 return 'pname:' + name
2031
2032 def makeFLink(self, name):
2033 return 'flink:' + name
2034
2035 #
2036 # Generate an include file
2037 #
2038 # directory - subdirectory to put file in
2039 # basename - base name of the file
2040 # contents - contents of the file (Asciidoc boilerplate aside)
2041 def writeInclude(self):
2042
2043 if self.threadsafety['parameters'] is not None:
2044 # Create file
2045 filename = self.genOpts.genDirectory + '/' + self.genOpts.filename + '/parameters.txt'
2046 self.logMsg('diag', '# Generating include file:', filename)
2047 fp = open(filename, 'w')
2048
2049 # Host Synchronization
2050 write('.Externally Synchronized Parameters', file=fp)
2051 write('*' * 80, file=fp)
2052 write(self.threadsafety['parameters'], file=fp, end='')
2053 write('*' * 80, file=fp)
2054 write('', file=fp)
2055
2056 if self.threadsafety['parameterlists'] is not None:
2057 # Create file
2058 filename = self.genOpts.genDirectory + '/' + self.genOpts.filename + '/parameterlists.txt'
2059 self.logMsg('diag', '# Generating include file:', filename)
2060 fp = open(filename, 'w')
2061
2062 # Host Synchronization
2063 write('.Externally Synchronized Parameter Lists', file=fp)
2064 write('*' * 80, file=fp)
2065 write(self.threadsafety['parameterlists'], file=fp, end='')
2066 write('*' * 80, file=fp)
2067 write('', file=fp)
2068
2069 if self.threadsafety['implicit'] is not None:
2070 # Create file
2071 filename = self.genOpts.genDirectory + '/' + self.genOpts.filename + '/implicit.txt'
2072 self.logMsg('diag', '# Generating include file:', filename)
2073 fp = open(filename, 'w')
2074
2075 # Host Synchronization
2076 write('.Implicit Externally Synchronized Parameters', file=fp)
2077 write('*' * 80, file=fp)
2078 write(self.threadsafety['implicit'], file=fp, end='')
2079 write('*' * 80, file=fp)
2080 write('', file=fp)
2081
2082 fp.close()
2083
2084 #
2085 # Check if the parameter passed in is a pointer to an array
2086 def paramIsArray(self, param):
2087 return param.attrib.get('len') is not None
2088
2089 # Check if the parameter passed in is a pointer
2090 def paramIsPointer(self, param):
2091 ispointer = False
2092 paramtype = param.find('type')
2093 if paramtype.tail is not None and '*' in paramtype.tail:
2094 ispointer = True
2095
2096 return ispointer
2097
2098 # Turn the "name[].member[]" notation into plain English.
2099 def makeThreadDereferenceHumanReadable(self, dereference):
2100 matches = re.findall(r"[\w]+[^\w]*",dereference)
2101 stringval = ''
2102 for match in reversed(matches):
2103 if '->' in match or '.' in match:
2104 stringval += 'member of '
2105 if '[]' in match:
2106 stringval += 'each element of '
2107
2108 stringval += 'the '
2109 stringval += self.makeParameterName(re.findall(r"[\w]+",match)[0])
2110 stringval += ' '
2111
2112 stringval += 'parameter'
2113
2114 return stringval[0].upper() + stringval[1:]
2115
2116 def makeThreadSafetyBlocks(self, cmd, paramtext):
2117 protoname = cmd.find('proto/name').text
2118
2119 # Find and add any parameters that are thread unsafe
2120 explicitexternsyncparams = cmd.findall(paramtext + "[@externsync]")
2121 if (explicitexternsyncparams is not None):
2122 for param in explicitexternsyncparams:
2123 externsyncattribs = param.attrib.get('externsync')
2124 paramname = param.find('name')
2125 for externsyncattrib in externsyncattribs.split(','):
2126
2127 tempstring = '* '
2128 if externsyncattrib == 'true':
2129 if self.paramIsArray(param):
2130 tempstring += 'Each element of the '
2131 elif self.paramIsPointer(param):
2132 tempstring += 'The object referenced by the '
2133 else:
2134 tempstring += 'The '
2135
2136 tempstring += self.makeParameterName(paramname.text)
2137 tempstring += ' parameter'
2138
2139 else:
2140 tempstring += self.makeThreadDereferenceHumanReadable(externsyncattrib)
2141
2142 tempstring += ' in '
2143 tempstring += self.makeFLink(protoname)
2144 tempstring += '\n'
2145
2146
2147 if ' element of ' in tempstring:
2148 self.threadsafety['parameterlists'] += tempstring
2149 else:
2150 self.threadsafety['parameters'] += tempstring
2151
2152
2153 # Find and add any "implicit" parameters that are thread unsafe
2154 implicitexternsyncparams = cmd.find('implicitexternsyncparams')
2155 if (implicitexternsyncparams is not None):
2156 for elem in implicitexternsyncparams:
2157 self.threadsafety['implicit'] += '* '
2158 self.threadsafety['implicit'] += elem.text[0].upper()
2159 self.threadsafety['implicit'] += elem.text[1:]
2160 self.threadsafety['implicit'] += ' in '
2161 self.threadsafety['implicit'] += self.makeFLink(protoname)
2162 self.threadsafety['implicit'] += '\n'
2163
2164
2165 # For any vkCmd* functions, the commandBuffer parameter must be being recorded
2166 if protoname is not None and 'vkCmd' in protoname:
2167 self.threadsafety['implicit'] += '* '
2168 self.threadsafety['implicit'] += 'The sname:VkCommandPool that pname:commandBuffer was allocated from, in '
2169 self.threadsafety['implicit'] += self.makeFLink(protoname)
2170
2171 self.threadsafety['implicit'] += '\n'
2172
2173 #
2174 # Command generation
2175 def genCmd(self, cmdinfo, name):
2176 OutputGenerator.genCmd(self, cmdinfo, name)
2177 #
2178 # Get all thh parameters
2179 params = cmdinfo.elem.findall('param')
2180 usages = cmdinfo.elem.findall('validity/usage')
2181
2182 self.makeThreadSafetyBlocks(cmdinfo.elem, 'param')
2183
2184 self.writeInclude()