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