Mercurial > games > semicongine
comparison fuhtark_test/Vulkan-Headers-1.4.334/registry/spec_tools/conventions.py @ 1501:f40d9d814c08 default tip main
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 | |
| 7 # Base class for working-group-specific style conventions, | |
| 8 # used in generation. | |
| 9 | |
| 10 from enum import Enum | |
| 11 import abc | |
| 12 import re | |
| 13 | |
| 14 # Type categories that respond "False" to isStructAlwaysValid | |
| 15 # basetype is home to typedefs like ..Bool32 | |
| 16 CATEGORIES_REQUIRING_VALIDATION = set(('handle', | |
| 17 'enum', | |
| 18 'bitmask', | |
| 19 'basetype', | |
| 20 None)) | |
| 21 | |
| 22 # These are basic C types pulled in via openxr_platform_defines.h | |
| 23 TYPES_KNOWN_ALWAYS_VALID = set(('char', | |
| 24 'float', | |
| 25 'int8_t', 'uint8_t', | |
| 26 'int16_t', 'uint16_t', | |
| 27 'int32_t', 'uint32_t', | |
| 28 'int64_t', 'uint64_t', | |
| 29 'size_t', | |
| 30 'intptr_t', 'uintptr_t', | |
| 31 'int', | |
| 32 )) | |
| 33 | |
| 34 # Split an extension name into vendor ID and name portions | |
| 35 EXT_NAME_DECOMPOSE_RE = re.compile(r'(?P<prefix>[A-Za-z]+)_(?P<vendor>[A-Za-z]+)_(?P<name>[\w_]+)') | |
| 36 | |
| 37 # Match an API version name. | |
| 38 # Match object includes API prefix, major, and minor version numbers. | |
| 39 # This could be refined further for specific APIs. | |
| 40 # Handles both simple versions (VK_VERSION_1_0) and component-specific versions (VK_BASE_VERSION_1_0) | |
| 41 API_VERSION_NAME_RE = re.compile(r'(?P<apivariant>[A-Za-z]+)(?:_(?:BASE|COMPUTE|GRAPHICS))?_VERSION_(?P<major>[0-9]+)_(?P<minor>[0-9]+)') | |
| 42 | |
| 43 class ProseListFormats(Enum): | |
| 44 """A connective, possibly with a quantifier.""" | |
| 45 AND = 0 | |
| 46 EACH_AND = 1 | |
| 47 OR = 2 | |
| 48 ANY_OR = 3 | |
| 49 | |
| 50 @classmethod | |
| 51 def from_string(cls, s): | |
| 52 if s == 'or': | |
| 53 return cls.OR | |
| 54 if s == 'and': | |
| 55 return cls.AND | |
| 56 raise RuntimeError(f"Unrecognized string connective: {s}") | |
| 57 | |
| 58 @property | |
| 59 def connective(self): | |
| 60 if self in (ProseListFormats.OR, ProseListFormats.ANY_OR): | |
| 61 return 'or' | |
| 62 return 'and' | |
| 63 | |
| 64 def quantifier(self, n): | |
| 65 """Return the desired quantifier for a list of a given length.""" | |
| 66 if self == ProseListFormats.ANY_OR: | |
| 67 if n > 1: | |
| 68 return 'any of ' | |
| 69 elif self == ProseListFormats.EACH_AND: | |
| 70 if n > 2: | |
| 71 return 'each of ' | |
| 72 if n == 2: | |
| 73 return 'both of ' | |
| 74 return '' | |
| 75 | |
| 76 | |
| 77 class ConventionsBase(abc.ABC): | |
| 78 """WG-specific conventions.""" | |
| 79 | |
| 80 def __init__(self): | |
| 81 self._command_prefix = None | |
| 82 self._type_prefix = None | |
| 83 | |
| 84 def formatVersionOrExtension(self, name): | |
| 85 """Mark up an API version or extension name as a link in the spec.""" | |
| 86 | |
| 87 # Is this a version name? | |
| 88 match = API_VERSION_NAME_RE.match(name) | |
| 89 if match is not None: | |
| 90 return self.formatVersion(name, | |
| 91 match.group('apivariant'), | |
| 92 match.group('major'), | |
| 93 match.group('minor')) | |
| 94 else: | |
| 95 # If not, assumed to be an extension name. Might be worth checking. | |
| 96 return self.formatExtension(name) | |
| 97 | |
| 98 def formatVersion(self, name, apivariant, major, minor): | |
| 99 """Mark up an API version name as a link in the spec.""" | |
| 100 return f'`<<{name}>>`' | |
| 101 | |
| 102 def formatExtension(self, name): | |
| 103 """Mark up an extension name as a link in the spec.""" | |
| 104 return f'`<<{name}>>`' | |
| 105 | |
| 106 def formatSPIRVlink(self, name): | |
| 107 """Mark up a SPIR-V extension name as an external link in the spec. | |
| 108 Since these are external links, the formatting probably will be | |
| 109 the same for all APIs creating such links, so long as they use | |
| 110 the asciidoctor {spirv} attribute for the base path to the SPIR-V | |
| 111 extensions.""" | |
| 112 | |
| 113 (vendor, _) = self.extension_name_split(name) | |
| 114 | |
| 115 return f'{{spirv}}/{vendor}/{name}.html[{name}]' | |
| 116 | |
| 117 @property | |
| 118 @abc.abstractmethod | |
| 119 def null(self): | |
| 120 """Preferred spelling of NULL.""" | |
| 121 raise NotImplementedError | |
| 122 | |
| 123 def makeProseList(self, elements, fmt=ProseListFormats.AND, with_verb=False, *args, **kwargs): | |
| 124 """Make a (comma-separated) list for use in prose. | |
| 125 | |
| 126 Adds a connective (by default, 'and') | |
| 127 before the last element if there are more than 1. | |
| 128 | |
| 129 Adds the right one of "is" or "are" to the end if with_verb is true. | |
| 130 | |
| 131 Optionally adds a quantifier (like 'any') before a list of 2 or more, | |
| 132 if specified by fmt. | |
| 133 | |
| 134 Override with a different method or different call to | |
| 135 _implMakeProseList if you want to add a comma for two elements, | |
| 136 or not use a serial comma. | |
| 137 """ | |
| 138 return self._implMakeProseList(elements, fmt, with_verb, *args, **kwargs) | |
| 139 | |
| 140 @property | |
| 141 def struct_macro(self): | |
| 142 """Get the appropriate format macro for a structure. | |
| 143 | |
| 144 May override. | |
| 145 """ | |
| 146 return 'slink:' | |
| 147 | |
| 148 @property | |
| 149 def external_macro(self): | |
| 150 """Get the appropriate format macro for an external type like uint32_t. | |
| 151 | |
| 152 May override. | |
| 153 """ | |
| 154 return 'code:' | |
| 155 | |
| 156 @property | |
| 157 def allows_x_number_suffix(self): | |
| 158 """Whether vendor tags can be suffixed with X and a number to mark experimental extensions.""" | |
| 159 return False | |
| 160 | |
| 161 @property | |
| 162 @abc.abstractmethod | |
| 163 def structtype_member_name(self): | |
| 164 """Return name of the structure type member. | |
| 165 | |
| 166 Must implement. | |
| 167 """ | |
| 168 raise NotImplementedError() | |
| 169 | |
| 170 @property | |
| 171 @abc.abstractmethod | |
| 172 def nextpointer_member_name(self): | |
| 173 """Return name of the structure pointer chain member. | |
| 174 | |
| 175 Must implement. | |
| 176 """ | |
| 177 raise NotImplementedError() | |
| 178 | |
| 179 @property | |
| 180 @abc.abstractmethod | |
| 181 def xml_api_name(self): | |
| 182 """Return the name used in the default API XML registry for the default API""" | |
| 183 raise NotImplementedError() | |
| 184 | |
| 185 @abc.abstractmethod | |
| 186 def generate_structure_type_from_name(self, structname): | |
| 187 """Generate a structure type name, like XR_TYPE_CREATE_INSTANCE_INFO. | |
| 188 | |
| 189 Must implement. | |
| 190 """ | |
| 191 raise NotImplementedError() | |
| 192 | |
| 193 def makeStructName(self, name): | |
| 194 """Prepend the appropriate format macro for a structure to a structure type name. | |
| 195 | |
| 196 Uses struct_macro, so just override that if you want to change behavior. | |
| 197 """ | |
| 198 return self.struct_macro + name | |
| 199 | |
| 200 def makeExternalTypeName(self, name): | |
| 201 """Prepend the appropriate format macro for an external type like uint32_t to a type name. | |
| 202 | |
| 203 Uses external_macro, so just override that if you want to change behavior. | |
| 204 """ | |
| 205 return self.external_macro + name | |
| 206 | |
| 207 def _implMakeProseList(self, elements, fmt, with_verb, comma_for_two_elts=False, serial_comma=True): | |
| 208 """Internal-use implementation to make a (comma-separated) list for use in prose. | |
| 209 | |
| 210 Adds a connective (by default, 'and') | |
| 211 before the last element if there are more than 1, | |
| 212 and only includes commas if there are more than 2 | |
| 213 (if comma_for_two_elts is False). | |
| 214 | |
| 215 Adds the right one of "is" or "are" to the end if with_verb is true. | |
| 216 | |
| 217 Optionally adds a quantifier (like 'any') before a list of 2 or more, | |
| 218 if specified by fmt. | |
| 219 | |
| 220 Do not edit these defaults, override self.makeProseList(). | |
| 221 """ | |
| 222 assert serial_comma # did not implement what we did not need | |
| 223 if isinstance(fmt, str): | |
| 224 fmt = ProseListFormats.from_string(fmt) | |
| 225 | |
| 226 my_elts = list(elements) | |
| 227 if len(my_elts) > 1: | |
| 228 my_elts[-1] = f'{fmt.connective} {my_elts[-1]}' | |
| 229 | |
| 230 if not comma_for_two_elts and len(my_elts) <= 2: | |
| 231 prose = ' '.join(my_elts) | |
| 232 else: | |
| 233 prose = ', '.join(my_elts) | |
| 234 | |
| 235 quantifier = fmt.quantifier(len(my_elts)) | |
| 236 | |
| 237 parts = [quantifier, prose] | |
| 238 | |
| 239 if with_verb: | |
| 240 if len(my_elts) > 1: | |
| 241 parts.append(' are') | |
| 242 else: | |
| 243 parts.append(' is') | |
| 244 return ''.join(parts) | |
| 245 | |
| 246 @property | |
| 247 @abc.abstractmethod | |
| 248 def file_suffix(self): | |
| 249 """Return suffix of generated Asciidoctor files""" | |
| 250 raise NotImplementedError | |
| 251 | |
| 252 @abc.abstractmethod | |
| 253 def api_name(self, spectype=None): | |
| 254 """Return API or specification name for citations in ref pages. | |
| 255 | |
| 256 spectype is the spec this refpage is for. | |
| 257 'api' (the default value) is the main API Specification. | |
| 258 If an unrecognized spectype is given, returns None. | |
| 259 | |
| 260 Must implement.""" | |
| 261 raise NotImplementedError | |
| 262 | |
| 263 def should_insert_may_alias_macro(self, genOpts): | |
| 264 """Return true if we should insert a "may alias" macro in this file. | |
| 265 | |
| 266 Only used by OpenXR right now.""" | |
| 267 return False | |
| 268 | |
| 269 @property | |
| 270 def command_prefix(self): | |
| 271 """Return the expected prefix of commands/functions. | |
| 272 | |
| 273 Implemented in terms of api_prefix.""" | |
| 274 if not self._command_prefix: | |
| 275 self._command_prefix = self.api_prefix[:].replace('_', '').lower() | |
| 276 return self._command_prefix | |
| 277 | |
| 278 @property | |
| 279 def type_prefix(self): | |
| 280 """Return the expected prefix of type names. | |
| 281 | |
| 282 Implemented in terms of command_prefix (and in turn, api_prefix).""" | |
| 283 if not self._type_prefix: | |
| 284 self._type_prefix = ''.join( | |
| 285 (self.command_prefix[0:1].upper(), self.command_prefix[1:])) | |
| 286 return self._type_prefix | |
| 287 | |
| 288 @property | |
| 289 @abc.abstractmethod | |
| 290 def api_prefix(self): | |
| 291 """Return API token prefix. | |
| 292 | |
| 293 Typically two uppercase letters followed by an underscore. | |
| 294 | |
| 295 Must implement.""" | |
| 296 raise NotImplementedError | |
| 297 | |
| 298 @property | |
| 299 def extension_name_prefix(self): | |
| 300 """Return extension name prefix. | |
| 301 | |
| 302 Typically two uppercase letters followed by an underscore. | |
| 303 | |
| 304 Assumed to be the same as api_prefix, but some APIs use different | |
| 305 case conventions.""" | |
| 306 | |
| 307 return self.api_prefix | |
| 308 | |
| 309 def extension_short_description(self, elem): | |
| 310 """Return a short description of an extension for use in refpages. | |
| 311 | |
| 312 elem is an ElementTree for the <extension> tag in the XML. | |
| 313 The default behavior is to use the 'type' field of this tag, but not | |
| 314 all APIs support this field.""" | |
| 315 | |
| 316 ext_type = elem.get('type') | |
| 317 | |
| 318 if ext_type is not None: | |
| 319 return f'{ext_type} extension' | |
| 320 else: | |
| 321 return '' | |
| 322 | |
| 323 @property | |
| 324 def write_contacts(self): | |
| 325 """Return whether contact list should be written to extension appendices""" | |
| 326 return False | |
| 327 | |
| 328 @property | |
| 329 def write_extension_type(self): | |
| 330 """Return whether extension type should be written to extension appendices""" | |
| 331 return True | |
| 332 | |
| 333 @property | |
| 334 def write_extension_number(self): | |
| 335 """Return whether extension number should be written to extension appendices""" | |
| 336 return True | |
| 337 | |
| 338 @property | |
| 339 def write_extension_revision(self): | |
| 340 """Return whether extension revision number should be written to extension appendices""" | |
| 341 return True | |
| 342 | |
| 343 @property | |
| 344 def write_refpage_include(self): | |
| 345 """Return whether refpage include should be written to extension appendices""" | |
| 346 return True | |
| 347 | |
| 348 @property | |
| 349 def api_version_prefix(self): | |
| 350 """Return API core version token prefix. | |
| 351 | |
| 352 Implemented in terms of api_prefix. | |
| 353 | |
| 354 May override.""" | |
| 355 return f"{self.api_prefix}VERSION_" | |
| 356 | |
| 357 @property | |
| 358 def KHR_prefix(self): | |
| 359 """Return extension name prefix for KHR extensions. | |
| 360 | |
| 361 Implemented in terms of api_prefix. | |
| 362 | |
| 363 May override.""" | |
| 364 return f"{self.api_prefix}KHR_" | |
| 365 | |
| 366 @property | |
| 367 def EXT_prefix(self): | |
| 368 """Return extension name prefix for EXT extensions. | |
| 369 | |
| 370 Implemented in terms of api_prefix. | |
| 371 | |
| 372 May override.""" | |
| 373 return f"{self.api_prefix}EXT_" | |
| 374 | |
| 375 def writeFeature(self, featureName, featureExtraProtect, filename): | |
| 376 """Return True if OutputGenerator.endFeature should write this feature. | |
| 377 | |
| 378 Defaults to always True. | |
| 379 Used in COutputGenerator. | |
| 380 | |
| 381 May override.""" | |
| 382 return True | |
| 383 | |
| 384 def requires_error_validation(self, return_type): | |
| 385 """Return True if the return_type element is an API result code | |
| 386 requiring error validation. | |
| 387 | |
| 388 Defaults to always False. | |
| 389 | |
| 390 May override.""" | |
| 391 return False | |
| 392 | |
| 393 @property | |
| 394 def required_errors(self): | |
| 395 """Return a list of required error codes for validation. | |
| 396 | |
| 397 Defaults to an empty list. | |
| 398 | |
| 399 May override.""" | |
| 400 return [] | |
| 401 | |
| 402 def is_voidpointer_alias(self, tag, text, tail): | |
| 403 """Return True if the declaration components (tag,text,tail) of an | |
| 404 element represents a void * type. | |
| 405 | |
| 406 Defaults to a reasonable implementation. | |
| 407 | |
| 408 May override.""" | |
| 409 return tag == 'type' and text == 'void' and tail.startswith('*') | |
| 410 | |
| 411 def make_voidpointer_alias(self, tail): | |
| 412 """Reformat a void * declaration to include the API alias macro. | |
| 413 | |
| 414 Defaults to a no-op. | |
| 415 | |
| 416 Must override if you actually want to use this feature in your project.""" | |
| 417 return tail | |
| 418 | |
| 419 def category_requires_validation(self, category): | |
| 420 """Return True if the given type 'category' always requires validation. | |
| 421 | |
| 422 Defaults to a reasonable implementation. | |
| 423 | |
| 424 May override.""" | |
| 425 return category in CATEGORIES_REQUIRING_VALIDATION | |
| 426 | |
| 427 def type_always_valid(self, typename): | |
| 428 """Return True if the given type name is always valid (never requires validation). | |
| 429 | |
| 430 This is for things like integers. | |
| 431 | |
| 432 Defaults to a reasonable implementation. | |
| 433 | |
| 434 May override.""" | |
| 435 return typename in TYPES_KNOWN_ALWAYS_VALID | |
| 436 | |
| 437 @property | |
| 438 def should_skip_checking_codes(self): | |
| 439 """Return True if more than the basic validation of return codes should | |
| 440 be skipped for a command.""" | |
| 441 | |
| 442 return False | |
| 443 | |
| 444 @property | |
| 445 def generate_index_terms(self): | |
| 446 """Return True if asiidoctor index terms should be generated as part | |
| 447 of an API interface from the docgenerator.""" | |
| 448 | |
| 449 return False | |
| 450 | |
| 451 @property | |
| 452 def generate_enum_table(self): | |
| 453 """Return True if asciidoctor tables describing enumerants in a | |
| 454 group should be generated as part of group generation.""" | |
| 455 return False | |
| 456 | |
| 457 @property | |
| 458 def generate_max_enum_in_docs(self): | |
| 459 """Return True if MAX_ENUM tokens should be generated in | |
| 460 documentation includes.""" | |
| 461 return False | |
| 462 | |
| 463 def extension_name_split(self, name): | |
| 464 """Split an extension name, returning (vendor, rest of name). | |
| 465 The API prefix of the name is ignored.""" | |
| 466 | |
| 467 match = EXT_NAME_DECOMPOSE_RE.match(name) | |
| 468 vendor = match.group('vendor') | |
| 469 bare_name = match.group('name') | |
| 470 | |
| 471 return (vendor, bare_name) | |
| 472 | |
| 473 @abc.abstractmethod | |
| 474 def extension_file_path(self, name): | |
| 475 """Return file path to an extension appendix relative to a directory | |
| 476 containing all such appendices. | |
| 477 - name - extension name | |
| 478 | |
| 479 Must implement.""" | |
| 480 raise NotImplementedError | |
| 481 | |
| 482 def extension_include_string(self, name): | |
| 483 """Return format string for include:: line for an extension appendix | |
| 484 file. | |
| 485 - name - extension name""" | |
| 486 | |
| 487 return f'include::{{appendices}}/{self.extension_file_path(name)}[]' | |
| 488 | |
| 489 @property | |
| 490 def provisional_extension_warning(self): | |
| 491 """Return True if a warning should be included in extension | |
| 492 appendices for provisional extensions.""" | |
| 493 return True | |
| 494 | |
| 495 @property | |
| 496 def generated_include_path(self): | |
| 497 """Return path relative to the generated reference pages, to the | |
| 498 generated API include files.""" | |
| 499 | |
| 500 return '{generated}' | |
| 501 | |
| 502 @property | |
| 503 def include_extension_appendix_in_refpage(self): | |
| 504 """Return True if generating extension refpages by embedding | |
| 505 extension appendix content (default), False otherwise | |
| 506 (OpenXR).""" | |
| 507 | |
| 508 return True | |
| 509 | |
| 510 def valid_flag_bit(self, bitpos): | |
| 511 """Return True if bitpos is an allowed numeric bit position for | |
| 512 an API flag. | |
| 513 | |
| 514 Behavior depends on the data type used for flags (which may be 32 | |
| 515 or 64 bits), and may depend on assumptions about compiler | |
| 516 handling of sign bits in enumerated types, as well.""" | |
| 517 return True | |
| 518 | |
| 519 @property | |
| 520 def duplicate_aliased_structs(self): | |
| 521 """ | |
| 522 Should aliased structs have the original struct definition listed in the | |
| 523 generated docs snippet? | |
| 524 """ | |
| 525 return False | |
| 526 | |
| 527 @property | |
| 528 def protectProtoComment(self): | |
| 529 """Return True if generated #endif should have a comment matching | |
| 530 the protection symbol used in the opening #ifdef/#ifndef.""" | |
| 531 return False | |
| 532 | |
| 533 @property | |
| 534 def extra_refpage_headers(self): | |
| 535 """Return any extra headers (preceding the title) for generated | |
| 536 reference pages.""" | |
| 537 return '' | |
| 538 | |
| 539 @property | |
| 540 def extra_refpage_body(self): | |
| 541 """Return any extra text (following the title) for generated | |
| 542 reference pages.""" | |
| 543 return '' | |
| 544 | |
| 545 def is_api_version_name(self, name): | |
| 546 """Return True if name is an API version name.""" | |
| 547 | |
| 548 return API_VERSION_NAME_RE.match(name) is not None | |
| 549 | |
| 550 @property | |
| 551 def docgen_language(self): | |
| 552 """Return the language to be used in docgenerator [source] | |
| 553 blocks.""" | |
| 554 | |
| 555 return 'c++' | |
| 556 | |
| 557 @property | |
| 558 def docgen_source_options(self): | |
| 559 """Return block options to be used in docgenerator [source] blocks, | |
| 560 which are appended to the 'source' block type. | |
| 561 Can be empty.""" | |
| 562 | |
| 563 return '%unbreakable' |
