• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/python3 -i
2#
3# Copyright 2013-2021 The Khronos Group Inc.
4#
5# SPDX-License-Identifier: Apache-2.0
6
7from pathlib import Path
8
9from generator import GeneratorOptions, OutputGenerator, noneStr, write
10
11ENUM_TABLE_PREFIX = """
12[cols=",",options="header",]
13|=======================================================================
14|Enum |Description"""
15
16ENUM_TABLE_SUFFIX = """|======================================================================="""
17
18FLAG_BLOCK_PREFIX = """.Flag Descriptions
19****"""
20
21FLAG_BLOCK_SUFFIX = """****"""
22
23def orgLevelKey(name):
24    # Sort key for organization levels of features / extensions
25    # From highest to lowest, core versions, KHR extensions, EXT extensions,
26    # and vendor extensions
27
28    prefixes = (
29        'VK_VERSION_',
30        'VKSC_VERSION_',
31        'VK_KHR_',
32        'VK_EXT_')
33
34    i = 0
35    for prefix in prefixes:
36        if name.startswith(prefix):
37            return i
38        i += 1
39
40    # Everything else (e.g. vendor extensions) is least important
41    return i
42
43
44def orgLevelKey(name):
45    # Sort key for organization levels of features / extensions
46    # From highest to lowest, core versions, KHR extensions, EXT extensions,
47    # and vendor extensions
48
49    prefixes = (
50        'VK_VERSION_',
51        'VKSC_VERSION_',
52        'VK_KHR_',
53        'VK_EXT_')
54
55    i = 0
56    for prefix in prefixes:
57        if name.startswith(prefix):
58            return i
59        i += 1
60
61    # Everything else (e.g. vendor extensions) is least important
62    return i
63
64class DocGeneratorOptions(GeneratorOptions):
65    """DocGeneratorOptions - subclass of GeneratorOptions for
66    generating declaration snippets for the spec.
67
68    Shares many members with CGeneratorOptions, since
69    both are writing C-style declarations."""
70
71    def __init__(self,
72                 prefixText="",
73                 apicall='',
74                 apientry='',
75                 apientryp='',
76                 indentFuncProto=True,
77                 indentFuncPointer=False,
78                 alignFuncParam=0,
79                 secondaryInclude=False,
80                 expandEnumerants=True,
81                 extEnumerantAdditions=False,
82                 extEnumerantFormatString=" (Added by the {} extension)",
83                 **kwargs):
84        """Constructor.
85
86        Since this generator outputs multiple files at once,
87        the filename is just a "stamp" to indicate last generation time.
88
89        Shares many parameters/members with CGeneratorOptions, since
90        both are writing C-style declarations:
91
92        - prefixText - list of strings to prefix generated header with
93        (usually a copyright statement + calling convention macros).
94        - apicall - string to use for the function declaration prefix,
95        such as APICALL on Windows.
96        - apientry - string to use for the calling convention macro,
97        in typedefs, such as APIENTRY.
98        - apientryp - string to use for the calling convention macro
99        in function pointer typedefs, such as APIENTRYP.
100        - indentFuncProto - True if prototype declarations should put each
101        parameter on a separate line
102        - indentFuncPointer - True if typedefed function pointers should put each
103        parameter on a separate line
104        - alignFuncParam - if nonzero and parameters are being put on a
105        separate line, align parameter names at the specified column
106
107        Additional parameters/members:
108
109        - expandEnumerants - if True, add BEGIN/END_RANGE macros in enumerated
110        type declarations
111        - secondaryInclude - if True, add secondary (no xref anchor) versions
112        of generated files
113        - extEnumerantAdditions - if True, include enumerants added by extensions
114        in comment tables for core enumeration types.
115        - extEnumerantFormatString - A format string for any additional message for
116        enumerants from extensions if extEnumerantAdditions is True. The correctly-
117        marked-up extension name will be passed.
118        """
119        GeneratorOptions.__init__(self, **kwargs)
120        self.prefixText = prefixText
121        """list of strings to prefix generated header with (usually a copyright statement + calling convention macros)."""
122
123        self.apicall = apicall
124        """string to use for the function declaration prefix, such as APICALL on Windows."""
125
126        self.apientry = apientry
127        """string to use for the calling convention macro, in typedefs, such as APIENTRY."""
128
129        self.apientryp = apientryp
130        """string to use for the calling convention macro in function pointer typedefs, such as APIENTRYP."""
131
132        self.indentFuncProto = indentFuncProto
133        """True if prototype declarations should put each parameter on a separate line"""
134
135        self.indentFuncPointer = indentFuncPointer
136        """True if typedefed function pointers should put each parameter on a separate line"""
137
138        self.alignFuncParam = alignFuncParam
139        """if nonzero and parameters are being put on a separate line, align parameter names at the specified column"""
140
141        self.secondaryInclude = secondaryInclude
142        """if True, add secondary (no xref anchor) versions of generated files"""
143
144        self.expandEnumerants = expandEnumerants
145        """if True, add BEGIN/END_RANGE macros in enumerated type declarations"""
146
147        self.extEnumerantAdditions = extEnumerantAdditions
148        """if True, include enumerants added by extensions in comment tables for core enumeration types."""
149
150        self.extEnumerantFormatString = extEnumerantFormatString
151        """A format string for any additional message for
152        enumerants from extensions if extEnumerantAdditions is True. The correctly-
153        marked-up extension name will be passed."""
154
155
156class DocOutputGenerator(OutputGenerator):
157    """DocOutputGenerator - subclass of OutputGenerator.
158
159    Generates AsciiDoc includes with C-language API interfaces, for reference
160    pages and the corresponding specification. Similar to COutputGenerator,
161    but each interface is written into a different file as determined by the
162    options, only actual C types are emitted, and none of the boilerplate
163    preprocessor code is emitted."""
164
165    def __init__(self, *args, **kwargs):
166        super().__init__(*args, **kwargs)
167        # Keep track of all extension numbers
168        self.extension_numbers = set()
169
170    def beginFile(self, genOpts):
171        OutputGenerator.beginFile(self, genOpts)
172
173        # This should be a separate conventions property rather than an
174        # inferred type name pattern for different APIs.
175        self.result_type = genOpts.conventions.type_prefix + "Result"
176
177    def endFile(self):
178        OutputGenerator.endFile(self)
179
180    def beginFeature(self, interface, emit):
181        # Start processing in superclass
182        OutputGenerator.beginFeature(self, interface, emit)
183
184        # Decide if we are in a core <feature> or an <extension>
185        self.in_core = (interface.tag == 'feature')
186
187        # Verify that each <extension> has a unique number during doc
188        # generation
189        # TODO move this to consistency_tools
190        if not self.in_core:
191            extension_number = interface.get('number')
192            if extension_number is not None and extension_number != "0":
193                if extension_number in self.extension_numbers:
194                    self.logMsg('error', 'Duplicate extension number ', extension_number, ' detected in feature ', interface.get('name'), '\n')
195                    exit(1)
196                else:
197                    self.extension_numbers.add(extension_number)
198
199    def endFeature(self):
200        # Finish processing in superclass
201        OutputGenerator.endFeature(self)
202
203    def genRequirements(self, name, mustBeFound = True):
204        """Generate text showing what core versions and extensions introduce
205        an API. This relies on the map in api.py, which may be loaded at
206        runtime into self.apidict. If not present, no message is
207        generated.
208
209        - name - name of the API
210        - mustBeFound - If True, when requirements for 'name' cannot be
211          determined, a warning comment is generated.
212        """
213
214        if self.apidict:
215            if name in self.apidict.requiredBy:
216                # It is possible to get both 'A with B' and 'B with A' for
217                # the same API.
218                # To simplify this, sort the (base,dependency) requirements
219                # and put them in a set to ensure they are unique.
220                features = set()
221                for (base,dependency) in self.apidict.requiredBy[name]:
222                    if dependency is not None:
223                        l = sorted(
224                                sorted((base, dependency)),
225                                key=orgLevelKey)
226                        features.add(' with '.join(l))
227                    else:
228                        features.add(base)
229                # Sort the overall dependencies so core versions are first
230                provider = ', '.join(sorted(
231                                        sorted(features),
232                                        key=orgLevelKey))
233                return f'// Provided by {provider}\n'
234            else:
235                if mustBeFound:
236                    self.logMsg('warn', 'genRequirements: API {} not found'.format(name))
237                return ''
238        else:
239            # No API dictionary available, return nothing
240            return ''
241
242    def writeInclude(self, directory, basename, contents):
243        """Generate an include file.
244
245        - directory - subdirectory to put file in
246        - basename - base name of the file
247        - contents - contents of the file (Asciidoc boilerplate aside)"""
248        # Create subdirectory, if needed
249        directory = self.genOpts.directory + '/' + directory
250        self.makeDir(directory)
251
252        # Create file
253        filename = directory + '/' + basename + '.txt'
254        self.logMsg('diag', '# Generating include file:', filename)
255        fp = open(filename, 'w', encoding='utf-8')
256
257        # Asciidoc anchor
258        write(self.genOpts.conventions.warning_comment, file=fp)
259        write('[[{0},{0}]]'.format(basename), file=fp)
260
261        if self.genOpts.conventions.generate_index_terms:
262            index_terms = []
263            if basename.startswith(self.conventions.command_prefix):
264                index_terms.append(basename[2:] + " (function)")
265            elif basename.startswith(self.conventions.type_prefix):
266                index_terms.append(basename[2:] + " (type)")
267            elif basename.startswith(self.conventions.api_prefix):
268                index_terms.append(basename[len(self.conventions.api_prefix):] + " (define)")
269            index_terms.append(basename)
270            write('indexterm:[{}]'.format(','.join(index_terms)), file=fp)
271
272        write('[source,c++]', file=fp)
273        write('----', file=fp)
274        write(contents, file=fp)
275        write('----', file=fp)
276        fp.close()
277
278        if self.genOpts.secondaryInclude:
279            # Create secondary no cross-reference include file
280            filename = directory + '/' + basename + '.no-xref.txt'
281            self.logMsg('diag', '# Generating include file:', filename)
282            fp = open(filename, 'w', encoding='utf-8')
283
284            # Asciidoc anchor
285            write(self.genOpts.conventions.warning_comment, file=fp)
286            write('// Include this no-xref version without cross reference id for multiple includes of same file', file=fp)
287            write('[source,c++]', file=fp)
288            write('----', file=fp)
289            write(contents, file=fp)
290            write('----', file=fp)
291            fp.close()
292
293    def writeTable(self, basename, values):
294        """Output a table of enumerants."""
295        directory = Path(self.genOpts.directory) / 'enums'
296        self.makeDir(str(directory))
297
298        filename = str(directory / '{}.comments.txt'.format(basename))
299        self.logMsg('diag', '# Generating include file:', filename)
300
301        with open(filename, 'w', encoding='utf-8') as fp:
302            write(self.conventions.warning_comment, file=fp)
303            write(ENUM_TABLE_PREFIX, file=fp)
304
305            for data in values:
306                write("|ename:{}".format(data['name']), file=fp)
307                write("|{}".format(data['comment']), file=fp)
308
309            write(ENUM_TABLE_SUFFIX, file=fp)
310
311    def writeFlagBox(self, basename, values):
312        """Output a box of flag bit comments."""
313        directory = Path(self.genOpts.directory) / 'enums'
314        self.makeDir(str(directory))
315
316        filename = str(directory / '{}.comments.txt'.format(basename))
317        self.logMsg('diag', '# Generating include file:', filename)
318
319        with open(filename, 'w', encoding='utf-8') as fp:
320            write(self.conventions.warning_comment, file=fp)
321            write(FLAG_BLOCK_PREFIX, file=fp)
322
323            for data in values:
324                write("* ename:{} -- {}".format(data['name'],
325                                                data['comment']),
326                      file=fp)
327
328            write(FLAG_BLOCK_SUFFIX, file=fp)
329
330    def genType(self, typeinfo, name, alias):
331        """Generate type."""
332        OutputGenerator.genType(self, typeinfo, name, alias)
333        typeElem = typeinfo.elem
334        # If the type is a struct type, traverse the embedded <member> tags
335        # generating a structure. Otherwise, emit the tag text.
336        category = typeElem.get('category')
337
338        if category in ('struct', 'union'):
339            # If the type is a struct type, generate it using the
340            # special-purpose generator.
341            self.genStruct(typeinfo, name, alias)
342        elif category not in OutputGenerator.categoryToPath:
343            # If there is no path, do not write output
344            self.logMsg('diag', 'NOT writing include for {} category {}'.format(
345                        name, category))
346        else:
347            body = self.genRequirements(name)
348            if alias:
349                # If the type is an alias, just emit a typedef declaration
350                body += 'typedef ' + alias + ' ' + name + ';\n'
351                self.writeInclude(OutputGenerator.categoryToPath[category],
352                                  name, body)
353            else:
354                # Replace <apientry /> tags with an APIENTRY-style string
355                # (from self.genOpts). Copy other text through unchanged.
356                # If the resulting text is an empty string, do not emit it.
357                body += noneStr(typeElem.text)
358                for elem in typeElem:
359                    if elem.tag == 'apientry':
360                        body += self.genOpts.apientry + noneStr(elem.tail)
361                    else:
362                        body += noneStr(elem.text) + noneStr(elem.tail)
363
364                if body:
365                    self.writeInclude(OutputGenerator.categoryToPath[category],
366                                      name, body + '\n')
367                else:
368                    self.logMsg('diag', 'NOT writing empty include file for type', name)
369
370    def genStruct(self, typeinfo, typeName, alias):
371        """Generate struct."""
372        OutputGenerator.genStruct(self, typeinfo, typeName, alias)
373
374        typeElem = typeinfo.elem
375
376        body = self.genRequirements(typeName)
377        if alias:
378            body += 'typedef ' + alias + ' ' + typeName + ';\n'
379        else:
380            body += 'typedef ' + typeElem.get('category') + ' ' + typeName + ' {\n'
381
382            targetLen = self.getMaxCParamTypeLength(typeinfo)
383            for member in typeElem.findall('.//member'):
384                body += self.makeCParamDecl(member, targetLen + 4)
385                body += ';\n'
386            body += '} ' + typeName + ';'
387
388        self.writeInclude('structs', typeName, body)
389
390    def genEnumTable(self, groupinfo, groupName):
391        """Generate tables of enumerant values and short descriptions from
392        the XML."""
393
394        values = []
395        got_comment = False
396        missing_comments = []
397        for elem in groupinfo.elem.findall('enum'):
398            if not elem.get('required'):
399                continue
400            name = elem.get('name')
401
402            data = {
403                'name': name,
404            }
405
406            (numVal, strVal) = self.enumToValue(elem, True)
407            data['value'] = numVal
408
409            extname = elem.get('extname')
410
411            added_by_extension_to_core = (extname is not None and self.in_core)
412            if added_by_extension_to_core and not self.genOpts.extEnumerantAdditions:
413                # We are skipping such values
414                continue
415
416            comment = elem.get('comment')
417            if comment:
418                got_comment = True
419            elif name.endswith('_UNKNOWN') and numVal == 0:
420                # This is a placeholder for 0-initialization to be clearly invalid.
421                # Just skip this silently
422                continue
423            else:
424                # Skip but record this in case it is an odd-one-out missing
425                # a comment.
426                missing_comments.append(name)
427                continue
428
429            if added_by_extension_to_core and self.genOpts.extEnumerantFormatString:
430                # Add a note to the comment
431                comment += self.genOpts.extEnumerantFormatString.format(
432                    self.conventions.formatExtension(extname))
433
434            data['comment'] = comment
435            values.append(data)
436
437        if got_comment:
438            # If any had a comment, output it.
439
440            if missing_comments:
441                self.logMsg('warn', 'The following values for', groupName,
442                            'were omitted from the table due to missing comment attributes:',
443                            ', '.join(missing_comments))
444
445            group_type = groupinfo.elem.get('type')
446            if groupName == self.result_type:
447                # Split this into success and failure
448                self.writeTable(groupName + '.success',
449                                (data for data in values
450                                 if data['value'] >= 0))
451                self.writeTable(groupName + '.error',
452                                (data for data in values
453                                 if data['value'] < 0))
454            elif group_type == 'bitmask':
455                self.writeFlagBox(groupName, values)
456            elif group_type == 'enum':
457                self.writeTable(groupName, values)
458            else:
459                raise RuntimeError("Unrecognized enums type: " + str(group_type))
460
461    def genGroup(self, groupinfo, groupName, alias):
462        """Generate group (e.g. C "enum" type)."""
463        OutputGenerator.genGroup(self, groupinfo, groupName, alias)
464
465        body = self.genRequirements(groupName)
466        if alias:
467            # If the group name is aliased, just emit a typedef declaration
468            # for the alias.
469            body += 'typedef ' + alias + ' ' + groupName + ';\n'
470        else:
471            expand = self.genOpts.expandEnumerants
472            (_, enumbody) = self.buildEnumCDecl(expand, groupinfo, groupName)
473            body += enumbody
474            if self.genOpts.conventions.generate_enum_table:
475                self.genEnumTable(groupinfo, groupName)
476
477        self.writeInclude('enums', groupName, body)
478
479    def genEnum(self, enuminfo, name, alias):
480        """Generate the C declaration for a constant (a single <enum> value)."""
481
482        OutputGenerator.genEnum(self, enuminfo, name, alias)
483
484        body = self.buildConstantCDecl(enuminfo, name, alias)
485
486        self.writeInclude('enums', name, body)
487
488    def genCmd(self, cmdinfo, name, alias):
489        "Generate command."
490        OutputGenerator.genCmd(self, cmdinfo, name, alias)
491
492        return_type = cmdinfo.elem.find('proto/type')
493        if self.genOpts.conventions.requires_error_validation(return_type):
494            # This command returns an API result code, so check that it
495            # returns at least the required errors.
496            # TODO move this to consistency_tools
497            required_errors = set(self.genOpts.conventions.required_errors)
498            errorcodes = cmdinfo.elem.get('errorcodes').split(',')
499            if not required_errors.issubset(set(errorcodes)):
500                self.logMsg('error', 'Missing required error code for command: ', name, '\n')
501                exit(1)
502
503        body = self.genRequirements(name)
504        decls = self.makeCDecls(cmdinfo.elem)
505        body += decls[0]
506        self.writeInclude('protos', name, body)
507