• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/python3 -i
2#
3# Copyright 2013-2022 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'[A-Z]+_(?P<vendor>[A-Z]+)_(?P<name>[\w_]+)')
36
37class ProseListFormats(Enum):
38    """A connective, possibly with a quantifier."""
39    AND = 0
40    EACH_AND = 1
41    OR = 2
42    ANY_OR = 3
43
44    @classmethod
45    def from_string(cls, s):
46        if s == 'or':
47            return cls.OR
48        if s == 'and':
49            return cls.AND
50        raise RuntimeError("Unrecognized string connective: " + s)
51
52    @property
53    def connective(self):
54        if self in (ProseListFormats.OR, ProseListFormats.ANY_OR):
55            return 'or'
56        return 'and'
57
58    def quantifier(self, n):
59        """Return the desired quantifier for a list of a given length."""
60        if self == ProseListFormats.ANY_OR:
61            if n > 1:
62                return 'any of '
63        elif self == ProseListFormats.EACH_AND:
64            if n > 2:
65                return 'each of '
66            if n == 2:
67                return 'both of '
68        return ''
69
70
71class ConventionsBase(abc.ABC):
72    """WG-specific conventions."""
73
74    def __init__(self):
75        self._command_prefix = None
76        self._type_prefix = None
77
78    def formatExtension(self, name):
79        """Mark up an extension name as a link the spec."""
80        return '`<<{}>>`'.format(name)
81
82    @property
83    @abc.abstractmethod
84    def null(self):
85        """Preferred spelling of NULL."""
86        raise NotImplementedError
87
88    def makeProseList(self, elements, fmt=ProseListFormats.AND, with_verb=False, *args, **kwargs):
89        """Make a (comma-separated) list for use in prose.
90
91        Adds a connective (by default, 'and')
92        before the last element if there are more than 1.
93
94        Adds the right one of "is" or "are" to the end if with_verb is true.
95
96        Optionally adds a quantifier (like 'any') before a list of 2 or more,
97        if specified by fmt.
98
99        Override with a different method or different call to
100        _implMakeProseList if you want to add a comma for two elements,
101        or not use a serial comma.
102        """
103        return self._implMakeProseList(elements, fmt, with_verb, *args, **kwargs)
104
105    @property
106    def struct_macro(self):
107        """Get the appropriate format macro for a structure.
108
109        May override.
110        """
111        return 'slink:'
112
113    @property
114    def external_macro(self):
115        """Get the appropriate format macro for an external type like uint32_t.
116
117        May override.
118        """
119        return 'code:'
120
121    @property
122    @abc.abstractmethod
123    def structtype_member_name(self):
124        """Return name of the structure type member.
125
126        Must implement.
127        """
128        raise NotImplementedError()
129
130    @property
131    @abc.abstractmethod
132    def nextpointer_member_name(self):
133        """Return name of the structure pointer chain member.
134
135        Must implement.
136        """
137        raise NotImplementedError()
138
139    @property
140    @abc.abstractmethod
141    def xml_api_name(self):
142        """Return the name used in the default API XML registry for the default API"""
143        raise NotImplementedError()
144
145    @abc.abstractmethod
146    def generate_structure_type_from_name(self, structname):
147        """Generate a structure type name, like XR_TYPE_CREATE_INSTANCE_INFO.
148
149        Must implement.
150        """
151        raise NotImplementedError()
152
153    def makeStructName(self, name):
154        """Prepend the appropriate format macro for a structure to a structure type name.
155
156        Uses struct_macro, so just override that if you want to change behavior.
157        """
158        return self.struct_macro + name
159
160    def makeExternalTypeName(self, name):
161        """Prepend the appropriate format macro for an external type like uint32_t to a type name.
162
163        Uses external_macro, so just override that if you want to change behavior.
164        """
165        return self.external_macro + name
166
167    def _implMakeProseList(self, elements, fmt, with_verb, comma_for_two_elts=False, serial_comma=True):
168        """Internal-use implementation to make a (comma-separated) list for use in prose.
169
170        Adds a connective (by default, 'and')
171        before the last element if there are more than 1,
172        and only includes commas if there are more than 2
173        (if comma_for_two_elts is False).
174
175        Adds the right one of "is" or "are" to the end if with_verb is true.
176
177        Optionally adds a quantifier (like 'any') before a list of 2 or more,
178        if specified by fmt.
179
180        Do not edit these defaults, override self.makeProseList().
181        """
182        assert(serial_comma)  # did not implement what we did not need
183        if isinstance(fmt, str):
184            fmt = ProseListFormats.from_string(fmt)
185
186        my_elts = list(elements)
187        if len(my_elts) > 1:
188            my_elts[-1] = '{} {}'.format(fmt.connective, my_elts[-1])
189
190        if not comma_for_two_elts and len(my_elts) <= 2:
191            prose = ' '.join(my_elts)
192        else:
193            prose = ', '.join(my_elts)
194
195        quantifier = fmt.quantifier(len(my_elts))
196
197        parts = [quantifier, prose]
198
199        if with_verb:
200            if len(my_elts) > 1:
201                parts.append(' are')
202            else:
203                parts.append(' is')
204        return ''.join(parts)
205
206    @property
207    @abc.abstractmethod
208    def file_suffix(self):
209        """Return suffix of generated Asciidoctor files"""
210        raise NotImplementedError
211
212    @abc.abstractmethod
213    def api_name(self, spectype=None):
214        """Return API or specification name for citations in ref pages.
215
216        spectype is the spec this refpage is for.
217        'api' (the default value) is the main API Specification.
218        If an unrecognized spectype is given, returns None.
219
220        Must implement."""
221        raise NotImplementedError
222
223    def should_insert_may_alias_macro(self, genOpts):
224        """Return true if we should insert a "may alias" macro in this file.
225
226        Only used by OpenXR right now."""
227        return False
228
229    @property
230    def command_prefix(self):
231        """Return the expected prefix of commands/functions.
232
233        Implemented in terms of api_prefix."""
234        if not self._command_prefix:
235            self._command_prefix = self.api_prefix[:].replace('_', '').lower()
236        return self._command_prefix
237
238    @property
239    def type_prefix(self):
240        """Return the expected prefix of type names.
241
242        Implemented in terms of command_prefix (and in turn, api_prefix)."""
243        if not self._type_prefix:
244            self._type_prefix = ''.join(
245                (self.command_prefix[0:1].upper(), self.command_prefix[1:]))
246        return self._type_prefix
247
248    @property
249    @abc.abstractmethod
250    def api_prefix(self):
251        """Return API token prefix.
252
253        Typically two uppercase letters followed by an underscore.
254
255        Must implement."""
256        raise NotImplementedError
257
258    @property
259    def api_version_prefix(self):
260        """Return API core version token prefix.
261
262        Implemented in terms of api_prefix.
263
264        May override."""
265        return self.api_prefix + 'VERSION_'
266
267    @property
268    def KHR_prefix(self):
269        """Return extension name prefix for KHR extensions.
270
271        Implemented in terms of api_prefix.
272
273        May override."""
274        return self.api_prefix + 'KHR_'
275
276    @property
277    def EXT_prefix(self):
278        """Return extension name prefix for EXT extensions.
279
280        Implemented in terms of api_prefix.
281
282        May override."""
283        return self.api_prefix + 'EXT_'
284
285    def writeFeature(self, featureExtraProtect, filename):
286        """Return True if OutputGenerator.endFeature should write this feature.
287
288        Defaults to always True.
289        Used in COutputGenerator.
290
291        May override."""
292        return True
293
294    def requires_error_validation(self, return_type):
295        """Return True if the return_type element is an API result code
296        requiring error validation.
297
298        Defaults to always False.
299
300        May override."""
301        return False
302
303    @property
304    def required_errors(self):
305        """Return a list of required error codes for validation.
306
307        Defaults to an empty list.
308
309        May override."""
310        return []
311
312    def is_voidpointer_alias(self, tag, text, tail):
313        """Return True if the declaration components (tag,text,tail) of an
314        element represents a void * type.
315
316        Defaults to a reasonable implementation.
317
318        May override."""
319        return tag == 'type' and text == 'void' and tail.startswith('*')
320
321    def make_voidpointer_alias(self, tail):
322        """Reformat a void * declaration to include the API alias macro.
323
324        Defaults to a no-op.
325
326        Must override if you actually want to use this feature in your project."""
327        return tail
328
329    def category_requires_validation(self, category):
330        """Return True if the given type 'category' always requires validation.
331
332        Defaults to a reasonable implementation.
333
334        May override."""
335        return category in CATEGORIES_REQUIRING_VALIDATION
336
337    def type_always_valid(self, typename):
338        """Return True if the given type name is always valid (never requires validation).
339
340        This is for things like integers.
341
342        Defaults to a reasonable implementation.
343
344        May override."""
345        return typename in TYPES_KNOWN_ALWAYS_VALID
346
347    @property
348    def should_skip_checking_codes(self):
349        """Return True if more than the basic validation of return codes should
350        be skipped for a command."""
351
352        return False
353
354    @property
355    def generate_index_terms(self):
356        """Return True if asiidoctor index terms should be generated as part
357           of an API interface from the docgenerator."""
358
359        return False
360
361    @property
362    def generate_enum_table(self):
363        """Return True if asciidoctor tables describing enumerants in a
364           group should be generated as part of group generation."""
365        return False
366
367    @property
368    def generate_max_enum_in_docs(self):
369        """Return True if MAX_ENUM tokens should be generated in
370           documentation includes."""
371        return False
372
373    @abc.abstractmethod
374    def extension_file_path(self, name):
375        """Return file path to an extension appendix relative to a directory
376           containing all such appendices.
377           - name - extension name
378
379           Must implement."""
380        raise NotImplementedError
381
382    def extension_include_string(self, name):
383        """Return format string for include:: line for an extension appendix
384           file.
385            - name - extension name"""
386
387        return 'include::{{appendices}}/{}[]'.format(
388                self.extension_file_path(name))
389
390    @property
391    def provisional_extension_warning(self):
392        """Return True if a warning should be included in extension
393           appendices for provisional extensions."""
394        return True
395
396    @property
397    def generated_include_path(self):
398        """Return path relative to the generated reference pages, to the
399           generated API include files."""
400
401        return '{generated}'
402
403    @property
404    def include_extension_appendix_in_refpage(self):
405        """Return True if generating extension refpages by embedding
406           extension appendix content (default), False otherwise
407           (OpenXR)."""
408
409        return True
410
411    def valid_flag_bit(self, bitpos):
412        """Return True if bitpos is an allowed numeric bit position for
413           an API flag.
414
415           Behavior depends on the data type used for flags (which may be 32
416           or 64 bits), and may depend on assumptions about compiler
417           handling of sign bits in enumerated types, as well."""
418        return True
419
420    @property
421    def duplicate_aliased_structs(self):
422        """
423        Should aliased structs have the original struct definition listed in the
424        generated docs snippet?
425        """
426        return False
427
428    @property
429    def protectProtoComment(self):
430        """Return True if generated #endif should have a comment matching
431           the protection symbol used in the opening #ifdef/#ifndef."""
432        return False
433
434    @property
435    def extra_refpage_headers(self):
436        """Return any extra headers (preceding the title) for generated
437           reference pages."""
438        return ''
439
440    @property
441    def extra_refpage_body(self):
442        """Return any extra text (following the title) for generated
443           reference pages."""
444        return ''
445
446