#!/usr/bin/python3 -i # # Copyright (c) 2013-2019 The Khronos Group Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from pathlib import Path from generator import GeneratorOptions, OutputGenerator, noneStr, write ENUM_TABLE_PREFIX = """ [cols=",",options="header",] |======================================================================= |Enum |Description""" ENUM_TABLE_SUFFIX = """|=======================================================================""" FLAG_BLOCK_PREFIX = """.Flag Descriptions ****""" FLAG_BLOCK_SUFFIX = """****""" class DocGeneratorOptions(GeneratorOptions): """DocGeneratorOptions - subclass of GeneratorOptions for generating declaration snippets for the spec. Shares many members with CGeneratorOptions, since both are writing C-style declarations.""" def __init__(self, prefixText="", apicall='', apientry='', apientryp='', indentFuncProto=True, indentFuncPointer=False, alignFuncParam=0, secondaryInclude=False, expandEnumerants=True, extEnumerantAdditions=False, extEnumerantFormatString=" (Added by the {} extension)", **kwargs): """Constructor. Since this generator outputs multiple files at once, the filename is just a "stamp" to indicate last generation time. Shares many parameters/members with CGeneratorOptions, since both are writing C-style declarations: - prefixText - list of strings to prefix generated header with (usually a copyright statement + calling convention macros). - apicall - string to use for the function declaration prefix, such as APICALL on Windows. - apientry - string to use for the calling convention macro, in typedefs, such as APIENTRY. - apientryp - string to use for the calling convention macro in function pointer typedefs, such as APIENTRYP. - indentFuncProto - True if prototype declarations should put each parameter on a separate line - indentFuncPointer - True if typedefed function pointers should put each parameter on a separate line - alignFuncParam - if nonzero and parameters are being put on a separate line, align parameter names at the specified column Additional parameters/members: - expandEnumerants - if True, add BEGIN/END_RANGE macros in enumerated type declarations - secondaryInclude - if True, add secondary (no xref anchor) versions of generated files - extEnumerantAdditions - if True, include enumerants added by extensions in comment tables for core enumeration types. - extEnumerantFormatString - A format string for any additional message for enumerants from extensions if extEnumerantAdditions is True. The correctly- marked-up extension name will be passed. """ GeneratorOptions.__init__(self, **kwargs) self.prefixText = prefixText """list of strings to prefix generated header with (usually a copyright statement + calling convention macros).""" self.apicall = apicall """string to use for the function declaration prefix, such as APICALL on Windows.""" self.apientry = apientry """string to use for the calling convention macro, in typedefs, such as APIENTRY.""" self.apientryp = apientryp """string to use for the calling convention macro in function pointer typedefs, such as APIENTRYP.""" self.indentFuncProto = indentFuncProto """True if prototype declarations should put each parameter on a separate line""" self.indentFuncPointer = indentFuncPointer """True if typedefed function pointers should put each parameter on a separate line""" self.alignFuncParam = alignFuncParam """if nonzero and parameters are being put on a separate line, align parameter names at the specified column""" self.secondaryInclude = secondaryInclude """if True, add secondary (no xref anchor) versions of generated files""" self.expandEnumerants = expandEnumerants """if True, add BEGIN/END_RANGE macros in enumerated type declarations""" self.extEnumerantAdditions = extEnumerantAdditions """if True, include enumerants added by extensions in comment tables for core enumeration types.""" self.extEnumerantFormatString = extEnumerantFormatString """A format string for any additional message for enumerants from extensions if extEnumerantAdditions is True. The correctly- marked-up extension name will be passed.""" class DocOutputGenerator(OutputGenerator): """DocOutputGenerator - subclass of OutputGenerator. Generates AsciiDoc includes with C-language API interfaces, for reference pages and the corresponding specification. Similar to COutputGenerator, but each interface is written into a different file as determined by the options, only actual C types are emitted, and none of the boilerplate preprocessor code is emitted.""" def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # Keep track of all extension numbers self.extension_numbers = set() def beginFile(self, genOpts): OutputGenerator.beginFile(self, genOpts) self.result_type = genOpts.conventions.type_prefix + "Result" def endFile(self): OutputGenerator.endFile(self) def beginFeature(self, interface, emit): # Start processing in superclass OutputGenerator.beginFeature(self, interface, emit) # Decide if we're in core or an extension name = interface.get('name') self.in_core = name.startswith(self.conventions.api_version_prefix) # Verify that each extension has a unique number during doc generation # TODO move this to consistency_tools extension_number = interface.get('number') if extension_number is not None and extension_number != "0": if extension_number in self.extension_numbers: self.logMsg('error', 'Duplicate extension number ', extension_number, ' detected in feature ', interface.get('name'), '\n') exit(1) else: self.extension_numbers.add(extension_number) def endFeature(self): # Finish processing in superclass OutputGenerator.endFeature(self) def writeInclude(self, directory, basename, contents): """Generate an include file. - directory - subdirectory to put file in - basename - base name of the file - contents - contents of the file (Asciidoc boilerplate aside)""" # Create subdirectory, if needed directory = self.genOpts.directory + '/' + directory self.makeDir(directory) # Create file filename = directory + '/' + basename + '.txt' self.logMsg('diag', '# Generating include file:', filename) fp = open(filename, 'w', encoding='utf-8') # Asciidoc anchor write(self.genOpts.conventions.warning_comment, file=fp) write('[[{0}]]'.format(basename), file=fp) index_terms = [] if basename.startswith(self.conventions.command_prefix): index_terms.append(basename[2:] + " (function)") elif basename.startswith(self.conventions.type_prefix): index_terms.append(basename[2:] + " (type)") elif basename.startswith(self.conventions.api_prefix): index_terms.append(basename[len(self.conventions.api_prefix):] + " (define)") index_terms.append(basename) write('indexterm:[{}]'.format(','.join(index_terms)), file=fp) write('[source,c++]', file=fp) write('----', file=fp) write(contents, file=fp) write('----', file=fp) fp.close() if self.genOpts.secondaryInclude: # Create secondary no cross-reference include file filename = directory + '/' + basename + '.no-xref.txt' self.logMsg('diag', '# Generating include file:', filename) fp = open(filename, 'w', encoding='utf-8') # Asciidoc anchor write(self.genOpts.conventions.warning_comment, file=fp) write('// Include this no-xref version without cross reference id for multiple includes of same file', file=fp) write('[source,c++]', file=fp) write('----', file=fp) write(contents, file=fp) write('----', file=fp) fp.close() def writeTable(self, basename, values): """Output a table of enumerants.""" directory = Path(self.genOpts.directory) / 'enums' self.makeDir(str(directory)) filename = str(directory / '{}.comments.txt'.format(basename)) self.logMsg('diag', '# Generating include file:', filename) with open(filename, 'w', encoding='utf-8') as fp: write(self.conventions.warning_comment, file=fp) write(ENUM_TABLE_PREFIX, file=fp) for data in values: write("|ename:{}".format(data['name']), file=fp) write("|{}".format(data['comment']), file=fp) write(ENUM_TABLE_SUFFIX, file=fp) def writeFlagBox(self, basename, values): """Output a box of flag bit comments.""" directory = Path(self.genOpts.directory) / 'enums' self.makeDir(str(directory)) filename = str(directory / '{}.comments.txt'.format(basename)) self.logMsg('diag', '# Generating include file:', filename) with open(filename, 'w', encoding='utf-8') as fp: write(self.conventions.warning_comment, file=fp) write(FLAG_BLOCK_PREFIX, file=fp) for data in values: write("* ename:{} -- {}".format(data['name'], data['comment']), file=fp) write(FLAG_BLOCK_SUFFIX, file=fp) def genType(self, typeinfo, name, alias): "Generate type." OutputGenerator.genType(self, typeinfo, name, alias) typeElem = typeinfo.elem # If the type is a struct type, traverse the embedded tags # generating a structure. Otherwise, emit the tag text. category = typeElem.get('category') body = '' if category in ('struct', 'union'): # If the type is a struct type, generate it using the # special-purpose generator. self.genStruct(typeinfo, name, alias) else: if alias: # If the type is an alias, just emit a typedef declaration body = 'typedef ' + alias + ' ' + name + ';\n' self.writeInclude(OutputGenerator.categoryToPath[category], name, body) else: # Replace tags with an APIENTRY-style string # (from self.genOpts). Copy other text through unchanged. # If the resulting text is an empty string, don't emit it. body = noneStr(typeElem.text) for elem in typeElem: if elem.tag == 'apientry': body += self.genOpts.apientry + noneStr(elem.tail) else: body += noneStr(elem.text) + noneStr(elem.tail) if body: if category in OutputGenerator.categoryToPath: self.writeInclude(OutputGenerator.categoryToPath[category], name, body + '\n') else: self.logMsg('diag', '# NOT writing include file for type:', name, '- bad category: ', category) else: self.logMsg('diag', '# NOT writing empty include file for type', name) def genStruct(self, typeinfo, typeName, alias): """Generate struct.""" OutputGenerator.genStruct(self, typeinfo, typeName, alias) typeElem = typeinfo.elem if alias: body = 'typedef ' + alias + ' ' + typeName + ';\n' else: body = 'typedef ' + typeElem.get('category') + ' ' + typeName + ' {\n' targetLen = self.getMaxCParamTypeLength(typeinfo) for member in typeElem.findall('.//member'): body += self.makeCParamDecl(member, targetLen + 4) body += ';\n' body += '} ' + typeName + ';' self.writeInclude('structs', typeName, body) def genEnumTable(self, groupinfo, groupName): values = [] got_comment = False missing_comments = [] for elem in groupinfo.elem.findall('enum'): if not elem.get('required'): continue name = elem.get('name') data = { 'name': name, } (numVal, strVal) = self.enumToValue(elem, True) data['value'] = numVal extname = elem.get('extname') added_by_extension_to_core = (extname is not None and self.in_core) if added_by_extension_to_core and not self.genOpts.extEnumerantAdditions: # We're skipping such values continue comment = elem.get('comment') if comment: got_comment = True elif name.endswith('_UNKNOWN') and numVal == 0: # This is a dummy placeholder for 0-initialization to be clearly invalid. # Just skip this silently continue else: # Skip but record this in case it's an odd-one-out missing a comment. missing_comments.append(name) continue if added_by_extension_to_core and self.genOpts.extEnumerantFormatString: # Add a note to the comment comment += self.genOpts.extEnumerantFormatString.format( self.conventions.formatExtension(extname)) data['comment'] = comment values.append(data) if got_comment: # If any had a comment, output it. if missing_comments: self.logMsg('warn', 'The following values for', groupName, 'were omitted from the table due to missing comment attributes:', ', '.join(missing_comments)) group_type = groupinfo.elem.get('type') if groupName == self.result_type: # Split this into success and failure self.writeTable(groupName + '.success', (data for data in values if data['value'] >= 0)) self.writeTable(groupName + '.error', (data for data in values if data['value'] < 0)) elif group_type == 'bitmask': self.writeFlagBox(groupName, values) elif group_type == 'enum': self.writeTable(groupName, values) else: raise RuntimeError("Unrecognized enums type: " + str(group_type)) def genGroup(self, groupinfo, groupName, alias): """Generate group (e.g. C "enum" type).""" OutputGenerator.genGroup(self, groupinfo, groupName, alias) if alias: # If the group name is aliased, just emit a typedef declaration # for the alias. body = 'typedef ' + alias + ' ' + groupName + ';\n' else: expand = self.genOpts.expandEnumerants self.genEnumTable(groupinfo, groupName) (_, body) = self.buildEnumCDecl(expand, groupinfo, groupName) self.writeInclude('enums', groupName, body) def genEnum(self, enuminfo, name, alias): """Generate enumerant.""" OutputGenerator.genEnum(self, enuminfo, name, alias) self.logMsg('diag', '# NOT writing compile-time constant', name) def genCmd(self, cmdinfo, name, alias): "Generate command." OutputGenerator.genCmd(self, cmdinfo, name, alias) return_type = cmdinfo.elem.find('proto/type') if self.genOpts.conventions.requires_error_validation(return_type): # This command returns an API result code, so check that it # returns at least the required errors. # TODO move this to consistency_tools required_errors = set(self.genOpts.conventions.required_errors) errorcodes = cmdinfo.elem.get('errorcodes').split(',') if not required_errors.issubset(set(errorcodes)): self.logMsg('error', 'Missing required error code for command: ', name, '\n') exit(1) decls = self.makeCDecls(cmdinfo.elem) self.writeInclude('protos', name, decls[0])