/ scripts / docgenerator.py
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])