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