1 | #!/usr/bin/python3 -i
|
---|
2 | #
|
---|
3 | # Copyright 2013-2024 The Khronos Group Inc.
|
---|
4 | #
|
---|
5 | # SPDX-License-Identifier: Apache-2.0
|
---|
6 | """Base class for source/header/doc generators, as well as some utility functions."""
|
---|
7 |
|
---|
8 | from __future__ import unicode_literals
|
---|
9 |
|
---|
10 | import io
|
---|
11 | import os
|
---|
12 | import pdb
|
---|
13 | import re
|
---|
14 | import shutil
|
---|
15 | import sys
|
---|
16 | import tempfile
|
---|
17 | try:
|
---|
18 | from pathlib import Path
|
---|
19 | except ImportError:
|
---|
20 | from pathlib2 import Path # type: ignore
|
---|
21 |
|
---|
22 | from spec_tools.util import getElemName, getElemType
|
---|
23 |
|
---|
24 |
|
---|
25 | def write(*args, **kwargs):
|
---|
26 | file = kwargs.pop('file', sys.stdout)
|
---|
27 | end = kwargs.pop('end', '\n')
|
---|
28 | file.write(' '.join(str(arg) for arg in args))
|
---|
29 | file.write(end)
|
---|
30 |
|
---|
31 |
|
---|
32 | def noneStr(s):
|
---|
33 | """Return string argument, or "" if argument is None.
|
---|
34 |
|
---|
35 | Used in converting etree Elements into text.
|
---|
36 | s - string to convert"""
|
---|
37 | if s:
|
---|
38 | return s
|
---|
39 | return ""
|
---|
40 |
|
---|
41 |
|
---|
42 | def enquote(s):
|
---|
43 | """Return string argument with surrounding quotes,
|
---|
44 | for serialization into Python code."""
|
---|
45 | if s:
|
---|
46 | if isinstance(s, str):
|
---|
47 | return f"'{s}'"
|
---|
48 | else:
|
---|
49 | return s
|
---|
50 | return None
|
---|
51 |
|
---|
52 |
|
---|
53 | def regSortCategoryKey(feature):
|
---|
54 | """Sort key for regSortFeatures.
|
---|
55 | Sorts by category of the feature name string:
|
---|
56 |
|
---|
57 | - Core API features (those defined with a `<feature>` tag)
|
---|
58 | - (sort VKSC after VK - this is Vulkan-specific)
|
---|
59 | - ARB/KHR/OES (Khronos extensions)
|
---|
60 | - other (EXT/vendor extensions)"""
|
---|
61 |
|
---|
62 | if feature.elem.tag == 'feature':
|
---|
63 | if feature.name.startswith('VKSC'):
|
---|
64 | return 0.5
|
---|
65 | else:
|
---|
66 | return 0
|
---|
67 |
|
---|
68 | if feature.category.upper() in ['ARB', 'KHR', 'OES']:
|
---|
69 | return 1
|
---|
70 |
|
---|
71 | return 2
|
---|
72 |
|
---|
73 |
|
---|
74 | def regSortOrderKey(feature):
|
---|
75 | """Sort key for regSortFeatures - key is the sortorder attribute."""
|
---|
76 |
|
---|
77 | return feature.sortorder
|
---|
78 |
|
---|
79 |
|
---|
80 | def regSortNameKey(feature):
|
---|
81 | """Sort key for regSortFeatures - key is the extension name."""
|
---|
82 |
|
---|
83 | return feature.name
|
---|
84 |
|
---|
85 |
|
---|
86 | def regSortFeatureVersionKey(feature):
|
---|
87 | """Sort key for regSortFeatures - key is the feature version.
|
---|
88 | `<extension>` elements all have version number 0."""
|
---|
89 |
|
---|
90 | return float(feature.versionNumber)
|
---|
91 |
|
---|
92 |
|
---|
93 | def regSortExtensionNumberKey(feature):
|
---|
94 | """Sort key for regSortFeatures - key is the extension number.
|
---|
95 | `<feature>` elements all have extension number 0."""
|
---|
96 |
|
---|
97 | return int(feature.number)
|
---|
98 |
|
---|
99 |
|
---|
100 | def regSortFeatures(featureList):
|
---|
101 | """Default sort procedure for features.
|
---|
102 |
|
---|
103 | - Sorts by explicit sort order (default 0) relative to other features
|
---|
104 | - then by feature category ('feature' or 'extension'),
|
---|
105 | - then by version number (for features)
|
---|
106 | - then by extension number (for extensions)"""
|
---|
107 | featureList.sort(key=regSortExtensionNumberKey)
|
---|
108 | featureList.sort(key=regSortFeatureVersionKey)
|
---|
109 | featureList.sort(key=regSortCategoryKey)
|
---|
110 | featureList.sort(key=regSortOrderKey)
|
---|
111 |
|
---|
112 |
|
---|
113 | class MissingGeneratorOptionsError(RuntimeError):
|
---|
114 | """Error raised when a Generator tries to do something that requires GeneratorOptions but it is None."""
|
---|
115 |
|
---|
116 | def __init__(self, msg=None):
|
---|
117 | full_msg = 'Missing generator options object self.genOpts'
|
---|
118 | if msg:
|
---|
119 | full_msg += ': ' + msg
|
---|
120 | super().__init__(full_msg)
|
---|
121 |
|
---|
122 |
|
---|
123 | class MissingRegistryError(RuntimeError):
|
---|
124 | """Error raised when a Generator tries to do something that requires a Registry object but it is None."""
|
---|
125 |
|
---|
126 | def __init__(self, msg=None):
|
---|
127 | full_msg = 'Missing Registry object self.registry'
|
---|
128 | if msg:
|
---|
129 | full_msg += ': ' + msg
|
---|
130 | super().__init__(full_msg)
|
---|
131 |
|
---|
132 |
|
---|
133 | class MissingGeneratorOptionsConventionsError(RuntimeError):
|
---|
134 | """Error raised when a Generator tries to do something that requires a Conventions object but it is None."""
|
---|
135 |
|
---|
136 | def __init__(self, msg=None):
|
---|
137 | full_msg = 'Missing Conventions object self.genOpts.conventions'
|
---|
138 | if msg:
|
---|
139 | full_msg += ': ' + msg
|
---|
140 | super().__init__(full_msg)
|
---|
141 |
|
---|
142 |
|
---|
143 | class GeneratorOptions:
|
---|
144 | """Base class for options used during header/documentation production.
|
---|
145 |
|
---|
146 | These options are target language independent, and used by
|
---|
147 | Registry.apiGen() and by base OutputGenerator objects."""
|
---|
148 |
|
---|
149 | def __init__(self,
|
---|
150 | conventions=None,
|
---|
151 | filename=None,
|
---|
152 | directory='.',
|
---|
153 | genpath=None,
|
---|
154 | apiname=None,
|
---|
155 | mergeApiNames=None,
|
---|
156 | profile=None,
|
---|
157 | versions='.*',
|
---|
158 | emitversions='.*',
|
---|
159 | defaultExtensions=None,
|
---|
160 | addExtensions=None,
|
---|
161 | removeExtensions=None,
|
---|
162 | emitExtensions=None,
|
---|
163 | emitSpirv=None,
|
---|
164 | emitFormats=None,
|
---|
165 | reparentEnums=True,
|
---|
166 | sortProcedure=regSortFeatures,
|
---|
167 | requireCommandAliases=False,
|
---|
168 | requireDepends=True,
|
---|
169 | ):
|
---|
170 | """Constructor.
|
---|
171 |
|
---|
172 | Arguments:
|
---|
173 |
|
---|
174 | - conventions - may be mandatory for some generators:
|
---|
175 | an object that implements ConventionsBase
|
---|
176 | - filename - basename of file to generate, or None to write to stdout.
|
---|
177 | - directory - directory in which to generate filename
|
---|
178 | - genpath - path to previously generated files, such as apimap.py
|
---|
179 | - apiname - string matching `<api>` 'apiname' attribute, e.g. 'gl'.
|
---|
180 | - mergeApiNames - If not None, a comma separated list of API names
|
---|
181 | to merge into the API specified by 'apiname'
|
---|
182 | - profile - string specifying API profile , e.g. 'core', or None.
|
---|
183 | - versions - regex matching API versions to process interfaces for.
|
---|
184 | Normally `'.*'` or `'[0-9][.][0-9]'` to match all defined versions.
|
---|
185 | - emitversions - regex matching API versions to actually emit
|
---|
186 | interfaces for (though all requested versions are considered
|
---|
187 | when deciding which interfaces to generate). For GL 4.3 glext.h,
|
---|
188 | this might be `'1[.][2-5]|[2-4][.][0-9]'`.
|
---|
189 | - defaultExtensions - If not None, a string which must in its
|
---|
190 | entirety match the pattern in the "supported" attribute of
|
---|
191 | the `<extension>`. Defaults to None. Usually the same as apiname.
|
---|
192 | - addExtensions - regex matching names of additional extensions
|
---|
193 | to include. Defaults to None.
|
---|
194 | - removeExtensions - regex matching names of extensions to
|
---|
195 | remove (after defaultExtensions and addExtensions). Defaults
|
---|
196 | to None.
|
---|
197 | - emitExtensions - regex matching names of extensions to actually emit
|
---|
198 | interfaces for (though all requested versions are considered when
|
---|
199 | deciding which interfaces to generate). Defaults to None.
|
---|
200 | - emitSpirv - regex matching names of extensions and capabilities
|
---|
201 | to actually emit interfaces for.
|
---|
202 | - emitFormats - regex matching names of formats to actually emit
|
---|
203 | interfaces for.
|
---|
204 | - reparentEnums - move <enum> elements which extend an enumerated
|
---|
205 | type from <feature> or <extension> elements to the target <enums>
|
---|
206 | element. This is required for almost all purposes, but the
|
---|
207 | InterfaceGenerator relies on the list of interfaces in the <feature>
|
---|
208 | or <extension> being complete. Defaults to True.
|
---|
209 | - sortProcedure - takes a list of FeatureInfo objects and sorts
|
---|
210 | them in place to a preferred order in the generated output.
|
---|
211 | - requireCommandAliases - if True, treat command aliases
|
---|
212 | as required dependencies.
|
---|
213 | - requireDepends - whether to follow API dependencies when emitting
|
---|
214 | APIs.
|
---|
215 |
|
---|
216 | Default is
|
---|
217 | - core API versions
|
---|
218 | - Khronos (ARB/KHR/OES) extensions
|
---|
219 | - All other extensions
|
---|
220 | - By core API version number or extension number in each group.
|
---|
221 |
|
---|
222 | The regex patterns can be None or empty, in which case they match
|
---|
223 | nothing."""
|
---|
224 | self.conventions = conventions
|
---|
225 | """may be mandatory for some generators:
|
---|
226 | an object that implements ConventionsBase"""
|
---|
227 |
|
---|
228 | self.filename = filename
|
---|
229 | "basename of file to generate, or None to write to stdout."
|
---|
230 |
|
---|
231 | self.genpath = genpath
|
---|
232 | """path to previously generated files, such as apimap.py"""
|
---|
233 |
|
---|
234 | self.directory = directory
|
---|
235 | "directory in which to generate filename"
|
---|
236 |
|
---|
237 | self.apiname = apiname
|
---|
238 | "string matching `<api>` 'apiname' attribute, e.g. 'gl'."
|
---|
239 |
|
---|
240 | self.mergeApiNames = mergeApiNames
|
---|
241 | "comma separated list of API names to merge into the API specified by 'apiname'"
|
---|
242 |
|
---|
243 | self.profile = profile
|
---|
244 | "string specifying API profile , e.g. 'core', or None."
|
---|
245 |
|
---|
246 | self.versions = self.emptyRegex(versions)
|
---|
247 | """regex matching API versions to process interfaces for.
|
---|
248 | Normally `'.*'` or `'[0-9][.][0-9]'` to match all defined versions."""
|
---|
249 |
|
---|
250 | self.emitversions = self.emptyRegex(emitversions)
|
---|
251 | """regex matching API versions to actually emit
|
---|
252 | interfaces for (though all requested versions are considered
|
---|
253 | when deciding which interfaces to generate). For GL 4.3 glext.h,
|
---|
254 | this might be `'1[.][2-5]|[2-4][.][0-9]'`."""
|
---|
255 |
|
---|
256 | self.defaultExtensions = defaultExtensions
|
---|
257 | """If not None, a string which must in its
|
---|
258 | entirety match the pattern in the "supported" attribute of
|
---|
259 | the `<extension>`. Defaults to None. Usually the same as apiname."""
|
---|
260 |
|
---|
261 | self.addExtensions = self.emptyRegex(addExtensions)
|
---|
262 | """regex matching names of additional extensions
|
---|
263 | to include. Defaults to None."""
|
---|
264 |
|
---|
265 | self.removeExtensions = self.emptyRegex(removeExtensions)
|
---|
266 | """regex matching names of extensions to
|
---|
267 | remove (after defaultExtensions and addExtensions). Defaults
|
---|
268 | to None."""
|
---|
269 |
|
---|
270 | self.emitExtensions = self.emptyRegex(emitExtensions)
|
---|
271 | """regex matching names of extensions to actually emit
|
---|
272 | interfaces for (though all requested versions are considered when
|
---|
273 | deciding which interfaces to generate)."""
|
---|
274 |
|
---|
275 | self.emitSpirv = self.emptyRegex(emitSpirv)
|
---|
276 | """regex matching names of extensions and capabilities
|
---|
277 | to actually emit interfaces for."""
|
---|
278 |
|
---|
279 | self.emitFormats = self.emptyRegex(emitFormats)
|
---|
280 | """regex matching names of formats
|
---|
281 | to actually emit interfaces for."""
|
---|
282 |
|
---|
283 | self.reparentEnums = reparentEnums
|
---|
284 | """boolean specifying whether to remove <enum> elements from
|
---|
285 | <feature> or <extension> when extending an <enums> type."""
|
---|
286 |
|
---|
287 | self.sortProcedure = sortProcedure
|
---|
288 | """takes a list of FeatureInfo objects and sorts
|
---|
289 | them in place to a preferred order in the generated output.
|
---|
290 | Default is core API versions, ARB/KHR/OES extensions, all
|
---|
291 | other extensions, alphabetically within each group."""
|
---|
292 |
|
---|
293 | self.codeGenerator = False
|
---|
294 | """True if this generator makes compilable code"""
|
---|
295 |
|
---|
296 | self.registry = None
|
---|
297 | """Populated later with the registry object."""
|
---|
298 |
|
---|
299 | self.requireCommandAliases = requireCommandAliases
|
---|
300 | """True if alias= attributes of <command> tags are transitively
|
---|
301 | required."""
|
---|
302 |
|
---|
303 | self.requireDepends = requireDepends
|
---|
304 | """True if dependencies of API tags are transitively required."""
|
---|
305 |
|
---|
306 | def emptyRegex(self, pat):
|
---|
307 | """Substitute a regular expression which matches no version
|
---|
308 | or extension names for None or the empty string."""
|
---|
309 | if not pat:
|
---|
310 | return '_nomatch_^'
|
---|
311 |
|
---|
312 | return pat
|
---|
313 |
|
---|
314 |
|
---|
315 | class OutputGenerator:
|
---|
316 | """Generate specified API interfaces in a specific style, such as a C header.
|
---|
317 |
|
---|
318 | Base class for generating API interfaces.
|
---|
319 | Manages basic logic, logging, and output file control.
|
---|
320 | Derived classes actually generate formatted output.
|
---|
321 | """
|
---|
322 |
|
---|
323 | # categoryToPath - map XML 'category' to include file directory name
|
---|
324 | categoryToPath = {
|
---|
325 | 'bitmask': 'flags',
|
---|
326 | 'enum': 'enums',
|
---|
327 | 'funcpointer': 'funcpointers',
|
---|
328 | 'handle': 'handles',
|
---|
329 | 'define': 'defines',
|
---|
330 | 'basetype': 'basetypes',
|
---|
331 | }
|
---|
332 |
|
---|
333 | def breakName(self, name, msg):
|
---|
334 | """Break into debugger if this is a special name"""
|
---|
335 |
|
---|
336 | # List of string names to break on
|
---|
337 | bad = (
|
---|
338 | )
|
---|
339 |
|
---|
340 | if name in bad and True:
|
---|
341 | print('breakName {}: {}'.format(name, msg))
|
---|
342 | pdb.set_trace()
|
---|
343 |
|
---|
344 | def __init__(self, errFile=sys.stderr, warnFile=sys.stderr, diagFile=sys.stdout):
|
---|
345 | """Constructor
|
---|
346 |
|
---|
347 | - errFile, warnFile, diagFile - file handles to write errors,
|
---|
348 | warnings, diagnostics to. May be None to not write."""
|
---|
349 | self.outFile = None
|
---|
350 | self.errFile = errFile
|
---|
351 | self.warnFile = warnFile
|
---|
352 | self.diagFile = diagFile
|
---|
353 | # Internal state
|
---|
354 | self.featureName = None
|
---|
355 | """The current feature name being generated."""
|
---|
356 |
|
---|
357 | self.genOpts = None
|
---|
358 | """The GeneratorOptions subclass instance."""
|
---|
359 |
|
---|
360 | self.registry = None
|
---|
361 | """The specification registry object."""
|
---|
362 |
|
---|
363 | self.featureDictionary = {}
|
---|
364 | """The dictionary of dictionaries of API features."""
|
---|
365 |
|
---|
366 | # Used for extension enum value generation
|
---|
367 | self.extBase = 1000000000
|
---|
368 | self.extBlockSize = 1000
|
---|
369 | self.madeDirs = {}
|
---|
370 |
|
---|
371 | # API dictionary, which may be loaded by the beginFile method of
|
---|
372 | # derived generators.
|
---|
373 | self.apidict = None
|
---|
374 |
|
---|
375 | # File suffix for generated files, set in beginFile below.
|
---|
376 | self.file_suffix = ''
|
---|
377 |
|
---|
378 | def logMsg(self, level, *args):
|
---|
379 | """Write a message of different categories to different
|
---|
380 | destinations.
|
---|
381 |
|
---|
382 | - `level`
|
---|
383 | - 'diag' (diagnostic, voluminous)
|
---|
384 | - 'warn' (warning)
|
---|
385 | - 'error' (fatal error - raises exception after logging)
|
---|
386 |
|
---|
387 | - `*args` - print()-style arguments to direct to corresponding log"""
|
---|
388 | if level == 'error':
|
---|
389 | strfile = io.StringIO()
|
---|
390 | write('ERROR:', *args, file=strfile)
|
---|
391 | if self.errFile is not None:
|
---|
392 | write(strfile.getvalue(), file=self.errFile)
|
---|
393 | raise UserWarning(strfile.getvalue())
|
---|
394 | elif level == 'warn':
|
---|
395 | if self.warnFile is not None:
|
---|
396 | write('WARNING:', *args, file=self.warnFile)
|
---|
397 | elif level == 'diag':
|
---|
398 | if self.diagFile is not None:
|
---|
399 | write('DIAG:', *args, file=self.diagFile)
|
---|
400 | else:
|
---|
401 | raise UserWarning(
|
---|
402 | '*** FATAL ERROR in Generator.logMsg: unknown level:' + level)
|
---|
403 |
|
---|
404 | def enumToValue(self, elem, needsNum, bitwidth = 32,
|
---|
405 | forceSuffix = False, parent_for_alias_dereference=None):
|
---|
406 | """Parse and convert an `<enum>` tag into a value.
|
---|
407 |
|
---|
408 | - elem - <enum> Element
|
---|
409 | - needsNum - generate a numeric representation of the element value
|
---|
410 | - bitwidth - size of the numeric representation in bits (32 or 64)
|
---|
411 | - forceSuffix - if True, always use a 'U' / 'ULL' suffix on integers
|
---|
412 | - parent_for_alias_dereference - if not None, an Element containing
|
---|
413 | the parent of elem, used to look for elements this is an alias of
|
---|
414 |
|
---|
415 | Returns a list:
|
---|
416 |
|
---|
417 | - first element - integer representation of the value, or None
|
---|
418 | if needsNum is False. The value must be a legal number
|
---|
419 | if needsNum is True.
|
---|
420 | - second element - string representation of the value
|
---|
421 |
|
---|
422 | There are several possible representations of values.
|
---|
423 |
|
---|
424 | - A 'value' attribute simply contains the value.
|
---|
425 | - A 'bitpos' attribute defines a value by specifying the bit
|
---|
426 | position which is set in that value.
|
---|
427 | - An 'offset','extbase','extends' triplet specifies a value
|
---|
428 | as an offset to a base value defined by the specified
|
---|
429 | 'extbase' extension name, which is then cast to the
|
---|
430 | typename specified by 'extends'. This requires probing
|
---|
431 | the registry database, and imbeds knowledge of the
|
---|
432 | API extension enum scheme in this function.
|
---|
433 | - An 'alias' attribute contains the name of another enum
|
---|
434 | which this is an alias of. The other enum must be
|
---|
435 | declared first when emitting this enum."""
|
---|
436 | if self.genOpts is None:
|
---|
437 | raise MissingGeneratorOptionsError()
|
---|
438 | if self.genOpts.conventions is None:
|
---|
439 | raise MissingGeneratorOptionsConventionsError()
|
---|
440 |
|
---|
441 | name = elem.get('name')
|
---|
442 | numVal = None
|
---|
443 | if 'value' in elem.keys():
|
---|
444 | value = elem.get('value')
|
---|
445 | # print('About to translate value =', value, 'type =', type(value))
|
---|
446 | if needsNum:
|
---|
447 | numVal = int(value, 0)
|
---|
448 | # If there is a non-integer, numeric 'type' attribute (e.g. 'u' or
|
---|
449 | # 'ull'), append it to the string value.
|
---|
450 | # t = enuminfo.elem.get('type')
|
---|
451 | # if t is not None and t != '' and t != 'i' and t != 's':
|
---|
452 | # value += enuminfo.type
|
---|
453 | if forceSuffix:
|
---|
454 | if bitwidth == 64:
|
---|
455 | value = value + 'ULL'
|
---|
456 | else:
|
---|
457 | value = value + 'U'
|
---|
458 | self.logMsg('diag', 'Enum', name, '-> value [', numVal, ',', value, ']')
|
---|
459 | return [numVal, value]
|
---|
460 | if 'bitpos' in elem.keys():
|
---|
461 | value = elem.get('bitpos')
|
---|
462 | bitpos = int(value, 0)
|
---|
463 | numVal = 1 << bitpos
|
---|
464 | value = '0x%08x' % numVal
|
---|
465 | if bitwidth == 64 or bitpos >= 32:
|
---|
466 | value = value + 'ULL'
|
---|
467 | elif forceSuffix:
|
---|
468 | value = value + 'U'
|
---|
469 | self.logMsg('diag', 'Enum', name, '-> bitpos [', numVal, ',', value, ']')
|
---|
470 | return [numVal, value]
|
---|
471 | if 'offset' in elem.keys():
|
---|
472 | # Obtain values in the mapping from the attributes
|
---|
473 | enumNegative = False
|
---|
474 | offset = int(elem.get('offset'), 0)
|
---|
475 | extnumber = int(elem.get('extnumber'), 0)
|
---|
476 | extends = elem.get('extends')
|
---|
477 | if 'dir' in elem.keys():
|
---|
478 | enumNegative = True
|
---|
479 | self.logMsg('diag', 'Enum', name, 'offset =', offset,
|
---|
480 | 'extnumber =', extnumber, 'extends =', extends,
|
---|
481 | 'enumNegative =', enumNegative)
|
---|
482 | # Now determine the actual enumerant value, as defined
|
---|
483 | # in the "Layers and Extensions" appendix of the spec.
|
---|
484 | numVal = self.extBase + (extnumber - 1) * self.extBlockSize + offset
|
---|
485 | if enumNegative:
|
---|
486 | numVal *= -1
|
---|
487 | value = '%d' % numVal
|
---|
488 | # More logic needed!
|
---|
489 | self.logMsg('diag', 'Enum', name, '-> offset [', numVal, ',', value, ']')
|
---|
490 | return [numVal, value]
|
---|
491 | if 'alias' in elem.keys():
|
---|
492 | alias_of = elem.get('alias')
|
---|
493 | if parent_for_alias_dereference is None:
|
---|
494 | return (None, alias_of)
|
---|
495 | siblings = parent_for_alias_dereference.findall('enum')
|
---|
496 | for sib in siblings:
|
---|
497 | sib_name = sib.get('name')
|
---|
498 | if sib_name == alias_of:
|
---|
499 | return self.enumToValue(sib, needsNum)
|
---|
500 | raise RuntimeError("Could not find the aliased enum value")
|
---|
501 | return [None, None]
|
---|
502 |
|
---|
503 | def checkDuplicateEnums(self, enums):
|
---|
504 | """Check enumerated values for duplicates.
|
---|
505 |
|
---|
506 | - enums - list of `<enum>` Elements
|
---|
507 |
|
---|
508 | returns the list with duplicates stripped"""
|
---|
509 | # Dictionaries indexed by name and numeric value.
|
---|
510 | # Entries are [ Element, numVal, strVal ] matching name or value
|
---|
511 |
|
---|
512 | nameMap = {}
|
---|
513 | valueMap = {}
|
---|
514 |
|
---|
515 | stripped = []
|
---|
516 | for elem in enums:
|
---|
517 | name = elem.get('name')
|
---|
518 | (numVal, strVal) = self.enumToValue(elem, True)
|
---|
519 |
|
---|
520 | if name in nameMap:
|
---|
521 | # Duplicate name found; check values
|
---|
522 | (name2, numVal2, strVal2) = nameMap[name]
|
---|
523 |
|
---|
524 | # Duplicate enum values for the same name are benign. This
|
---|
525 | # happens when defining the same enum conditionally in
|
---|
526 | # several extension blocks.
|
---|
527 | if (strVal2 == strVal or (numVal is not None
|
---|
528 | and numVal == numVal2)):
|
---|
529 | True
|
---|
530 | # self.logMsg('info', 'checkDuplicateEnums: Duplicate enum (' + name +
|
---|
531 | # ') found with the same value:' + strVal)
|
---|
532 | else:
|
---|
533 | self.logMsg('warn', 'checkDuplicateEnums: Duplicate enum (' + name
|
---|
534 | + ') found with different values:' + strVal
|
---|
535 | + ' and ' + strVal2)
|
---|
536 |
|
---|
537 | # Do not add the duplicate to the returned list
|
---|
538 | continue
|
---|
539 | elif numVal in valueMap:
|
---|
540 | # Duplicate value found (such as an alias); report it, but
|
---|
541 | # still add this enum to the list.
|
---|
542 | (name2, numVal2, strVal2) = valueMap[numVal]
|
---|
543 |
|
---|
544 | msg = 'Two enums found with the same value: {} = {} = {}'.format(
|
---|
545 | name, name2.get('name'), strVal)
|
---|
546 | self.logMsg('error', msg)
|
---|
547 |
|
---|
548 | # Track this enum to detect followon duplicates
|
---|
549 | nameMap[name] = [elem, numVal, strVal]
|
---|
550 | if numVal is not None:
|
---|
551 | valueMap[numVal] = [elem, numVal, strVal]
|
---|
552 |
|
---|
553 | # Add this enum to the list
|
---|
554 | stripped.append(elem)
|
---|
555 |
|
---|
556 | # Return the list
|
---|
557 | return stripped
|
---|
558 |
|
---|
559 | def misracstyle(self):
|
---|
560 | return False;
|
---|
561 |
|
---|
562 | def misracppstyle(self):
|
---|
563 | return False;
|
---|
564 |
|
---|
565 | def buildEnumCDecl(self, expand, groupinfo, groupName):
|
---|
566 | """Generate the C declaration for an enum"""
|
---|
567 | if self.genOpts is None:
|
---|
568 | raise MissingGeneratorOptionsError()
|
---|
569 | if self.genOpts.conventions is None:
|
---|
570 | raise MissingGeneratorOptionsConventionsError()
|
---|
571 |
|
---|
572 | groupElem = groupinfo.elem
|
---|
573 |
|
---|
574 | # Determine the required bit width for the enum group.
|
---|
575 | # 32 is the default, which generates C enum types for the values.
|
---|
576 | bitwidth = 32
|
---|
577 |
|
---|
578 | # If the constFlagBits preference is set, 64 is the default for bitmasks
|
---|
579 | if self.genOpts.conventions.constFlagBits and groupElem.get('type') == 'bitmask':
|
---|
580 | bitwidth = 64
|
---|
581 |
|
---|
582 | # Check for an explicitly defined bitwidth, which will override any defaults.
|
---|
583 | if groupElem.get('bitwidth'):
|
---|
584 | try:
|
---|
585 | bitwidth = int(groupElem.get('bitwidth'))
|
---|
586 | except ValueError as ve:
|
---|
587 | self.logMsg('error', 'Invalid value for bitwidth attribute (', groupElem.get('bitwidth'), ') for ', groupName, ' - must be an integer value\n')
|
---|
588 | exit(1)
|
---|
589 |
|
---|
590 | usebitmask = False
|
---|
591 | usedefine = False
|
---|
592 |
|
---|
593 | # Bitmask flags can be generated as either "static const uint{32,64}_t" values,
|
---|
594 | # or as 32-bit C enums. 64-bit types must use uint64_t values.
|
---|
595 | if groupElem.get('type') == 'bitmask':
|
---|
596 | if bitwidth > 32 or self.misracppstyle():
|
---|
597 | usebitmask = True
|
---|
598 | if self.misracstyle():
|
---|
599 | usedefine = True
|
---|
600 |
|
---|
601 | if usedefine or usebitmask:
|
---|
602 | # Validate the bitwidth and generate values appropriately
|
---|
603 | if bitwidth > 64:
|
---|
604 | self.logMsg('error', 'Invalid value for bitwidth attribute (', groupElem.get('bitwidth'), ') for bitmask type ', groupName, ' - must be less than or equal to 64\n')
|
---|
605 | exit(1)
|
---|
606 | else:
|
---|
607 | return self.buildEnumCDecl_BitmaskOrDefine(groupinfo, groupName, bitwidth, usedefine)
|
---|
608 | else:
|
---|
609 | # Validate the bitwidth and generate values appropriately
|
---|
610 | if bitwidth > 32:
|
---|
611 | self.logMsg('error', 'Invalid value for bitwidth attribute (', groupElem.get('bitwidth'), ') for enum type ', groupName, ' - must be less than or equal to 32\n')
|
---|
612 | exit(1)
|
---|
613 | else:
|
---|
614 | return self.buildEnumCDecl_Enum(expand, groupinfo, groupName)
|
---|
615 |
|
---|
616 | def buildEnumCDecl_BitmaskOrDefine(self, groupinfo, groupName, bitwidth, usedefine):
|
---|
617 | """Generate the C declaration for an "enum" that is actually a
|
---|
618 | set of flag bits"""
|
---|
619 | groupElem = groupinfo.elem
|
---|
620 | flagTypeName = groupElem.get('name')
|
---|
621 |
|
---|
622 | # Prefix
|
---|
623 | body = "// Flag bits for " + flagTypeName + "\n"
|
---|
624 |
|
---|
625 | if bitwidth == 64:
|
---|
626 | body += "typedef VkFlags64 %s;\n" % flagTypeName;
|
---|
627 | else:
|
---|
628 | body += "typedef VkFlags %s;\n" % flagTypeName;
|
---|
629 |
|
---|
630 | # Maximum allowable value for a flag (unsigned 64-bit integer)
|
---|
631 | maxValidValue = 2**(64) - 1
|
---|
632 | minValidValue = 0
|
---|
633 |
|
---|
634 | # Get a list of nested 'enum' tags.
|
---|
635 | enums = groupElem.findall('enum')
|
---|
636 |
|
---|
637 | # Check for and report duplicates, and return a list with them
|
---|
638 | # removed.
|
---|
639 | enums = self.checkDuplicateEnums(enums)
|
---|
640 |
|
---|
641 | # Accumulate non-numeric enumerant values separately and append
|
---|
642 | # them following the numeric values, to allow for aliases.
|
---|
643 | # NOTE: this does not do a topological sort yet, so aliases of
|
---|
644 | # aliases can still get in the wrong order.
|
---|
645 | aliasText = ''
|
---|
646 |
|
---|
647 | # Loop over the nested 'enum' tags.
|
---|
648 | for elem in enums:
|
---|
649 | # Convert the value to an integer and use that to track min/max.
|
---|
650 | # Values of form -(number) are accepted but nothing more complex.
|
---|
651 | # Should catch exceptions here for more complex constructs. Not yet.
|
---|
652 | (numVal, strVal) = self.enumToValue(elem, True, bitwidth, True)
|
---|
653 | name = elem.get('name')
|
---|
654 |
|
---|
655 | # Range check for the enum value
|
---|
656 | if numVal is not None and (numVal > maxValidValue or numVal < minValidValue):
|
---|
657 | self.logMsg('error', 'Allowable range for flag types in C is [', minValidValue, ',', maxValidValue, '], but', name, 'flag has a value outside of this (', strVal, ')\n')
|
---|
658 | exit(1)
|
---|
659 |
|
---|
660 | decl = self.genRequirements(name, mustBeFound = False)
|
---|
661 |
|
---|
662 | if self.isEnumRequired(elem):
|
---|
663 | protect = elem.get('protect')
|
---|
664 | if protect is not None:
|
---|
665 | body += '#ifdef {}\n'.format(protect)
|
---|
666 |
|
---|
667 | if usedefine:
|
---|
668 | decl += "#define {} {}\n".format(name, strVal)
|
---|
669 | elif self.misracppstyle():
|
---|
670 | decl += "static constexpr {} {} {{{}}};\n".format(flagTypeName, name, strVal)
|
---|
671 | else:
|
---|
672 | # Some C compilers only allow initializing a 'static const' variable with a literal value.
|
---|
673 | # So initializing an alias from another 'static const' value would fail to compile.
|
---|
674 | # Work around this by chasing the aliases to get the actual value.
|
---|
675 | while numVal is None:
|
---|
676 | alias = self.registry.tree.find("enums/enum[@name='" + strVal + "']")
|
---|
677 | if alias is not None:
|
---|
678 | (numVal, strVal) = self.enumToValue(alias, True, bitwidth, True)
|
---|
679 | else:
|
---|
680 | self.logMsg('error', 'No such alias {} for enum {}'.format(strVal, name))
|
---|
681 | decl += "static const {} {} = {};\n".format(flagTypeName, name, strVal)
|
---|
682 |
|
---|
683 | if numVal is not None:
|
---|
684 | body += decl
|
---|
685 | else:
|
---|
686 | aliasText += decl
|
---|
687 |
|
---|
688 | if protect is not None:
|
---|
689 | body += '#endif\n'
|
---|
690 |
|
---|
691 | # Now append the non-numeric enumerant values
|
---|
692 | body += aliasText
|
---|
693 |
|
---|
694 | # Postfix
|
---|
695 |
|
---|
696 | return ("bitmask", body)
|
---|
697 |
|
---|
698 | def buildEnumCDecl_Enum(self, expand, groupinfo, groupName):
|
---|
699 | """Generate the C declaration for an enumerated type"""
|
---|
700 | groupElem = groupinfo.elem
|
---|
701 |
|
---|
702 | # Break the group name into prefix and suffix portions for range
|
---|
703 | # enum generation
|
---|
704 | expandName = re.sub(r'([0-9]+|[a-z_])([A-Z0-9])', r'\1_\2', groupName).upper()
|
---|
705 | expandPrefix = expandName
|
---|
706 | expandSuffix = ''
|
---|
707 | expandSuffixMatch = re.search(r'[A-Z][A-Z]+$', groupName)
|
---|
708 | if expandSuffixMatch:
|
---|
709 | expandSuffix = '_' + expandSuffixMatch.group()
|
---|
710 | # Strip off the suffix from the prefix
|
---|
711 | expandPrefix = expandName.rsplit(expandSuffix, 1)[0]
|
---|
712 |
|
---|
713 | # Prefix
|
---|
714 | body = ["typedef enum %s {" % groupName]
|
---|
715 |
|
---|
716 | # @@ Should use the type="bitmask" attribute instead
|
---|
717 | isEnum = ('FLAG_BITS' not in expandPrefix)
|
---|
718 |
|
---|
719 | # Allowable range for a C enum - which is that of a signed 32-bit integer
|
---|
720 | maxValidValue = 2**(32 - 1) - 1
|
---|
721 | minValidValue = (maxValidValue * -1) - 1
|
---|
722 |
|
---|
723 | # Get a list of nested 'enum' tags.
|
---|
724 | enums = groupElem.findall('enum')
|
---|
725 |
|
---|
726 | # Check for and report duplicates, and return a list with them
|
---|
727 | # removed.
|
---|
728 | enums = self.checkDuplicateEnums(enums)
|
---|
729 |
|
---|
730 | # Loop over the nested 'enum' tags. Keep track of the minimum and
|
---|
731 | # maximum numeric values, if they can be determined; but only for
|
---|
732 | # core API enumerants, not extension enumerants. This is inferred
|
---|
733 | # by looking for 'extends' attributes.
|
---|
734 | minName = None
|
---|
735 |
|
---|
736 | # Accumulate non-numeric enumerant values separately and append
|
---|
737 | # them following the numeric values, to allow for aliases.
|
---|
738 | # NOTE: this does not do a topological sort yet, so aliases of
|
---|
739 | # aliases can still get in the wrong order.
|
---|
740 | aliasText = []
|
---|
741 |
|
---|
742 | maxName = None
|
---|
743 | minValue = None
|
---|
744 | maxValue = None
|
---|
745 | for elem in enums:
|
---|
746 | # Convert the value to an integer and use that to track min/max.
|
---|
747 | # Values of form -(number) are accepted but nothing more complex.
|
---|
748 | # Should catch exceptions here for more complex constructs. Not yet.
|
---|
749 | (numVal, strVal) = self.enumToValue(elem, True)
|
---|
750 | name = elem.get('name')
|
---|
751 |
|
---|
752 | # Extension enumerants are only included if they are required
|
---|
753 | if self.isEnumRequired(elem):
|
---|
754 | decl = ''
|
---|
755 |
|
---|
756 | protect = elem.get('protect')
|
---|
757 | if protect is not None:
|
---|
758 | decl += '#ifdef {}\n'.format(protect)
|
---|
759 |
|
---|
760 | # Indent requirements comment, if there is one
|
---|
761 | requirements = self.genRequirements(name, mustBeFound = False)
|
---|
762 | if requirements != '':
|
---|
763 | requirements = ' ' + requirements
|
---|
764 | decl += requirements
|
---|
765 | decl += ' {} = {},'.format(name, strVal)
|
---|
766 |
|
---|
767 | if protect is not None:
|
---|
768 | decl += '\n#endif'
|
---|
769 |
|
---|
770 | if numVal is not None:
|
---|
771 | body.append(decl)
|
---|
772 | else:
|
---|
773 | aliasText.append(decl)
|
---|
774 |
|
---|
775 | # Range check for the enum value
|
---|
776 | if numVal is not None and (numVal > maxValidValue or numVal < minValidValue):
|
---|
777 | self.logMsg('error', 'Allowable range for C enum types is [', minValidValue, ',', maxValidValue, '], but', name, 'has a value outside of this (', strVal, ')\n')
|
---|
778 | exit(1)
|
---|
779 |
|
---|
780 | # Do not track min/max for non-numbers (numVal is None)
|
---|
781 | if isEnum and numVal is not None and elem.get('extends') is None:
|
---|
782 | if minName is None:
|
---|
783 | minName = maxName = name
|
---|
784 | minValue = maxValue = numVal
|
---|
785 | elif minValue is None or numVal < minValue:
|
---|
786 | minName = name
|
---|
787 | minValue = numVal
|
---|
788 | elif maxValue is None or numVal > maxValue:
|
---|
789 | maxName = name
|
---|
790 | maxValue = numVal
|
---|
791 |
|
---|
792 | # Now append the non-numeric enumerant values
|
---|
793 | body.extend(aliasText)
|
---|
794 |
|
---|
795 | # Generate min/max value tokens - legacy use case.
|
---|
796 | if isEnum and expand:
|
---|
797 | body.extend((f' {expandPrefix}_BEGIN_RANGE{expandSuffix} = {minName},',
|
---|
798 | f' {expandPrefix}_END_RANGE{expandSuffix} = {maxName},',
|
---|
799 | f' {expandPrefix}_RANGE_SIZE{expandSuffix} = ({maxName} - {minName} + 1),'))
|
---|
800 |
|
---|
801 | # Generate a range-padding value to ensure the enum is 32 bits, but
|
---|
802 | # only in code generators, so it does not appear in documentation
|
---|
803 | if (self.genOpts.codeGenerator or
|
---|
804 | self.conventions.generate_max_enum_in_docs):
|
---|
805 | body.append(f' {expandPrefix}_MAX_ENUM{expandSuffix} = 0x7FFFFFFF')
|
---|
806 |
|
---|
807 | # Postfix
|
---|
808 | body.append("} %s;" % groupName)
|
---|
809 |
|
---|
810 | # Determine appropriate section for this declaration
|
---|
811 | if groupElem.get('type') == 'bitmask':
|
---|
812 | section = 'bitmask'
|
---|
813 | else:
|
---|
814 | section = 'group'
|
---|
815 |
|
---|
816 | return (section, '\n'.join(body))
|
---|
817 |
|
---|
818 | def buildConstantCDecl(self, enuminfo, name, alias):
|
---|
819 | """Generate the C declaration for a constant (a single <enum>
|
---|
820 | value).
|
---|
821 |
|
---|
822 | <enum> tags may specify their values in several ways, but are
|
---|
823 | usually just integers or floating-point numbers."""
|
---|
824 |
|
---|
825 | (_, strVal) = self.enumToValue(enuminfo.elem, False)
|
---|
826 |
|
---|
827 | if self.misracppstyle() and enuminfo.elem.get('type') and not alias:
|
---|
828 | # Generate e.g.: static constexpr uint32_t x = ~static_cast<uint32_t>(1U);
|
---|
829 | # This appeases MISRA "underlying type" rules.
|
---|
830 | typeStr = enuminfo.elem.get('type');
|
---|
831 | invert = '~' in strVal
|
---|
832 | number = strVal.strip("()~UL")
|
---|
833 | if typeStr != "float":
|
---|
834 | number += 'U'
|
---|
835 | strVal = "~" if invert else ""
|
---|
836 | strVal += "static_cast<" + typeStr + ">(" + number + ")"
|
---|
837 | body = 'static constexpr ' + typeStr.ljust(9) + name.ljust(33) + ' {' + strVal + '};'
|
---|
838 | elif enuminfo.elem.get('type') and not alias:
|
---|
839 | # Generate e.g.: #define x (~0ULL)
|
---|
840 | typeStr = enuminfo.elem.get('type');
|
---|
841 | invert = '~' in strVal
|
---|
842 | paren = '(' in strVal
|
---|
843 | number = strVal.strip("()~UL")
|
---|
844 | if typeStr != "float":
|
---|
845 | if typeStr == "uint64_t":
|
---|
846 | number += 'ULL'
|
---|
847 | else:
|
---|
848 | number += 'U'
|
---|
849 | strVal = "~" if invert else ""
|
---|
850 | strVal += number
|
---|
851 | if paren:
|
---|
852 | strVal = "(" + strVal + ")";
|
---|
853 | body = '#define ' + name.ljust(33) + ' ' + strVal;
|
---|
854 | else:
|
---|
855 | body = '#define ' + name.ljust(33) + ' ' + strVal
|
---|
856 |
|
---|
857 | return body
|
---|
858 |
|
---|
859 | def makeDir(self, path):
|
---|
860 | """Create a directory, if not already done.
|
---|
861 |
|
---|
862 | Generally called from derived generators creating hierarchies."""
|
---|
863 | self.logMsg('diag', 'OutputGenerator::makeDir(' + path + ')')
|
---|
864 | if path not in self.madeDirs:
|
---|
865 | # This can get race conditions with multiple writers, see
|
---|
866 | # https://stackoverflow.com/questions/273192/
|
---|
867 | if not os.path.exists(path):
|
---|
868 | os.makedirs(path)
|
---|
869 | self.madeDirs[path] = None
|
---|
870 |
|
---|
871 | def beginFile(self, genOpts):
|
---|
872 | """Start a new interface file
|
---|
873 |
|
---|
874 | - genOpts - GeneratorOptions controlling what is generated and how"""
|
---|
875 |
|
---|
876 | self.genOpts = genOpts
|
---|
877 | if self.genOpts is None:
|
---|
878 | raise MissingGeneratorOptionsError()
|
---|
879 | if self.genOpts.conventions is None:
|
---|
880 | raise MissingGeneratorOptionsConventionsError()
|
---|
881 | self.should_insert_may_alias_macro = \
|
---|
882 | self.genOpts.conventions.should_insert_may_alias_macro(self.genOpts)
|
---|
883 | self.file_suffix = self.genOpts.conventions.file_suffix
|
---|
884 |
|
---|
885 | # Try to import the API dictionary, apimap.py, if it exists. Nothing
|
---|
886 | # in apimap.py cannot be extracted directly from the XML, and in the
|
---|
887 | # future we should do that.
|
---|
888 | if self.genOpts.genpath is not None:
|
---|
889 | try:
|
---|
890 | sys.path.insert(0, self.genOpts.genpath)
|
---|
891 | import apimap
|
---|
892 | self.apidict = apimap
|
---|
893 | except ImportError:
|
---|
894 | self.apidict = None
|
---|
895 |
|
---|
896 | self.conventions = genOpts.conventions
|
---|
897 |
|
---|
898 | # Open a temporary file for accumulating output.
|
---|
899 | if self.genOpts.filename is not None:
|
---|
900 | self.outFile = tempfile.NamedTemporaryFile(mode='w', encoding='utf-8', newline='\n', delete=False)
|
---|
901 | else:
|
---|
902 | self.outFile = sys.stdout
|
---|
903 |
|
---|
904 | def endFile(self):
|
---|
905 | if self.errFile:
|
---|
906 | self.errFile.flush()
|
---|
907 | if self.warnFile:
|
---|
908 | self.warnFile.flush()
|
---|
909 | if self.diagFile:
|
---|
910 | self.diagFile.flush()
|
---|
911 | if self.outFile:
|
---|
912 | self.outFile.flush()
|
---|
913 | if self.outFile != sys.stdout and self.outFile != sys.stderr:
|
---|
914 | self.outFile.close()
|
---|
915 |
|
---|
916 | if self.genOpts is None:
|
---|
917 | raise MissingGeneratorOptionsError()
|
---|
918 |
|
---|
919 | # On successfully generating output, move the temporary file to the
|
---|
920 | # target file.
|
---|
921 | if self.genOpts.filename is not None:
|
---|
922 | if sys.platform == 'win32':
|
---|
923 | directory = Path(self.genOpts.directory)
|
---|
924 | if not Path.exists(directory):
|
---|
925 | os.makedirs(directory)
|
---|
926 | shutil.copy(self.outFile.name, self.genOpts.directory + '/' + self.genOpts.filename)
|
---|
927 | os.remove(self.outFile.name)
|
---|
928 | self.genOpts = None
|
---|
929 |
|
---|
930 | def beginFeature(self, interface, emit):
|
---|
931 | """Write interface for a feature and tag generated features as having been done.
|
---|
932 |
|
---|
933 | - interface - element for the `<version>` / `<extension>` to generate
|
---|
934 | - emit - actually write to the header only when True"""
|
---|
935 | self.emit = emit
|
---|
936 | self.featureName = interface.get('name')
|
---|
937 | # If there is an additional 'protect' attribute in the feature, save it
|
---|
938 | self.featureExtraProtect = interface.get('protect')
|
---|
939 |
|
---|
940 | def endFeature(self):
|
---|
941 | """Finish an interface file, closing it when done.
|
---|
942 |
|
---|
943 | Derived classes responsible for emitting feature"""
|
---|
944 | self.featureName = None
|
---|
945 | self.featureExtraProtect = None
|
---|
946 |
|
---|
947 | def genRequirements(self, name, mustBeFound = True):
|
---|
948 | """Generate text showing what core versions and extensions introduce
|
---|
949 | an API. This exists in the base Generator class because it is used by
|
---|
950 | the shared enumerant-generating interfaces (buildEnumCDecl, etc.).
|
---|
951 | Here it returns an empty string for most generators, but can be
|
---|
952 | overridden by e.g. DocGenerator.
|
---|
953 |
|
---|
954 | - name - name of the API
|
---|
955 | - mustBeFound - If True, when requirements for 'name' cannot be
|
---|
956 | determined, a warning comment is generated.
|
---|
957 | """
|
---|
958 |
|
---|
959 | return ''
|
---|
960 |
|
---|
961 | def validateFeature(self, featureType, featureName):
|
---|
962 | """Validate we are generating something only inside a `<feature>` tag"""
|
---|
963 | if self.featureName is None:
|
---|
964 | raise UserWarning('Attempt to generate', featureType,
|
---|
965 | featureName, 'when not in feature')
|
---|
966 |
|
---|
967 | def genType(self, typeinfo, name, alias):
|
---|
968 | """Generate interface for a type
|
---|
969 |
|
---|
970 | - typeinfo - TypeInfo for a type
|
---|
971 |
|
---|
972 | Extend to generate as desired in your derived class."""
|
---|
973 | self.validateFeature('type', name)
|
---|
974 |
|
---|
975 | def genStruct(self, typeinfo, typeName, alias):
|
---|
976 | """Generate interface for a C "struct" type.
|
---|
977 |
|
---|
978 | - typeinfo - TypeInfo for a type interpreted as a struct
|
---|
979 |
|
---|
980 | Extend to generate as desired in your derived class."""
|
---|
981 | self.validateFeature('struct', typeName)
|
---|
982 |
|
---|
983 | # The mixed-mode <member> tags may contain no-op <comment> tags.
|
---|
984 | # It is convenient to remove them here where all output generators
|
---|
985 | # will benefit.
|
---|
986 | for member in typeinfo.elem.findall('.//member'):
|
---|
987 | for comment in member.findall('comment'):
|
---|
988 | member.remove(comment)
|
---|
989 |
|
---|
990 | def genGroup(self, groupinfo, groupName, alias):
|
---|
991 | """Generate interface for a group of enums (C "enum")
|
---|
992 |
|
---|
993 | - groupinfo - GroupInfo for a group.
|
---|
994 |
|
---|
995 | Extend to generate as desired in your derived class."""
|
---|
996 |
|
---|
997 | self.validateFeature('group', groupName)
|
---|
998 |
|
---|
999 | def genEnum(self, enuminfo, typeName, alias):
|
---|
1000 | """Generate interface for an enum (constant).
|
---|
1001 |
|
---|
1002 | - enuminfo - EnumInfo for an enum
|
---|
1003 | - name - enum name
|
---|
1004 |
|
---|
1005 | Extend to generate as desired in your derived class."""
|
---|
1006 | self.validateFeature('enum', typeName)
|
---|
1007 |
|
---|
1008 | def genCmd(self, cmd, cmdinfo, alias):
|
---|
1009 | """Generate interface for a command.
|
---|
1010 |
|
---|
1011 | - cmdinfo - CmdInfo for a command
|
---|
1012 |
|
---|
1013 | Extend to generate as desired in your derived class."""
|
---|
1014 | self.validateFeature('command', cmdinfo)
|
---|
1015 |
|
---|
1016 | def genSpirv(self, spirv, spirvinfo, alias):
|
---|
1017 | """Generate interface for a spirv element.
|
---|
1018 |
|
---|
1019 | - spirvinfo - SpirvInfo for a command
|
---|
1020 |
|
---|
1021 | Extend to generate as desired in your derived class."""
|
---|
1022 | return
|
---|
1023 |
|
---|
1024 | def genFormat(self, format, formatinfo, alias):
|
---|
1025 | """Generate interface for a format element.
|
---|
1026 |
|
---|
1027 | - formatinfo - FormatInfo
|
---|
1028 |
|
---|
1029 | Extend to generate as desired in your derived class."""
|
---|
1030 | return
|
---|
1031 |
|
---|
1032 | def genSyncStage(self, stageinfo):
|
---|
1033 | """Generate interface for a sync stage element.
|
---|
1034 |
|
---|
1035 | - stageinfo - SyncStageInfo
|
---|
1036 |
|
---|
1037 | Extend to generate as desired in your derived class."""
|
---|
1038 | return
|
---|
1039 |
|
---|
1040 | def genSyncAccess(self, accessinfo):
|
---|
1041 | """Generate interface for a sync stage element.
|
---|
1042 |
|
---|
1043 | - accessinfo - AccessInfo
|
---|
1044 |
|
---|
1045 | Extend to generate as desired in your derived class."""
|
---|
1046 | return
|
---|
1047 |
|
---|
1048 | def genSyncPipeline(self, pipelineinfo):
|
---|
1049 | """Generate interface for a sync stage element.
|
---|
1050 |
|
---|
1051 | - pipelineinfo - SyncPipelineInfo
|
---|
1052 |
|
---|
1053 | Extend to generate as desired in your derived class."""
|
---|
1054 | return
|
---|
1055 |
|
---|
1056 | def makeProtoName(self, name, tail):
|
---|
1057 | """Turn a `<proto>` `<name>` into C-language prototype
|
---|
1058 | and typedef declarations for that name.
|
---|
1059 |
|
---|
1060 | - name - contents of `<name>` tag
|
---|
1061 | - tail - whatever text follows that tag in the Element"""
|
---|
1062 | if self.genOpts is None:
|
---|
1063 | raise MissingGeneratorOptionsError()
|
---|
1064 | return self.genOpts.apientry + name + tail
|
---|
1065 |
|
---|
1066 | def makeTypedefName(self, name, tail):
|
---|
1067 | """Make the function-pointer typedef name for a command."""
|
---|
1068 | if self.genOpts is None:
|
---|
1069 | raise MissingGeneratorOptionsError()
|
---|
1070 | return '(' + self.genOpts.apientryp + 'PFN_' + name + tail + ')'
|
---|
1071 |
|
---|
1072 | def makeCParamDecl(self, param, aligncol):
|
---|
1073 | """Return a string which is an indented, formatted
|
---|
1074 | declaration for a `<param>` or `<member>` block (e.g. function parameter
|
---|
1075 | or structure/union member).
|
---|
1076 |
|
---|
1077 | - param - Element (`<param>` or `<member>`) to format
|
---|
1078 | - aligncol - if non-zero, attempt to align the nested `<name>` element
|
---|
1079 | at this column"""
|
---|
1080 | if self.genOpts is None:
|
---|
1081 | raise MissingGeneratorOptionsError()
|
---|
1082 | if self.genOpts.conventions is None:
|
---|
1083 | raise MissingGeneratorOptionsConventionsError()
|
---|
1084 | indent = ' '
|
---|
1085 | paramdecl = indent
|
---|
1086 | prefix = noneStr(param.text)
|
---|
1087 |
|
---|
1088 | for elem in param:
|
---|
1089 | text = noneStr(elem.text)
|
---|
1090 | tail = noneStr(elem.tail)
|
---|
1091 |
|
---|
1092 | if self.should_insert_may_alias_macro and self.genOpts.conventions.is_voidpointer_alias(elem.tag, text, tail):
|
---|
1093 | # OpenXR-specific macro insertion - but not in apiinc for the spec
|
---|
1094 | tail = self.genOpts.conventions.make_voidpointer_alias(tail)
|
---|
1095 | if elem.tag == 'name' and aligncol > 0:
|
---|
1096 | self.logMsg('diag', 'Aligning parameter', elem.text, 'to column', self.genOpts.alignFuncParam)
|
---|
1097 | # Align at specified column, if possible
|
---|
1098 | paramdecl = paramdecl.rstrip()
|
---|
1099 | oldLen = len(paramdecl)
|
---|
1100 | # This works around a problem where very long type names -
|
---|
1101 | # longer than the alignment column - would run into the tail
|
---|
1102 | # text.
|
---|
1103 | paramdecl = paramdecl.ljust(aligncol - 1) + ' '
|
---|
1104 | newLen = len(paramdecl)
|
---|
1105 | self.logMsg('diag', 'Adjust length of parameter decl from', oldLen, 'to', newLen, ':', paramdecl)
|
---|
1106 |
|
---|
1107 | if (self.misracppstyle() and prefix.find('const ') != -1):
|
---|
1108 | # Change pointer type order from e.g. "const void *" to "void const *".
|
---|
1109 | # If the string starts with 'const', reorder it to be after the first type.
|
---|
1110 | paramdecl += prefix.replace('const ', '') + text + ' const' + tail
|
---|
1111 | else:
|
---|
1112 | paramdecl += prefix + text + tail
|
---|
1113 |
|
---|
1114 | # Clear prefix for subsequent iterations
|
---|
1115 | prefix = ''
|
---|
1116 |
|
---|
1117 | paramdecl = paramdecl + prefix
|
---|
1118 |
|
---|
1119 | if aligncol == 0:
|
---|
1120 | # Squeeze out multiple spaces other than the indentation
|
---|
1121 | paramdecl = indent + ' '.join(paramdecl.split())
|
---|
1122 | return paramdecl
|
---|
1123 |
|
---|
1124 | def getCParamTypeLength(self, param):
|
---|
1125 | """Return the length of the type field is an indented, formatted
|
---|
1126 | declaration for a `<param>` or `<member>` block (e.g. function parameter
|
---|
1127 | or structure/union member).
|
---|
1128 |
|
---|
1129 | - param - Element (`<param>` or `<member>`) to identify"""
|
---|
1130 | if self.genOpts is None:
|
---|
1131 | raise MissingGeneratorOptionsError()
|
---|
1132 | if self.genOpts.conventions is None:
|
---|
1133 | raise MissingGeneratorOptionsConventionsError()
|
---|
1134 |
|
---|
1135 | # Allow for missing <name> tag
|
---|
1136 | newLen = 0
|
---|
1137 | paramdecl = ' ' + noneStr(param.text)
|
---|
1138 | for elem in param:
|
---|
1139 | text = noneStr(elem.text)
|
---|
1140 | tail = noneStr(elem.tail)
|
---|
1141 |
|
---|
1142 | if self.should_insert_may_alias_macro and self.genOpts.conventions.is_voidpointer_alias(elem.tag, text, tail):
|
---|
1143 | # OpenXR-specific macro insertion
|
---|
1144 | tail = self.genOpts.conventions.make_voidpointer_alias(tail)
|
---|
1145 | if elem.tag == 'name':
|
---|
1146 | # Align at specified column, if possible
|
---|
1147 | newLen = len(paramdecl.rstrip())
|
---|
1148 | self.logMsg('diag', 'Identifying length of', elem.text, 'as', newLen)
|
---|
1149 | paramdecl += text + tail
|
---|
1150 |
|
---|
1151 | return newLen
|
---|
1152 |
|
---|
1153 | def getMaxCParamTypeLength(self, info):
|
---|
1154 | """Return the length of the longest type field for a member/parameter.
|
---|
1155 |
|
---|
1156 | - info - TypeInfo or CommandInfo.
|
---|
1157 | """
|
---|
1158 | lengths = (self.getCParamTypeLength(member)
|
---|
1159 | for member in info.getMembers())
|
---|
1160 | return max(lengths)
|
---|
1161 |
|
---|
1162 | def getHandleParent(self, typename):
|
---|
1163 | """Get the parent of a handle object."""
|
---|
1164 | if self.registry is None:
|
---|
1165 | raise MissingRegistryError()
|
---|
1166 |
|
---|
1167 | info = self.registry.typedict.get(typename)
|
---|
1168 | if info is None:
|
---|
1169 | return None
|
---|
1170 |
|
---|
1171 | elem = info.elem
|
---|
1172 | if elem is not None:
|
---|
1173 | return elem.get('parent')
|
---|
1174 |
|
---|
1175 | return None
|
---|
1176 |
|
---|
1177 | def iterateHandleAncestors(self, typename):
|
---|
1178 | """Iterate through the ancestors of a handle type."""
|
---|
1179 | current = self.getHandleParent(typename)
|
---|
1180 | while current is not None:
|
---|
1181 | yield current
|
---|
1182 | current = self.getHandleParent(current)
|
---|
1183 |
|
---|
1184 | def getHandleAncestors(self, typename):
|
---|
1185 | """Get the ancestors of a handle object."""
|
---|
1186 | return list(self.iterateHandleAncestors(typename))
|
---|
1187 |
|
---|
1188 | def getTypeCategory(self, typename):
|
---|
1189 | """Get the category of a type."""
|
---|
1190 | if self.registry is None:
|
---|
1191 | raise MissingRegistryError()
|
---|
1192 |
|
---|
1193 | info = self.registry.typedict.get(typename)
|
---|
1194 | if info is None:
|
---|
1195 | return None
|
---|
1196 |
|
---|
1197 | elem = info.elem
|
---|
1198 | if elem is not None:
|
---|
1199 | return elem.get('category')
|
---|
1200 | return None
|
---|
1201 |
|
---|
1202 | def isStructAlwaysValid(self, structname):
|
---|
1203 | """Try to do check if a structure is always considered valid (i.e. there is no rules to its acceptance)."""
|
---|
1204 | # A conventions object is required for this call.
|
---|
1205 | if not self.conventions:
|
---|
1206 | raise RuntimeError("To use isStructAlwaysValid, be sure your options include a Conventions object.")
|
---|
1207 | if self.registry is None:
|
---|
1208 | raise MissingRegistryError()
|
---|
1209 |
|
---|
1210 | if self.conventions.type_always_valid(structname):
|
---|
1211 | return True
|
---|
1212 |
|
---|
1213 | category = self.getTypeCategory(structname)
|
---|
1214 | if self.conventions.category_requires_validation(category):
|
---|
1215 | return False
|
---|
1216 |
|
---|
1217 | info = self.registry.typedict.get(structname)
|
---|
1218 | if info is None:
|
---|
1219 | self.logMsg('error', f'isStructAlwaysValid({structname}) - structure not found in typedict')
|
---|
1220 |
|
---|
1221 | members = info.getMembers()
|
---|
1222 |
|
---|
1223 | for member in members:
|
---|
1224 | member_name = getElemName(member)
|
---|
1225 | if member_name in (self.conventions.structtype_member_name,
|
---|
1226 | self.conventions.nextpointer_member_name):
|
---|
1227 | return False
|
---|
1228 |
|
---|
1229 | if member.get('noautovalidity'):
|
---|
1230 | return False
|
---|
1231 |
|
---|
1232 | member_type = getElemType(member)
|
---|
1233 |
|
---|
1234 | if member_type in ('void', 'char') or self.paramIsArray(member) or self.paramIsPointer(member):
|
---|
1235 | return False
|
---|
1236 |
|
---|
1237 | if self.conventions.type_always_valid(member_type):
|
---|
1238 | continue
|
---|
1239 |
|
---|
1240 | member_category = self.getTypeCategory(member_type)
|
---|
1241 |
|
---|
1242 | if self.conventions.category_requires_validation(member_category):
|
---|
1243 | return False
|
---|
1244 |
|
---|
1245 | if member_category in ('struct', 'union'):
|
---|
1246 | if self.isStructAlwaysValid(member_type) is False:
|
---|
1247 | return False
|
---|
1248 |
|
---|
1249 | return True
|
---|
1250 |
|
---|
1251 | def paramIsArray(self, param):
|
---|
1252 | """Check if the parameter passed in is a pointer to an array.
|
---|
1253 |
|
---|
1254 | param the XML information for the param
|
---|
1255 | """
|
---|
1256 | return param.get('len') is not None
|
---|
1257 |
|
---|
1258 | def paramIsPointer(self, param):
|
---|
1259 | """Check if the parameter passed in is a pointer.
|
---|
1260 |
|
---|
1261 | param the XML information for the param
|
---|
1262 | """
|
---|
1263 | tail = param.find('type').tail
|
---|
1264 | return tail is not None and '*' in tail
|
---|
1265 |
|
---|
1266 | def isEnumRequired(self, elem):
|
---|
1267 | """Return True if this `<enum>` element is
|
---|
1268 | required, False otherwise
|
---|
1269 |
|
---|
1270 | - elem - `<enum>` element to test"""
|
---|
1271 | required = elem.get('required') is not None
|
---|
1272 | self.logMsg('diag', 'isEnumRequired:', elem.get('name'),
|
---|
1273 | '->', required)
|
---|
1274 | return required
|
---|
1275 |
|
---|
1276 | # @@@ This code is overridden by equivalent code now run in
|
---|
1277 | # @@@ Registry.generateFeature
|
---|
1278 |
|
---|
1279 | required = False
|
---|
1280 |
|
---|
1281 | extname = elem.get('extname')
|
---|
1282 | if extname is not None:
|
---|
1283 | # 'supported' attribute was injected when the <enum> element was
|
---|
1284 | # moved into the <enums> group in Registry.parseTree()
|
---|
1285 | if self.genOpts.defaultExtensions == elem.get('supported'):
|
---|
1286 | required = True
|
---|
1287 | elif re.match(self.genOpts.addExtensions, extname) is not None:
|
---|
1288 | required = True
|
---|
1289 | elif elem.get('version') is not None:
|
---|
1290 | required = re.match(self.genOpts.emitversions, elem.get('version')) is not None
|
---|
1291 | else:
|
---|
1292 | required = True
|
---|
1293 |
|
---|
1294 | return required
|
---|
1295 |
|
---|
1296 | def makeCDecls(self, cmd):
|
---|
1297 | """Return C prototype and function pointer typedef for a
|
---|
1298 | `<command>` Element, as a two-element list of strings.
|
---|
1299 |
|
---|
1300 | - cmd - Element containing a `<command>` tag"""
|
---|
1301 | if self.genOpts is None:
|
---|
1302 | raise MissingGeneratorOptionsError()
|
---|
1303 | proto = cmd.find('proto')
|
---|
1304 | params = cmd.findall('param')
|
---|
1305 | # Begin accumulating prototype and typedef strings
|
---|
1306 | pdecl = self.genOpts.apicall
|
---|
1307 | tdecl = 'typedef '
|
---|
1308 |
|
---|
1309 | # Insert the function return type/name.
|
---|
1310 | # For prototypes, add APIENTRY macro before the name
|
---|
1311 | # For typedefs, add (APIENTRY *<name>) around the name and
|
---|
1312 | # use the PFN_cmdnameproc naming convention.
|
---|
1313 | # Done by walking the tree for <proto> element by element.
|
---|
1314 | # etree has elem.text followed by (elem[i], elem[i].tail)
|
---|
1315 | # for each child element and any following text
|
---|
1316 | # Leading text
|
---|
1317 | pdecl += noneStr(proto.text)
|
---|
1318 | tdecl += noneStr(proto.text)
|
---|
1319 | # For each child element, if it is a <name> wrap in appropriate
|
---|
1320 | # declaration. Otherwise append its contents and tail contents.
|
---|
1321 | for elem in proto:
|
---|
1322 | text = noneStr(elem.text)
|
---|
1323 | tail = noneStr(elem.tail)
|
---|
1324 | if elem.tag == 'name':
|
---|
1325 | pdecl += self.makeProtoName(text, tail)
|
---|
1326 | tdecl += self.makeTypedefName(text, tail)
|
---|
1327 | else:
|
---|
1328 | pdecl += text + tail
|
---|
1329 | tdecl += text + tail
|
---|
1330 |
|
---|
1331 | if self.genOpts.alignFuncParam == 0:
|
---|
1332 | # Squeeze out multiple spaces - there is no indentation
|
---|
1333 | pdecl = ' '.join(pdecl.split())
|
---|
1334 | tdecl = ' '.join(tdecl.split())
|
---|
1335 |
|
---|
1336 | # Now add the parameter declaration list, which is identical
|
---|
1337 | # for prototypes and typedefs. Concatenate all the text from
|
---|
1338 | # a <param> node without the tags. No tree walking required
|
---|
1339 | # since all tags are ignored.
|
---|
1340 | # Uses: self.indentFuncProto
|
---|
1341 | # self.indentFuncPointer
|
---|
1342 | # self.alignFuncParam
|
---|
1343 | n = len(params)
|
---|
1344 | # Indented parameters
|
---|
1345 | if n > 0:
|
---|
1346 | indentdecl = '(\n'
|
---|
1347 | indentdecl += ',\n'.join(self.makeCParamDecl(p, self.genOpts.alignFuncParam)
|
---|
1348 | for p in params)
|
---|
1349 | indentdecl += ');'
|
---|
1350 | else:
|
---|
1351 | indentdecl = '(void);'
|
---|
1352 | # Non-indented parameters
|
---|
1353 | paramdecl = '('
|
---|
1354 | if n > 0:
|
---|
1355 | paramnames = []
|
---|
1356 | if self.misracppstyle():
|
---|
1357 | for p in params:
|
---|
1358 | param = ''
|
---|
1359 | firstIter = True;
|
---|
1360 | for t in p.itertext():
|
---|
1361 | if (firstIter):
|
---|
1362 | prefix = t
|
---|
1363 | firstIter = False
|
---|
1364 | else:
|
---|
1365 | # Change pointer type order from e.g. "const void *" to "void const *".
|
---|
1366 | # If the string starts with 'const', reorder it to be after the first type.
|
---|
1367 | if (prefix.find('const ') != -1):
|
---|
1368 | param += prefix.replace('const ', '') + t + ' const '
|
---|
1369 | else:
|
---|
1370 | param += prefix + t
|
---|
1371 | # Clear prefix for subsequent iterations
|
---|
1372 | prefix = ''
|
---|
1373 | paramnames.append(param);
|
---|
1374 | else:
|
---|
1375 | paramnames = (''.join(t for t in p.itertext())
|
---|
1376 | for p in params)
|
---|
1377 | paramdecl += ', '.join(paramnames)
|
---|
1378 | else:
|
---|
1379 | paramdecl += 'void'
|
---|
1380 | paramdecl += ");"
|
---|
1381 | return [pdecl + indentdecl, tdecl + paramdecl]
|
---|
1382 |
|
---|
1383 | def newline(self):
|
---|
1384 | """Print a newline to the output file (utility function)"""
|
---|
1385 | write('', file=self.outFile)
|
---|
1386 |
|
---|
1387 | def setRegistry(self, registry):
|
---|
1388 | self.registry = registry
|
---|