#!/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. import sys from generator import GeneratorOptions, OutputGenerator, regSortFeatures, noneStr, write class DocGeneratorOptions(GeneratorOptions): """DocGeneratorOptions - subclass of GeneratorOptions. Shares many 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. directory - directory into which to generate include files 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 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 """ def __init__(self, conventions = None, filename = None, directory = '.', apiname = None, profile = None, versions = '.*', emitversions = '.*', defaultExtensions = None, addExtensions = None, removeExtensions = None, emitExtensions = None, sortProcedure = regSortFeatures, prefixText = "", apicall = '', apientry = '', apientryp = '', indentFuncProto = True, indentFuncPointer = False, alignFuncParam = 0, secondaryInclude = False, expandEnumerants = True): GeneratorOptions.__init__(self, conventions, filename, directory, apiname, profile, versions, emitversions, defaultExtensions, addExtensions, removeExtensions, emitExtensions, sortProcedure) self.prefixText = prefixText self.apicall = apicall self.apientry = apientry self.apientryp = apientryp self.indentFuncProto = indentFuncProto self.indentFuncPointer = indentFuncPointer self.alignFuncParam = alignFuncParam self.secondaryInclude = secondaryInclude self.expandEnumerants = expandEnumerants # 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. # # ---- methods ---- # DocOutputGenerator(errFile, warnFile, diagFile) - args as for # OutputGenerator. Defines additional internal state. # ---- methods overriding base class ---- # beginFile(genOpts) # endFile() # beginFeature(interface, emit) # endFeature() # genType(typeinfo,name) # genStruct(typeinfo,name) # genGroup(groupinfo,name) # genEnum(enuminfo, name) # genCmd(cmdinfo) class DocOutputGenerator(OutputGenerator): """Generate specified API interfaces in a specific style, such as a C header""" def __init__(self, errFile = sys.stderr, warnFile = sys.stderr, diagFile = sys.stdout): OutputGenerator.__init__(self, errFile, warnFile, diagFile) # Keep track of all extension numbers self.extension_numbers = set() def beginFile(self, genOpts): OutputGenerator.beginFile(self, genOpts) def endFile(self): OutputGenerator.endFile(self) def beginFeature(self, interface, emit): # Start processing in superclass OutputGenerator.beginFeature(self, interface, emit) # Verify that each extension has a unique number during doc generation 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) # Generate an include file # # directory - subdirectory to put file in # basename - base name of the file # contents - contents of the file (Asciidoc boilerplate aside) def writeInclude(self, directory, basename, contents): # 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},{0}]]'.format(basename), 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() # # Type generation def genType(self, typeinfo, name, alias): 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) # Struct (e.g. C "struct" type) generation. # This is a special case of the tag where the contents are # interpreted as a set of tags instead of freeform C # C type declarations. The tags are just like # tags - they are a declaration of a struct or union member. # Only simple member declarations are supported (no nested # structs etc.) # If alias is not None, then this struct aliases another; just # generate a typedef of that alias. def genStruct(self, typeinfo, typeName, alias): 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 = 0 for member in typeElem.findall('.//member'): targetLen = max(targetLen, self.getCParamTypeLength(member)) for member in typeElem.findall('.//member'): body += self.makeCParamDecl(member, targetLen + 4) body += ';\n' body += '} ' + typeName + ';' self.writeInclude('structs', typeName, body) # Group (e.g. C "enum" type) generation. # These are concatenated together with other types. # If alias is not None, it is the name of another group type # which aliases this type; just generate that alias. def genGroup(self, groupinfo, groupName, alias): 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 (_, body) = self.buildEnumCDecl(expand, groupinfo, groupName) self.writeInclude('enums', groupName, body) # Enumerant generation # tags may specify their values in several ways, but are usually # just integers. def genEnum(self, enuminfo, name, alias): OutputGenerator.genEnum(self, enuminfo, name, alias) self.logMsg('diag', '# NOT writing compile-time constant', name) # (_, strVal) = self.enumToValue(enuminfo.elem, False) # body = '#define ' + name.ljust(33) + ' ' + strVal # self.writeInclude('consts', name, body) # Command generation def genCmd(self, cmdinfo, name, alias): 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. required_errors = 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])