Mercurial > games > semicongine
comparison fuhtark_test/Vulkan-Headers-1.4.334/registry/generator.py @ 1501:f40d9d814c08 default tip
did: correct vulkan-api generator
| author | sam <sam@basx.dev> |
|---|---|
| date | Wed, 26 Nov 2025 23:34:29 +0700 |
| parents | |
| children |
comparison
equal
deleted
inserted
replaced
| 1500:91c8c3b7cbf0 | 1501:f40d9d814c08 |
|---|---|
| 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 |
