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