381 lines
15 KiB
Python
381 lines
15 KiB
Python
#!/usr/bin/python3 -i
|
|
#
|
|
# Copyright 2013-2021 The Khronos Group Inc.
|
|
#
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
|
|
from generator import OutputGenerator, enquote, noneStr
|
|
|
|
def mostOfficial(api, newapi):
|
|
"""Return the 'most official' of two related names, api and newapi.
|
|
KHR is more official than EXT is more official than everything else.
|
|
If there is ambiguity, return api."""
|
|
|
|
if api[-3:] == 'KHR':
|
|
return api
|
|
if newapi[-3:] == 'KHR':
|
|
return newapi;
|
|
if api[-3:] == 'EXT':
|
|
return api
|
|
if newapi[-3:] == 'EXT':
|
|
return newapi;
|
|
return api
|
|
|
|
class ScriptOutputGenerator(OutputGenerator):
|
|
"""ScriptOutputGenerator - subclass of OutputGenerator.
|
|
Base class to Generate script (Python/Ruby/etc.) data structures
|
|
describing API names and relationships.
|
|
Similar to DocOutputGenerator, but writes a single file."""
|
|
|
|
def apiName(self, name):
|
|
"""Return True if name is in the reserved API namespace.
|
|
|
|
Delegates to the conventions object. """
|
|
return self.genOpts.conventions.is_api_name(name)
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
|
|
# Track features being generated
|
|
self.features = []
|
|
|
|
# Reverse map from interface names to features requiring them
|
|
self.apimap = {}
|
|
|
|
# Reverse map from unsupported APIs in this build to aliases which
|
|
# are supported
|
|
self.nonexistent = {}
|
|
|
|
def beginFile(self, genOpts):
|
|
OutputGenerator.beginFile(self, genOpts)
|
|
#
|
|
# Dictionaries are keyed by the name of the entity (e.g.
|
|
# self.structs is keyed by structure names). Values are
|
|
# the names of related entities (e.g. structs contain
|
|
# a list of type names of members, enums contain a list
|
|
# of enumerants belong to the enumerated type, etc.), or
|
|
# just None if there are no directly related entities.
|
|
#
|
|
# Collect the mappings, then emit the Python script in endFile
|
|
self.basetypes = {}
|
|
self.consts = {}
|
|
self.enums = {}
|
|
self.flags = {}
|
|
self.funcpointers = {}
|
|
self.protos = {}
|
|
self.structs = {}
|
|
self.handles = {}
|
|
self.defines = {}
|
|
self.alias = {}
|
|
# Dictionary containing the type of a type name
|
|
# (e.g. the string name of the dictionary with its contents).
|
|
self.typeCategory = {}
|
|
self.mapDict = {}
|
|
|
|
def addInterfaceMapping(self, api, feature, required):
|
|
"""Add a reverse mapping in self.apimap from an API to a feature
|
|
requiring that API.
|
|
|
|
- api - name of the API
|
|
- feature - name of the feature requiring it
|
|
- required - None, or an additional feature dependency within
|
|
'feature' """
|
|
|
|
# Each entry in self.apimap contains one or more
|
|
# ( feature, required ) tuples.
|
|
deps = ( feature, required )
|
|
|
|
if api in self.apimap:
|
|
self.apimap[api].append(deps)
|
|
else:
|
|
self.apimap[api] = [ deps ]
|
|
|
|
def mapInterfaceKeys(self, feature, key):
|
|
"""Construct reverse mapping of APIs to features requiring them in
|
|
self.apimap.
|
|
|
|
- feature - name of the feature being generated
|
|
- key - API category - 'define', 'basetype', etc."""
|
|
|
|
dict = self.featureDictionary[feature][key]
|
|
|
|
if dict:
|
|
# Not clear why handling of command vs. type APIs is different -
|
|
# see interfacedocgenerator.py, which this was based on.
|
|
if key == 'command':
|
|
for required in dict:
|
|
for api in dict[required]:
|
|
self.addInterfaceMapping(api, feature, required)
|
|
else:
|
|
for required in dict:
|
|
for parent in dict[required]:
|
|
for api in dict[required][parent]:
|
|
self.addInterfaceMapping(api, feature, required)
|
|
|
|
def mapInterfaces(self, feature):
|
|
"""Construct reverse mapping of APIs to features requiring them in
|
|
self.apimap.
|
|
|
|
- feature - name of the feature being generated"""
|
|
|
|
# Map each category of interface
|
|
self.mapInterfaceKeys(feature, 'basetype')
|
|
self.mapInterfaceKeys(feature, 'bitmask')
|
|
self.mapInterfaceKeys(feature, 'command')
|
|
self.mapInterfaceKeys(feature, 'define')
|
|
self.mapInterfaceKeys(feature, 'enum')
|
|
self.mapInterfaceKeys(feature, 'enumconstant')
|
|
self.mapInterfaceKeys(feature, 'funcpointer')
|
|
self.mapInterfaceKeys(feature, 'handle')
|
|
self.mapInterfaceKeys(feature, 'include')
|
|
self.mapInterfaceKeys(feature, 'struct')
|
|
self.mapInterfaceKeys(feature, 'union')
|
|
|
|
def endFile(self):
|
|
super().endFile()
|
|
|
|
def beginFeature(self, interface, emit):
|
|
# Start processing in superclass
|
|
OutputGenerator.beginFeature(self, interface, emit)
|
|
|
|
# Add this feature to the list being tracked
|
|
self.features.append( self.featureName )
|
|
|
|
def endFeature(self):
|
|
# Finish processing in superclass
|
|
OutputGenerator.endFeature(self)
|
|
|
|
def addName(self, dict, name, value):
|
|
"""Add a string entry to the dictionary, quoting it so it gets
|
|
printed out correctly in self.endFile()."""
|
|
dict[name] = value
|
|
|
|
def addMapping(self, baseType, refType):
|
|
"""Add a mapping between types to mapDict.
|
|
|
|
Only include API types, so we don't end up with a lot of useless uint32_t and void types."""
|
|
if not self.apiName(baseType) or not self.apiName(refType):
|
|
self.logMsg('diag', 'ScriptOutputGenerator::addMapping: IGNORE map from', baseType, '<->', refType)
|
|
return
|
|
|
|
self.logMsg('diag', 'ScriptOutputGenerator::addMapping: map from',
|
|
baseType, '<->', refType)
|
|
|
|
if baseType not in self.mapDict:
|
|
baseDict = {}
|
|
self.mapDict[baseType] = baseDict
|
|
else:
|
|
baseDict = self.mapDict[baseType]
|
|
if refType not in self.mapDict:
|
|
refDict = {}
|
|
self.mapDict[refType] = refDict
|
|
else:
|
|
refDict = self.mapDict[refType]
|
|
|
|
baseDict[refType] = None
|
|
refDict[baseType] = None
|
|
|
|
def breakCheck(self, procname, name):
|
|
"""Debugging aid - call from procname to break on API 'name' if it
|
|
matches logic in this call."""
|
|
|
|
pat = 'VkExternalFenceFeatureFlagBits'
|
|
if name[0:len(pat)] == pat:
|
|
print('{}(name = {}) matches {}'.format(procname, name, pat))
|
|
import pdb
|
|
pdb.set_trace()
|
|
|
|
def genType(self, typeinfo, name, alias):
|
|
"""Generate type.
|
|
|
|
- For 'struct' or 'union' types, defer to genStruct() to
|
|
add to the dictionary.
|
|
- For 'bitmask' types, add the type name to the 'flags' dictionary,
|
|
with the value being the corresponding 'enums' name defining
|
|
the acceptable flag bits.
|
|
- For 'enum' types, add the type name to the 'enums' dictionary,
|
|
with the value being '@STOPHERE@' (because this case seems
|
|
never to happen).
|
|
- For 'funcpointer' types, add the type name to the 'funcpointers'
|
|
dictionary.
|
|
- For 'handle' and 'define' types, add the handle or #define name
|
|
to the 'struct' dictionary, because that's how the spec sources
|
|
tag these types even though they aren't structs."""
|
|
OutputGenerator.genType(self, typeinfo, name, alias)
|
|
|
|
typeElem = typeinfo.elem
|
|
# If the type is a struct type, traverse the embedded <member> tags
|
|
# generating a structure. Otherwise, emit the tag text.
|
|
category = typeElem.get('category')
|
|
|
|
# Add a typeCategory{} entry for the category of this type.
|
|
self.addName(self.typeCategory, name, category)
|
|
|
|
if category in ('struct', 'union'):
|
|
self.genStruct(typeinfo, name, alias)
|
|
else:
|
|
if alias:
|
|
# Add name -> alias mapping
|
|
self.addName(self.alias, name, alias)
|
|
|
|
# Always emit an alias (?!)
|
|
count = 1
|
|
|
|
# May want to only emit full type definition when not an alias?
|
|
else:
|
|
# Extract the type name
|
|
# (from self.genOpts). Copy other text through unchanged.
|
|
# If the resulting text is an empty string, don't emit it.
|
|
count = len(noneStr(typeElem.text))
|
|
for elem in typeElem:
|
|
count += len(noneStr(elem.text)) + len(noneStr(elem.tail))
|
|
|
|
if count > 0:
|
|
if category == 'bitmask':
|
|
requiredEnum = typeElem.get('requires')
|
|
self.addName(self.flags, name, requiredEnum)
|
|
|
|
# This happens when the Flags type is defined, but no
|
|
# FlagBits are defined yet.
|
|
if requiredEnum is not None:
|
|
self.addMapping(name, requiredEnum)
|
|
elif category == 'enum':
|
|
# This case does not seem to come up. It nominally would
|
|
# result from
|
|
# <type name="Something" category="enum"/>,
|
|
# but the output generator doesn't emit them directly.
|
|
self.logMsg('warn', 'ScriptOutputGenerator::genType: invalid \'enum\' category for name:', name)
|
|
elif category == 'funcpointer':
|
|
self.funcpointers[name] = None
|
|
elif category == 'handle':
|
|
self.handles[name] = None
|
|
elif category == 'define':
|
|
self.defines[name] = None
|
|
elif category == 'basetype':
|
|
# Don't add an entry for base types that are not API types
|
|
# e.g. an API Bool type gets an entry, uint32_t does not
|
|
if self.apiName(name):
|
|
self.basetypes[name] = None
|
|
self.addName(self.typeCategory, name, 'basetype')
|
|
else:
|
|
self.logMsg('diag', 'ScriptOutputGenerator::genType: unprocessed type:', name, 'category:', category)
|
|
else:
|
|
self.logMsg('diag', 'ScriptOutputGenerator::genType: unprocessed type:', name)
|
|
|
|
def genStruct(self, typeinfo, typeName, alias):
|
|
"""Generate struct (e.g. C "struct" type).
|
|
|
|
Add the struct name to the 'structs' dictionary, with the
|
|
value being an ordered list of the struct member names."""
|
|
OutputGenerator.genStruct(self, typeinfo, typeName, alias)
|
|
|
|
if alias:
|
|
# Add name -> alias mapping
|
|
self.addName(self.alias, typeName, alias)
|
|
else:
|
|
# May want to only emit definition on this branch
|
|
True
|
|
|
|
members = [member.text for member in typeinfo.elem.findall('.//member/name')]
|
|
self.structs[typeName] = members
|
|
memberTypes = [member.text for member in typeinfo.elem.findall('.//member/type')]
|
|
for member_type in memberTypes:
|
|
self.addMapping(typeName, member_type)
|
|
|
|
def genGroup(self, groupinfo, groupName, alias):
|
|
"""Generate group (e.g. C "enum" type).
|
|
|
|
These are concatenated together with other types.
|
|
|
|
- Add the enum type name to the 'enums' dictionary, with
|
|
the value being an ordered list of the enumerant names.
|
|
- Add each enumerant name to the 'consts' dictionary, with
|
|
the value being the enum type the enumerant is part of."""
|
|
OutputGenerator.genGroup(self, groupinfo, groupName, alias)
|
|
groupElem = groupinfo.elem
|
|
|
|
# Add a typeCategory{} entry for the category of this type.
|
|
self.addName(self.typeCategory, groupName, 'group')
|
|
|
|
if alias:
|
|
# Add name -> alias mapping
|
|
self.addName(self.alias, groupName, alias)
|
|
else:
|
|
# May want to only emit definition on this branch
|
|
True
|
|
|
|
# Add each nested 'enum' tag
|
|
enumerants = [elem.get('name') for elem in groupElem.findall('enum')]
|
|
for name in enumerants:
|
|
self.addName(self.consts, name, groupName)
|
|
|
|
# Sort enums for output stability, since their order is irrelevant
|
|
self.enums[groupName] = sorted(enumerants)
|
|
|
|
def genEnum(self, enuminfo, name, alias):
|
|
"""Generate enumerant (compile-time constants).
|
|
|
|
- Add the constant name to the 'consts' dictionary, with the
|
|
value being None to indicate that the constant isn't
|
|
an enumeration value."""
|
|
OutputGenerator.genEnum(self, enuminfo, name, alias)
|
|
|
|
if name not in self.consts:
|
|
# Add a typeCategory{} entry for the category of this type.
|
|
self.addName(self.typeCategory, name, 'consts')
|
|
self.consts[name] = None
|
|
# Otherwise, don't add it to the consts dictionary because it's
|
|
# already present. This happens due to the generator 'reparentEnums'
|
|
# parameter being False, so each extension enum appears in both the
|
|
# <enums> type and in the <extension> or <feature> it originally
|
|
# came from.
|
|
|
|
def genCmd(self, cmdinfo, name, alias):
|
|
"""Generate command.
|
|
|
|
- Add the command name to the 'protos' dictionary, with the
|
|
value being an ordered list of the parameter names."""
|
|
OutputGenerator.genCmd(self, cmdinfo, name, alias)
|
|
|
|
# Add a typeCategory{} entry for the category of this type.
|
|
self.addName(self.typeCategory, name, 'protos')
|
|
|
|
if alias:
|
|
# Add name -> alias mapping
|
|
self.addName(self.alias, name, alias)
|
|
else:
|
|
# May want to only emit definition on this branch
|
|
True
|
|
|
|
params = [param.text for param in cmdinfo.elem.findall('param/name')]
|
|
self.protos[name] = params
|
|
paramTypes = [param.text for param in cmdinfo.elem.findall('param/type')]
|
|
for param_type in paramTypes:
|
|
self.addMapping(name, param_type)
|
|
|
|
def createInverseMap(self):
|
|
"""This creates the inverse mapping of nonexistent APIs in this
|
|
build to their aliases which are supported. Must be called by
|
|
language-specific subclasses before emitting that mapping."""
|
|
|
|
# Map from APIs not supported in this build to aliases that are.
|
|
# When there are multiple valid choices for remapping, choose the
|
|
# most-official suffixed one (KHR > EXT > vendor).
|
|
for key in self.alias:
|
|
# If the API key is aliased to something which doesn't exist,
|
|
# then add the thing that doesn't exist to the nonexistent map.
|
|
# This is used in spec macros to make promoted extension links
|
|
# in specs built without the promoted interface refer to the
|
|
# older interface instead.
|
|
|
|
invkey = self.alias[key]
|
|
|
|
if invkey not in self.typeCategory:
|
|
if invkey in self.nonexistent:
|
|
# Potentially remap existing mapping to a more official
|
|
# alias.
|
|
self.nonexistent[invkey] = mostOfficial(self.nonexistent[invkey], key)
|
|
else:
|
|
# Create remapping to an alias
|
|
self.nonexistent[invkey] = key
|