#!/usr/bin/python3 -i # # Copyright 2013-2021 The Khronos Group Inc. # # SPDX-License-Identifier: Apache-2.0 import os import re from generator import (GeneratorOptions, OutputGenerator, noneStr, regSortFeatures, write) class CGeneratorOptions(GeneratorOptions): """CGeneratorOptions - subclass of GeneratorOptions. Adds options used by COutputGenerator objects during C language header generation.""" def __init__(self, prefixText="", genFuncPointers=True, protectFile=True, protectFeature=True, protectProto=None, protectProtoStr=None, apicall='', apientry='', apientryp='', indentFuncProto=True, indentFuncPointer=False, alignFuncParam=0, genEnumBeginEndRange=False, genAliasMacro=False, aliasMacro='', misracstyle=False, misracppstyle=False, **kwargs ): """Constructor. Additional parameters beyond parent class: - prefixText - list of strings to prefix generated header with (usually a copyright statement + calling convention macros). - protectFile - True if multiple inclusion protection should be generated (based on the filename) around the entire header. - protectFeature - True if #ifndef..#endif protection should be generated around a feature interface in the header file. - genFuncPointers - True if function pointer typedefs should be generated - protectProto - 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. - protectProtoStr - #ifdef/#ifndef symbol to use around prototype declarations, if protectProto is set - apicall - string to use for the function declaration prefix, such as APICALL on Windows. - apientry - string to use for the calling convention macro, in typedefs, such as APIENTRY. - apientryp - string to use for the calling convention macro in function pointer typedefs, such as APIENTRYP. - indentFuncProto - True if prototype declarations should put each parameter on a separate line - indentFuncPointer - True if typedefed function pointers should put each parameter on a separate line - alignFuncParam - if nonzero and parameters are being put on a separate line, align parameter names at the specified column - genEnumBeginEndRange - True if BEGIN_RANGE / END_RANGE macros should be generated for enumerated types - genAliasMacro - True if the OpenXR alias macro should be generated for aliased types (unclear what other circumstances this is useful) - aliasMacro - alias macro to inject when genAliasMacro is True - misracstyle - generate MISRA C-friendly headers - misracppstyle - generate MISRA C++-friendly headers""" GeneratorOptions.__init__(self, **kwargs) self.prefixText = prefixText """list of strings to prefix generated header with (usually a copyright statement + calling convention macros).""" self.genFuncPointers = genFuncPointers """True if function pointer typedefs should be generated""" self.protectFile = protectFile """True if multiple inclusion protection should be generated (based on the filename) around the entire header.""" self.protectFeature = protectFeature """True if #ifndef..#endif protection should be generated around a feature interface in the header file.""" self.protectProto = protectProto """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.""" self.protectProtoStr = protectProtoStr """#ifdef/#ifndef symbol to use around prototype declarations, if protectProto is set""" self.apicall = apicall """string to use for the function declaration prefix, such as APICALL on Windows.""" self.apientry = apientry """string to use for the calling convention macro, in typedefs, such as APIENTRY.""" self.apientryp = apientryp """string to use for the calling convention macro in function pointer typedefs, such as APIENTRYP.""" self.indentFuncProto = indentFuncProto """True if prototype declarations should put each parameter on a separate line""" self.indentFuncPointer = indentFuncPointer """True if typedefed function pointers should put each parameter on a separate line""" self.alignFuncParam = alignFuncParam """if nonzero and parameters are being put on a separate line, align parameter names at the specified column""" self.genEnumBeginEndRange = genEnumBeginEndRange """True if BEGIN_RANGE / END_RANGE macros should be generated for enumerated types""" self.genAliasMacro = genAliasMacro """True if the OpenXR alias macro should be generated for aliased types (unclear what other circumstances this is useful)""" self.aliasMacro = aliasMacro """alias macro to inject when genAliasMacro is True""" self.misracstyle = misracstyle """generate MISRA C-friendly headers""" self.misracppstyle = misracppstyle """generate MISRA C++-friendly headers""" self.codeGenerator = True """True if this generator makes compilable code""" class COutputGenerator(OutputGenerator): """Generates C-language API interfaces.""" # This is an ordered list of sections in the header file. TYPE_SECTIONS = ['include', 'define', 'basetype', 'handle', 'enum', 'group', 'bitmask', 'funcpointer', 'struct'] ALL_SECTIONS = TYPE_SECTIONS + ['commandPointer', 'command'] def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # Internal state - accumulators for different inner block text self.sections = {section: [] for section in self.ALL_SECTIONS} self.feature_not_empty = False self.may_alias = None def beginFile(self, genOpts): OutputGenerator.beginFile(self, genOpts) # C-specific # # Multiple inclusion protection & C++ wrappers. if genOpts.protectFile and self.genOpts.filename: headerSym = re.sub(r'\.h', '_h_', os.path.basename(self.genOpts.filename)).upper() write('#ifndef', headerSym, file=self.outFile) write('#define', headerSym, '1', file=self.outFile) self.newline() # User-supplied prefix text, if any (list of strings) if genOpts.prefixText: for s in genOpts.prefixText: write(s, file=self.outFile) # C++ extern wrapper - after prefix lines so they can add includes. self.newline() write('#ifdef __cplusplus', file=self.outFile) write('extern "C" {', file=self.outFile) write('#endif', file=self.outFile) self.newline() def endFile(self): # C-specific # Finish C++ wrapper and multiple inclusion protection self.newline() write('#ifdef __cplusplus', file=self.outFile) write('}', file=self.outFile) write('#endif', file=self.outFile) if self.genOpts.protectFile and self.genOpts.filename: self.newline() write('#endif', file=self.outFile) # Finish processing in superclass OutputGenerator.endFile(self) def beginFeature(self, interface, emit): # Start processing in superclass OutputGenerator.beginFeature(self, interface, emit) # C-specific # Accumulate includes, defines, types, enums, function pointer typedefs, # end function prototypes separately for this feature. They're only # printed in endFeature(). self.sections = {section: [] for section in self.ALL_SECTIONS} self.feature_not_empty = False def endFeature(self): "Actually write the interface to the output file." # C-specific if self.emit: if self.feature_not_empty: if self.genOpts.conventions.writeFeature(self.featureExtraProtect, self.genOpts.filename): self.newline() if self.genOpts.protectFeature: write('#ifndef', self.featureName, file=self.outFile) # If type declarations are needed by other features based on # this one, it may be necessary to suppress the ExtraProtect, # or move it below the 'for section...' loop. if self.featureExtraProtect is not None: write('#ifdef', self.featureExtraProtect, file=self.outFile) self.newline() write('#define', self.featureName, '1', file=self.outFile) for section in self.TYPE_SECTIONS: contents = self.sections[section] if contents: write('\n'.join(contents), file=self.outFile) if self.genOpts.genFuncPointers and self.sections['commandPointer']: write('\n'.join(self.sections['commandPointer']), file=self.outFile) self.newline() if self.sections['command']: if self.genOpts.protectProto: write(self.genOpts.protectProto, self.genOpts.protectProtoStr, file=self.outFile) write('\n'.join(self.sections['command']), end='', file=self.outFile) if self.genOpts.protectProto: write('#endif', file=self.outFile) else: self.newline() if self.featureExtraProtect is not None: write('#endif /*', self.featureExtraProtect, '*/', file=self.outFile) if self.genOpts.protectFeature: write('#endif /*', self.featureName, '*/', file=self.outFile) # Finish processing in superclass OutputGenerator.endFeature(self) def appendSection(self, section, text): "Append a definition to the specified section" # self.sections[section].append('SECTION: ' + section + '\n') self.sections[section].append(text) self.feature_not_empty = True def genType(self, typeinfo, name, alias): "Generate type." OutputGenerator.genType(self, typeinfo, name, alias) typeElem = typeinfo.elem # Vulkan: # Determine the category of the type, and the type section to add # its definition to. # 'funcpointer' is added to the 'struct' section as a workaround for # internal issue #877, since structures and function pointer types # can have cross-dependencies. category = typeElem.get('category') if category == 'funcpointer': section = 'struct' else: section = category if category in ('struct', 'union'): # If the type is a struct type, generate it using the # special-purpose generator. self.genStruct(typeinfo, name, alias) else: # OpenXR: this section was not under 'else:' previously, just fell through if alias: # If the type is an alias, just emit a typedef declaration body = 'typedef ' + alias + ' ' + name + ';\n' else: # Replace tags with an APIENTRY-style string # (from self.genOpts). Copy other text through unchanged. # If the resulting text is an empty string, don't emit it. body = noneStr(typeElem.text) for elem in typeElem: if elem.tag == 'apientry': body += self.genOpts.apientry + noneStr(elem.tail) else: body += noneStr(elem.text) + noneStr(elem.tail) if body: # Add extra newline after multi-line entries. if '\n' in body[0:-1]: body += '\n' self.appendSection(section, body) def genProtectString(self, protect_str): """Generate protection string. Protection strings are the strings defining the OS/Platform/Graphics requirements for a given OpenXR command. When generating the language header files, we need to make sure the items specific to a graphics API or OS platform are properly wrapped in #ifs.""" protect_if_str = '' protect_end_str = '' if not protect_str: return (protect_if_str, protect_end_str) if ',' in protect_str: protect_list = protect_str.split(",") protect_defs = ('defined(%s)' % d for d in protect_list) protect_def_str = ' && '.join(protect_defs) protect_if_str = '#if %s\n' % protect_def_str protect_end_str = '#endif // %s\n' % protect_def_str else: protect_if_str = '#ifdef %s\n' % protect_str protect_end_str = '#endif // %s\n' % protect_str return (protect_if_str, protect_end_str) def typeMayAlias(self, typeName): if not self.may_alias: # First time we've asked if a type may alias. # So, let's populate the set of all names of types that may. # Everyone with an explicit mayalias="true" self.may_alias = set(typeName for typeName, data in self.registry.typedict.items() if data.elem.get('mayalias') == 'true') # Every type mentioned in some other type's parentstruct attribute. parent_structs = (otherType.elem.get('parentstruct') for otherType in self.registry.typedict.values()) self.may_alias.update(set(x for x in parent_structs if x is not None)) return typeName in self.may_alias def genStruct(self, typeinfo, typeName, alias): """Generate struct (e.g. C "struct" type). This is a special case of the tag where the contents are interpreted as a set of tags instead of freeform C C type declarations. The tags are just like tags - they are a declaration of a struct or union member. Only simple member declarations are supported (no nested structs etc.) If alias is not None, then this struct aliases another; just generate a typedef of that alias.""" OutputGenerator.genStruct(self, typeinfo, typeName, alias) typeElem = typeinfo.elem if alias: body = 'typedef ' + alias + ' ' + typeName + ';\n' else: body = '' (protect_begin, protect_end) = self.genProtectString(typeElem.get('protect')) if protect_begin: body += protect_begin body += 'typedef ' + typeElem.get('category') # This is an OpenXR-specific alternative where aliasing refers # to an inheritance hierarchy of types rather than C-level type # aliases. if self.genOpts.genAliasMacro and self.typeMayAlias(typeName): body += ' ' + self.genOpts.aliasMacro body += ' ' + typeName + ' {\n' targetLen = self.getMaxCParamTypeLength(typeinfo) for member in typeElem.findall('.//member'): body += self.makeCParamDecl(member, targetLen + 4) body += ';\n' body += '} ' + typeName + ';\n' if protect_end: body += protect_end self.appendSection('struct', body) def genGroup(self, groupinfo, groupName, alias=None): """Generate groups (e.g. C "enum" type). These are concatenated together with other types. If alias is not None, it is the name of another group type which aliases this type; just generate that alias.""" OutputGenerator.genGroup(self, groupinfo, groupName, alias) groupElem = groupinfo.elem # After either enumerated type or alias paths, add the declaration # to the appropriate section for the group being defined. if groupElem.get('type') == 'bitmask': section = 'bitmask' else: section = 'group' if alias: # If the group name is aliased, just emit a typedef declaration # for the alias. body = 'typedef ' + alias + ' ' + groupName + ';\n' self.appendSection(section, body) else: (section, body) = self.buildEnumCDecl(self.genOpts.genEnumBeginEndRange, groupinfo, groupName) self.appendSection(section, "\n" + body) def genEnum(self, enuminfo, name, alias): """Generate enumerants. tags may specify their values in several ways, but are usually just integers.""" OutputGenerator.genEnum(self, enuminfo, name, alias) (_, strVal) = self.enumToValue(enuminfo.elem, False) if self.misracppstyle() and enuminfo.elem.get('type') and not alias: # Generate e.g.: static constexpr uint32_t x = ~static_cast(1U); # This appeases MISRA "underlying type" rules. typeStr = enuminfo.elem.get('type'); invert = '~' in strVal number = strVal.strip("()~UL") if typeStr != "float": number += 'U' strVal = "~" if invert else "" strVal += "static_cast<" + typeStr + ">(" + number + ")" body = 'static constexpr ' + typeStr.ljust(9) + name.ljust(33) + ' {' + strVal + '};' self.appendSection('enum', body) else: body = '#define ' + name.ljust(33) + ' ' + strVal self.appendSection('enum', body) def genCmd(self, cmdinfo, name, alias): "Command generation" OutputGenerator.genCmd(self, cmdinfo, name, alias) # if alias: # prefix = '// ' + name + ' is an alias of command ' + alias + '\n' # else: # prefix = '' prefix = '' decls = self.makeCDecls(cmdinfo.elem) self.appendSection('command', prefix + decls[0] + '\n') if self.genOpts.genFuncPointers: self.appendSection('commandPointer', decls[1]) def misracstyle(self): return self.genOpts.misracstyle; def misracppstyle(self): return self.genOpts.misracppstyle;