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