1#!/usr/bin/python3 -i 2# 3# Copyright (c) 2013-2020 The Khronos Group Inc. 4# 5# SPDX-License-Identifier: Apache-2.0 6 7import sys 8from generator import OutputGenerator, enquote, noneStr, write 9import pprint 10 11class PyOutputGenerator(OutputGenerator): 12 """PyOutputGenerator - subclass of OutputGenerator. 13 Generates Python data structures describing API names and relationships. 14 Similar to DocOutputGenerator, but writes a single file.""" 15 16 def apiName(self, name): 17 """Return True if name is in the reserved API namespace. 18 19 Delegates to the conventions object. """ 20 return self.genOpts.conventions.is_api_name(name) 21 22 def __init__(self, *args, **kwargs): 23 super().__init__(*args, **kwargs) 24 25 # Track features being generated 26 self.features = [] 27 28 # Reverse map from interface names to features requiring them 29 self.apimap = {} 30 31 def beginFile(self, genOpts): 32 OutputGenerator.beginFile(self, genOpts) 33 # 34 # Dictionaries are keyed by the name of the entity (e.g. 35 # self.structs is keyed by structure names). Values are 36 # the names of related entities (e.g. structs contain 37 # a list of type names of members, enums contain a list 38 # of enumerants belong to the enumerated type, etc.), or 39 # just None if there are no directly related entities. 40 # 41 # Collect the mappings, then emit the Python script in endFile 42 self.basetypes = {} 43 self.consts = {} 44 self.enums = {} 45 self.flags = {} 46 self.funcpointers = {} 47 self.protos = {} 48 self.structs = {} 49 self.handles = {} 50 self.defines = {} 51 self.alias = {} 52 # Dictionary containing the type of a type name 53 # (e.g. the string name of the dictionary with its contents). 54 self.typeCategory = {} 55 self.mapDict = {} 56 57 def addInterfaceMapping(self, api, feature, required): 58 """Add a reverse mapping in self.apimap from an API to a feature 59 requiring that API. 60 61 - api - name of the API 62 - feature - name of the feature requiring it 63 - required - None, or an additional feature dependency within 64 'feature' """ 65 66 # Each entry in self.apimap contains one or more 67 # ( feature, required ) tuples. 68 deps = ( feature, required ) 69 70 if api in self.apimap: 71 self.apimap[api].append(deps) 72 else: 73 self.apimap[api] = [ deps ] 74 75 def mapInterfaceKeys(self, feature, key): 76 """Construct reverse mapping of APIs to features requiring them in 77 self.apimap. 78 79 - feature - name of the feature being generated 80 - key - API category - 'define', 'basetype', etc.""" 81 82 dict = self.featureDictionary[feature][key] 83 84 if dict: 85 # Not clear why handling of command vs. type APIs is different - 86 # see interfacedocgenerator.py, which this was based on. 87 if key == 'command': 88 for required in dict: 89 for api in dict[required]: 90 self.addInterfaceMapping(api, feature, required) 91 else: 92 for required in dict: 93 for parent in dict[required]: 94 for api in dict[required][parent]: 95 self.addInterfaceMapping(api, feature, required) 96 97 def mapInterfaces(self, feature): 98 """Construct reverse mapping of APIs to features requiring them in 99 self.apimap. 100 101 - feature - name of the feature being generated""" 102 103 # Map each category of interface 104 self.mapInterfaceKeys(feature, 'basetype') 105 self.mapInterfaceKeys(feature, 'bitmask') 106 self.mapInterfaceKeys(feature, 'command') 107 self.mapInterfaceKeys(feature, 'define') 108 self.mapInterfaceKeys(feature, 'enum') 109 self.mapInterfaceKeys(feature, 'enumconstant') 110 self.mapInterfaceKeys(feature, 'funcpointer') 111 self.mapInterfaceKeys(feature, 'handle') 112 self.mapInterfaceKeys(feature, 'include') 113 self.mapInterfaceKeys(feature, 'struct') 114 self.mapInterfaceKeys(feature, 'union') 115 116 def endFile(self): 117 # Print out all the dictionaries as Python strings. 118 # Could just print(dict) but that's not human-readable 119 dicts = ( [ self.basetypes, 'basetypes' ], 120 [ self.consts, 'consts' ], 121 [ self.enums, 'enums' ], 122 [ self.flags, 'flags' ], 123 [ self.funcpointers, 'funcpointers' ], 124 [ self.protos, 'protos' ], 125 [ self.structs, 'structs' ], 126 [ self.handles, 'handles' ], 127 [ self.defines, 'defines' ], 128 [ self.typeCategory, 'typeCategory' ], 129 [ self.alias, 'alias' ] ) 130 for (entry_dict, name) in dicts: 131 write(name + ' = {}', file=self.outFile) 132 for key in sorted(entry_dict.keys()): 133 write(name + '[' + enquote(key) + '] = ', entry_dict[key], 134 file=self.outFile) 135 136 # Dictionary containing the relationships of a type 137 # (e.g. a dictionary with each related type as keys). 138 write('mapDict = {}', file=self.outFile) 139 140 # Could just print(self.mapDict), but prefer something 141 # human-readable and stable-ordered 142 for baseType in sorted(self.mapDict.keys()): 143 write('mapDict[' + enquote(baseType) + '] = ', file=self.outFile, end='') 144 pprint.pprint(self.mapDict[baseType], self.outFile) 145 146 # Generate feature <-> interface mappings 147 for feature in self.features: 148 self.mapInterfaces(feature) 149 150 # Write out the reverse map from APIs to requiring features 151 write('requiredBy = {}', file=self.outFile) 152 153 for api in sorted(self.apimap): 154 # Construct list of requirements as Python list arguments 155 ##reqs = ', '.join('({}, {})'.format(enquote(dep[0]), enquote(dep[1])) for dep in self.apimap[api]) 156 ##write('requiredBy[{}] = ( {} )'.format(enquote(api), reqs), file=self.outFile) 157 158 # Ideally these would be sorted by dep[0] as well 159 reqs = ', '.join('({}, {})'.format(enquote(dep[0]), enquote(dep[1])) for dep in self.apimap[api]) 160 write('requiredBy[{}] = {}'.format(enquote(api), pprint.saferepr(self.apimap[api])), file=self.outFile) 161 162 OutputGenerator.endFile(self) 163 164 def beginFeature(self, interface, emit): 165 # Start processing in superclass 166 OutputGenerator.beginFeature(self, interface, emit) 167 168 # Add this feature to the list being tracked 169 self.features.append( self.featureName ) 170 171 def endFeature(self): 172 # Finish processing in superclass 173 OutputGenerator.endFeature(self) 174 175 def addName(self, entry_dict, name, value): 176 """Add a string entry to the dictionary, quoting it so it gets printed 177 out correctly in self.endFile().""" 178 entry_dict[name] = enquote(value) 179 180 def addMapping(self, baseType, refType): 181 """Add a mapping between types to mapDict. 182 183 Only include API types, so we don't end up with a lot of useless uint32_t and void types.""" 184 if not self.apiName(baseType) or not self.apiName(refType): 185 self.logMsg('diag', 'PyOutputGenerator::addMapping: IGNORE map from', baseType, '<->', refType) 186 return 187 188 self.logMsg('diag', 'PyOutputGenerator::addMapping: map from', 189 baseType, '<->', refType) 190 191 if baseType not in self.mapDict: 192 baseDict = {} 193 self.mapDict[baseType] = baseDict 194 else: 195 baseDict = self.mapDict[baseType] 196 if refType not in self.mapDict: 197 refDict = {} 198 self.mapDict[refType] = refDict 199 else: 200 refDict = self.mapDict[refType] 201 202 baseDict[refType] = None 203 refDict[baseType] = None 204 205 def genType(self, typeinfo, name, alias): 206 """Generate type. 207 208 - For 'struct' or 'union' types, defer to genStruct() to 209 add to the dictionary. 210 - For 'bitmask' types, add the type name to the 'flags' dictionary, 211 with the value being the corresponding 'enums' name defining 212 the acceptable flag bits. 213 - For 'enum' types, add the type name to the 'enums' dictionary, 214 with the value being '@STOPHERE@' (because this case seems 215 never to happen). 216 - For 'funcpointer' types, add the type name to the 'funcpointers' 217 dictionary. 218 - For 'handle' and 'define' types, add the handle or #define name 219 to the 'struct' dictionary, because that's how the spec sources 220 tag these types even though they aren't structs.""" 221 OutputGenerator.genType(self, typeinfo, name, alias) 222 typeElem = typeinfo.elem 223 # If the type is a struct type, traverse the embedded <member> tags 224 # generating a structure. Otherwise, emit the tag text. 225 category = typeElem.get('category') 226 227 # Add a typeCategory{} entry for the category of this type. 228 self.addName(self.typeCategory, name, category) 229 230 if category in ('struct', 'union'): 231 self.genStruct(typeinfo, name, alias) 232 else: 233 if alias: 234 # Add name -> alias mapping 235 self.addName(self.alias, name, alias) 236 237 # Always emit an alias (?!) 238 count = 1 239 240 # May want to only emit full type definition when not an alias? 241 else: 242 # Extract the type name 243 # (from self.genOpts). Copy other text through unchanged. 244 # If the resulting text is an empty string, don't emit it. 245 count = len(noneStr(typeElem.text)) 246 for elem in typeElem: 247 count += len(noneStr(elem.text)) + len(noneStr(elem.tail)) 248 249 if count > 0: 250 if category == 'bitmask': 251 requiredEnum = typeElem.get('requires') 252 self.addName(self.flags, name, requiredEnum) 253 254 # This happens when the Flags type is defined, but no 255 # FlagBits are defined yet. 256 if requiredEnum is not None: 257 self.addMapping(name, requiredEnum) 258 elif category == 'enum': 259 # This case does not seem to come up. It nominally would 260 # result from 261 # <type name="Something" category="enum"/>, 262 # but the output generator doesn't emit them directly. 263 self.logMsg('warn', 'PyOutputGenerator::genType: invalid \'enum\' category for name:', name) 264 elif category == 'funcpointer': 265 self.funcpointers[name] = None 266 elif category == 'handle': 267 self.handles[name] = None 268 elif category == 'define': 269 self.defines[name] = None 270 elif category == 'basetype': 271 # Don't add an entry for base types that are not API types 272 # e.g. an API Bool type gets an entry, uint32_t does not 273 if self.apiName(name): 274 self.basetypes[name] = None 275 self.addName(self.typeCategory, name, 'basetype') 276 else: 277 self.logMsg('diag', 'PyOutputGenerator::genType: unprocessed type:', name, 'category:', category) 278 else: 279 self.logMsg('diag', 'PyOutputGenerator::genType: unprocessed type:', name) 280 281 def genStruct(self, typeinfo, typeName, alias): 282 """Generate struct (e.g. C "struct" type). 283 284 Add the struct name to the 'structs' dictionary, with the 285 value being an ordered list of the struct member names.""" 286 OutputGenerator.genStruct(self, typeinfo, typeName, alias) 287 288 if alias: 289 # Add name -> alias mapping 290 self.addName(self.alias, typeName, alias) 291 else: 292 # May want to only emit definition on this branch 293 True 294 295 members = [member.text for member in typeinfo.elem.findall('.//member/name')] 296 self.structs[typeName] = members 297 memberTypes = [member.text for member in typeinfo.elem.findall('.//member/type')] 298 for member_type in memberTypes: 299 self.addMapping(typeName, member_type) 300 301 def genGroup(self, groupinfo, groupName, alias): 302 """Generate group (e.g. C "enum" type). 303 304 These are concatenated together with other types. 305 306 - Add the enum type name to the 'enums' dictionary, with 307 the value being an ordered list of the enumerant names. 308 - Add each enumerant name to the 'consts' dictionary, with 309 the value being the enum type the enumerant is part of.""" 310 OutputGenerator.genGroup(self, groupinfo, groupName, alias) 311 groupElem = groupinfo.elem 312 313 if alias: 314 # Add name -> alias mapping 315 self.addName(self.alias, groupName, alias) 316 else: 317 # May want to only emit definition on this branch 318 True 319 320 # Loop over the nested 'enum' tags. 321 enumerants = [elem.get('name') for elem in groupElem.findall('enum')] 322 for name in enumerants: 323 self.addName(self.consts, name, groupName) 324 self.enums[groupName] = enumerants 325 326 def genEnum(self, enuminfo, name, alias): 327 """Generate enumerant (compile-time constants). 328 329 - Add the constant name to the 'consts' dictionary, with the 330 value being None to indicate that the constant isn't 331 an enumeration value.""" 332 OutputGenerator.genEnum(self, enuminfo, name, alias) 333 334 if name not in self.consts: 335 # Add a typeCategory{} entry for the category of this type. 336 self.addName(self.typeCategory, name, 'consts') 337 self.consts[name] = None 338 # Otherwise, don't add it to the consts dictionary because it's 339 # already present. This happens due to the generator 'reparentEnums' 340 # parameter being False, so each extension enum appears in both the 341 # <enums> type and in the <extension> or <feature> it originally 342 # came from. 343 344 def genCmd(self, cmdinfo, name, alias): 345 """Generate command. 346 347 - Add the command name to the 'protos' dictionary, with the 348 value being an ordered list of the parameter names.""" 349 OutputGenerator.genCmd(self, cmdinfo, name, alias) 350 351 if alias: 352 # Add name -> alias mapping 353 self.addName(self.alias, name, alias) 354 else: 355 # May want to only emit definition on this branch 356 True 357 358 # Add a typeCategory{} entry for the category of this type. 359 self.addName(self.typeCategory, name, 'protos') 360 361 params = [param.text for param in cmdinfo.elem.findall('param/name')] 362 self.protos[name] = params 363 paramTypes = [param.text for param in cmdinfo.elem.findall('param/type')] 364 for param_type in paramTypes: 365 self.addMapping(name, param_type) 366