/ scripts / cgenerator.py
cgenerator.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 os
 18  import re
 19  import sys
 20  from generator import (GeneratorOptions, OutputGenerator, noneStr,
 21                         regSortFeatures, write)
 22  
 23  # CGeneratorOptions - subclass of GeneratorOptions.
 24  #
 25  # Adds options used by COutputGenerator objects during C language header
 26  # generation.
 27  #
 28  # Additional members
 29  #   prefixText - list of strings to prefix generated header with
 30  #     (usually a copyright statement + calling convention macros).
 31  #   protectFile - True if multiple inclusion protection should be
 32  #     generated (based on the filename) around the entire header.
 33  #   protectFeature - True if #ifndef..#endif protection should be
 34  #     generated around a feature interface in the header file.
 35  #   genFuncPointers - True if function pointer typedefs should be
 36  #     generated
 37  #   protectProto - If conditional protection should be generated
 38  #     around prototype declarations, set to either '#ifdef'
 39  #     to require opt-in (#ifdef protectProtoStr) or '#ifndef'
 40  #     to require opt-out (#ifndef protectProtoStr). Otherwise
 41  #     set to None.
 42  #   protectProtoStr - #ifdef/#ifndef symbol to use around prototype
 43  #     declarations, if protectProto is set
 44  #   apicall - string to use for the function declaration prefix,
 45  #     such as APICALL on Windows.
 46  #   apientry - string to use for the calling convention macro,
 47  #     in typedefs, such as APIENTRY.
 48  #   apientryp - string to use for the calling convention macro
 49  #     in function pointer typedefs, such as APIENTRYP.
 50  #   directory - directory into which to generate include files
 51  #   indentFuncProto - True if prototype declarations should put each
 52  #     parameter on a separate line
 53  #   indentFuncPointer - True if typedefed function pointers should put each
 54  #     parameter on a separate line
 55  #   alignFuncParam - if nonzero and parameters are being put on a
 56  #     separate line, align parameter names at the specified column
 57  #   genEnumBeginEndRange - True if BEGIN_RANGE / END_RANGE macros should
 58  #     be generated for enumerated types
 59  #   genAliasMacro - True if the OpenXR alias macro should be generated
 60  #     for aliased types (unclear what other circumstances this is useful)
 61  #   aliasMacro - alias macro to inject when genAliasMacro is True
 62  class CGeneratorOptions(GeneratorOptions):
 63      """Represents options during C interface generation for headers"""
 64  
 65      def __init__(self,
 66                   conventions = None,
 67                   filename = None,
 68                   directory = '.',
 69                   apiname = None,
 70                   profile = None,
 71                   versions = '.*',
 72                   emitversions = '.*',
 73                   defaultExtensions = None,
 74                   addExtensions = None,
 75                   removeExtensions = None,
 76                   emitExtensions = None,
 77                   sortProcedure = regSortFeatures,
 78                   prefixText = "",
 79                   genFuncPointers = True,
 80                   protectFile = True,
 81                   protectFeature = True,
 82                   protectProto = None,
 83                   protectProtoStr = None,
 84                   apicall = '',
 85                   apientry = '',
 86                   apientryp = '',
 87                   indentFuncProto = True,
 88                   indentFuncPointer = False,
 89                   alignFuncParam = 0,
 90                   genEnumBeginEndRange = False,
 91                   genAliasMacro = False,
 92                   aliasMacro = ''
 93                  ):
 94          GeneratorOptions.__init__(self, conventions, filename, directory, apiname, profile,
 95                                    versions, emitversions, defaultExtensions,
 96                                    addExtensions, removeExtensions,
 97                                    emitExtensions, sortProcedure)
 98          self.prefixText      = prefixText
 99          self.genFuncPointers = genFuncPointers
100          self.protectFile     = protectFile
101          self.protectFeature  = protectFeature
102          self.protectProto    = protectProto
103          self.protectProtoStr = protectProtoStr
104          self.apicall         = apicall
105          self.apientry        = apientry
106          self.apientryp       = apientryp
107          self.indentFuncProto = indentFuncProto
108          self.indentFuncPointer = indentFuncPointer
109          self.alignFuncParam  = alignFuncParam
110          self.genEnumBeginEndRange = genEnumBeginEndRange
111          self.genAliasMacro   = genAliasMacro
112          self.aliasMacro      = aliasMacro
113  
114  # COutputGenerator - subclass of OutputGenerator.
115  # Generates C-language API interfaces.
116  #
117  # ---- methods ----
118  # COutputGenerator(errFile, warnFile, diagFile) - args as for
119  #   OutputGenerator. Defines additional internal state.
120  # ---- methods overriding base class ----
121  # beginFile(genOpts)
122  # endFile()
123  # beginFeature(interface, emit)
124  # endFeature()
125  # genType(typeinfo,name)
126  # genStruct(typeinfo,name)
127  # genGroup(groupinfo,name)
128  # genEnum(enuminfo, name)
129  # genCmd(cmdinfo)
130  class COutputGenerator(OutputGenerator):
131      """Generate specified API interfaces in a specific style, such as a C header"""
132      # This is an ordered list of sections in the header file.
133      TYPE_SECTIONS = ['include', 'define', 'basetype', 'handle', 'enum',
134                       'group', 'bitmask', 'funcpointer', 'struct']
135      ALL_SECTIONS = TYPE_SECTIONS + ['commandPointer', 'command']
136  
137      def __init__(self,
138                   errFile = sys.stderr,
139                   warnFile = sys.stderr,
140                   diagFile = sys.stdout):
141          OutputGenerator.__init__(self, errFile, warnFile, diagFile)
142          # Internal state - accumulators for different inner block text
143          self.sections = {section: [] for section in self.ALL_SECTIONS}
144          self.feature_not_empty = False
145          self.need_platform_include = False
146          self.may_alias = None
147  
148      def beginFile(self, genOpts):
149          OutputGenerator.beginFile(self, genOpts)
150          # C-specific
151          #
152          # Multiple inclusion protection & C++ wrappers.
153          if genOpts.protectFile and self.genOpts.filename:
154              headerSym = re.sub(r'\.h', '_h_',
155                                 os.path.basename(self.genOpts.filename)).upper()
156              write('#ifndef', headerSym, file=self.outFile)
157              write('#define', headerSym, '1', file=self.outFile)
158              self.newline()
159          write('#ifdef __cplusplus', file=self.outFile)
160          write('extern "C" {', file=self.outFile)
161          write('#endif', file=self.outFile)
162          self.newline()
163  
164          # User-supplied prefix text, if any (list of strings)
165          if genOpts.prefixText:
166              for s in genOpts.prefixText:
167                  write(s, file=self.outFile)
168  
169      def endFile(self):
170          # C-specific
171          # Finish C++ wrapper and multiple inclusion protection
172          self.newline()
173          write('#ifdef __cplusplus', file=self.outFile)
174          write('}', file=self.outFile)
175          write('#endif', file=self.outFile)
176          if self.genOpts.protectFile and self.genOpts.filename:
177              self.newline()
178              write('#endif', file=self.outFile)
179          # Finish processing in superclass
180          OutputGenerator.endFile(self)
181  
182      def beginFeature(self, interface, emit):
183          # Start processing in superclass
184          OutputGenerator.beginFeature(self, interface, emit)
185          # C-specific
186          # Accumulate includes, defines, types, enums, function pointer typedefs,
187          # end function prototypes separately for this feature. They're only
188          # printed in endFeature().
189          self.sections = {section: [] for section in self.ALL_SECTIONS}
190          self.feature_not_empty = False
191  
192      def endFeature(self):
193          # C-specific
194          # Actually write the interface to the output file.
195          if self.emit:
196              if self.feature_not_empty:
197                  if self.genOpts.conventions.writeFeature(self.featureExtraProtect, self.genOpts.filename):
198                      self.newline()
199                      if self.genOpts.protectFeature:
200                          write('#ifndef', self.featureName, file=self.outFile)
201                      # If type declarations are needed by other features based on
202                      # this one, it may be necessary to suppress the ExtraProtect,
203                      # or move it below the 'for section...' loop.
204                      if self.featureExtraProtect is not None:
205                          write('#ifdef', self.featureExtraProtect, file=self.outFile)
206                      self.newline()
207                      write('#define', self.featureName, '1', file=self.outFile)
208                      for section in self.TYPE_SECTIONS:
209                          # OpenXR:
210                          # If we need the explicit include of the external platform header,
211                          # put it right before the function pointer definitions
212                          if section == "funcpointer" and self.need_platform_include:
213                              write('// Include for OpenXR Platform-Specific Types', file=self.outFile)
214                              write('#include "openxr_platform.h"', file=self.outFile)
215                              self.newline()
216                              self.need_platform_include = False
217                          contents = self.sections[section]
218                          if contents:
219                              write('\n'.join(contents), file=self.outFile)
220                      if self.genOpts.genFuncPointers and self.sections['commandPointer']:
221                          write('\n'.join(self.sections['commandPointer']), file=self.outFile)
222                          self.newline()
223                      if self.sections['command']:
224                          if self.genOpts.protectProto:
225                              write(self.genOpts.protectProto,
226                                  self.genOpts.protectProtoStr, file=self.outFile)
227                          write('\n'.join(self.sections['command']), end='', file=self.outFile)
228                          if self.genOpts.protectProto:
229                              write('#endif', file=self.outFile)
230                          else:
231                              self.newline()
232                      if self.featureExtraProtect is not None:
233                          write('#endif /*', self.featureExtraProtect, '*/', file=self.outFile)
234                      if self.genOpts.protectFeature:
235                          write('#endif /*', self.featureName, '*/', file=self.outFile)
236          # Finish processing in superclass
237          OutputGenerator.endFeature(self)
238  
239      # Append a definition to the specified section
240      def appendSection(self, section, text):
241          # self.sections[section].append('SECTION: ' + section + '\n')
242          self.sections[section].append(text)
243          self.feature_not_empty = True
244  
245      # Type generation
246      def genType(self, typeinfo, name, alias):
247          OutputGenerator.genType(self, typeinfo, name, alias)
248          typeElem = typeinfo.elem
249  
250          # Vulkan:
251          # Determine the category of the type, and the type section to add
252          # its definition to.
253          # 'funcpointer' is added to the 'struct' section as a workaround for
254          # internal issue #877, since structures and function pointer types
255          # can have cross-dependencies.
256          category = typeElem.get('category')
257          if category == 'funcpointer':
258              section = 'struct'
259          else:
260              section = category
261  
262          if category in ('struct', 'union'):
263              # If the type is a struct type, generate it using the
264              # special-purpose generator.
265              self.genStruct(typeinfo, name, alias)
266          else:
267              # OpenXR: this section was not under 'else:' previously, just fell through
268              if alias:
269                  # If the type is an alias, just emit a typedef declaration
270                  body = 'typedef ' + alias + ' ' + name + ';\n'
271              else:
272                  # Replace <apientry /> tags with an APIENTRY-style string
273                  # (from self.genOpts). Copy other text through unchanged.
274                  # If the resulting text is an empty string, don't emit it.
275                  body = noneStr(typeElem.text)
276                  for elem in typeElem:
277                      if elem.tag == 'apientry':
278                          body += self.genOpts.apientry + noneStr(elem.tail)
279                      else:
280                          body += noneStr(elem.text) + noneStr(elem.tail)
281              if body:
282                  # Add extra newline after multi-line entries.
283                  if '\n' in body[0:-1]:
284                      body += '\n'
285                  self.appendSection(section, body)
286  
287      # Protection string generation
288      # Protection strings are the strings defining the OS/Platform/Graphics
289      # requirements for a given OpenXR command.  When generating the
290      # language header files, we need to make sure the items specific to a
291      # graphics API or OS platform are properly wrapped in #ifs.
292      def genProtectString(self, protect_str):
293          protect_if_str = ''
294          protect_end_str = ''
295          protect_list = []
296          if protect_str:
297              if ',' in protect_str:
298                  protect_list.extend(protect_str.split(","))
299                  protect_def_str = ''
300                  count = 0
301                  for protect_define in protect_list:
302                      if count > 0:
303                          protect_def_str += ' &&'
304                      protect_def_str += ' defined(%s)' % protect_define
305                      count = count + 1
306                      count = count + 1
307                  protect_if_str  = '#if'
308                  protect_if_str += protect_def_str
309                  protect_if_str += '\n'
310                  protect_end_str  = '#endif //'
311                  protect_end_str += protect_def_str
312                  protect_end_str += '\n'
313              else:
314                  protect_if_str += '#ifdef %s\n' % protect_str
315                  protect_end_str += '#endif // %s\n' % protect_str
316          return (protect_if_str, protect_end_str)
317  
318      def typeMayAlias(self, typeName):
319          if not self.may_alias:
320              # First time we've asked if a type may alias.
321              # So, let's populate the set of all names of types that may.
322  
323              # Everyone with an explicit mayalias="true"
324              self.may_alias = set(typeName
325                                   for typeName, data in self.registry.typedict.items()
326                                   if data.elem.get('mayalias') == 'true')
327  
328              # Every type mentioned in some other type's parentstruct attribute.
329              self.may_alias.update(set(x for x in
330                                        [otherType.elem.get('parentstruct')
331                                         for _, otherType in self.registry.typedict.items()]
332                                        if x is not None
333                                        ))
334          return typeName in self.may_alias
335  
336      # Struct (e.g. C "struct" type) generation.
337      # This is a special case of the <type> tag where the contents are
338      # interpreted as a set of <member> tags instead of freeform C
339      # C type declarations. The <member> tags are just like <param>
340      # tags - they are a declaration of a struct or union member.
341      # Only simple member declarations are supported (no nested
342      # structs etc.)
343      # If alias is not None, then this struct aliases another; just
344      #   generate a typedef of that alias.
345      def genStruct(self, typeinfo, typeName, alias):
346          OutputGenerator.genStruct(self, typeinfo, typeName, alias)
347  
348          typeElem = typeinfo.elem
349  
350          if alias:
351              body = 'typedef ' + alias + ' ' + typeName + ';\n'
352          else:
353              body = ''
354              (protect_begin, protect_end) = self.genProtectString(typeElem.get('protect'))
355              if protect_begin:
356                  body += protect_begin
357              body += 'typedef ' + typeElem.get('category')
358  
359              # This is an OpenXR-specific alternative where aliasing refers
360              # to an inheritance hierarchy of types rather than C-level type
361              # aliases.
362              if self.genOpts.genAliasMacro and self.typeMayAlias(typeName):
363                  body += ' ' + self.genOpts.aliasMacro
364  
365              body += ' ' + typeName + ' {\n'
366  
367              targetLen = 0
368              for member in typeElem.findall('.//member'):
369                  targetLen = max(targetLen, self.getCParamTypeLength(member))
370              for member in typeElem.findall('.//member'):
371                  body += self.makeCParamDecl(member, targetLen + 4)
372                  body += ';\n'
373              body += '} ' + typeName + ';\n'
374              if protect_end:
375                  body += protect_end
376  
377          self.appendSection('struct', body)
378  
379      # Group (e.g. C "enum" type) generation.
380      # These are concatenated together with other types.
381      # If alias is not None, it is the name of another group type
382      #   which aliases this type; just generate that alias.
383      def genGroup(self, groupinfo, groupName, alias = None):
384          OutputGenerator.genGroup(self, groupinfo, groupName, alias)
385          groupElem = groupinfo.elem
386  
387          # After either enumerated type or alias paths, add the declaration
388          # to the appropriate section for the group being defined.
389          if groupElem.get('type') == 'bitmask':
390              section = 'bitmask'
391          else:
392              section = 'group'
393  
394          if alias:
395              # If the group name is aliased, just emit a typedef declaration
396              # for the alias.
397              body = 'typedef ' + alias + ' ' + groupName + ';\n'
398              self.appendSection(section, body)
399          else:
400              (section, body) = self.buildEnumCDecl(self.genOpts.genEnumBeginEndRange, groupinfo, groupName)
401              self.appendSection(section, "\n" + body)
402  
403      # Enumerant generation
404      # <enum> tags may specify their values in several ways, but are usually
405      # just integers.
406      def genEnum(self, enuminfo, name, alias):
407          OutputGenerator.genEnum(self, enuminfo, name, alias)
408          (_, strVal) = self.enumToValue(enuminfo.elem, False)
409          body = '#define ' + name.ljust(33) + ' ' + strVal
410          self.appendSection('enum', body)
411  
412      # Command generation
413      def genCmd(self, cmdinfo, name, alias):
414          OutputGenerator.genCmd(self, cmdinfo, name, alias)
415  
416          # if alias:
417          #     prefix = '// ' + name + ' is an alias of command ' + alias + '\n'
418          # else:
419          #     prefix = ''
420  
421          prefix = ''
422          decls = self.makeCDecls(cmdinfo.elem)
423          self.appendSection('command', prefix + decls[0] + '\n')
424          if self.genOpts.genFuncPointers:
425              self.appendSection('commandPointer', decls[1])