1#!/usr/bin/python3 -i 2# 3# Copyright 2013-2023 The Khronos Group Inc. 4# 5# SPDX-License-Identifier: Apache-2.0 6 7import os 8import re 9 10from generator import (GeneratorOptions, 11 MissingGeneratorOptionsConventionsError, 12 MissingGeneratorOptionsError, MissingRegistryError, 13 OutputGenerator, noneStr, regSortFeatures, write) 14 15class CGeneratorOptions(GeneratorOptions): 16 """CGeneratorOptions - subclass of GeneratorOptions. 17 18 Adds options used by COutputGenerator objects during C language header 19 generation.""" 20 21 def __init__(self, 22 prefixText='', 23 genFuncPointers=True, 24 protectFile=True, 25 protectFeature=True, 26 protectProto=None, 27 protectProtoStr=None, 28 protectExtensionProto=None, 29 protectExtensionProtoStr=None, 30 apicall='', 31 apientry='', 32 apientryp='', 33 indentFuncProto=True, 34 indentFuncPointer=False, 35 alignFuncParam=0, 36 genEnumBeginEndRange=False, 37 genAliasMacro=False, 38 genStructExtendsComment=False, 39 aliasMacro='', 40 misracstyle=False, 41 misracppstyle=False, 42 **kwargs 43 ): 44 """Constructor. 45 Additional parameters beyond parent class: 46 47 - prefixText - list of strings to prefix generated header with 48 (usually a copyright statement + calling convention macros) 49 - protectFile - True if multiple inclusion protection should be 50 generated (based on the filename) around the entire header 51 - protectFeature - True if #ifndef..#endif protection should be 52 generated around a feature interface in the header file 53 - genFuncPointers - True if function pointer typedefs should be 54 generated 55 - protectProto - If conditional protection should be generated 56 around prototype declarations, set to either '#ifdef' 57 to require opt-in (#ifdef protectProtoStr) or '#ifndef' 58 to require opt-out (#ifndef protectProtoStr). Otherwise 59 set to None. 60 - protectProtoStr - #ifdef/#ifndef symbol to use around prototype 61 declarations, if protectProto is set 62 - protectExtensionProto - If conditional protection should be generated 63 around extension prototype declarations, set to either '#ifdef' 64 to require opt-in (#ifdef protectExtensionProtoStr) or '#ifndef' 65 to require opt-out (#ifndef protectExtensionProtoStr). Otherwise 66 set to None 67 - protectExtensionProtoStr - #ifdef/#ifndef symbol to use around 68 extension prototype declarations, if protectExtensionProto is set 69 - apicall - string to use for the function declaration prefix, 70 such as APICALL on Windows 71 - apientry - string to use for the calling convention macro, 72 in typedefs, such as APIENTRY 73 - apientryp - string to use for the calling convention macro 74 in function pointer typedefs, such as APIENTRYP 75 - indentFuncProto - True if prototype declarations should put each 76 parameter on a separate line 77 - indentFuncPointer - True if typedefed function pointers should put each 78 parameter on a separate line 79 - alignFuncParam - if nonzero and parameters are being put on a 80 separate line, align parameter names at the specified column 81 - genEnumBeginEndRange - True if BEGIN_RANGE / END_RANGE macros should 82 be generated for enumerated types 83 - genAliasMacro - True if the OpenXR alias macro should be generated 84 for aliased types (unclear what other circumstances this is useful) 85 - genStructExtendsComment - True if comments showing the structures 86 whose pNext chain a structure extends are included before its 87 definition 88 - aliasMacro - alias macro to inject when genAliasMacro is True 89 - misracstyle - generate MISRA C-friendly headers 90 - misracppstyle - generate MISRA C++-friendly headers""" 91 92 GeneratorOptions.__init__(self, **kwargs) 93 94 self.prefixText = prefixText 95 """list of strings to prefix generated header with (usually a copyright statement + calling convention macros).""" 96 97 self.genFuncPointers = genFuncPointers 98 """True if function pointer typedefs should be generated""" 99 100 self.protectFile = protectFile 101 """True if multiple inclusion protection should be generated (based on the filename) around the entire header.""" 102 103 self.protectFeature = protectFeature 104 """True if #ifndef..#endif protection should be generated around a feature interface in the header file.""" 105 106 self.protectProto = protectProto 107 """If conditional protection should be generated around prototype declarations, set to either '#ifdef' to require opt-in (#ifdef protectProtoStr) or '#ifndef' to require opt-out (#ifndef protectProtoStr). Otherwise set to None.""" 108 109 self.protectProtoStr = protectProtoStr 110 """#ifdef/#ifndef symbol to use around prototype declarations, if protectProto is set""" 111 112 self.protectExtensionProto = protectExtensionProto 113 """If conditional protection should be generated around extension prototype declarations, set to either '#ifdef' to require opt-in (#ifdef protectExtensionProtoStr) or '#ifndef' to require opt-out (#ifndef protectExtensionProtoStr). Otherwise set to None.""" 114 115 self.protectExtensionProtoStr = protectExtensionProtoStr 116 """#ifdef/#ifndef symbol to use around extension prototype declarations, if protectExtensionProto is set""" 117 118 self.apicall = apicall 119 """string to use for the function declaration prefix, such as APICALL on Windows.""" 120 121 self.apientry = apientry 122 """string to use for the calling convention macro, in typedefs, such as APIENTRY.""" 123 124 self.apientryp = apientryp 125 """string to use for the calling convention macro in function pointer typedefs, such as APIENTRYP.""" 126 127 self.indentFuncProto = indentFuncProto 128 """True if prototype declarations should put each parameter on a separate line""" 129 130 self.indentFuncPointer = indentFuncPointer 131 """True if typedefed function pointers should put each parameter on a separate line""" 132 133 self.alignFuncParam = alignFuncParam 134 """if nonzero and parameters are being put on a separate line, align parameter names at the specified column""" 135 136 self.genEnumBeginEndRange = genEnumBeginEndRange 137 """True if BEGIN_RANGE / END_RANGE macros should be generated for enumerated types""" 138 139 self.genAliasMacro = genAliasMacro 140 """True if the OpenXR alias macro should be generated for aliased types (unclear what other circumstances this is useful)""" 141 142 self.genStructExtendsComment = genStructExtendsComment 143 """True if comments showing the structures whose pNext chain a structure extends are included before its definition""" 144 145 self.aliasMacro = aliasMacro 146 """alias macro to inject when genAliasMacro is True""" 147 148 self.misracstyle = misracstyle 149 """generate MISRA C-friendly headers""" 150 151 self.misracppstyle = misracppstyle 152 """generate MISRA C++-friendly headers""" 153 154 self.codeGenerator = True 155 """True if this generator makes compilable code""" 156 157 158class COutputGenerator(OutputGenerator): 159 """Generates C-language API interfaces.""" 160 161 # This is an ordered list of sections in the header file. 162 TYPE_SECTIONS = ['include', 'define', 'basetype', 'handle', 'enum', 163 'group', 'bitmask', 'funcpointer', 'struct'] 164 ALL_SECTIONS = TYPE_SECTIONS + ['commandPointer', 'command'] 165 166 def __init__(self, *args, **kwargs): 167 super().__init__(*args, **kwargs) 168 # Internal state - accumulators for different inner block text 169 self.sections = {section: [] for section in self.ALL_SECTIONS} 170 self.feature_not_empty = False 171 self.may_alias = None 172 173 def beginFile(self, genOpts): 174 OutputGenerator.beginFile(self, genOpts) 175 if self.genOpts is None: 176 raise MissingGeneratorOptionsError() 177 # C-specific 178 # 179 # Multiple inclusion protection & C++ wrappers. 180 if self.genOpts.protectFile and self.genOpts.filename: 181 headerSym = re.sub(r'\.h', '_h_', 182 os.path.basename(self.genOpts.filename)).upper() 183 write('#ifndef', headerSym, file=self.outFile) 184 write('#define', headerSym, '1', file=self.outFile) 185 self.newline() 186 187 # User-supplied prefix text, if any (list of strings) 188 if genOpts.prefixText: 189 for s in genOpts.prefixText: 190 write(s, file=self.outFile) 191 192 # C++ extern wrapper - after prefix lines so they can add includes. 193 self.newline() 194 write('#ifdef __cplusplus', file=self.outFile) 195 write('extern "C" {', file=self.outFile) 196 write('#endif', file=self.outFile) 197 self.newline() 198 199 def endFile(self): 200 # C-specific 201 # Finish C++ wrapper and multiple inclusion protection 202 if self.genOpts is None: 203 raise MissingGeneratorOptionsError() 204 self.newline() 205 write('#ifdef __cplusplus', file=self.outFile) 206 write('}', file=self.outFile) 207 write('#endif', file=self.outFile) 208 if self.genOpts.protectFile and self.genOpts.filename: 209 self.newline() 210 write('#endif', file=self.outFile) 211 # Finish processing in superclass 212 OutputGenerator.endFile(self) 213 214 def beginFeature(self, interface, emit): 215 # Start processing in superclass 216 OutputGenerator.beginFeature(self, interface, emit) 217 # C-specific 218 # Accumulate includes, defines, types, enums, function pointer typedefs, 219 # end function prototypes separately for this feature. They are only 220 # printed in endFeature(). 221 self.sections = {section: [] for section in self.ALL_SECTIONS} 222 self.feature_not_empty = False 223 224 def _endProtectComment(self, protect_str, protect_directive='#ifdef'): 225 if protect_directive is None or protect_str is None: 226 raise RuntimeError('Should not call in here without something to protect') 227 228 # Do not put comments after #endif closing blocks if this is not set 229 if not self.genOpts.conventions.protectProtoComment: 230 return '' 231 elif 'ifdef' in protect_directive: 232 return f' /* {protect_str} */' 233 else: 234 return f' /* !{protect_str} */' 235 236 def endFeature(self): 237 "Actually write the interface to the output file." 238 # C-specific 239 if self.emit: 240 if self.feature_not_empty: 241 if self.genOpts is None: 242 raise MissingGeneratorOptionsError() 243 if self.genOpts.conventions is None: 244 raise MissingGeneratorOptionsConventionsError() 245 is_core = self.featureName and self.featureName.startswith(self.conventions.api_prefix + 'VERSION_') 246 if self.genOpts.conventions.writeFeature(self.featureExtraProtect, self.genOpts.filename): 247 self.newline() 248 if self.genOpts.protectFeature: 249 write('#ifndef', self.featureName, file=self.outFile) 250 251 # If type declarations are needed by other features based on 252 # this one, it may be necessary to suppress the ExtraProtect, 253 # or move it below the 'for section...' loop. 254 if self.featureExtraProtect is not None: 255 write('#ifdef', self.featureExtraProtect, file=self.outFile) 256 self.newline() 257 258 # Generate warning of possible use in IDEs 259 write(f'// {self.featureName} is a preprocessor guard. Do not pass it to API calls.', file=self.outFile) 260 write('#define', self.featureName, '1', file=self.outFile) 261 for section in self.TYPE_SECTIONS: 262 contents = self.sections[section] 263 if contents: 264 write('\n'.join(contents), file=self.outFile) 265 266 if self.genOpts.genFuncPointers and self.sections['commandPointer']: 267 write('\n'.join(self.sections['commandPointer']), file=self.outFile) 268 self.newline() 269 270 if self.sections['command']: 271 if self.genOpts.protectProto: 272 write(self.genOpts.protectProto, 273 self.genOpts.protectProtoStr, file=self.outFile) 274 if self.genOpts.protectExtensionProto and not is_core: 275 write(self.genOpts.protectExtensionProto, 276 self.genOpts.protectExtensionProtoStr, file=self.outFile) 277 write('\n'.join(self.sections['command']), end='', file=self.outFile) 278 if self.genOpts.protectExtensionProto and not is_core: 279 write('#endif' + 280 self._endProtectComment(protect_directive=self.genOpts.protectExtensionProto, 281 protect_str=self.genOpts.protectExtensionProtoStr), 282 file=self.outFile) 283 if self.genOpts.protectProto: 284 write('#endif' + 285 self._endProtectComment(protect_directive=self.genOpts.protectProto, 286 protect_str=self.genOpts.protectProtoStr), 287 file=self.outFile) 288 else: 289 self.newline() 290 291 if self.featureExtraProtect is not None: 292 write('#endif' + 293 self._endProtectComment(protect_str=self.featureExtraProtect), 294 file=self.outFile) 295 296 if self.genOpts.protectFeature: 297 write('#endif' + 298 self._endProtectComment(protect_str=self.featureName), 299 file=self.outFile) 300 # Finish processing in superclass 301 OutputGenerator.endFeature(self) 302 303 def appendSection(self, section, text): 304 "Append a definition to the specified section" 305 306 if section is None: 307 self.logMsg('error', 'Missing section in appendSection (probably a <type> element missing its \'category\' attribute. Text:', text) 308 exit(1) 309 310 self.sections[section].append(text) 311 self.feature_not_empty = True 312 313 def genType(self, typeinfo, name, alias): 314 "Generate type." 315 OutputGenerator.genType(self, typeinfo, name, alias) 316 typeElem = typeinfo.elem 317 318 # Vulkan: 319 # Determine the category of the type, and the type section to add 320 # its definition to. 321 # 'funcpointer' is added to the 'struct' section as a workaround for 322 # internal issue #877, since structures and function pointer types 323 # can have cross-dependencies. 324 category = typeElem.get('category') 325 if category == 'funcpointer': 326 section = 'struct' 327 else: 328 section = category 329 330 if category in ('struct', 'union'): 331 # If the type is a struct type, generate it using the 332 # special-purpose generator. 333 self.genStruct(typeinfo, name, alias) 334 else: 335 if self.genOpts is None: 336 raise MissingGeneratorOptionsError() 337 # OpenXR: this section was not under 'else:' previously, just fell through 338 if alias: 339 # If the type is an alias, just emit a typedef declaration 340 body = 'typedef ' + alias + ' ' + name + ';\n' 341 else: 342 # Replace <apientry /> tags with an APIENTRY-style string 343 # (from self.genOpts). Copy other text through unchanged. 344 # If the resulting text is an empty string, do not emit it. 345 body = noneStr(typeElem.text) 346 for elem in typeElem: 347 if elem.tag == 'apientry': 348 body += self.genOpts.apientry + noneStr(elem.tail) 349 else: 350 body += noneStr(elem.text) + noneStr(elem.tail) 351 if category == 'define' and self.misracppstyle(): 352 body = body.replace("(uint32_t)", "static_cast<uint32_t>") 353 if body: 354 # Add extra newline after multi-line entries. 355 if '\n' in body[0:-1]: 356 body += '\n' 357 self.appendSection(section, body) 358 359 def genProtectString(self, protect_str): 360 """Generate protection string. 361 362 Protection strings are the strings defining the OS/Platform/Graphics 363 requirements for a given API command. When generating the 364 language header files, we need to make sure the items specific to a 365 graphics API or OS platform are properly wrapped in #ifs.""" 366 protect_if_str = '' 367 protect_end_str = '' 368 if not protect_str: 369 return (protect_if_str, protect_end_str) 370 371 if ',' in protect_str: 372 protect_list = protect_str.split(',') 373 protect_defs = ('defined(%s)' % d for d in protect_list) 374 protect_def_str = ' && '.join(protect_defs) 375 protect_if_str = '#if %s\n' % protect_def_str 376 protect_end_str = '#endif // %s\n' % protect_def_str 377 else: 378 protect_if_str = '#ifdef %s\n' % protect_str 379 protect_end_str = '#endif // %s\n' % protect_str 380 381 return (protect_if_str, protect_end_str) 382 383 def typeMayAlias(self, typeName): 384 if not self.may_alias: 385 if self.registry is None: 386 raise MissingRegistryError() 387 # First time we have asked if a type may alias. 388 # So, populate the set of all names of types that may. 389 390 # Everyone with an explicit mayalias="true" 391 self.may_alias = set(typeName 392 for typeName, data in self.registry.typedict.items() 393 if data.elem.get('mayalias') == 'true') 394 395 # Every type mentioned in some other type's parentstruct attribute. 396 polymorphic_bases = (otherType.elem.get('parentstruct') 397 for otherType in self.registry.typedict.values()) 398 self.may_alias.update(set(x for x in polymorphic_bases 399 if x is not None)) 400 return typeName in self.may_alias 401 402 def genStruct(self, typeinfo, typeName, alias): 403 """Generate struct (e.g. C "struct" type). 404 405 This is a special case of the <type> tag where the contents are 406 interpreted as a set of <member> tags instead of freeform C 407 C type declarations. The <member> tags are just like <param> 408 tags - they are a declaration of a struct or union member. 409 Only simple member declarations are supported (no nested 410 structs etc.) 411 412 If alias is not None, then this struct aliases another; just 413 generate a typedef of that alias.""" 414 OutputGenerator.genStruct(self, typeinfo, typeName, alias) 415 416 if self.genOpts is None: 417 raise MissingGeneratorOptionsError() 418 419 typeElem = typeinfo.elem 420 421 if alias: 422 body = 'typedef ' + alias + ' ' + typeName + ';\n' 423 else: 424 body = '' 425 (protect_begin, protect_end) = self.genProtectString(typeElem.get('protect')) 426 if protect_begin: 427 body += protect_begin 428 429 if self.genOpts.genStructExtendsComment: 430 structextends = typeElem.get('structextends') 431 body += '// ' + typeName + ' extends ' + structextends + '\n' if structextends else '' 432 433 body += 'typedef ' + typeElem.get('category') 434 435 # This is an OpenXR-specific alternative where aliasing refers 436 # to an inheritance hierarchy of types rather than C-level type 437 # aliases. 438 if self.genOpts.genAliasMacro and self.typeMayAlias(typeName): 439 body += ' ' + self.genOpts.aliasMacro 440 441 body += ' ' + typeName + ' {\n' 442 443 targetLen = self.getMaxCParamTypeLength(typeinfo) 444 for member in typeElem.findall('.//member'): 445 body += self.makeCParamDecl(member, targetLen + 4) 446 body += ';\n' 447 body += '} ' + typeName + ';\n' 448 if protect_end: 449 body += protect_end 450 451 self.appendSection('struct', body) 452 453 def genGroup(self, groupinfo, groupName, alias=None): 454 """Generate groups (e.g. C "enum" type). 455 456 These are concatenated together with other types. 457 458 If alias is not None, it is the name of another group type 459 which aliases this type; just generate that alias.""" 460 OutputGenerator.genGroup(self, groupinfo, groupName, alias) 461 groupElem = groupinfo.elem 462 463 # After either enumerated type or alias paths, add the declaration 464 # to the appropriate section for the group being defined. 465 if groupElem.get('type') == 'bitmask': 466 section = 'bitmask' 467 else: 468 section = 'group' 469 470 if alias: 471 # If the group name is aliased, just emit a typedef declaration 472 # for the alias. 473 body = 'typedef ' + alias + ' ' + groupName + ';\n' 474 self.appendSection(section, body) 475 else: 476 if self.genOpts is None: 477 raise MissingGeneratorOptionsError() 478 (section, body) = self.buildEnumCDecl(self.genOpts.genEnumBeginEndRange, groupinfo, groupName) 479 self.appendSection(section, '\n' + body) 480 481 def genEnum(self, enuminfo, name, alias): 482 """Generate the C declaration for a constant (a single <enum> value). 483 484 <enum> tags may specify their values in several ways, but are usually 485 just integers.""" 486 487 OutputGenerator.genEnum(self, enuminfo, name, alias) 488 489 body = self.buildConstantCDecl(enuminfo, name, alias) 490 self.appendSection('enum', body) 491 492 def genCmd(self, cmdinfo, name, alias): 493 "Command generation" 494 OutputGenerator.genCmd(self, cmdinfo, name, alias) 495 496 # if alias: 497 # prefix = '// ' + name + ' is an alias of command ' + alias + '\n' 498 # else: 499 # prefix = '' 500 if self.genOpts is None: 501 raise MissingGeneratorOptionsError() 502 503 prefix = '' 504 decls = self.makeCDecls(cmdinfo.elem) 505 self.appendSection('command', prefix + decls[0] + '\n') 506 if self.genOpts.genFuncPointers: 507 self.appendSection('commandPointer', decls[1]) 508 509 def misracstyle(self): 510 return self.genOpts.misracstyle; 511 512 def misracppstyle(self): 513 return self.genOpts.misracppstyle; 514