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'