• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/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    @abc.abstractmethod
157    def structtype_member_name(self):
158        """Return name of the structure type member.
159
160        Must implement.
161        """
162        raise NotImplementedError()
163
164    @property
165    @abc.abstractmethod
166    def nextpointer_member_name(self):
167        """Return name of the structure pointer chain member.
168
169        Must implement.
170        """
171        raise NotImplementedError()
172
173    @property
174    @abc.abstractmethod
175    def xml_api_name(self):
176        """Return the name used in the default API XML registry for the default API"""
177        raise NotImplementedError()
178
179    @abc.abstractmethod
180    def generate_structure_type_from_name(self, structname):
181        """Generate a structure type name, like XR_TYPE_CREATE_INSTANCE_INFO.
182
183        Must implement.
184        """
185        raise NotImplementedError()
186
187    def makeStructName(self, name):
188        """Prepend the appropriate format macro for a structure to a structure type name.
189
190        Uses struct_macro, so just override that if you want to change behavior.
191        """
192        return self.struct_macro + name
193
194    def makeExternalTypeName(self, name):
195        """Prepend the appropriate format macro for an external type like uint32_t to a type name.
196
197        Uses external_macro, so just override that if you want to change behavior.
198        """
199        return self.external_macro + name
200
201    def _implMakeProseList(self, elements, fmt, with_verb, comma_for_two_elts=False, serial_comma=True):
202        """Internal-use implementation to make a (comma-separated) list for use in prose.
203
204        Adds a connective (by default, 'and')
205        before the last element if there are more than 1,
206        and only includes commas if there are more than 2
207        (if comma_for_two_elts is False).
208
209        Adds the right one of "is" or "are" to the end if with_verb is true.
210
211        Optionally adds a quantifier (like 'any') before a list of 2 or more,
212        if specified by fmt.
213
214        Do not edit these defaults, override self.makeProseList().
215        """
216        assert(serial_comma)  # did not implement what we did not need
217        if isinstance(fmt, str):
218            fmt = ProseListFormats.from_string(fmt)
219
220        my_elts = list(elements)
221        if len(my_elts) > 1:
222            my_elts[-1] = '{} {}'.format(fmt.connective, my_elts[-1])
223
224        if not comma_for_two_elts and len(my_elts) <= 2:
225            prose = ' '.join(my_elts)
226        else:
227            prose = ', '.join(my_elts)
228
229        quantifier = fmt.quantifier(len(my_elts))
230
231        parts = [quantifier, prose]
232
233        if with_verb:
234            if len(my_elts) > 1:
235                parts.append(' are')
236            else:
237                parts.append(' is')
238        return ''.join(parts)
239
240    @property
241    @abc.abstractmethod
242    def file_suffix(self):
243        """Return suffix of generated Asciidoctor files"""
244        raise NotImplementedError
245
246    @abc.abstractmethod
247    def api_name(self, spectype=None):
248        """Return API or specification name for citations in ref pages.
249
250        spectype is the spec this refpage is for.
251        'api' (the default value) is the main API Specification.
252        If an unrecognized spectype is given, returns None.
253
254        Must implement."""
255        raise NotImplementedError
256
257    def should_insert_may_alias_macro(self, genOpts):
258        """Return true if we should insert a "may alias" macro in this file.
259
260        Only used by OpenXR right now."""
261        return False
262
263    @property
264    def command_prefix(self):
265        """Return the expected prefix of commands/functions.
266
267        Implemented in terms of api_prefix."""
268        if not self._command_prefix:
269            self._command_prefix = self.api_prefix[:].replace('_', '').lower()
270        return self._command_prefix
271
272    @property
273    def type_prefix(self):
274        """Return the expected prefix of type names.
275
276        Implemented in terms of command_prefix (and in turn, api_prefix)."""
277        if not self._type_prefix:
278            self._type_prefix = ''.join(
279                (self.command_prefix[0:1].upper(), self.command_prefix[1:]))
280        return self._type_prefix
281
282    @property
283    @abc.abstractmethod
284    def api_prefix(self):
285        """Return API token prefix.
286
287        Typically two uppercase letters followed by an underscore.
288
289        Must implement."""
290        raise NotImplementedError
291
292    @property
293    def extension_name_prefix(self):
294        """Return extension name prefix.
295
296        Typically two uppercase letters followed by an underscore.
297
298        Assumed to be the same as api_prefix, but some APIs use different
299        case conventions."""
300
301        return self.api_prefix
302
303    @property
304    def write_contacts(self):
305        """Return whether contact list should be written to extension appendices"""
306        return False
307
308    @property
309    def write_extension_type(self):
310        """Return whether extension type should be written to extension appendices"""
311        return True
312
313    @property
314    def write_extension_number(self):
315        """Return whether extension number should be written to extension appendices"""
316        return True
317
318    @property
319    def write_extension_revision(self):
320        """Return whether extension revision number should be written to extension appendices"""
321        return True
322
323    @property
324    def write_refpage_include(self):
325        """Return whether refpage include should be written to extension appendices"""
326        return True
327
328    @property
329    def api_version_prefix(self):
330        """Return API core version token prefix.
331
332        Implemented in terms of api_prefix.
333
334        May override."""
335        return self.api_prefix + 'VERSION_'
336
337    @property
338    def KHR_prefix(self):
339        """Return extension name prefix for KHR extensions.
340
341        Implemented in terms of api_prefix.
342
343        May override."""
344        return self.api_prefix + 'KHR_'
345
346    @property
347    def EXT_prefix(self):
348        """Return extension name prefix for EXT extensions.
349
350        Implemented in terms of api_prefix.
351
352        May override."""
353        return self.api_prefix + 'EXT_'
354
355    def writeFeature(self, featureExtraProtect, filename):
356        """Return True if OutputGenerator.endFeature should write this feature.
357
358        Defaults to always True.
359        Used in COutputGenerator.
360
361        May override."""
362        return True
363
364    def requires_error_validation(self, return_type):
365        """Return True if the return_type element is an API result code
366        requiring error validation.
367
368        Defaults to always False.
369
370        May override."""
371        return False
372
373    @property
374    def required_errors(self):
375        """Return a list of required error codes for validation.
376
377        Defaults to an empty list.
378
379        May override."""
380        return []
381
382    def is_voidpointer_alias(self, tag, text, tail):
383        """Return True if the declaration components (tag,text,tail) of an
384        element represents a void * type.
385
386        Defaults to a reasonable implementation.
387
388        May override."""
389        return tag == 'type' and text == 'void' and tail.startswith('*')
390
391    def make_voidpointer_alias(self, tail):
392        """Reformat a void * declaration to include the API alias macro.
393
394        Defaults to a no-op.
395
396        Must override if you actually want to use this feature in your project."""
397        return tail
398
399    def category_requires_validation(self, category):
400        """Return True if the given type 'category' always requires validation.
401
402        Defaults to a reasonable implementation.
403
404        May override."""
405        return category in CATEGORIES_REQUIRING_VALIDATION
406
407    def type_always_valid(self, typename):
408        """Return True if the given type name is always valid (never requires validation).
409
410        This is for things like integers.
411
412        Defaults to a reasonable implementation.
413
414        May override."""
415        return typename in TYPES_KNOWN_ALWAYS_VALID
416
417    @property
418    def should_skip_checking_codes(self):
419        """Return True if more than the basic validation of return codes should
420        be skipped for a command."""
421
422        return False
423
424    @property
425    def generate_index_terms(self):
426        """Return True if asiidoctor index terms should be generated as part
427           of an API interface from the docgenerator."""
428
429        return False
430
431    @property
432    def generate_enum_table(self):
433        """Return True if asciidoctor tables describing enumerants in a
434           group should be generated as part of group generation."""
435        return False
436
437    @property
438    def generate_max_enum_in_docs(self):
439        """Return True if MAX_ENUM tokens should be generated in
440           documentation includes."""
441        return False
442
443    def extension_name_split(self, name):
444        """Split an extension name, returning (vendor, rest of name).
445           The API prefix of the name is ignored."""
446
447        match = EXT_NAME_DECOMPOSE_RE.match(name)
448        vendor = match.group('vendor')
449        bare_name = match.group('name')
450
451        return (vendor, bare_name)
452
453    @abc.abstractmethod
454    def extension_file_path(self, name):
455        """Return file path to an extension appendix relative to a directory
456           containing all such appendices.
457           - name - extension name
458
459           Must implement."""
460        raise NotImplementedError
461
462    def extension_include_string(self, name):
463        """Return format string for include:: line for an extension appendix
464           file.
465            - name - extension name"""
466
467        return 'include::{{appendices}}/{}[]'.format(
468                self.extension_file_path(name))
469
470    @property
471    def provisional_extension_warning(self):
472        """Return True if a warning should be included in extension
473           appendices for provisional extensions."""
474        return True
475
476    @property
477    def generated_include_path(self):
478        """Return path relative to the generated reference pages, to the
479           generated API include files."""
480
481        return '{generated}'
482
483    @property
484    def include_extension_appendix_in_refpage(self):
485        """Return True if generating extension refpages by embedding
486           extension appendix content (default), False otherwise
487           (OpenXR)."""
488
489        return True
490
491    def valid_flag_bit(self, bitpos):
492        """Return True if bitpos is an allowed numeric bit position for
493           an API flag.
494
495           Behavior depends on the data type used for flags (which may be 32
496           or 64 bits), and may depend on assumptions about compiler
497           handling of sign bits in enumerated types, as well."""
498        return True
499
500    @property
501    def duplicate_aliased_structs(self):
502        """
503        Should aliased structs have the original struct definition listed in the
504        generated docs snippet?
505        """
506        return False
507
508    @property
509    def protectProtoComment(self):
510        """Return True if generated #endif should have a comment matching
511           the protection symbol used in the opening #ifdef/#ifndef."""
512        return False
513
514    @property
515    def extra_refpage_headers(self):
516        """Return any extra headers (preceding the title) for generated
517           reference pages."""
518        return ''
519
520    @property
521    def extra_refpage_body(self):
522        """Return any extra text (following the title) for generated
523           reference pages."""
524        return ''
525
526    def is_api_version_name(self, name):
527        """Return True if name is an API version name."""
528
529        return API_VERSION_NAME_RE.match(name) is not None
530
531    @property
532    def docgen_language(self):
533        """Return the language to be used in docgenerator [source]
534           blocks."""
535
536        return 'c++'
537