• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/python3 -i
2#
3# Copyright (c) 2013-2020 The Khronos Group Inc.
4#
5# SPDX-License-Identifier: Apache-2.0
6
7import sys
8from generator import OutputGenerator, enquote, noneStr, write
9import pprint
10
11class PyOutputGenerator(OutputGenerator):
12    """PyOutputGenerator - subclass of OutputGenerator.
13    Generates Python data structures describing API names and relationships.
14    Similar to DocOutputGenerator, but writes a single file."""
15
16    def apiName(self, name):
17        """Return True if name is in the reserved API namespace.
18
19        Delegates to the conventions object. """
20        return self.genOpts.conventions.is_api_name(name)
21
22    def __init__(self, *args, **kwargs):
23        super().__init__(*args, **kwargs)
24
25        # Track features being generated
26        self.features = []
27
28        # Reverse map from interface names to features requiring them
29        self.apimap = {}
30
31    def beginFile(self, genOpts):
32        OutputGenerator.beginFile(self, genOpts)
33        #
34        # Dictionaries are keyed by the name of the entity (e.g.
35        # self.structs is keyed by structure names). Values are
36        # the names of related entities (e.g. structs contain
37        # a list of type names of members, enums contain a list
38        # of enumerants belong to the enumerated type, etc.), or
39        # just None if there are no directly related entities.
40        #
41        # Collect the mappings, then emit the Python script in endFile
42        self.basetypes = {}
43        self.consts = {}
44        self.enums = {}
45        self.flags = {}
46        self.funcpointers = {}
47        self.protos = {}
48        self.structs = {}
49        self.handles = {}
50        self.defines = {}
51        self.alias = {}
52        # Dictionary containing the type of a type name
53        # (e.g. the string name of the dictionary with its contents).
54        self.typeCategory = {}
55        self.mapDict = {}
56
57    def addInterfaceMapping(self, api, feature, required):
58        """Add a reverse mapping in self.apimap from an API to a feature
59           requiring that API.
60
61        - api - name of the API
62        - feature - name of the feature requiring it
63        - required - None, or an additional feature dependency within
64          'feature' """
65
66        # Each entry in self.apimap contains one or more
67        # ( feature, required ) tuples.
68        deps = ( feature, required )
69
70        if api in self.apimap:
71            self.apimap[api].append(deps)
72        else:
73            self.apimap[api] = [ deps ]
74
75    def mapInterfaceKeys(self, feature, key):
76        """Construct reverse mapping of APIs to features requiring them in
77           self.apimap.
78
79        - feature - name of the feature being generated
80        - key - API category - 'define', 'basetype', etc."""
81
82        dict = self.featureDictionary[feature][key]
83
84        if dict:
85            # Not clear why handling of command vs. type APIs is different -
86            # see interfacedocgenerator.py, which this was based on.
87            if key == 'command':
88                for required in dict:
89                    for api in dict[required]:
90                        self.addInterfaceMapping(api, feature, required)
91            else:
92                for required in dict:
93                    for parent in dict[required]:
94                        for api in dict[required][parent]:
95                            self.addInterfaceMapping(api, feature, required)
96
97    def mapInterfaces(self, feature):
98        """Construct reverse mapping of APIs to features requiring them in
99           self.apimap.
100
101        - feature - name of the feature being generated"""
102
103        # Map each category of interface
104        self.mapInterfaceKeys(feature, 'basetype')
105        self.mapInterfaceKeys(feature, 'bitmask')
106        self.mapInterfaceKeys(feature, 'command')
107        self.mapInterfaceKeys(feature, 'define')
108        self.mapInterfaceKeys(feature, 'enum')
109        self.mapInterfaceKeys(feature, 'enumconstant')
110        self.mapInterfaceKeys(feature, 'funcpointer')
111        self.mapInterfaceKeys(feature, 'handle')
112        self.mapInterfaceKeys(feature, 'include')
113        self.mapInterfaceKeys(feature, 'struct')
114        self.mapInterfaceKeys(feature, 'union')
115
116    def endFile(self):
117        # Print out all the dictionaries as Python strings.
118        # Could just print(dict) but that's not human-readable
119        dicts = ( [ self.basetypes,     'basetypes' ],
120                  [ self.consts,        'consts' ],
121                  [ self.enums,         'enums' ],
122                  [ self.flags,         'flags' ],
123                  [ self.funcpointers,  'funcpointers' ],
124                  [ self.protos,        'protos' ],
125                  [ self.structs,       'structs' ],
126                  [ self.handles,       'handles' ],
127                  [ self.defines,       'defines' ],
128                  [ self.typeCategory,  'typeCategory' ],
129                  [ self.alias,         'alias' ] )
130        for (entry_dict, name) in dicts:
131            write(name + ' = {}', file=self.outFile)
132            for key in sorted(entry_dict.keys()):
133                write(name + '[' + enquote(key) + '] = ', entry_dict[key],
134                      file=self.outFile)
135
136        # Dictionary containing the relationships of a type
137        # (e.g. a dictionary with each related type as keys).
138        write('mapDict = {}', file=self.outFile)
139
140        # Could just print(self.mapDict), but prefer something
141        # human-readable and stable-ordered
142        for baseType in sorted(self.mapDict.keys()):
143            write('mapDict[' + enquote(baseType) + '] = ', file=self.outFile, end='')
144            pprint.pprint(self.mapDict[baseType], self.outFile)
145
146        # Generate feature <-> interface mappings
147        for feature in self.features:
148            self.mapInterfaces(feature)
149
150        # Write out the reverse map from APIs to requiring features
151        write('requiredBy = {}', file=self.outFile)
152
153        for api in sorted(self.apimap):
154            # Construct list of requirements as Python list arguments
155            ##reqs = ', '.join('({}, {})'.format(enquote(dep[0]), enquote(dep[1])) for dep in self.apimap[api])
156            ##write('requiredBy[{}] = ( {} )'.format(enquote(api), reqs), file=self.outFile)
157
158            # Ideally these would be sorted by dep[0] as well
159            reqs = ', '.join('({}, {})'.format(enquote(dep[0]), enquote(dep[1])) for dep in self.apimap[api])
160            write('requiredBy[{}] = {}'.format(enquote(api), pprint.saferepr(self.apimap[api])), file=self.outFile)
161
162        OutputGenerator.endFile(self)
163
164    def beginFeature(self, interface, emit):
165        # Start processing in superclass
166        OutputGenerator.beginFeature(self, interface, emit)
167
168        # Add this feature to the list being tracked
169        self.features.append( self.featureName )
170
171    def endFeature(self):
172        # Finish processing in superclass
173        OutputGenerator.endFeature(self)
174
175    def addName(self, entry_dict, name, value):
176        """Add a string entry to the dictionary, quoting it so it gets printed
177        out correctly in self.endFile()."""
178        entry_dict[name] = enquote(value)
179
180    def addMapping(self, baseType, refType):
181        """Add a mapping between types to mapDict.
182
183        Only include API types, so we don't end up with a lot of useless uint32_t and void types."""
184        if not self.apiName(baseType) or not self.apiName(refType):
185            self.logMsg('diag', 'PyOutputGenerator::addMapping: IGNORE map from', baseType, '<->', refType)
186            return
187
188        self.logMsg('diag', 'PyOutputGenerator::addMapping: map from',
189                    baseType, '<->', refType)
190
191        if baseType not in self.mapDict:
192            baseDict = {}
193            self.mapDict[baseType] = baseDict
194        else:
195            baseDict = self.mapDict[baseType]
196        if refType not in self.mapDict:
197            refDict = {}
198            self.mapDict[refType] = refDict
199        else:
200            refDict = self.mapDict[refType]
201
202        baseDict[refType] = None
203        refDict[baseType] = None
204
205    def genType(self, typeinfo, name, alias):
206        """Generate type.
207
208        - For 'struct' or 'union' types, defer to genStruct() to
209          add to the dictionary.
210        - For 'bitmask' types, add the type name to the 'flags' dictionary,
211          with the value being the corresponding 'enums' name defining
212          the acceptable flag bits.
213        - For 'enum' types, add the type name to the 'enums' dictionary,
214          with the value being '@STOPHERE@' (because this case seems
215          never to happen).
216        - For 'funcpointer' types, add the type name to the 'funcpointers'
217          dictionary.
218        - For 'handle' and 'define' types, add the handle or #define name
219          to the 'struct' dictionary, because that's how the spec sources
220          tag these types even though they aren't structs."""
221        OutputGenerator.genType(self, typeinfo, name, alias)
222        typeElem = typeinfo.elem
223        # If the type is a struct type, traverse the embedded <member> tags
224        # generating a structure. Otherwise, emit the tag text.
225        category = typeElem.get('category')
226
227        # Add a typeCategory{} entry for the category of this type.
228        self.addName(self.typeCategory, name, category)
229
230        if category in ('struct', 'union'):
231            self.genStruct(typeinfo, name, alias)
232        else:
233            if alias:
234                # Add name -> alias mapping
235                self.addName(self.alias, name, alias)
236
237                # Always emit an alias (?!)
238                count = 1
239
240                # May want to only emit full type definition when not an alias?
241            else:
242                # Extract the type name
243                # (from self.genOpts). Copy other text through unchanged.
244                # If the resulting text is an empty string, don't emit it.
245                count = len(noneStr(typeElem.text))
246                for elem in typeElem:
247                    count += len(noneStr(elem.text)) + len(noneStr(elem.tail))
248
249            if count > 0:
250                if category == 'bitmask':
251                    requiredEnum = typeElem.get('requires')
252                    self.addName(self.flags, name, requiredEnum)
253
254                    # This happens when the Flags type is defined, but no
255                    # FlagBits are defined yet.
256                    if requiredEnum is not None:
257                        self.addMapping(name, requiredEnum)
258                elif category == 'enum':
259                    # This case does not seem to come up. It nominally would
260                    # result from
261                    #   <type name="Something" category="enum"/>,
262                    # but the output generator doesn't emit them directly.
263                    self.logMsg('warn', 'PyOutputGenerator::genType: invalid \'enum\' category for name:', name)
264                elif category == 'funcpointer':
265                    self.funcpointers[name] = None
266                elif category == 'handle':
267                    self.handles[name] = None
268                elif category == 'define':
269                    self.defines[name] = None
270                elif category == 'basetype':
271                    # Don't add an entry for base types that are not API types
272                    # e.g. an API Bool type gets an entry, uint32_t does not
273                    if self.apiName(name):
274                        self.basetypes[name] = None
275                        self.addName(self.typeCategory, name, 'basetype')
276                    else:
277                        self.logMsg('diag', 'PyOutputGenerator::genType: unprocessed type:', name, 'category:', category)
278            else:
279                self.logMsg('diag', 'PyOutputGenerator::genType: unprocessed type:', name)
280
281    def genStruct(self, typeinfo, typeName, alias):
282        """Generate struct (e.g. C "struct" type).
283
284        Add the struct name to the 'structs' dictionary, with the
285        value being an ordered list of the struct member names."""
286        OutputGenerator.genStruct(self, typeinfo, typeName, alias)
287
288        if alias:
289            # Add name -> alias mapping
290            self.addName(self.alias, typeName, alias)
291        else:
292            # May want to only emit definition on this branch
293            True
294
295        members = [member.text for member in typeinfo.elem.findall('.//member/name')]
296        self.structs[typeName] = members
297        memberTypes = [member.text for member in typeinfo.elem.findall('.//member/type')]
298        for member_type in memberTypes:
299            self.addMapping(typeName, member_type)
300
301    def genGroup(self, groupinfo, groupName, alias):
302        """Generate group (e.g. C "enum" type).
303
304        These are concatenated together with other types.
305
306        - Add the enum type name to the 'enums' dictionary, with
307          the value being an ordered list of the enumerant names.
308        - Add each enumerant name to the 'consts' dictionary, with
309          the value being the enum type the enumerant is part of."""
310        OutputGenerator.genGroup(self, groupinfo, groupName, alias)
311        groupElem = groupinfo.elem
312
313        if alias:
314            # Add name -> alias mapping
315            self.addName(self.alias, groupName, alias)
316        else:
317            # May want to only emit definition on this branch
318            True
319
320        # Loop over the nested 'enum' tags.
321        enumerants = [elem.get('name') for elem in groupElem.findall('enum')]
322        for name in enumerants:
323            self.addName(self.consts, name, groupName)
324        self.enums[groupName] = enumerants
325
326    def genEnum(self, enuminfo, name, alias):
327        """Generate enumerant (compile-time constants).
328
329        - Add the constant name to the 'consts' dictionary, with the
330          value being None to indicate that the constant isn't
331          an enumeration value."""
332        OutputGenerator.genEnum(self, enuminfo, name, alias)
333
334        if name not in self.consts:
335            # Add a typeCategory{} entry for the category of this type.
336            self.addName(self.typeCategory, name, 'consts')
337            self.consts[name] = None
338        # Otherwise, don't add it to the consts dictionary because it's
339        # already present. This happens due to the generator 'reparentEnums'
340        # parameter being False, so each extension enum appears in both the
341        # <enums> type and in the <extension> or <feature> it originally
342        # came from.
343
344    def genCmd(self, cmdinfo, name, alias):
345        """Generate command.
346
347        - Add the command name to the 'protos' dictionary, with the
348          value being an ordered list of the parameter names."""
349        OutputGenerator.genCmd(self, cmdinfo, name, alias)
350
351        if alias:
352            # Add name -> alias mapping
353            self.addName(self.alias, name, alias)
354        else:
355            # May want to only emit definition on this branch
356            True
357
358        # Add a typeCategory{} entry for the category of this type.
359        self.addName(self.typeCategory, name, 'protos')
360
361        params = [param.text for param in cmdinfo.elem.findall('param/name')]
362        self.protos[name] = params
363        paramTypes = [param.text for param in cmdinfo.elem.findall('param/type')]
364        for param_type in paramTypes:
365            self.addMapping(name, param_type)
366