docgenerator.py
1 #!/usr/bin/python3 -i 2 # 3 # Copyright (c) 2013-2019 The Khronos Group Inc. 4 # 5 # Licensed under the Apache License, Version 2.0 (the "License"); 6 # you may not use this file except in compliance with the License. 7 # You may obtain a copy of the License at 8 # 9 # http://www.apache.org/licenses/LICENSE-2.0 10 # 11 # Unless required by applicable law or agreed to in writing, software 12 # distributed under the License is distributed on an "AS IS" BASIS, 13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 # See the License for the specific language governing permissions and 15 # limitations under the License. 16 17 import sys 18 19 from generator import GeneratorOptions, OutputGenerator, regSortFeatures, noneStr, write 20 21 class DocGeneratorOptions(GeneratorOptions): 22 """DocGeneratorOptions - subclass of GeneratorOptions. 23 24 Shares many members with CGeneratorOptions, since 25 both are writing C-style declarations: 26 27 prefixText - list of strings to prefix generated header with 28 (usually a copyright statement + calling convention macros). 29 apicall - string to use for the function declaration prefix, 30 such as APICALL on Windows. 31 apientry - string to use for the calling convention macro, 32 in typedefs, such as APIENTRY. 33 apientryp - string to use for the calling convention macro 34 in function pointer typedefs, such as APIENTRYP. 35 directory - directory into which to generate include files 36 indentFuncProto - True if prototype declarations should put each 37 parameter on a separate line 38 indentFuncPointer - True if typedefed function pointers should put each 39 parameter on a separate line 40 alignFuncParam - if nonzero and parameters are being put on a 41 separate line, align parameter names at the specified column 42 43 Additional members: 44 45 expandEnumerants - if True, add BEGIN/END_RANGE macros in enumerated 46 type declarations 47 secondaryInclude - if True, add secondary (no xref anchor) versions 48 of generated files 49 """ 50 51 def __init__(self, 52 conventions = None, 53 filename = None, 54 directory = '.', 55 apiname = None, 56 profile = None, 57 versions = '.*', 58 emitversions = '.*', 59 defaultExtensions = None, 60 addExtensions = None, 61 removeExtensions = None, 62 emitExtensions = None, 63 sortProcedure = regSortFeatures, 64 prefixText = "", 65 apicall = '', 66 apientry = '', 67 apientryp = '', 68 indentFuncProto = True, 69 indentFuncPointer = False, 70 alignFuncParam = 0, 71 secondaryInclude = False, 72 expandEnumerants = True): 73 GeneratorOptions.__init__(self, conventions, filename, directory, apiname, profile, 74 versions, emitversions, defaultExtensions, 75 addExtensions, removeExtensions, 76 emitExtensions, sortProcedure) 77 self.prefixText = prefixText 78 self.apicall = apicall 79 self.apientry = apientry 80 self.apientryp = apientryp 81 self.indentFuncProto = indentFuncProto 82 self.indentFuncPointer = indentFuncPointer 83 self.alignFuncParam = alignFuncParam 84 self.secondaryInclude = secondaryInclude 85 self.expandEnumerants = expandEnumerants 86 87 # DocOutputGenerator - subclass of OutputGenerator. 88 # Generates AsciiDoc includes with C-language API interfaces, for reference 89 # pages and the corresponding specification. Similar to COutputGenerator, 90 # but each interface is written into a different file as determined by the 91 # options, only actual C types are emitted, and none of the boilerplate 92 # preprocessor code is emitted. 93 # 94 # ---- methods ---- 95 # DocOutputGenerator(errFile, warnFile, diagFile) - args as for 96 # OutputGenerator. Defines additional internal state. 97 # ---- methods overriding base class ---- 98 # beginFile(genOpts) 99 # endFile() 100 # beginFeature(interface, emit) 101 # endFeature() 102 # genType(typeinfo,name) 103 # genStruct(typeinfo,name) 104 # genGroup(groupinfo,name) 105 # genEnum(enuminfo, name) 106 # genCmd(cmdinfo) 107 class DocOutputGenerator(OutputGenerator): 108 """Generate specified API interfaces in a specific style, such as a C header""" 109 110 def __init__(self, 111 errFile = sys.stderr, 112 warnFile = sys.stderr, 113 diagFile = sys.stdout): 114 OutputGenerator.__init__(self, errFile, warnFile, diagFile) 115 # Keep track of all extension numbers 116 self.extension_numbers = set() 117 118 def beginFile(self, genOpts): 119 OutputGenerator.beginFile(self, genOpts) 120 121 def endFile(self): 122 OutputGenerator.endFile(self) 123 124 def beginFeature(self, interface, emit): 125 # Start processing in superclass 126 OutputGenerator.beginFeature(self, interface, emit) 127 # Verify that each extension has a unique number during doc generation 128 extension_number = interface.get('number') 129 if extension_number is not None and extension_number != "0": 130 if extension_number in self.extension_numbers: 131 self.logMsg('error', 'Duplicate extension number ', extension_number, ' detected in feature ', interface.get('name'), '\n') 132 exit(1) 133 else: 134 self.extension_numbers.add(extension_number) 135 136 def endFeature(self): 137 # Finish processing in superclass 138 OutputGenerator.endFeature(self) 139 140 # Generate an include file 141 # 142 # directory - subdirectory to put file in 143 # basename - base name of the file 144 # contents - contents of the file (Asciidoc boilerplate aside) 145 def writeInclude(self, directory, basename, contents): 146 # Create subdirectory, if needed 147 directory = self.genOpts.directory + '/' + directory 148 self.makeDir(directory) 149 150 # Create file 151 filename = directory + '/' + basename + '.txt' 152 self.logMsg('diag', '# Generating include file:', filename) 153 fp = open(filename, 'w', encoding='utf-8') 154 155 # Asciidoc anchor 156 write(self.genOpts.conventions.warning_comment, file=fp) 157 write('[[{0},{0}]]'.format(basename), file=fp) 158 write('[source,c++]', file=fp) 159 write('----', file=fp) 160 write(contents, file=fp) 161 write('----', file=fp) 162 fp.close() 163 164 if self.genOpts.secondaryInclude: 165 # Create secondary no cross-reference include file 166 filename = directory + '/' + basename + '.no-xref.txt' 167 self.logMsg('diag', '# Generating include file:', filename) 168 fp = open(filename, 'w', encoding='utf-8') 169 170 # Asciidoc anchor 171 write(self.genOpts.conventions.warning_comment, file=fp) 172 write('// Include this no-xref version without cross reference id for multiple includes of same file', file=fp) 173 write('[source,c++]', file=fp) 174 write('----', file=fp) 175 write(contents, file=fp) 176 write('----', file=fp) 177 fp.close() 178 179 # 180 # Type generation 181 def genType(self, typeinfo, name, alias): 182 OutputGenerator.genType(self, typeinfo, name, alias) 183 typeElem = typeinfo.elem 184 # If the type is a struct type, traverse the embedded <member> tags 185 # generating a structure. Otherwise, emit the tag text. 186 category = typeElem.get('category') 187 188 body = '' 189 if category in ('struct', 'union'): 190 # If the type is a struct type, generate it using the 191 # special-purpose generator. 192 self.genStruct(typeinfo, name, alias) 193 else: 194 if alias: 195 # If the type is an alias, just emit a typedef declaration 196 body = 'typedef ' + alias + ' ' + name + ';\n' 197 self.writeInclude(OutputGenerator.categoryToPath[category], 198 name, body) 199 else: 200 # Replace <apientry /> tags with an APIENTRY-style string 201 # (from self.genOpts). Copy other text through unchanged. 202 # If the resulting text is an empty string, don't emit it. 203 body = noneStr(typeElem.text) 204 for elem in typeElem: 205 if elem.tag == 'apientry': 206 body += self.genOpts.apientry + noneStr(elem.tail) 207 else: 208 body += noneStr(elem.text) + noneStr(elem.tail) 209 210 if body: 211 if category in OutputGenerator.categoryToPath: 212 self.writeInclude(OutputGenerator.categoryToPath[category], 213 name, body + '\n') 214 else: 215 self.logMsg('diag', '# NOT writing include file for type:', 216 name, '- bad category: ', category) 217 else: 218 self.logMsg('diag', '# NOT writing empty include file for type', name) 219 220 # Struct (e.g. C "struct" type) generation. 221 # This is a special case of the <type> tag where the contents are 222 # interpreted as a set of <member> tags instead of freeform C 223 # C type declarations. The <member> tags are just like <param> 224 # tags - they are a declaration of a struct or union member. 225 # Only simple member declarations are supported (no nested 226 # structs etc.) 227 # If alias is not None, then this struct aliases another; just 228 # generate a typedef of that alias. 229 def genStruct(self, typeinfo, typeName, alias): 230 OutputGenerator.genStruct(self, typeinfo, typeName, alias) 231 232 typeElem = typeinfo.elem 233 234 if alias: 235 body = 'typedef ' + alias + ' ' + typeName + ';\n' 236 else: 237 body = 'typedef ' + typeElem.get('category') + ' ' + typeName + ' {\n' 238 239 targetLen = 0 240 for member in typeElem.findall('.//member'): 241 targetLen = max(targetLen, self.getCParamTypeLength(member)) 242 for member in typeElem.findall('.//member'): 243 body += self.makeCParamDecl(member, targetLen + 4) 244 body += ';\n' 245 body += '} ' + typeName + ';' 246 247 self.writeInclude('structs', typeName, body) 248 249 # Group (e.g. C "enum" type) generation. 250 # These are concatenated together with other types. 251 # If alias is not None, it is the name of another group type 252 # which aliases this type; just generate that alias. 253 def genGroup(self, groupinfo, groupName, alias): 254 OutputGenerator.genGroup(self, groupinfo, groupName, alias) 255 256 if alias: 257 # If the group name is aliased, just emit a typedef declaration 258 # for the alias. 259 body = 'typedef ' + alias + ' ' + groupName + ';\n' 260 else: 261 expand = self.genOpts.expandEnumerants 262 (_, body) = self.buildEnumCDecl(expand, groupinfo, groupName) 263 264 self.writeInclude('enums', groupName, body) 265 266 # Enumerant generation 267 # <enum> tags may specify their values in several ways, but are usually 268 # just integers. 269 def genEnum(self, enuminfo, name, alias): 270 OutputGenerator.genEnum(self, enuminfo, name, alias) 271 self.logMsg('diag', '# NOT writing compile-time constant', name) 272 273 # (_, strVal) = self.enumToValue(enuminfo.elem, False) 274 # body = '#define ' + name.ljust(33) + ' ' + strVal 275 # self.writeInclude('consts', name, body) 276 277 # Command generation 278 def genCmd(self, cmdinfo, name, alias): 279 OutputGenerator.genCmd(self, cmdinfo, name, alias) 280 281 return_type = cmdinfo.elem.find('proto/type') 282 if self.genOpts.conventions.requires_error_validation(return_type): 283 # This command returns an API result code, so check that it 284 # returns at least the required errors. 285 required_errors = self.genOpts.conventions.required_errors 286 errorcodes = cmdinfo.elem.get('errorcodes').split(',') 287 if not required_errors.issubset(set(errorcodes)): 288 self.logMsg('error', 'Missing required error code for command: ', name, '\n') 289 exit(1) 290 291 decls = self.makeCDecls(cmdinfo.elem) 292 self.writeInclude('protos', name, decls[0])