1#!/usr/bin/env python3 -i 2# 3# Copyright 2013-2024 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.featureName, 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 338 body = self.deprecationComment(typeElem) 339 340 # OpenXR: this section was not under 'else:' previously, just fell through 341 if alias: 342 # If the type is an alias, just emit a typedef declaration 343 body += 'typedef ' + alias + ' ' + name + ';\n' 344 else: 345 # Replace <apientry /> tags with an APIENTRY-style string 346 # (from self.genOpts). Copy other text through unchanged. 347 # If the resulting text is an empty string, do not emit it. 348 body += noneStr(typeElem.text) 349 for elem in typeElem: 350 if elem.tag == 'apientry': 351 body += self.genOpts.apientry + noneStr(elem.tail) 352 else: 353 body += noneStr(elem.text) + noneStr(elem.tail) 354 if category == 'define' and self.misracppstyle(): 355 body = body.replace("(uint32_t)", "static_cast<uint32_t>") 356 if body: 357 # Add extra newline after multi-line entries. 358 if '\n' in body[0:-1]: 359 body += '\n' 360 self.appendSection(section, body) 361 362 def genProtectString(self, protect_str): 363 """Generate protection string. 364 365 Protection strings are the strings defining the OS/Platform/Graphics 366 requirements for a given API command. When generating the 367 language header files, we need to make sure the items specific to a 368 graphics API or OS platform are properly wrapped in #ifs.""" 369 protect_if_str = '' 370 protect_end_str = '' 371 if not protect_str: 372 return (protect_if_str, protect_end_str) 373 374 if ',' in protect_str: 375 protect_list = protect_str.split(',') 376 protect_defs = ('defined(%s)' % d for d in protect_list) 377 protect_def_str = ' && '.join(protect_defs) 378 protect_if_str = '#if %s\n' % protect_def_str 379 protect_end_str = '#endif // %s\n' % protect_def_str 380 else: 381 protect_if_str = '#ifdef %s\n' % protect_str 382 protect_end_str = '#endif // %s\n' % protect_str 383 384 return (protect_if_str, protect_end_str) 385 386 def typeMayAlias(self, typeName): 387 if not self.may_alias: 388 if self.registry is None: 389 raise MissingRegistryError() 390 # First time we have asked if a type may alias. 391 # So, populate the set of all names of types that may. 392 393 # Everyone with an explicit mayalias="true" 394 self.may_alias = set(typeName 395 for typeName, data in self.registry.typedict.items() 396 if data.elem.get('mayalias') == 'true') 397 398 # Every type mentioned in some other type's parentstruct attribute. 399 polymorphic_bases = (otherType.elem.get('parentstruct') 400 for otherType in self.registry.typedict.values()) 401 self.may_alias.update(set(x for x in polymorphic_bases 402 if x is not None)) 403 return typeName in self.may_alias 404 405 def genStruct(self, typeinfo, typeName, alias): 406 """Generate struct (e.g. C "struct" type). 407 408 This is a special case of the <type> tag where the contents are 409 interpreted as a set of <member> tags instead of freeform C 410 C type declarations. The <member> tags are just like <param> 411 tags - they are a declaration of a struct or union member. 412 Only simple member declarations are supported (no nested 413 structs etc.) 414 415 If alias is not None, then this struct aliases another; just 416 generate a typedef of that alias.""" 417 OutputGenerator.genStruct(self, typeinfo, typeName, alias) 418 419 if self.genOpts is None: 420 raise MissingGeneratorOptionsError() 421 422 typeElem = typeinfo.elem 423 body = self.deprecationComment(typeElem) 424 425 if alias: 426 body += 'typedef ' + alias + ' ' + typeName + ';\n' 427 else: 428 (protect_begin, protect_end) = self.genProtectString(typeElem.get('protect')) 429 if protect_begin: 430 body += protect_begin 431 432 if self.genOpts.genStructExtendsComment: 433 structextends = typeElem.get('structextends') 434 body += '// ' + typeName + ' extends ' + structextends + '\n' if structextends else '' 435 436 body += 'typedef ' + typeElem.get('category') 437 438 # This is an OpenXR-specific alternative where aliasing refers 439 # to an inheritance hierarchy of types rather than C-level type 440 # aliases. 441 if self.genOpts.genAliasMacro and self.typeMayAlias(typeName): 442 body += ' ' + self.genOpts.aliasMacro 443 444 body += ' ' + typeName + ' {\n' 445 446 targetLen = self.getMaxCParamTypeLength(typeinfo) 447 for member in typeElem.findall('.//member'): 448 body += self.deprecationComment(member, indent = 4) 449 body += self.makeCParamDecl(member, targetLen + 4) 450 body += ';\n' 451 body += '} ' + typeName + ';\n' 452 if protect_end: 453 body += protect_end 454 455 self.appendSection('struct', body) 456 457 def genGroup(self, groupinfo, groupName, alias=None): 458 """Generate groups (e.g. C "enum" type). 459 460 These are concatenated together with other types. 461 462 If alias is not None, it is the name of another group type 463 which aliases this type; just generate that alias.""" 464 OutputGenerator.genGroup(self, groupinfo, groupName, alias) 465 groupElem = groupinfo.elem 466 467 # After either enumerated type or alias paths, add the declaration 468 # to the appropriate section for the group being defined. 469 if groupElem.get('type') == 'bitmask': 470 section = 'bitmask' 471 else: 472 section = 'group' 473 474 if alias: 475 # If the group name is aliased, just emit a typedef declaration 476 # for the alias. 477 body = 'typedef ' + alias + ' ' + groupName + ';\n' 478 self.appendSection(section, body) 479 else: 480 if self.genOpts is None: 481 raise MissingGeneratorOptionsError() 482 (section, body) = self.buildEnumCDecl(self.genOpts.genEnumBeginEndRange, groupinfo, groupName) 483 self.appendSection(section, '\n' + body) 484 485 def genEnum(self, enuminfo, name, alias): 486 """Generate the C declaration for a constant (a single <enum> value). 487 488 <enum> tags may specify their values in several ways, but are usually 489 just integers.""" 490 491 OutputGenerator.genEnum(self, enuminfo, name, alias) 492 493 body = self.deprecationComment(enuminfo.elem) 494 body += self.buildConstantCDecl(enuminfo, name, alias) 495 self.appendSection('enum', body) 496 497 def genCmd(self, cmdinfo, name, alias): 498 "Command generation" 499 OutputGenerator.genCmd(self, cmdinfo, name, alias) 500 501 # if alias: 502 # prefix = '// ' + name + ' is an alias of command ' + alias + '\n' 503 # else: 504 # prefix = '' 505 if self.genOpts is None: 506 raise MissingGeneratorOptionsError() 507 508 prefix = '' 509 decls = self.makeCDecls(cmdinfo.elem) 510 self.appendSection('command', prefix + decls[0] + '\n') 511 if self.genOpts.genFuncPointers: 512 self.appendSection('commandPointer', decls[1]) 513 514 def misracstyle(self): 515 return self.genOpts.misracstyle 516 517 def misracppstyle(self): 518 return self.genOpts.misracppstyle 519