• 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 do not end up with a lot of useless
157        uint32_t and void types."""
158        if not self.apiName(baseType) or not self.apiName(refType):
159            self.logMsg('diag', 'ScriptOutputGenerator::addMapping: IGNORE map from', baseType, '<->', refType)
160            return
161
162        self.logMsg('diag', 'ScriptOutputGenerator::addMapping: map from',
163                    baseType, '<->', refType)
164
165        if baseType not in self.mapDict:
166            baseDict = {}
167            self.mapDict[baseType] = baseDict
168        else:
169            baseDict = self.mapDict[baseType]
170        if refType not in self.mapDict:
171            refDict = {}
172            self.mapDict[refType] = refDict
173        else:
174            refDict = self.mapDict[refType]
175
176        baseDict[refType] = None
177        refDict[baseType] = None
178
179    def breakCheck(self, procname, name):
180        """Debugging aid - call from procname to break on API 'name' if it
181           matches logic in this call."""
182
183        pat = 'VkExternalFenceFeatureFlagBits'
184        if name[0:len(pat)] == pat:
185            print('{}(name = {}) matches {}'.format(procname, name, pat))
186            import pdb
187            pdb.set_trace()
188
189    def genType(self, typeinfo, name, alias):
190        """Generate type.
191
192        - For 'struct' or 'union' types, defer to genStruct() to
193          add to the dictionary.
194        - For 'bitmask' types, add the type name to the 'flags' dictionary,
195          with the value being the corresponding 'enums' name defining
196          the acceptable flag bits.
197        - For 'enum' types, add the type name to the 'enums' dictionary,
198          with the value being '@STOPHERE@' (because this case seems
199          never to happen).
200        - For 'funcpointer' types, add the type name to the 'funcpointers'
201          dictionary.
202        - For 'handle' and 'define' types, add the handle or #define name
203          to the 'struct' dictionary, because that is how the spec sources
204          tag these types even though they are not structs."""
205        OutputGenerator.genType(self, typeinfo, name, alias)
206
207        typeElem = typeinfo.elem
208        # If the type is a struct type, traverse the embedded <member> tags
209        # generating a structure. Otherwise, emit the tag text.
210        category = typeElem.get('category')
211
212        # Add a typeCategory{} entry for the category of this type.
213        self.addName(self.typeCategory, name, category)
214
215        if category in ('struct', 'union'):
216            self.genStruct(typeinfo, name, alias)
217        else:
218            if alias:
219                # Add name -> alias mapping
220                self.addName(self.alias, name, alias)
221
222                # Always emit an alias (?!)
223                count = 1
224
225                # May want to only emit full type definition when not an alias?
226            else:
227                # Extract the type name
228                # (from self.genOpts). Copy other text through unchanged.
229                # If the resulting text is an empty string, do not emit it.
230                count = len(noneStr(typeElem.text))
231                for elem in typeElem:
232                    count += len(noneStr(elem.text)) + len(noneStr(elem.tail))
233
234            if count > 0:
235                if category == 'bitmask':
236                    requiredEnum = typeElem.get('requires')
237                    self.addName(self.flags, name, requiredEnum)
238
239                    # This happens when the Flags type is defined, but no
240                    # FlagBits are defined yet.
241                    if requiredEnum is not None:
242                        self.addMapping(name, requiredEnum)
243                elif category == 'enum':
244                    # This case does not seem to come up. It nominally would
245                    # result from
246                    #   <type name="Something" category="enum"/>,
247                    # but the output generator does not emit them directly.
248                    self.logMsg('warn', 'ScriptOutputGenerator::genType: invalid \'enum\' category for name:', name)
249                elif category == 'funcpointer':
250                    self.funcpointers[name] = None
251                elif category == 'handle':
252                    self.handles[name] = None
253                elif category == 'define':
254                    self.defines[name] = None
255                elif category == 'basetype':
256                    # Do not add an entry for base types that are not API types
257                    # e.g. an API Bool type gets an entry, uint32_t does not
258                    if self.apiName(name):
259                        self.basetypes[name] = None
260                        self.addName(self.typeCategory, name, 'basetype')
261                    else:
262                        self.logMsg('diag', 'ScriptOutputGenerator::genType: unprocessed type:', name, 'category:', category)
263            else:
264                self.logMsg('diag', 'ScriptOutputGenerator::genType: unprocessed type:', name)
265
266    def genStruct(self, typeinfo, typeName, alias):
267        """Generate struct (e.g. C "struct" type).
268
269        Add the struct name to the 'structs' dictionary, with the
270        value being an ordered list of the struct member names."""
271        OutputGenerator.genStruct(self, typeinfo, typeName, alias)
272
273        if alias:
274            # Add name -> alias mapping
275            self.addName(self.alias, typeName, alias)
276        else:
277            # May want to only emit definition on this branch
278            True
279
280        members = [member.text for member in typeinfo.elem.findall('.//member/name')]
281        self.structs[typeName] = members
282        memberTypes = [member.text for member in typeinfo.elem.findall('.//member/type')]
283        for member_type in memberTypes:
284            self.addMapping(typeName, member_type)
285
286    def genGroup(self, groupinfo, groupName, alias):
287        """Generate group (e.g. C "enum" type).
288
289        These are concatenated together with other types.
290
291        - Add the enum type name to the 'enums' dictionary, with
292          the value being an ordered list of the enumerant names.
293        - Add each enumerant name to the 'consts' dictionary, with
294          the value being the enum type the enumerant is part of."""
295        OutputGenerator.genGroup(self, groupinfo, groupName, alias)
296        groupElem = groupinfo.elem
297
298        # Add a typeCategory{} entry for the category of this type.
299        self.addName(self.typeCategory, groupName, 'group')
300
301        if alias:
302            # Add name -> alias mapping
303            self.addName(self.alias, groupName, alias)
304        else:
305            # May want to only emit definition on this branch
306            True
307
308        # Add each nested 'enum' tag
309        enumerants = [elem.get('name') for elem in groupElem.findall('enum')]
310        for name in enumerants:
311            self.addName(self.consts, name, groupName)
312
313        # Sort enums for output stability, since their order is irrelevant
314        self.enums[groupName] = sorted(enumerants)
315
316    def genEnum(self, enuminfo, name, alias):
317        """Generate enumerant (compile-time constants).
318
319        - Add the constant name to the 'consts' dictionary, with the
320          value being None to indicate that the constant is not
321          an enumeration value."""
322        OutputGenerator.genEnum(self, enuminfo, name, alias)
323
324        if name not in self.consts:
325            # Add a typeCategory{} entry for the category of this type.
326            self.addName(self.typeCategory, name, 'consts')
327            self.consts[name] = None
328
329        if alias:
330            # Add name -> alias mapping
331            self.addName(self.alias, name, alias)
332        else:
333            # May want to only emit definition on this branch
334            True
335
336        # Otherwise, do not add it to the consts dictionary because it is
337        # already present. This happens due to the generator 'reparentEnums'
338        # parameter being False, so each extension enum appears in both the
339        # <enums> type and in the <extension> or <feature> it originally
340        # came from.
341
342    def genCmd(self, cmdinfo, name, alias):
343        """Generate command.
344
345        - Add the command name to the 'protos' dictionary, with the
346          value being an ordered list of the parameter names."""
347        OutputGenerator.genCmd(self, cmdinfo, name, alias)
348
349        # Add a typeCategory{} entry for the category of this type.
350        self.addName(self.typeCategory, name, 'protos')
351
352        if alias:
353            # Add name -> alias mapping
354            self.addName(self.alias, name, alias)
355        else:
356            # May want to only emit definition on this branch
357            True
358
359        params = [param.text for param in cmdinfo.elem.findall('param/name')]
360        self.protos[name] = params
361        paramTypes = [param.text for param in cmdinfo.elem.findall('param/type')]
362        for param_type in paramTypes:
363            self.addMapping(name, param_type)
364
365    def createInverseMap(self):
366        """This creates the inverse mapping of nonexistent APIs in this
367           build to their aliases which are supported. Must be called by
368           language-specific subclasses before emitting that mapping."""
369
370        # Map from APIs not supported in this build to aliases that are.
371        # When there are multiple valid choices for remapping, choose the
372        # most-official suffixed one (KHR > EXT > vendor).
373        for key in self.alias:
374            # If the API key is aliased to something which does not exist,
375            # then add the thing that does not exist to the nonexistent map.
376            # This is used in spec macros to make promoted extension links
377            # in specs built without the promoted interface refer to the
378            # older interface instead.
379
380            invkey = self.alias[key]
381
382            if invkey not in self.typeCategory:
383                if invkey in self.nonexistent:
384                    # Potentially remap existing mapping to a more official
385                    # alias.
386                    self.nonexistent[invkey] = mostOfficial(self.nonexistent[invkey], key)
387                else:
388                    # Create remapping to an alias
389                    self.nonexistent[invkey] = key
390