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