pygenerator.py
1 #!/usr/bin/python3 -i 2 # 3 # Copyright (c) 2013-2019 The Khronos Group Inc. 4 # 5 # Licensed under the Apache License, Version 2.0 (the "License"); 6 # you may not use this file except in compliance with the License. 7 # You may obtain a copy of the License at 8 # 9 # http://www.apache.org/licenses/LICENSE-2.0 10 # 11 # Unless required by applicable law or agreed to in writing, software 12 # distributed under the License is distributed on an "AS IS" BASIS, 13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 # See the License for the specific language governing permissions and 15 # limitations under the License. 16 17 import sys 18 from generator import OutputGenerator, enquote, noneStr, write 19 from pprint import pprint 20 21 # PyOutputGenerator - subclass of OutputGenerator. 22 # Generates Python data structures describing API names and relationships. 23 # Similar to DocOutputGenerator, but writes a single file. 24 # 25 # ---- methods ---- 26 # PyOutputGenerator(errFile, warnFile, diagFile) - args as for 27 # OutputGenerator. Defines additional internal state. 28 # ---- methods overriding base class ---- 29 # beginFile(genOpts) 30 # endFile() 31 # genType(typeinfo,name) 32 # genStruct(typeinfo,name) 33 # genGroup(groupinfo,name) 34 # genEnum(enuminfo, name) 35 # genCmd(cmdinfo) 36 class PyOutputGenerator(OutputGenerator): 37 """Generate specified API interfaces in a specific style, such as a C header""" 38 def __init__(self, 39 errFile = sys.stderr, 40 warnFile = sys.stderr, 41 diagFile = sys.stdout): 42 OutputGenerator.__init__(self, errFile, warnFile, diagFile) 43 44 def apiName(self, name): 45 """Returns True if name is in the reserved API namespace. 46 Delegate to the conventions object. 47 """ 48 return self.genOpts.conventions.is_api_name(name) 49 50 def beginFile(self, genOpts): 51 OutputGenerator.beginFile(self, genOpts) 52 # 53 # Dictionaries are keyed by the name of the entity (e.g. 54 # self.structs is keyed by structure names). Values are 55 # the names of related entities (e.g. structs contain 56 # a list of type names of members, enums contain a list 57 # of enumerants belong to the enumerated type, etc.), or 58 # just None if there are no directly related entities. 59 # 60 # Collect the mappings, then emit the Python script in endFile 61 self.basetypes = {} 62 self.consts = {} 63 self.enums = {} 64 self.flags = {} 65 self.funcpointers = {} 66 self.protos = {} 67 self.structs = {} 68 self.handles = {} 69 self.defines = {} 70 self.alias = {} 71 # Dictionary containing the type of a type name 72 # (e.g. the string name of the dictionary with its contents). 73 self.typeCategory = {} 74 self.mapDict = {} 75 76 def endFile(self): 77 # Print out all the dictionaries as Python strings. 78 # Could just print(dict) but that's not human-readable 79 dicts = [ [ self.basetypes, 'basetypes' ], 80 [ self.consts, 'consts' ], 81 [ self.enums, 'enums' ], 82 [ self.flags, 'flags' ], 83 [ self.funcpointers, 'funcpointers' ], 84 [ self.protos, 'protos' ], 85 [ self.structs, 'structs' ], 86 [ self.handles, 'handles' ], 87 [ self.defines, 'defines' ], 88 [ self.typeCategory, 'typeCategory' ], 89 [ self.alias, 'alias' ], 90 ] 91 for (entry_dict, name) in dicts: 92 write(name + ' = {}', file=self.outFile) 93 for key in sorted(entry_dict.keys()): 94 write(name + '[' + enquote(key) + '] = ', entry_dict[key], 95 file=self.outFile) 96 97 # Dictionary containing the relationships of a type 98 # (e.g. a dictionary with each related type as keys). 99 write('mapDict = {}', file=self.outFile) 100 101 # Could just print(self.mapDict), but prefer something 102 # human-readable and stable-ordered 103 for baseType in sorted(self.mapDict.keys()): 104 write('mapDict[' + enquote(baseType) + '] = ', file=self.outFile, end='') 105 pprint(self.mapDict[baseType], self.outFile) 106 107 OutputGenerator.endFile(self) 108 109 # Add a string entry to the dictionary, quoting it so it gets printed 110 # out correctly in self.endFile() 111 def addName(self, entry_dict, name, value): 112 entry_dict[name] = enquote(value) 113 114 # Add a mapping between types to mapDict. Only include API types, 115 # so we don't end up with a lot of useless uint32_t and void types. 116 def addMapping(self, baseType, refType): 117 if not self.apiName(baseType) or not self.apiName(refType): 118 self.logMsg('diag', 'PyOutputGenerator::addMapping: IGNORE map from', baseType, '<->', refType) 119 return 120 121 self.logMsg('diag', 'PyOutputGenerator::addMapping: map from', 122 baseType, '<->', refType) 123 124 if baseType not in self.mapDict: 125 baseDict = {} 126 self.mapDict[baseType] = baseDict 127 else: 128 baseDict = self.mapDict[baseType] 129 if refType not in self.mapDict: 130 refDict = {} 131 self.mapDict[refType] = refDict 132 else: 133 refDict = self.mapDict[refType] 134 135 baseDict[refType] = None 136 refDict[baseType] = None 137 138 # Type generation 139 # For 'struct' or 'union' types, defer to genStruct() to 140 # add to the dictionary. 141 # For 'bitmask' types, add the type name to the 'flags' dictionary, 142 # with the value being the corresponding 'enums' name defining 143 # the acceptable flag bits. 144 # For 'enum' types, add the type name to the 'enums' dictionary, 145 # with the value being '@STOPHERE@' (because this case seems 146 # never to happen). 147 # For 'funcpointer' types, add the type name to the 'funcpointers' 148 # dictionary. 149 # For 'handle' and 'define' types, add the handle or #define name 150 # to the 'struct' dictionary, because that's how the spec sources 151 # tag these types even though they aren't structs. 152 def genType(self, typeinfo, name, alias): 153 OutputGenerator.genType(self, typeinfo, name, alias) 154 typeElem = typeinfo.elem 155 # If the type is a struct type, traverse the embedded <member> tags 156 # generating a structure. Otherwise, emit the tag text. 157 category = typeElem.get('category') 158 159 # Add a typeCategory{} entry for the category of this type. 160 self.addName(self.typeCategory, name, category) 161 162 if category in ('struct', 'union'): 163 self.genStruct(typeinfo, name, alias) 164 else: 165 if alias: 166 # Add name -> alias mapping 167 self.addName(self.alias, name, alias) 168 169 # Always emit an alias (?!) 170 count = 1 171 172 # May want to only emit full type definition when not an alias? 173 else: 174 # Extract the type name 175 # (from self.genOpts). Copy other text through unchanged. 176 # If the resulting text is an empty string, don't emit it. 177 count = len(noneStr(typeElem.text)) 178 for elem in typeElem: 179 count += len(noneStr(elem.text)) + len(noneStr(elem.tail)) 180 181 if count > 0: 182 if category == 'bitmask': 183 requiredEnum = typeElem.get('requires') 184 self.addName(self.flags, name, requiredEnum) 185 186 # This happens when the Flags type is defined, but no 187 # FlagBits are defined yet. 188 if requiredEnum is not None: 189 self.addMapping(name, requiredEnum) 190 elif category == 'enum': 191 # This case does not seem to come up. It nominally would 192 # result from 193 # <type name="Something" category="enum"/>, 194 # but the output generator doesn't emit them directly. 195 self.logMsg('warn', 'PyOutputGenerator::genType: invalid \'enum\' category for name:', name) 196 elif category == 'funcpointer': 197 self.funcpointers[name] = None 198 elif category == 'handle': 199 self.handles[name] = None 200 elif category == 'define': 201 self.defines[name] = None 202 elif category == 'basetype': 203 # Don't add an entry for base types that are not API types 204 # e.g. an API Bool type gets an entry, uint32_t does not 205 if self.apiName(name): 206 self.basetypes[name] = None 207 self.addName(self.typeCategory, name, 'basetype') 208 else: 209 self.logMsg('diag', 'PyOutputGenerator::genType: unprocessed type:', name, 'category:', category) 210 else: 211 self.logMsg('diag', 'PyOutputGenerator::genType: unprocessed type:', name) 212 213 # Struct (e.g. C "struct" type) generation. 214 # 215 # Add the struct name to the 'structs' dictionary, with the 216 # value being an ordered list of the struct member names. 217 def genStruct(self, typeinfo, typeName, alias): 218 OutputGenerator.genStruct(self, typeinfo, typeName, alias) 219 220 if alias: 221 # Add name -> alias mapping 222 self.addName(self.alias, typeName, alias) 223 else: 224 # May want to only emit definition on this branch 225 True 226 227 members = [member.text for member in typeinfo.elem.findall('.//member/name')] 228 self.structs[typeName] = members 229 memberTypes = [member.text for member in typeinfo.elem.findall('.//member/type')] 230 for member_type in memberTypes: 231 self.addMapping(typeName, member_type) 232 233 # Group (e.g. C "enum" type) generation. 234 # These are concatenated together with other types. 235 # 236 # Add the enum type name to the 'enums' dictionary, with 237 # the value being an ordered list of the enumerant names. 238 # Add each enumerant name to the 'consts' dictionary, with 239 # the value being the enum type the enumerant is part of. 240 def genGroup(self, groupinfo, groupName, alias): 241 OutputGenerator.genGroup(self, groupinfo, groupName, alias) 242 groupElem = groupinfo.elem 243 244 if alias: 245 # Add name -> alias mapping 246 self.addName(self.alias, groupName, alias) 247 else: 248 # May want to only emit definition on this branch 249 True 250 251 # Loop over the nested 'enum' tags. 252 enumerants = [elem.get('name') for elem in groupElem.findall('enum')] 253 for name in enumerants: 254 self.addName(self.consts, name, groupName) 255 self.enums[groupName] = enumerants 256 257 # Enumerant generation (compile-time constants) 258 # 259 # Add the constant name to the 'consts' dictionary, with the 260 # value being None to indicate that the constant isn't 261 # an enumeration value. 262 def genEnum(self, enuminfo, name, alias): 263 OutputGenerator.genEnum(self, enuminfo, name, alias) 264 265 # Add a typeCategory{} entry for the category of this type. 266 self.addName(self.typeCategory, name, 'consts') 267 268 self.consts[name] = None 269 270 # Command generation 271 # 272 # Add the command name to the 'protos' dictionary, with the 273 # value being an ordered list of the parameter names. 274 def genCmd(self, cmdinfo, name, alias): 275 OutputGenerator.genCmd(self, cmdinfo, name, alias) 276 277 if alias: 278 # Add name -> alias mapping 279 self.addName(self.alias, name, alias) 280 else: 281 # May want to only emit definition on this branch 282 True 283 284 # Add a typeCategory{} entry for the category of this type. 285 self.addName(self.typeCategory, name, 'protos') 286 287 params = [param.text for param in cmdinfo.elem.findall('param/name')] 288 self.protos[name] = params 289 paramTypes = [param.text for param in cmdinfo.elem.findall('param/type')] 290 for param_type in paramTypes: 291 self.addMapping(name, param_type)