Mark Lobodzinski | f96a932 | 2017-02-13 10:33:08 -0700 | [diff] [blame] | 1 | #!/usr/bin/python3 -i |
| 2 | # |
| 3 | # Copyright (c) 2013-2017 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,re,sys |
| 18 | from generator import * |
| 19 | |
| 20 | # CGeneratorOptions - subclass of GeneratorOptions. |
| 21 | # |
| 22 | # Adds options used by COutputGenerator objects during C language header |
| 23 | # generation. |
| 24 | # |
| 25 | # Additional members |
| 26 | # prefixText - list of strings to prefix generated header with |
| 27 | # (usually a copyright statement + calling convention macros). |
| 28 | # protectFile - True if multiple inclusion protection should be |
| 29 | # generated (based on the filename) around the entire header. |
| 30 | # protectFeature - True if #ifndef..#endif protection should be |
| 31 | # generated around a feature interface in the header file. |
| 32 | # genFuncPointers - True if function pointer typedefs should be |
| 33 | # generated |
| 34 | # protectProto - If conditional protection should be generated |
| 35 | # around prototype declarations, set to either '#ifdef' |
| 36 | # to require opt-in (#ifdef protectProtoStr) or '#ifndef' |
| 37 | # to require opt-out (#ifndef protectProtoStr). Otherwise |
| 38 | # set to None. |
| 39 | # protectProtoStr - #ifdef/#ifndef symbol to use around prototype |
| 40 | # declarations, if protectProto is set |
| 41 | # apicall - string to use for the function declaration prefix, |
| 42 | # such as APICALL on Windows. |
| 43 | # apientry - string to use for the calling convention macro, |
| 44 | # in typedefs, such as APIENTRY. |
| 45 | # apientryp - string to use for the calling convention macro |
| 46 | # in function pointer typedefs, such as APIENTRYP. |
| 47 | # indentFuncProto - True if prototype declarations should put each |
| 48 | # parameter on a separate line |
| 49 | # indentFuncPointer - True if typedefed function pointers should put each |
| 50 | # parameter on a separate line |
| 51 | # alignFuncParam - if nonzero and parameters are being put on a |
| 52 | # separate line, align parameter names at the specified column |
| 53 | class CGeneratorOptions(GeneratorOptions): |
| 54 | """Represents options during C interface generation for headers""" |
| 55 | def __init__(self, |
| 56 | filename = None, |
| 57 | directory = '.', |
| 58 | apiname = None, |
| 59 | profile = None, |
| 60 | versions = '.*', |
| 61 | emitversions = '.*', |
| 62 | defaultExtensions = None, |
| 63 | addExtensions = None, |
| 64 | removeExtensions = None, |
| 65 | sortProcedure = regSortFeatures, |
| 66 | prefixText = "", |
| 67 | genFuncPointers = True, |
| 68 | protectFile = True, |
| 69 | protectFeature = True, |
| 70 | protectProto = None, |
| 71 | protectProtoStr = None, |
| 72 | apicall = '', |
| 73 | apientry = '', |
| 74 | apientryp = '', |
| 75 | indentFuncProto = True, |
| 76 | indentFuncPointer = False, |
| 77 | alignFuncParam = 0): |
| 78 | GeneratorOptions.__init__(self, filename, directory, apiname, profile, |
| 79 | versions, emitversions, defaultExtensions, |
| 80 | addExtensions, removeExtensions, sortProcedure) |
| 81 | self.prefixText = prefixText |
| 82 | self.genFuncPointers = genFuncPointers |
| 83 | self.protectFile = protectFile |
| 84 | self.protectFeature = protectFeature |
| 85 | self.protectProto = protectProto |
| 86 | self.protectProtoStr = protectProtoStr |
| 87 | self.apicall = apicall |
| 88 | self.apientry = apientry |
| 89 | self.apientryp = apientryp |
| 90 | self.indentFuncProto = indentFuncProto |
| 91 | self.indentFuncPointer = indentFuncPointer |
| 92 | self.alignFuncParam = alignFuncParam |
| 93 | |
| 94 | # COutputGenerator - subclass of OutputGenerator. |
| 95 | # Generates C-language API interfaces. |
| 96 | # |
| 97 | # ---- methods ---- |
| 98 | # COutputGenerator(errFile, warnFile, diagFile) - args as for |
| 99 | # OutputGenerator. Defines additional internal state. |
| 100 | # ---- methods overriding base class ---- |
| 101 | # beginFile(genOpts) |
| 102 | # endFile() |
| 103 | # beginFeature(interface, emit) |
| 104 | # endFeature() |
| 105 | # genType(typeinfo,name) |
| 106 | # genStruct(typeinfo,name) |
| 107 | # genGroup(groupinfo,name) |
| 108 | # genEnum(enuminfo, name) |
| 109 | # genCmd(cmdinfo) |
| 110 | class COutputGenerator(OutputGenerator): |
| 111 | """Generate specified API interfaces in a specific style, such as a C header""" |
| 112 | # This is an ordered list of sections in the header file. |
| 113 | TYPE_SECTIONS = ['include', 'define', 'basetype', 'handle', 'enum', |
| 114 | 'group', 'bitmask', 'funcpointer', 'struct'] |
| 115 | ALL_SECTIONS = TYPE_SECTIONS + ['commandPointer', 'command'] |
| 116 | def __init__(self, |
| 117 | errFile = sys.stderr, |
| 118 | warnFile = sys.stderr, |
| 119 | diagFile = sys.stdout): |
| 120 | OutputGenerator.__init__(self, errFile, warnFile, diagFile) |
| 121 | # Internal state - accumulators for different inner block text |
| 122 | self.sections = dict([(section, []) for section in self.ALL_SECTIONS]) |
| 123 | # |
| 124 | def beginFile(self, genOpts): |
| 125 | OutputGenerator.beginFile(self, genOpts) |
| 126 | # C-specific |
| 127 | # |
| 128 | # Multiple inclusion protection & C++ wrappers. |
| 129 | if (genOpts.protectFile and self.genOpts.filename): |
| 130 | headerSym = re.sub('\.h', '_h_', |
| 131 | os.path.basename(self.genOpts.filename)).upper() |
| 132 | write('#ifndef', headerSym, file=self.outFile) |
| 133 | write('#define', headerSym, '1', file=self.outFile) |
| 134 | self.newline() |
| 135 | write('#ifdef __cplusplus', file=self.outFile) |
| 136 | write('extern "C" {', file=self.outFile) |
| 137 | write('#endif', file=self.outFile) |
| 138 | self.newline() |
| 139 | # |
| 140 | # User-supplied prefix text, if any (list of strings) |
| 141 | if (genOpts.prefixText): |
| 142 | for s in genOpts.prefixText: |
| 143 | write(s, file=self.outFile) |
| 144 | # |
| 145 | # Some boilerplate describing what was generated - this |
| 146 | # will probably be removed later since the extensions |
| 147 | # pattern may be very long. |
| 148 | # write('/* Generated C header for:', file=self.outFile) |
| 149 | # write(' * API:', genOpts.apiname, file=self.outFile) |
| 150 | # if (genOpts.profile): |
| 151 | # write(' * Profile:', genOpts.profile, file=self.outFile) |
| 152 | # write(' * Versions considered:', genOpts.versions, file=self.outFile) |
| 153 | # write(' * Versions emitted:', genOpts.emitversions, file=self.outFile) |
| 154 | # write(' * Default extensions included:', genOpts.defaultExtensions, file=self.outFile) |
| 155 | # write(' * Additional extensions included:', genOpts.addExtensions, file=self.outFile) |
| 156 | # write(' * Extensions removed:', genOpts.removeExtensions, file=self.outFile) |
| 157 | # write(' */', file=self.outFile) |
| 158 | def endFile(self): |
| 159 | # C-specific |
| 160 | # Finish C++ wrapper and multiple inclusion protection |
| 161 | self.newline() |
| 162 | write('#ifdef __cplusplus', file=self.outFile) |
| 163 | write('}', file=self.outFile) |
| 164 | write('#endif', file=self.outFile) |
| 165 | if (self.genOpts.protectFile and self.genOpts.filename): |
| 166 | self.newline() |
| 167 | write('#endif', file=self.outFile) |
| 168 | # Finish processing in superclass |
| 169 | OutputGenerator.endFile(self) |
| 170 | def beginFeature(self, interface, emit): |
| 171 | # Start processing in superclass |
| 172 | OutputGenerator.beginFeature(self, interface, emit) |
| 173 | # C-specific |
| 174 | # Accumulate includes, defines, types, enums, function pointer typedefs, |
| 175 | # end function prototypes separately for this feature. They're only |
| 176 | # printed in endFeature(). |
| 177 | self.sections = dict([(section, []) for section in self.ALL_SECTIONS]) |
| 178 | def endFeature(self): |
| 179 | # C-specific |
| 180 | # Actually write the interface to the output file. |
| 181 | if (self.emit): |
| 182 | self.newline() |
| 183 | if (self.genOpts.protectFeature): |
| 184 | write('#ifndef', self.featureName, file=self.outFile) |
| 185 | # If type declarations are needed by other features based on |
| 186 | # this one, it may be necessary to suppress the ExtraProtect, |
| 187 | # or move it below the 'for section...' loop. |
| 188 | if (self.featureExtraProtect != None): |
| 189 | write('#ifdef', self.featureExtraProtect, file=self.outFile) |
| 190 | write('#define', self.featureName, '1', file=self.outFile) |
| 191 | for section in self.TYPE_SECTIONS: |
| 192 | contents = self.sections[section] |
| 193 | if contents: |
| 194 | write('\n'.join(contents), file=self.outFile) |
| 195 | self.newline() |
| 196 | if (self.genOpts.genFuncPointers and self.sections['commandPointer']): |
| 197 | write('\n'.join(self.sections['commandPointer']), file=self.outFile) |
| 198 | self.newline() |
| 199 | if (self.sections['command']): |
| 200 | if (self.genOpts.protectProto): |
| 201 | write(self.genOpts.protectProto, |
| 202 | self.genOpts.protectProtoStr, file=self.outFile) |
| 203 | write('\n'.join(self.sections['command']), end='', file=self.outFile) |
| 204 | if (self.genOpts.protectProto): |
| 205 | write('#endif', file=self.outFile) |
| 206 | else: |
| 207 | self.newline() |
| 208 | if (self.featureExtraProtect != None): |
| 209 | write('#endif /*', self.featureExtraProtect, '*/', file=self.outFile) |
| 210 | if (self.genOpts.protectFeature): |
| 211 | write('#endif /*', self.featureName, '*/', file=self.outFile) |
| 212 | # Finish processing in superclass |
| 213 | OutputGenerator.endFeature(self) |
| 214 | # |
| 215 | # Append a definition to the specified section |
| 216 | def appendSection(self, section, text): |
| 217 | # self.sections[section].append('SECTION: ' + section + '\n') |
| 218 | self.sections[section].append(text) |
| 219 | # |
| 220 | # Type generation |
| 221 | def genType(self, typeinfo, name): |
| 222 | OutputGenerator.genType(self, typeinfo, name) |
| 223 | typeElem = typeinfo.elem |
| 224 | # If the type is a struct type, traverse the imbedded <member> tags |
| 225 | # generating a structure. Otherwise, emit the tag text. |
| 226 | category = typeElem.get('category') |
| 227 | if (category == 'struct' or category == 'union'): |
| 228 | self.genStruct(typeinfo, name) |
| 229 | else: |
| 230 | # Replace <apientry /> tags with an APIENTRY-style string |
| 231 | # (from self.genOpts). Copy other text through unchanged. |
| 232 | # If the resulting text is an empty string, don't emit it. |
| 233 | s = noneStr(typeElem.text) |
| 234 | for elem in typeElem: |
| 235 | if (elem.tag == 'apientry'): |
| 236 | s += self.genOpts.apientry + noneStr(elem.tail) |
| 237 | else: |
| 238 | s += noneStr(elem.text) + noneStr(elem.tail) |
| 239 | if s: |
| 240 | # Add extra newline after multi-line entries. |
| 241 | if '\n' in s: |
| 242 | s += '\n' |
Mark Lobodzinski | e273b94 | 2017-08-01 09:15:06 -0600 | [diff] [blame] | 243 | # This is a temporary workaround for internal issue #877, |
| 244 | # while we consider other approaches. The problem is that |
| 245 | # function pointer types can have dependencies on structures |
| 246 | # and vice-versa, so they can't be strictly separated into |
| 247 | # sections. The workaround is to define those types in the |
| 248 | # same section, in dependency order. |
| 249 | if (category == 'funcpointer'): |
| 250 | self.appendSection('struct', s) |
| 251 | else: |
| 252 | self.appendSection(category, s) |
Mark Lobodzinski | f96a932 | 2017-02-13 10:33:08 -0700 | [diff] [blame] | 253 | # |
| 254 | # Struct (e.g. C "struct" type) generation. |
| 255 | # This is a special case of the <type> tag where the contents are |
| 256 | # interpreted as a set of <member> tags instead of freeform C |
| 257 | # C type declarations. The <member> tags are just like <param> |
| 258 | # tags - they are a declaration of a struct or union member. |
| 259 | # Only simple member declarations are supported (no nested |
| 260 | # structs etc.) |
| 261 | def genStruct(self, typeinfo, typeName): |
| 262 | OutputGenerator.genStruct(self, typeinfo, typeName) |
| 263 | body = 'typedef ' + typeinfo.elem.get('category') + ' ' + typeName + ' {\n' |
| 264 | # paramdecl = self.makeCParamDecl(typeinfo.elem, self.genOpts.alignFuncParam) |
| 265 | targetLen = 0; |
| 266 | for member in typeinfo.elem.findall('.//member'): |
| 267 | targetLen = max(targetLen, self.getCParamTypeLength(member)) |
| 268 | for member in typeinfo.elem.findall('.//member'): |
| 269 | body += self.makeCParamDecl(member, targetLen + 4) |
| 270 | body += ';\n' |
| 271 | body += '} ' + typeName + ';\n' |
| 272 | self.appendSection('struct', body) |
| 273 | # |
| 274 | # Group (e.g. C "enum" type) generation. |
| 275 | # These are concatenated together with other types. |
| 276 | def genGroup(self, groupinfo, groupName): |
| 277 | OutputGenerator.genGroup(self, groupinfo, groupName) |
| 278 | groupElem = groupinfo.elem |
| 279 | |
Mark Lobodzinski | 1d218cc | 2017-03-14 10:12:43 -0600 | [diff] [blame] | 280 | expandName = re.sub(r'([0-9a-z_])([A-Z0-9])',r'\1_\2',groupName).upper() |
Mark Lobodzinski | f96a932 | 2017-02-13 10:33:08 -0700 | [diff] [blame] | 281 | |
| 282 | expandPrefix = expandName |
| 283 | expandSuffix = '' |
| 284 | expandSuffixMatch = re.search(r'[A-Z][A-Z]+$',groupName) |
| 285 | if expandSuffixMatch: |
| 286 | expandSuffix = '_' + expandSuffixMatch.group() |
| 287 | # Strip off the suffix from the prefix |
| 288 | expandPrefix = expandName.rsplit(expandSuffix, 1)[0] |
| 289 | |
| 290 | # Prefix |
| 291 | body = "\ntypedef enum " + groupName + " {\n" |
| 292 | |
| 293 | # @@ Should use the type="bitmask" attribute instead |
| 294 | isEnum = ('FLAG_BITS' not in expandPrefix) |
| 295 | |
| 296 | # Loop over the nested 'enum' tags. Keep track of the minimum and |
| 297 | # maximum numeric values, if they can be determined; but only for |
| 298 | # core API enumerants, not extension enumerants. This is inferred |
| 299 | # by looking for 'extends' attributes. |
| 300 | minName = None |
| 301 | for elem in groupElem.findall('enum'): |
| 302 | # Convert the value to an integer and use that to track min/max. |
| 303 | # Values of form -(number) are accepted but nothing more complex. |
| 304 | # Should catch exceptions here for more complex constructs. Not yet. |
| 305 | (numVal,strVal) = self.enumToValue(elem, True) |
| 306 | name = elem.get('name') |
| 307 | |
Mark Lobodzinski | 541e683 | 2017-08-14 15:26:30 -0600 | [diff] [blame^] | 308 | # Check for duplicate enum values and raise an error if found. |
| 309 | for elem2 in groupElem.findall('enum'): |
| 310 | if (elem != elem2): |
| 311 | (numVal2,strVal2) = self.enumToValue(elem2, True) |
| 312 | if (numVal2 == numVal): |
| 313 | raise UserWarning('Duplicate enum ' + name + ' = ' + elem2.get('name') + ' = ' + strVal) |
| 314 | |
Mark Lobodzinski | f96a932 | 2017-02-13 10:33:08 -0700 | [diff] [blame] | 315 | # Extension enumerants are only included if they are required |
| 316 | if (self.isEnumRequired(elem)): |
| 317 | body += " " + name + " = " + strVal + ",\n" |
| 318 | |
| 319 | if (isEnum and elem.get('extends') is None): |
| 320 | if (minName == None): |
| 321 | minName = maxName = name |
| 322 | minValue = maxValue = numVal |
| 323 | elif (numVal < minValue): |
| 324 | minName = name |
| 325 | minValue = numVal |
| 326 | elif (numVal > maxValue): |
| 327 | maxName = name |
| 328 | maxValue = numVal |
| 329 | # Generate min/max value tokens and a range-padding enum. Need some |
| 330 | # additional padding to generate correct names... |
| 331 | if isEnum: |
| 332 | body += " " + expandPrefix + "_BEGIN_RANGE" + expandSuffix + " = " + minName + ",\n" |
| 333 | body += " " + expandPrefix + "_END_RANGE" + expandSuffix + " = " + maxName + ",\n" |
| 334 | body += " " + expandPrefix + "_RANGE_SIZE" + expandSuffix + " = (" + maxName + " - " + minName + " + 1),\n" |
| 335 | |
| 336 | body += " " + expandPrefix + "_MAX_ENUM" + expandSuffix + " = 0x7FFFFFFF\n" |
| 337 | |
| 338 | # Postfix |
| 339 | body += "} " + groupName + ";" |
| 340 | if groupElem.get('type') == 'bitmask': |
| 341 | section = 'bitmask' |
| 342 | else: |
| 343 | section = 'group' |
| 344 | self.appendSection(section, body) |
| 345 | # Enumerant generation |
| 346 | # <enum> tags may specify their values in several ways, but are usually |
| 347 | # just integers. |
| 348 | def genEnum(self, enuminfo, name): |
| 349 | OutputGenerator.genEnum(self, enuminfo, name) |
| 350 | (numVal,strVal) = self.enumToValue(enuminfo.elem, False) |
| 351 | body = '#define ' + name.ljust(33) + ' ' + strVal |
| 352 | self.appendSection('enum', body) |
| 353 | # |
| 354 | # Command generation |
| 355 | def genCmd(self, cmdinfo, name): |
| 356 | OutputGenerator.genCmd(self, cmdinfo, name) |
| 357 | # |
| 358 | decls = self.makeCDecls(cmdinfo.elem) |
| 359 | self.appendSection('command', decls[0] + '\n') |
| 360 | if (self.genOpts.genFuncPointers): |
| 361 | self.appendSection('commandPointer', decls[1]) |