xref: /aosp_15_r20/external/vulkan-headers/registry/spec_tools/conventions.py (revision 902771965e4c6d39c75c62130a6a330c08b024db)
1#!/usr/bin/env python3 -i
2#
3# Copyright 2013-2024 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
10from enum import Enum
11import abc
12import re
13
14# Type categories that respond "False" to isStructAlwaysValid
15# basetype is home to typedefs like ..Bool32
16CATEGORIES_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
23TYPES_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
35EXT_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.
40API_VERSION_NAME_RE = re.compile(r'(?P<apivariant>[A-Za-z]+)_VERSION_(?P<major>[0-9]+)_(?P<minor>[0-9]+)')
41
42class ProseListFormats(Enum):
43    """A connective, possibly with a quantifier."""
44    AND = 0
45    EACH_AND = 1
46    OR = 2
47    ANY_OR = 3
48
49    @classmethod
50    def from_string(cls, s):
51        if s == 'or':
52            return cls.OR
53        if s == 'and':
54            return cls.AND
55        raise RuntimeError("Unrecognized string connective: " + s)
56
57    @property
58    def connective(self):
59        if self in (ProseListFormats.OR, ProseListFormats.ANY_OR):
60            return 'or'
61        return 'and'
62
63    def quantifier(self, n):
64        """Return the desired quantifier for a list of a given length."""
65        if self == ProseListFormats.ANY_OR:
66            if n > 1:
67                return 'any of '
68        elif self == ProseListFormats.EACH_AND:
69            if n > 2:
70                return 'each of '
71            if n == 2:
72                return 'both of '
73        return ''
74
75
76class ConventionsBase(abc.ABC):
77    """WG-specific conventions."""
78
79    def __init__(self):
80        self._command_prefix = None
81        self._type_prefix = None
82
83    def formatVersionOrExtension(self, name):
84        """Mark up an API version or extension name as a link in the spec."""
85
86        # Is this a version name?
87        match = API_VERSION_NAME_RE.match(name)
88        if match is not None:
89            return self.formatVersion(name,
90                match.group('apivariant'),
91                match.group('major'),
92                match.group('minor'))
93        else:
94            # If not, assumed to be an extension name. Might be worth checking.
95            return self.formatExtension(name)
96
97    def formatVersion(self, name, apivariant, major, minor):
98        """Mark up an API version name as a link in the spec."""
99        return '`<<{}>>`'.format(name)
100
101    def formatExtension(self, name):
102        """Mark up an extension name as a link in the spec."""
103        return '`<<{}>>`'.format(name)
104
105    def formatSPIRVlink(self, name):
106        """Mark up a SPIR-V extension name as an external link in the spec.
107           Since these are external links, the formatting probably will be
108           the same for all APIs creating such links, so long as they use
109           the asciidoctor {spirv} attribute for the base path to the SPIR-V
110           extensions."""
111
112        (vendor, _) = self.extension_name_split(name)
113
114        return f'{{spirv}}/{vendor}/{name}.html[{name}]'
115
116    @property
117    @abc.abstractmethod
118    def null(self):
119        """Preferred spelling of NULL."""
120        raise NotImplementedError
121
122    def makeProseList(self, elements, fmt=ProseListFormats.AND, with_verb=False, *args, **kwargs):
123        """Make a (comma-separated) list for use in prose.
124
125        Adds a connective (by default, 'and')
126        before the last element if there are more than 1.
127
128        Adds the right one of "is" or "are" to the end if with_verb is true.
129
130        Optionally adds a quantifier (like 'any') before a list of 2 or more,
131        if specified by fmt.
132
133        Override with a different method or different call to
134        _implMakeProseList if you want to add a comma for two elements,
135        or not use a serial comma.
136        """
137        return self._implMakeProseList(elements, fmt, with_verb, *args, **kwargs)
138
139    @property
140    def struct_macro(self):
141        """Get the appropriate format macro for a structure.
142
143        May override.
144        """
145        return 'slink:'
146
147    @property
148    def external_macro(self):
149        """Get the appropriate format macro for an external type like uint32_t.
150
151        May override.
152        """
153        return 'code:'
154
155    @property
156    def allows_x_number_suffix(self):
157        """Whether vendor tags can be suffixed with X and a number to mark experimental extensions."""
158        return False
159
160    @property
161    @abc.abstractmethod
162    def structtype_member_name(self):
163        """Return name of the structure type member.
164
165        Must implement.
166        """
167        raise NotImplementedError()
168
169    @property
170    @abc.abstractmethod
171    def nextpointer_member_name(self):
172        """Return name of the structure pointer chain member.
173
174        Must implement.
175        """
176        raise NotImplementedError()
177
178    @property
179    @abc.abstractmethod
180    def xml_api_name(self):
181        """Return the name used in the default API XML registry for the default API"""
182        raise NotImplementedError()
183
184    @abc.abstractmethod
185    def generate_structure_type_from_name(self, structname):
186        """Generate a structure type name, like XR_TYPE_CREATE_INSTANCE_INFO.
187
188        Must implement.
189        """
190        raise NotImplementedError()
191
192    def makeStructName(self, name):
193        """Prepend the appropriate format macro for a structure to a structure type name.
194
195        Uses struct_macro, so just override that if you want to change behavior.
196        """
197        return self.struct_macro + name
198
199    def makeExternalTypeName(self, name):
200        """Prepend the appropriate format macro for an external type like uint32_t to a type name.
201
202        Uses external_macro, so just override that if you want to change behavior.
203        """
204        return self.external_macro + name
205
206    def _implMakeProseList(self, elements, fmt, with_verb, comma_for_two_elts=False, serial_comma=True):
207        """Internal-use implementation to make a (comma-separated) list for use in prose.
208
209        Adds a connective (by default, 'and')
210        before the last element if there are more than 1,
211        and only includes commas if there are more than 2
212        (if comma_for_two_elts is False).
213
214        Adds the right one of "is" or "are" to the end if with_verb is true.
215
216        Optionally adds a quantifier (like 'any') before a list of 2 or more,
217        if specified by fmt.
218
219        Do not edit these defaults, override self.makeProseList().
220        """
221        assert serial_comma  # did not implement what we did not need
222        if isinstance(fmt, str):
223            fmt = ProseListFormats.from_string(fmt)
224
225        my_elts = list(elements)
226        if len(my_elts) > 1:
227            my_elts[-1] = '{} {}'.format(fmt.connective, my_elts[-1])
228
229        if not comma_for_two_elts and len(my_elts) <= 2:
230            prose = ' '.join(my_elts)
231        else:
232            prose = ', '.join(my_elts)
233
234        quantifier = fmt.quantifier(len(my_elts))
235
236        parts = [quantifier, prose]
237
238        if with_verb:
239            if len(my_elts) > 1:
240                parts.append(' are')
241            else:
242                parts.append(' is')
243        return ''.join(parts)
244
245    @property
246    @abc.abstractmethod
247    def file_suffix(self):
248        """Return suffix of generated Asciidoctor files"""
249        raise NotImplementedError
250
251    @abc.abstractmethod
252    def api_name(self, spectype=None):
253        """Return API or specification name for citations in ref pages.
254
255        spectype is the spec this refpage is for.
256        'api' (the default value) is the main API Specification.
257        If an unrecognized spectype is given, returns None.
258
259        Must implement."""
260        raise NotImplementedError
261
262    def should_insert_may_alias_macro(self, genOpts):
263        """Return true if we should insert a "may alias" macro in this file.
264
265        Only used by OpenXR right now."""
266        return False
267
268    @property
269    def command_prefix(self):
270        """Return the expected prefix of commands/functions.
271
272        Implemented in terms of api_prefix."""
273        if not self._command_prefix:
274            self._command_prefix = self.api_prefix[:].replace('_', '').lower()
275        return self._command_prefix
276
277    @property
278    def type_prefix(self):
279        """Return the expected prefix of type names.
280
281        Implemented in terms of command_prefix (and in turn, api_prefix)."""
282        if not self._type_prefix:
283            self._type_prefix = ''.join(
284                (self.command_prefix[0:1].upper(), self.command_prefix[1:]))
285        return self._type_prefix
286
287    @property
288    @abc.abstractmethod
289    def api_prefix(self):
290        """Return API token prefix.
291
292        Typically two uppercase letters followed by an underscore.
293
294        Must implement."""
295        raise NotImplementedError
296
297    @property
298    def extension_name_prefix(self):
299        """Return extension name prefix.
300
301        Typically two uppercase letters followed by an underscore.
302
303        Assumed to be the same as api_prefix, but some APIs use different
304        case conventions."""
305
306        return self.api_prefix
307
308    def extension_short_description(self, elem):
309        """Return a short description of an extension for use in refpages.
310
311        elem is an ElementTree for the <extension> tag in the XML.
312        The default behavior is to use the 'type' field of this tag, but not
313        all APIs support this field."""
314
315        ext_type = elem.get('type')
316
317        if ext_type is not None:
318            return f'{ext_type} extension'
319        else:
320            return ''
321
322    @property
323    def write_contacts(self):
324        """Return whether contact list should be written to extension appendices"""
325        return False
326
327    @property
328    def write_extension_type(self):
329        """Return whether extension type should be written to extension appendices"""
330        return True
331
332    @property
333    def write_extension_number(self):
334        """Return whether extension number should be written to extension appendices"""
335        return True
336
337    @property
338    def write_extension_revision(self):
339        """Return whether extension revision number should be written to extension appendices"""
340        return True
341
342    @property
343    def write_refpage_include(self):
344        """Return whether refpage include should be written to extension appendices"""
345        return True
346
347    @property
348    def api_version_prefix(self):
349        """Return API core version token prefix.
350
351        Implemented in terms of api_prefix.
352
353        May override."""
354        return self.api_prefix + 'VERSION_'
355
356    @property
357    def KHR_prefix(self):
358        """Return extension name prefix for KHR extensions.
359
360        Implemented in terms of api_prefix.
361
362        May override."""
363        return self.api_prefix + 'KHR_'
364
365    @property
366    def EXT_prefix(self):
367        """Return extension name prefix for EXT extensions.
368
369        Implemented in terms of api_prefix.
370
371        May override."""
372        return self.api_prefix + 'EXT_'
373
374    def writeFeature(self, featureName, featureExtraProtect, filename):
375        """Return True if OutputGenerator.endFeature should write this feature.
376
377        Defaults to always True.
378        Used in COutputGenerator.
379
380        May override."""
381        return True
382
383    def requires_error_validation(self, return_type):
384        """Return True if the return_type element is an API result code
385        requiring error validation.
386
387        Defaults to always False.
388
389        May override."""
390        return False
391
392    @property
393    def required_errors(self):
394        """Return a list of required error codes for validation.
395
396        Defaults to an empty list.
397
398        May override."""
399        return []
400
401    def is_voidpointer_alias(self, tag, text, tail):
402        """Return True if the declaration components (tag,text,tail) of an
403        element represents a void * type.
404
405        Defaults to a reasonable implementation.
406
407        May override."""
408        return tag == 'type' and text == 'void' and tail.startswith('*')
409
410    def make_voidpointer_alias(self, tail):
411        """Reformat a void * declaration to include the API alias macro.
412
413        Defaults to a no-op.
414
415        Must override if you actually want to use this feature in your project."""
416        return tail
417
418    def category_requires_validation(self, category):
419        """Return True if the given type 'category' always requires validation.
420
421        Defaults to a reasonable implementation.
422
423        May override."""
424        return category in CATEGORIES_REQUIRING_VALIDATION
425
426    def type_always_valid(self, typename):
427        """Return True if the given type name is always valid (never requires validation).
428
429        This is for things like integers.
430
431        Defaults to a reasonable implementation.
432
433        May override."""
434        return typename in TYPES_KNOWN_ALWAYS_VALID
435
436    @property
437    def should_skip_checking_codes(self):
438        """Return True if more than the basic validation of return codes should
439        be skipped for a command."""
440
441        return False
442
443    @property
444    def generate_index_terms(self):
445        """Return True if asiidoctor index terms should be generated as part
446           of an API interface from the docgenerator."""
447
448        return False
449
450    @property
451    def generate_enum_table(self):
452        """Return True if asciidoctor tables describing enumerants in a
453           group should be generated as part of group generation."""
454        return False
455
456    @property
457    def generate_max_enum_in_docs(self):
458        """Return True if MAX_ENUM tokens should be generated in
459           documentation includes."""
460        return False
461
462    def extension_name_split(self, name):
463        """Split an extension name, returning (vendor, rest of name).
464           The API prefix of the name is ignored."""
465
466        match = EXT_NAME_DECOMPOSE_RE.match(name)
467        vendor = match.group('vendor')
468        bare_name = match.group('name')
469
470        return (vendor, bare_name)
471
472    @abc.abstractmethod
473    def extension_file_path(self, name):
474        """Return file path to an extension appendix relative to a directory
475           containing all such appendices.
476           - name - extension name
477
478           Must implement."""
479        raise NotImplementedError
480
481    def extension_include_string(self, name):
482        """Return format string for include:: line for an extension appendix
483           file.
484            - name - extension name"""
485
486        return 'include::{{appendices}}/{}[]'.format(
487                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'
564