/ scripts / pygenerator.py
pygenerator.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  from generator import OutputGenerator, enquote, noneStr, write
 19  from pprint import pprint
 20  
 21  # PyOutputGenerator - subclass of OutputGenerator.
 22  # Generates Python data structures describing API names and relationships.
 23  # Similar to DocOutputGenerator, but writes a single file.
 24  #
 25  # ---- methods ----
 26  # PyOutputGenerator(errFile, warnFile, diagFile) - args as for
 27  #   OutputGenerator. Defines additional internal state.
 28  # ---- methods overriding base class ----
 29  # beginFile(genOpts)
 30  # endFile()
 31  # genType(typeinfo,name)
 32  # genStruct(typeinfo,name)
 33  # genGroup(groupinfo,name)
 34  # genEnum(enuminfo, name)
 35  # genCmd(cmdinfo)
 36  class PyOutputGenerator(OutputGenerator):
 37      """Generate specified API interfaces in a specific style, such as a C header"""
 38      def __init__(self,
 39                   errFile = sys.stderr,
 40                   warnFile = sys.stderr,
 41                   diagFile = sys.stdout):
 42          OutputGenerator.__init__(self, errFile, warnFile, diagFile)
 43  
 44      def apiName(self, name):
 45          """Returns True if name is in the reserved API namespace.
 46             Delegate to the conventions object.
 47          """
 48          return self.genOpts.conventions.is_api_name(name)
 49  
 50      def beginFile(self, genOpts):
 51          OutputGenerator.beginFile(self, genOpts)
 52          #
 53          # Dictionaries are keyed by the name of the entity (e.g.
 54          # self.structs is keyed by structure names). Values are
 55          # the names of related entities (e.g. structs contain
 56          # a list of type names of members, enums contain a list
 57          # of enumerants belong to the enumerated type, etc.), or
 58          # just None if there are no directly related entities.
 59          #
 60          # Collect the mappings, then emit the Python script in endFile
 61          self.basetypes = {}
 62          self.consts = {}
 63          self.enums = {}
 64          self.flags = {}
 65          self.funcpointers = {}
 66          self.protos = {}
 67          self.structs = {}
 68          self.handles = {}
 69          self.defines = {}
 70          self.alias = {}
 71          # Dictionary containing the type of a type name
 72          # (e.g. the string name of the dictionary with its contents).
 73          self.typeCategory = {}
 74          self.mapDict = {}
 75  
 76      def endFile(self):
 77          # Print out all the dictionaries as Python strings.
 78          # Could just print(dict) but that's not human-readable
 79          dicts = [ [ self.basetypes,     'basetypes' ],
 80                    [ self.consts,        'consts' ],
 81                    [ self.enums,         'enums' ],
 82                    [ self.flags,         'flags' ],
 83                    [ self.funcpointers,  'funcpointers' ],
 84                    [ self.protos,        'protos' ],
 85                    [ self.structs,       'structs' ],
 86                    [ self.handles,       'handles' ],
 87                    [ self.defines,       'defines' ],
 88                    [ self.typeCategory,  'typeCategory' ],
 89                    [ self.alias,         'alias' ],
 90                  ]
 91          for (entry_dict, name) in dicts:
 92              write(name + ' = {}', file=self.outFile)
 93              for key in sorted(entry_dict.keys()):
 94                  write(name + '[' + enquote(key) + '] = ', entry_dict[key],
 95                        file=self.outFile)
 96  
 97          # Dictionary containing the relationships of a type
 98          # (e.g. a dictionary with each related type as keys).
 99          write('mapDict = {}', file=self.outFile)
100  
101          # Could just print(self.mapDict), but prefer something
102          # human-readable and stable-ordered
103          for baseType in sorted(self.mapDict.keys()):
104              write('mapDict[' + enquote(baseType) + '] = ', file=self.outFile, end='')
105              pprint(self.mapDict[baseType], self.outFile)
106  
107          OutputGenerator.endFile(self)
108  
109      # Add a string entry to the dictionary, quoting it so it gets printed
110      # out correctly in self.endFile()
111      def addName(self, entry_dict, name, value):
112          entry_dict[name] = enquote(value)
113  
114      # Add a mapping between types to mapDict. Only include API types,
115      # so we don't end up with a lot of useless uint32_t and void types.
116      def addMapping(self, baseType, refType):
117          if not self.apiName(baseType) or not self.apiName(refType):
118              self.logMsg('diag', 'PyOutputGenerator::addMapping: IGNORE map from', baseType, '<->', refType)
119              return
120  
121          self.logMsg('diag', 'PyOutputGenerator::addMapping: map from',
122                      baseType, '<->', refType)
123  
124          if baseType not in self.mapDict:
125              baseDict = {}
126              self.mapDict[baseType] = baseDict
127          else:
128              baseDict = self.mapDict[baseType]
129          if refType not in self.mapDict:
130              refDict = {}
131              self.mapDict[refType] = refDict
132          else:
133              refDict = self.mapDict[refType]
134  
135          baseDict[refType] = None
136          refDict[baseType] = None
137  
138      # Type generation
139      # For 'struct' or 'union' types, defer to genStruct() to
140      #   add to the dictionary.
141      # For 'bitmask' types, add the type name to the 'flags' dictionary,
142      #   with the value being the corresponding 'enums' name defining
143      #   the acceptable flag bits.
144      # For 'enum' types, add the type name to the 'enums' dictionary,
145      #   with the value being '@STOPHERE@' (because this case seems
146      #   never to happen).
147      # For 'funcpointer' types, add the type name to the 'funcpointers'
148      #   dictionary.
149      # For 'handle' and 'define' types, add the handle or #define name
150      #   to the 'struct' dictionary, because that's how the spec sources
151      #   tag these types even though they aren't structs.
152      def genType(self, typeinfo, name, alias):
153          OutputGenerator.genType(self, typeinfo, name, alias)
154          typeElem = typeinfo.elem
155          # If the type is a struct type, traverse the embedded <member> tags
156          # generating a structure. Otherwise, emit the tag text.
157          category = typeElem.get('category')
158  
159          # Add a typeCategory{} entry for the category of this type.
160          self.addName(self.typeCategory, name, category)
161  
162          if category in ('struct', 'union'):
163              self.genStruct(typeinfo, name, alias)
164          else:
165              if alias:
166                  # Add name -> alias mapping
167                  self.addName(self.alias, name, alias)
168  
169                  # Always emit an alias (?!)
170                  count = 1
171  
172                  # May want to only emit full type definition when not an alias?
173              else:
174                  # Extract the type name
175                  # (from self.genOpts). Copy other text through unchanged.
176                  # If the resulting text is an empty string, don't emit it.
177                  count = len(noneStr(typeElem.text))
178                  for elem in typeElem:
179                      count += len(noneStr(elem.text)) + len(noneStr(elem.tail))
180  
181              if count > 0:
182                  if category == 'bitmask':
183                      requiredEnum = typeElem.get('requires')
184                      self.addName(self.flags, name, requiredEnum)
185  
186                      # This happens when the Flags type is defined, but no
187                      # FlagBits are defined yet.
188                      if requiredEnum is not None:
189                          self.addMapping(name, requiredEnum)
190                  elif category == 'enum':
191                      # This case does not seem to come up. It nominally would
192                      # result from
193                      #   <type name="Something" category="enum"/>,
194                      # but the output generator doesn't emit them directly.
195                      self.logMsg('warn', 'PyOutputGenerator::genType: invalid \'enum\' category for name:', name)
196                  elif category == 'funcpointer':
197                      self.funcpointers[name] = None
198                  elif category == 'handle':
199                      self.handles[name] = None
200                  elif category == 'define':
201                      self.defines[name] = None
202                  elif category == 'basetype':
203                      # Don't add an entry for base types that are not API types
204                      # e.g. an API Bool type gets an entry, uint32_t does not
205                      if self.apiName(name):
206                          self.basetypes[name] = None
207                          self.addName(self.typeCategory, name, 'basetype')
208                      else:
209                          self.logMsg('diag', 'PyOutputGenerator::genType: unprocessed type:', name, 'category:', category)
210              else:
211                  self.logMsg('diag', 'PyOutputGenerator::genType: unprocessed type:', name)
212  
213      # Struct (e.g. C "struct" type) generation.
214      #
215      # Add the struct name to the 'structs' dictionary, with the
216      # value being an ordered list of the struct member names.
217      def genStruct(self, typeinfo, typeName, alias):
218          OutputGenerator.genStruct(self, typeinfo, typeName, alias)
219  
220          if alias:
221              # Add name -> alias mapping
222              self.addName(self.alias, typeName, alias)
223          else:
224              # May want to only emit definition on this branch
225              True
226  
227          members = [member.text for member in typeinfo.elem.findall('.//member/name')]
228          self.structs[typeName] = members
229          memberTypes = [member.text for member in typeinfo.elem.findall('.//member/type')]
230          for member_type in memberTypes:
231              self.addMapping(typeName, member_type)
232  
233      # Group (e.g. C "enum" type) generation.
234      # These are concatenated together with other types.
235      #
236      # Add the enum type name to the 'enums' dictionary, with
237      #   the value being an ordered list of the enumerant names.
238      # Add each enumerant name to the 'consts' dictionary, with
239      #   the value being the enum type the enumerant is part of.
240      def genGroup(self, groupinfo, groupName, alias):
241          OutputGenerator.genGroup(self, groupinfo, groupName, alias)
242          groupElem = groupinfo.elem
243  
244          if alias:
245              # Add name -> alias mapping
246              self.addName(self.alias, groupName, alias)
247          else:
248              # May want to only emit definition on this branch
249              True
250  
251          # Loop over the nested 'enum' tags.
252          enumerants = [elem.get('name') for elem in groupElem.findall('enum')]
253          for name in enumerants:
254              self.addName(self.consts, name, groupName)
255          self.enums[groupName] = enumerants
256  
257      # Enumerant generation (compile-time constants)
258      #
259      # Add the constant name to the 'consts' dictionary, with the
260      #   value being None to indicate that the constant isn't
261      #   an enumeration value.
262      def genEnum(self, enuminfo, name, alias):
263          OutputGenerator.genEnum(self, enuminfo, name, alias)
264  
265          # Add a typeCategory{} entry for the category of this type.
266          self.addName(self.typeCategory, name, 'consts')
267  
268          self.consts[name] = None
269  
270      # Command generation
271      #
272      # Add the command name to the 'protos' dictionary, with the
273      #   value being an ordered list of the parameter names.
274      def genCmd(self, cmdinfo, name, alias):
275          OutputGenerator.genCmd(self, cmdinfo, name, alias)
276  
277          if alias:
278              # Add name -> alias mapping
279              self.addName(self.alias, name, alias)
280          else:
281              # May want to only emit definition on this branch
282              True
283  
284          # Add a typeCategory{} entry for the category of this type.
285          self.addName(self.typeCategory, name, 'protos')
286  
287          params = [param.text for param in cmdinfo.elem.findall('param/name')]
288          self.protos[name] = params
289          paramTypes = [param.text for param in cmdinfo.elem.findall('param/type')]
290          for param_type in paramTypes:
291              self.addMapping(name, param_type)