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