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