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