• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/python3 -i
2#
3# Copyright 2013-2021 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' """
83
84        # Each entry in self.apimap contains one or more
85        # ( feature, required ) tuples.
86        deps = ( feature, required )
87
88        if api in self.apimap:
89            self.apimap[api].append(deps)
90        else:
91            self.apimap[api] = [ deps ]
92
93    def mapInterfaceKeys(self, feature, key):
94        """Construct reverse mapping of APIs to features requiring them in
95           self.apimap.
96
97        - feature - name of the feature being generated
98        - key - API category - 'define', 'basetype', etc."""
99
100        dict = self.featureDictionary[feature][key]
101
102        if dict:
103            # Not clear why handling of command vs. type APIs is different -
104            # see interfacedocgenerator.py, which this was based on.
105            if key == 'command':
106                for required in dict:
107                    for api in dict[required]:
108                        self.addInterfaceMapping(api, feature, required)
109            else:
110                for required in dict:
111                    for parent in dict[required]:
112                        for api in dict[required][parent]:
113                            self.addInterfaceMapping(api, feature, required)
114
115    def mapInterfaces(self, feature):
116        """Construct reverse mapping of APIs to features requiring them in
117           self.apimap.
118
119        - feature - name of the feature being generated"""
120
121        # Map each category of interface
122        self.mapInterfaceKeys(feature, 'basetype')
123        self.mapInterfaceKeys(feature, 'bitmask')
124        self.mapInterfaceKeys(feature, 'command')
125        self.mapInterfaceKeys(feature, 'define')
126        self.mapInterfaceKeys(feature, 'enum')
127        self.mapInterfaceKeys(feature, 'enumconstant')
128        self.mapInterfaceKeys(feature, 'funcpointer')
129        self.mapInterfaceKeys(feature, 'handle')
130        self.mapInterfaceKeys(feature, 'include')
131        self.mapInterfaceKeys(feature, 'struct')
132        self.mapInterfaceKeys(feature, 'union')
133
134    def endFile(self):
135        super().endFile()
136
137    def beginFeature(self, interface, emit):
138        # Start processing in superclass
139        OutputGenerator.beginFeature(self, interface, emit)
140
141        # Add this feature to the list being tracked
142        self.features.append( self.featureName )
143
144    def endFeature(self):
145        # Finish processing in superclass
146        OutputGenerator.endFeature(self)
147
148    def addName(self, dict, name, value):
149        """Add a string entry to the dictionary, quoting it so it gets
150           printed out correctly in self.endFile()."""
151        dict[name] = value
152
153    def addMapping(self, baseType, refType):
154        """Add a mapping between types to mapDict.
155
156        Only include API types, so we don't end up with a lot of useless uint32_t and void types."""
157        if not self.apiName(baseType) or not self.apiName(refType):
158            self.logMsg('diag', 'ScriptOutputGenerator::addMapping: IGNORE map from', baseType, '<->', refType)
159            return
160
161        self.logMsg('diag', 'ScriptOutputGenerator::addMapping: map from',
162                    baseType, '<->', refType)
163
164        if baseType not in self.mapDict:
165            baseDict = {}
166            self.mapDict[baseType] = baseDict
167        else:
168            baseDict = self.mapDict[baseType]
169        if refType not in self.mapDict:
170            refDict = {}
171            self.mapDict[refType] = refDict
172        else:
173            refDict = self.mapDict[refType]
174
175        baseDict[refType] = None
176        refDict[baseType] = None
177
178    def breakCheck(self, procname, name):
179        """Debugging aid - call from procname to break on API 'name' if it
180           matches logic in this call."""
181
182        pat = 'VkExternalFenceFeatureFlagBits'
183        if name[0:len(pat)] == pat:
184            print('{}(name = {}) matches {}'.format(procname, name, pat))
185            import pdb
186            pdb.set_trace()
187
188    def genType(self, typeinfo, name, alias):
189        """Generate type.
190
191        - For 'struct' or 'union' types, defer to genStruct() to
192          add to the dictionary.
193        - For 'bitmask' types, add the type name to the 'flags' dictionary,
194          with the value being the corresponding 'enums' name defining
195          the acceptable flag bits.
196        - For 'enum' types, add the type name to the 'enums' dictionary,
197          with the value being '@STOPHERE@' (because this case seems
198          never to happen).
199        - For 'funcpointer' types, add the type name to the 'funcpointers'
200          dictionary.
201        - For 'handle' and 'define' types, add the handle or #define name
202          to the 'struct' dictionary, because that's how the spec sources
203          tag these types even though they aren't structs."""
204        OutputGenerator.genType(self, typeinfo, name, alias)
205
206        typeElem = typeinfo.elem
207        # If the type is a struct type, traverse the embedded <member> tags
208        # generating a structure. Otherwise, emit the tag text.
209        category = typeElem.get('category')
210
211        # Add a typeCategory{} entry for the category of this type.
212        self.addName(self.typeCategory, name, category)
213
214        if category in ('struct', 'union'):
215            self.genStruct(typeinfo, name, alias)
216        else:
217            if alias:
218                # Add name -> alias mapping
219                self.addName(self.alias, name, alias)
220
221                # Always emit an alias (?!)
222                count = 1
223
224                # May want to only emit full type definition when not an alias?
225            else:
226                # Extract the type name
227                # (from self.genOpts). Copy other text through unchanged.
228                # If the resulting text is an empty string, don't emit it.
229                count = len(noneStr(typeElem.text))
230                for elem in typeElem:
231                    count += len(noneStr(elem.text)) + len(noneStr(elem.tail))
232
233            if count > 0:
234                if category == 'bitmask':
235                    requiredEnum = typeElem.get('requires')
236                    self.addName(self.flags, name, requiredEnum)
237
238                    # This happens when the Flags type is defined, but no
239                    # FlagBits are defined yet.
240                    if requiredEnum is not None:
241                        self.addMapping(name, requiredEnum)
242                elif category == 'enum':
243                    # This case does not seem to come up. It nominally would
244                    # result from
245                    #   <type name="Something" category="enum"/>,
246                    # but the output generator doesn't emit them directly.
247                    self.logMsg('warn', 'ScriptOutputGenerator::genType: invalid \'enum\' category for name:', name)
248                elif category == 'funcpointer':
249                    self.funcpointers[name] = None
250                elif category == 'handle':
251                    self.handles[name] = None
252                elif category == 'define':
253                    self.defines[name] = None
254                elif category == 'basetype':
255                    # Don't add an entry for base types that are not API types
256                    # e.g. an API Bool type gets an entry, uint32_t does not
257                    if self.apiName(name):
258                        self.basetypes[name] = None
259                        self.addName(self.typeCategory, name, 'basetype')
260                    else:
261                        self.logMsg('diag', 'ScriptOutputGenerator::genType: unprocessed type:', name, 'category:', category)
262            else:
263                self.logMsg('diag', 'ScriptOutputGenerator::genType: unprocessed type:', name)
264
265    def genStruct(self, typeinfo, typeName, alias):
266        """Generate struct (e.g. C "struct" type).
267
268        Add the struct name to the 'structs' dictionary, with the
269        value being an ordered list of the struct member names."""
270        OutputGenerator.genStruct(self, typeinfo, typeName, alias)
271
272        if alias:
273            # Add name -> alias mapping
274            self.addName(self.alias, typeName, alias)
275        else:
276            # May want to only emit definition on this branch
277            True
278
279        members = [member.text for member in typeinfo.elem.findall('.//member/name')]
280        self.structs[typeName] = members
281        memberTypes = [member.text for member in typeinfo.elem.findall('.//member/type')]
282        for member_type in memberTypes:
283            self.addMapping(typeName, member_type)
284
285    def genGroup(self, groupinfo, groupName, alias):
286        """Generate group (e.g. C "enum" type).
287
288        These are concatenated together with other types.
289
290        - Add the enum type name to the 'enums' dictionary, with
291          the value being an ordered list of the enumerant names.
292        - Add each enumerant name to the 'consts' dictionary, with
293          the value being the enum type the enumerant is part of."""
294        OutputGenerator.genGroup(self, groupinfo, groupName, alias)
295        groupElem = groupinfo.elem
296
297        # Add a typeCategory{} entry for the category of this type.
298        self.addName(self.typeCategory, groupName, 'group')
299
300        if alias:
301            # Add name -> alias mapping
302            self.addName(self.alias, groupName, alias)
303        else:
304            # May want to only emit definition on this branch
305            True
306
307        # Add each nested 'enum' tag
308        enumerants = [elem.get('name') for elem in groupElem.findall('enum')]
309        for name in enumerants:
310            self.addName(self.consts, name, groupName)
311
312        # Sort enums for output stability, since their order is irrelevant
313        self.enums[groupName] = sorted(enumerants)
314
315    def genEnum(self, enuminfo, name, alias):
316        """Generate enumerant (compile-time constants).
317
318        - Add the constant name to the 'consts' dictionary, with the
319          value being None to indicate that the constant isn't
320          an enumeration value."""
321        OutputGenerator.genEnum(self, enuminfo, name, alias)
322
323        if name not in self.consts:
324            # Add a typeCategory{} entry for the category of this type.
325            self.addName(self.typeCategory, name, 'consts')
326            self.consts[name] = None
327        # Otherwise, don't add it to the consts dictionary because it's
328        # already present. This happens due to the generator 'reparentEnums'
329        # parameter being False, so each extension enum appears in both the
330        # <enums> type and in the <extension> or <feature> it originally
331        # came from.
332
333    def genCmd(self, cmdinfo, name, alias):
334        """Generate command.
335
336        - Add the command name to the 'protos' dictionary, with the
337          value being an ordered list of the parameter names."""
338        OutputGenerator.genCmd(self, cmdinfo, name, alias)
339
340        # Add a typeCategory{} entry for the category of this type.
341        self.addName(self.typeCategory, name, 'protos')
342
343        if alias:
344            # Add name -> alias mapping
345            self.addName(self.alias, name, alias)
346        else:
347            # May want to only emit definition on this branch
348            True
349
350        params = [param.text for param in cmdinfo.elem.findall('param/name')]
351        self.protos[name] = params
352        paramTypes = [param.text for param in cmdinfo.elem.findall('param/type')]
353        for param_type in paramTypes:
354            self.addMapping(name, param_type)
355
356    def createInverseMap(self):
357        """This creates the inverse mapping of nonexistent APIs in this
358           build to their aliases which are supported. Must be called by
359           language-specific subclasses before emitting that mapping."""
360
361        # Map from APIs not supported in this build to aliases that are.
362        # When there are multiple valid choices for remapping, choose the
363        # most-official suffixed one (KHR > EXT > vendor).
364        for key in self.alias:
365            # If the API key is aliased to something which doesn't exist,
366            # then add the thing that doesn't exist to the nonexistent map.
367            # This is used in spec macros to make promoted extension links
368            # in specs built without the promoted interface refer to the
369            # older interface instead.
370
371            invkey = self.alias[key]
372
373            if invkey not in self.typeCategory:
374                if invkey in self.nonexistent:
375                    # Potentially remap existing mapping to a more official
376                    # alias.
377                    self.nonexistent[invkey] = mostOfficial(self.nonexistent[invkey], key)
378                else:
379                    # Create remapping to an alias
380                    self.nonexistent[invkey] = key
381