cgenerator.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 os 18 import re 19 import sys 20 from generator import (GeneratorOptions, OutputGenerator, noneStr, 21 regSortFeatures, write) 22 23 # CGeneratorOptions - subclass of GeneratorOptions. 24 # 25 # Adds options used by COutputGenerator objects during C language header 26 # generation. 27 # 28 # Additional members 29 # prefixText - list of strings to prefix generated header with 30 # (usually a copyright statement + calling convention macros). 31 # protectFile - True if multiple inclusion protection should be 32 # generated (based on the filename) around the entire header. 33 # protectFeature - True if #ifndef..#endif protection should be 34 # generated around a feature interface in the header file. 35 # genFuncPointers - True if function pointer typedefs should be 36 # generated 37 # protectProto - If conditional protection should be generated 38 # around prototype declarations, set to either '#ifdef' 39 # to require opt-in (#ifdef protectProtoStr) or '#ifndef' 40 # to require opt-out (#ifndef protectProtoStr). Otherwise 41 # set to None. 42 # protectProtoStr - #ifdef/#ifndef symbol to use around prototype 43 # declarations, if protectProto is set 44 # apicall - string to use for the function declaration prefix, 45 # such as APICALL on Windows. 46 # apientry - string to use for the calling convention macro, 47 # in typedefs, such as APIENTRY. 48 # apientryp - string to use for the calling convention macro 49 # in function pointer typedefs, such as APIENTRYP. 50 # directory - directory into which to generate include files 51 # indentFuncProto - True if prototype declarations should put each 52 # parameter on a separate line 53 # indentFuncPointer - True if typedefed function pointers should put each 54 # parameter on a separate line 55 # alignFuncParam - if nonzero and parameters are being put on a 56 # separate line, align parameter names at the specified column 57 # genEnumBeginEndRange - True if BEGIN_RANGE / END_RANGE macros should 58 # be generated for enumerated types 59 # genAliasMacro - True if the OpenXR alias macro should be generated 60 # for aliased types (unclear what other circumstances this is useful) 61 # aliasMacro - alias macro to inject when genAliasMacro is True 62 class CGeneratorOptions(GeneratorOptions): 63 """Represents options during C interface generation for headers""" 64 65 def __init__(self, 66 conventions = None, 67 filename = None, 68 directory = '.', 69 apiname = None, 70 profile = None, 71 versions = '.*', 72 emitversions = '.*', 73 defaultExtensions = None, 74 addExtensions = None, 75 removeExtensions = None, 76 emitExtensions = None, 77 sortProcedure = regSortFeatures, 78 prefixText = "", 79 genFuncPointers = True, 80 protectFile = True, 81 protectFeature = True, 82 protectProto = None, 83 protectProtoStr = None, 84 apicall = '', 85 apientry = '', 86 apientryp = '', 87 indentFuncProto = True, 88 indentFuncPointer = False, 89 alignFuncParam = 0, 90 genEnumBeginEndRange = False, 91 genAliasMacro = False, 92 aliasMacro = '' 93 ): 94 GeneratorOptions.__init__(self, conventions, filename, directory, apiname, profile, 95 versions, emitversions, defaultExtensions, 96 addExtensions, removeExtensions, 97 emitExtensions, sortProcedure) 98 self.prefixText = prefixText 99 self.genFuncPointers = genFuncPointers 100 self.protectFile = protectFile 101 self.protectFeature = protectFeature 102 self.protectProto = protectProto 103 self.protectProtoStr = protectProtoStr 104 self.apicall = apicall 105 self.apientry = apientry 106 self.apientryp = apientryp 107 self.indentFuncProto = indentFuncProto 108 self.indentFuncPointer = indentFuncPointer 109 self.alignFuncParam = alignFuncParam 110 self.genEnumBeginEndRange = genEnumBeginEndRange 111 self.genAliasMacro = genAliasMacro 112 self.aliasMacro = aliasMacro 113 114 # COutputGenerator - subclass of OutputGenerator. 115 # Generates C-language API interfaces. 116 # 117 # ---- methods ---- 118 # COutputGenerator(errFile, warnFile, diagFile) - args as for 119 # OutputGenerator. Defines additional internal state. 120 # ---- methods overriding base class ---- 121 # beginFile(genOpts) 122 # endFile() 123 # beginFeature(interface, emit) 124 # endFeature() 125 # genType(typeinfo,name) 126 # genStruct(typeinfo,name) 127 # genGroup(groupinfo,name) 128 # genEnum(enuminfo, name) 129 # genCmd(cmdinfo) 130 class COutputGenerator(OutputGenerator): 131 """Generate specified API interfaces in a specific style, such as a C header""" 132 # This is an ordered list of sections in the header file. 133 TYPE_SECTIONS = ['include', 'define', 'basetype', 'handle', 'enum', 134 'group', 'bitmask', 'funcpointer', 'struct'] 135 ALL_SECTIONS = TYPE_SECTIONS + ['commandPointer', 'command'] 136 137 def __init__(self, 138 errFile = sys.stderr, 139 warnFile = sys.stderr, 140 diagFile = sys.stdout): 141 OutputGenerator.__init__(self, errFile, warnFile, diagFile) 142 # Internal state - accumulators for different inner block text 143 self.sections = {section: [] for section in self.ALL_SECTIONS} 144 self.feature_not_empty = False 145 self.need_platform_include = False 146 self.may_alias = None 147 148 def beginFile(self, genOpts): 149 OutputGenerator.beginFile(self, genOpts) 150 # C-specific 151 # 152 # Multiple inclusion protection & C++ wrappers. 153 if genOpts.protectFile and self.genOpts.filename: 154 headerSym = re.sub(r'\.h', '_h_', 155 os.path.basename(self.genOpts.filename)).upper() 156 write('#ifndef', headerSym, file=self.outFile) 157 write('#define', headerSym, '1', file=self.outFile) 158 self.newline() 159 write('#ifdef __cplusplus', file=self.outFile) 160 write('extern "C" {', file=self.outFile) 161 write('#endif', file=self.outFile) 162 self.newline() 163 164 # User-supplied prefix text, if any (list of strings) 165 if genOpts.prefixText: 166 for s in genOpts.prefixText: 167 write(s, file=self.outFile) 168 169 def endFile(self): 170 # C-specific 171 # Finish C++ wrapper and multiple inclusion protection 172 self.newline() 173 write('#ifdef __cplusplus', file=self.outFile) 174 write('}', file=self.outFile) 175 write('#endif', file=self.outFile) 176 if self.genOpts.protectFile and self.genOpts.filename: 177 self.newline() 178 write('#endif', file=self.outFile) 179 # Finish processing in superclass 180 OutputGenerator.endFile(self) 181 182 def beginFeature(self, interface, emit): 183 # Start processing in superclass 184 OutputGenerator.beginFeature(self, interface, emit) 185 # C-specific 186 # Accumulate includes, defines, types, enums, function pointer typedefs, 187 # end function prototypes separately for this feature. They're only 188 # printed in endFeature(). 189 self.sections = {section: [] for section in self.ALL_SECTIONS} 190 self.feature_not_empty = False 191 192 def endFeature(self): 193 # C-specific 194 # Actually write the interface to the output file. 195 if self.emit: 196 if self.feature_not_empty: 197 if self.genOpts.conventions.writeFeature(self.featureExtraProtect, self.genOpts.filename): 198 self.newline() 199 if self.genOpts.protectFeature: 200 write('#ifndef', self.featureName, file=self.outFile) 201 # If type declarations are needed by other features based on 202 # this one, it may be necessary to suppress the ExtraProtect, 203 # or move it below the 'for section...' loop. 204 if self.featureExtraProtect is not None: 205 write('#ifdef', self.featureExtraProtect, file=self.outFile) 206 self.newline() 207 write('#define', self.featureName, '1', file=self.outFile) 208 for section in self.TYPE_SECTIONS: 209 # OpenXR: 210 # If we need the explicit include of the external platform header, 211 # put it right before the function pointer definitions 212 if section == "funcpointer" and self.need_platform_include: 213 write('// Include for OpenXR Platform-Specific Types', file=self.outFile) 214 write('#include "openxr_platform.h"', file=self.outFile) 215 self.newline() 216 self.need_platform_include = False 217 contents = self.sections[section] 218 if contents: 219 write('\n'.join(contents), file=self.outFile) 220 if self.genOpts.genFuncPointers and self.sections['commandPointer']: 221 write('\n'.join(self.sections['commandPointer']), file=self.outFile) 222 self.newline() 223 if self.sections['command']: 224 if self.genOpts.protectProto: 225 write(self.genOpts.protectProto, 226 self.genOpts.protectProtoStr, file=self.outFile) 227 write('\n'.join(self.sections['command']), end='', file=self.outFile) 228 if self.genOpts.protectProto: 229 write('#endif', file=self.outFile) 230 else: 231 self.newline() 232 if self.featureExtraProtect is not None: 233 write('#endif /*', self.featureExtraProtect, '*/', file=self.outFile) 234 if self.genOpts.protectFeature: 235 write('#endif /*', self.featureName, '*/', file=self.outFile) 236 # Finish processing in superclass 237 OutputGenerator.endFeature(self) 238 239 # Append a definition to the specified section 240 def appendSection(self, section, text): 241 # self.sections[section].append('SECTION: ' + section + '\n') 242 self.sections[section].append(text) 243 self.feature_not_empty = True 244 245 # Type generation 246 def genType(self, typeinfo, name, alias): 247 OutputGenerator.genType(self, typeinfo, name, alias) 248 typeElem = typeinfo.elem 249 250 # Vulkan: 251 # Determine the category of the type, and the type section to add 252 # its definition to. 253 # 'funcpointer' is added to the 'struct' section as a workaround for 254 # internal issue #877, since structures and function pointer types 255 # can have cross-dependencies. 256 category = typeElem.get('category') 257 if category == 'funcpointer': 258 section = 'struct' 259 else: 260 section = category 261 262 if category in ('struct', 'union'): 263 # If the type is a struct type, generate it using the 264 # special-purpose generator. 265 self.genStruct(typeinfo, name, alias) 266 else: 267 # OpenXR: this section was not under 'else:' previously, just fell through 268 if alias: 269 # If the type is an alias, just emit a typedef declaration 270 body = 'typedef ' + alias + ' ' + name + ';\n' 271 else: 272 # Replace <apientry /> tags with an APIENTRY-style string 273 # (from self.genOpts). Copy other text through unchanged. 274 # If the resulting text is an empty string, don't emit it. 275 body = noneStr(typeElem.text) 276 for elem in typeElem: 277 if elem.tag == 'apientry': 278 body += self.genOpts.apientry + noneStr(elem.tail) 279 else: 280 body += noneStr(elem.text) + noneStr(elem.tail) 281 if body: 282 # Add extra newline after multi-line entries. 283 if '\n' in body[0:-1]: 284 body += '\n' 285 self.appendSection(section, body) 286 287 # Protection string generation 288 # Protection strings are the strings defining the OS/Platform/Graphics 289 # requirements for a given OpenXR command. When generating the 290 # language header files, we need to make sure the items specific to a 291 # graphics API or OS platform are properly wrapped in #ifs. 292 def genProtectString(self, protect_str): 293 protect_if_str = '' 294 protect_end_str = '' 295 protect_list = [] 296 if protect_str: 297 if ',' in protect_str: 298 protect_list.extend(protect_str.split(",")) 299 protect_def_str = '' 300 count = 0 301 for protect_define in protect_list: 302 if count > 0: 303 protect_def_str += ' &&' 304 protect_def_str += ' defined(%s)' % protect_define 305 count = count + 1 306 count = count + 1 307 protect_if_str = '#if' 308 protect_if_str += protect_def_str 309 protect_if_str += '\n' 310 protect_end_str = '#endif //' 311 protect_end_str += protect_def_str 312 protect_end_str += '\n' 313 else: 314 protect_if_str += '#ifdef %s\n' % protect_str 315 protect_end_str += '#endif // %s\n' % protect_str 316 return (protect_if_str, protect_end_str) 317 318 def typeMayAlias(self, typeName): 319 if not self.may_alias: 320 # First time we've asked if a type may alias. 321 # So, let's populate the set of all names of types that may. 322 323 # Everyone with an explicit mayalias="true" 324 self.may_alias = set(typeName 325 for typeName, data in self.registry.typedict.items() 326 if data.elem.get('mayalias') == 'true') 327 328 # Every type mentioned in some other type's parentstruct attribute. 329 self.may_alias.update(set(x for x in 330 [otherType.elem.get('parentstruct') 331 for _, otherType in self.registry.typedict.items()] 332 if x is not None 333 )) 334 return typeName in self.may_alias 335 336 # Struct (e.g. C "struct" type) generation. 337 # This is a special case of the <type> tag where the contents are 338 # interpreted as a set of <member> tags instead of freeform C 339 # C type declarations. The <member> tags are just like <param> 340 # tags - they are a declaration of a struct or union member. 341 # Only simple member declarations are supported (no nested 342 # structs etc.) 343 # If alias is not None, then this struct aliases another; just 344 # generate a typedef of that alias. 345 def genStruct(self, typeinfo, typeName, alias): 346 OutputGenerator.genStruct(self, typeinfo, typeName, alias) 347 348 typeElem = typeinfo.elem 349 350 if alias: 351 body = 'typedef ' + alias + ' ' + typeName + ';\n' 352 else: 353 body = '' 354 (protect_begin, protect_end) = self.genProtectString(typeElem.get('protect')) 355 if protect_begin: 356 body += protect_begin 357 body += 'typedef ' + typeElem.get('category') 358 359 # This is an OpenXR-specific alternative where aliasing refers 360 # to an inheritance hierarchy of types rather than C-level type 361 # aliases. 362 if self.genOpts.genAliasMacro and self.typeMayAlias(typeName): 363 body += ' ' + self.genOpts.aliasMacro 364 365 body += ' ' + typeName + ' {\n' 366 367 targetLen = 0 368 for member in typeElem.findall('.//member'): 369 targetLen = max(targetLen, self.getCParamTypeLength(member)) 370 for member in typeElem.findall('.//member'): 371 body += self.makeCParamDecl(member, targetLen + 4) 372 body += ';\n' 373 body += '} ' + typeName + ';\n' 374 if protect_end: 375 body += protect_end 376 377 self.appendSection('struct', body) 378 379 # Group (e.g. C "enum" type) generation. 380 # These are concatenated together with other types. 381 # If alias is not None, it is the name of another group type 382 # which aliases this type; just generate that alias. 383 def genGroup(self, groupinfo, groupName, alias = None): 384 OutputGenerator.genGroup(self, groupinfo, groupName, alias) 385 groupElem = groupinfo.elem 386 387 # After either enumerated type or alias paths, add the declaration 388 # to the appropriate section for the group being defined. 389 if groupElem.get('type') == 'bitmask': 390 section = 'bitmask' 391 else: 392 section = 'group' 393 394 if alias: 395 # If the group name is aliased, just emit a typedef declaration 396 # for the alias. 397 body = 'typedef ' + alias + ' ' + groupName + ';\n' 398 self.appendSection(section, body) 399 else: 400 (section, body) = self.buildEnumCDecl(self.genOpts.genEnumBeginEndRange, groupinfo, groupName) 401 self.appendSection(section, "\n" + body) 402 403 # Enumerant generation 404 # <enum> tags may specify their values in several ways, but are usually 405 # just integers. 406 def genEnum(self, enuminfo, name, alias): 407 OutputGenerator.genEnum(self, enuminfo, name, alias) 408 (_, strVal) = self.enumToValue(enuminfo.elem, False) 409 body = '#define ' + name.ljust(33) + ' ' + strVal 410 self.appendSection('enum', body) 411 412 # Command generation 413 def genCmd(self, cmdinfo, name, alias): 414 OutputGenerator.genCmd(self, cmdinfo, name, alias) 415 416 # if alias: 417 # prefix = '// ' + name + ' is an alias of command ' + alias + '\n' 418 # else: 419 # prefix = '' 420 421 prefix = '' 422 decls = self.makeCDecls(cmdinfo.elem) 423 self.appendSection('command', prefix + decls[0] + '\n') 424 if self.genOpts.genFuncPointers: 425 self.appendSection('commandPointer', decls[1])