Home | History | Annotate | Download | only in registry
      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,re,sys,pdb
     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 #   directory - directory into which to generate include files
     48 #   indentFuncProto - True if prototype declarations should put each
     49 #     parameter on a separate line
     50 #   indentFuncPointer - True if typedefed function pointers should put each
     51 #     parameter on a separate line
     52 #   alignFuncParam - if nonzero and parameters are being put on a
     53 #     separate line, align parameter names at the specified column
     54 class CGeneratorOptions(GeneratorOptions):
     55     """Represents options during C interface generation for headers"""
     56     def __init__(self,
     57                  filename = None,
     58                  directory = '.',
     59                  apiname = None,
     60                  profile = None,
     61                  versions = '.*',
     62                  emitversions = '.*',
     63                  defaultExtensions = None,
     64                  addExtensions = None,
     65                  removeExtensions = None,
     66                  emitExtensions = None,
     67                  sortProcedure = regSortFeatures,
     68                  prefixText = "",
     69                  genFuncPointers = True,
     70                  protectFile = True,
     71                  protectFeature = True,
     72                  protectProto = None,
     73                  protectProtoStr = None,
     74                  apicall = '',
     75                  apientry = '',
     76                  apientryp = '',
     77                  indentFuncProto = True,
     78                  indentFuncPointer = False,
     79                  alignFuncParam = 0):
     80         GeneratorOptions.__init__(self, filename, directory, apiname, profile,
     81                                   versions, emitversions, defaultExtensions,
     82                                   addExtensions, removeExtensions,
     83                                   emitExtensions, sortProcedure)
     84         self.prefixText      = prefixText
     85         self.genFuncPointers = genFuncPointers
     86         self.protectFile     = protectFile
     87         self.protectFeature  = protectFeature
     88         self.protectProto    = protectProto
     89         self.protectProtoStr = protectProtoStr
     90         self.apicall         = apicall
     91         self.apientry        = apientry
     92         self.apientryp       = apientryp
     93         self.indentFuncProto = indentFuncProto
     94         self.indentFuncPointer = indentFuncPointer
     95         self.alignFuncParam  = alignFuncParam
     96 
     97 # COutputGenerator - subclass of OutputGenerator.
     98 # Generates C-language API interfaces.
     99 #
    100 # ---- methods ----
    101 # COutputGenerator(errFile, warnFile, diagFile) - args as for
    102 #   OutputGenerator. Defines additional internal state.
    103 # ---- methods overriding base class ----
    104 # beginFile(genOpts)
    105 # endFile()
    106 # beginFeature(interface, emit)
    107 # endFeature()
    108 # genType(typeinfo,name)
    109 # genStruct(typeinfo,name)
    110 # genGroup(groupinfo,name)
    111 # genEnum(enuminfo, name)
    112 # genCmd(cmdinfo)
    113 class COutputGenerator(OutputGenerator):
    114     """Generate specified API interfaces in a specific style, such as a C header"""
    115     # This is an ordered list of sections in the header file.
    116     TYPE_SECTIONS = ['include', 'define', 'basetype', 'handle', 'enum',
    117                      'group', 'bitmask', 'funcpointer', 'struct']
    118     ALL_SECTIONS = TYPE_SECTIONS + ['commandPointer', 'command']
    119     def __init__(self,
    120                  errFile = sys.stderr,
    121                  warnFile = sys.stderr,
    122                  diagFile = sys.stdout):
    123         OutputGenerator.__init__(self, errFile, warnFile, diagFile)
    124         # Internal state - accumulators for different inner block text
    125         self.sections = dict([(section, []) for section in self.ALL_SECTIONS])
    126     #
    127     def beginFile(self, genOpts):
    128         OutputGenerator.beginFile(self, genOpts)
    129         # C-specific
    130         #
    131         # Multiple inclusion protection & C++ wrappers.
    132         if (genOpts.protectFile and self.genOpts.filename):
    133             headerSym = re.sub('\.h', '_h_',
    134                                os.path.basename(self.genOpts.filename)).upper()
    135             write('#ifndef', headerSym, file=self.outFile)
    136             write('#define', headerSym, '1', file=self.outFile)
    137             self.newline()
    138         write('#ifdef __cplusplus', file=self.outFile)
    139         write('extern "C" {', file=self.outFile)
    140         write('#endif', file=self.outFile)
    141         self.newline()
    142         #
    143         # User-supplied prefix text, if any (list of strings)
    144         if (genOpts.prefixText):
    145             for s in genOpts.prefixText:
    146                 write(s, file=self.outFile)
    147         #
    148         # Some boilerplate describing what was generated - this
    149         # will probably be removed later since the extensions
    150         # pattern may be very long.
    151         # write('/* Generated C header for:', file=self.outFile)
    152         # write(' * API:', genOpts.apiname, file=self.outFile)
    153         # if (genOpts.profile):
    154         #     write(' * Profile:', genOpts.profile, file=self.outFile)
    155         # write(' * Versions considered:', genOpts.versions, file=self.outFile)
    156         # write(' * Versions emitted:', genOpts.emitversions, file=self.outFile)
    157         # write(' * Default extensions included:', genOpts.defaultExtensions, file=self.outFile)
    158         # write(' * Additional extensions included:', genOpts.addExtensions, file=self.outFile)
    159         # write(' * Extensions removed:', genOpts.removeExtensions, file=self.outFile)
    160         # write(' * Extensions emitted:', genOpts.emitExtensions, file=self.outFile)
    161         # write(' */', file=self.outFile)
    162     def endFile(self):
    163         # C-specific
    164         # Finish C++ wrapper and multiple inclusion protection
    165         self.newline()
    166         write('#ifdef __cplusplus', file=self.outFile)
    167         write('}', file=self.outFile)
    168         write('#endif', file=self.outFile)
    169         if (self.genOpts.protectFile and self.genOpts.filename):
    170             self.newline()
    171             write('#endif', file=self.outFile)
    172         # Finish processing in superclass
    173         OutputGenerator.endFile(self)
    174     def beginFeature(self, interface, emit):
    175         # Start processing in superclass
    176         OutputGenerator.beginFeature(self, interface, emit)
    177         # C-specific
    178         # Accumulate includes, defines, types, enums, function pointer typedefs,
    179         # end function prototypes separately for this feature. They're only
    180         # printed in endFeature().
    181         self.sections = dict([(section, []) for section in self.ALL_SECTIONS])
    182     def endFeature(self):
    183         # C-specific
    184         # Actually write the interface to the output file.
    185         if (self.emit):
    186             self.newline()
    187             if (self.genOpts.protectFeature):
    188                 write('#ifndef', self.featureName, file=self.outFile)
    189             # If type declarations are needed by other features based on
    190             # this one, it may be necessary to suppress the ExtraProtect,
    191             # or move it below the 'for section...' loop.
    192             if (self.featureExtraProtect != None):
    193                 write('#ifdef', self.featureExtraProtect, file=self.outFile)
    194             write('#define', self.featureName, '1', file=self.outFile)
    195             for section in self.TYPE_SECTIONS:
    196                 contents = self.sections[section]
    197                 if contents:
    198                     write('\n'.join(contents), file=self.outFile)
    199                     self.newline()
    200             if (self.genOpts.genFuncPointers and self.sections['commandPointer']):
    201                 write('\n'.join(self.sections['commandPointer']), file=self.outFile)
    202                 self.newline()
    203             if (self.sections['command']):
    204                 if (self.genOpts.protectProto):
    205                     write(self.genOpts.protectProto,
    206                           self.genOpts.protectProtoStr, file=self.outFile)
    207                 write('\n'.join(self.sections['command']), end='', file=self.outFile)
    208                 if (self.genOpts.protectProto):
    209                     write('#endif', file=self.outFile)
    210                 else:
    211                     self.newline()
    212             if (self.featureExtraProtect != None):
    213                 write('#endif /*', self.featureExtraProtect, '*/', file=self.outFile)
    214             if (self.genOpts.protectFeature):
    215                 write('#endif /*', self.featureName, '*/', file=self.outFile)
    216         # Finish processing in superclass
    217         OutputGenerator.endFeature(self)
    218     #
    219     # Append a definition to the specified section
    220     def appendSection(self, section, text):
    221         # self.sections[section].append('SECTION: ' + section + '\n')
    222         self.sections[section].append(text)
    223         # self.logMsg('diag', 'appendSection(section =', section, 'text =', text)
    224     #
    225     # Type generation
    226     def genType(self, typeinfo, name, alias):
    227         OutputGenerator.genType(self, typeinfo, name, alias)
    228         typeElem = typeinfo.elem
    229 
    230         # Determine the category of the type, and the type section to add
    231         # its definition to.
    232         # 'funcpointer' is added to the 'struct' section as a workaround for
    233         # internal issue #877, since structures and function pointer types
    234         # can have cross-dependencies.
    235         category = typeElem.get('category')
    236         if category == 'funcpointer':
    237             section = 'struct'
    238         else:
    239             section = category
    240 
    241         if category == 'struct' or category == 'union':
    242             # If the type is a struct type, generate it using the
    243             # special-purpose generator.
    244             self.genStruct(typeinfo, name, alias)
    245         else:
    246             if alias:
    247                 # If the type is an alias, just emit a typedef declaration
    248                 body = 'typedef ' + alias + ' ' + name + ';\n'
    249             else:
    250                 # Replace <apientry /> tags with an APIENTRY-style string
    251                 # (from self.genOpts). Copy other text through unchanged.
    252                 # If the resulting text is an empty string, don't emit it.
    253                 body = noneStr(typeElem.text)
    254                 for elem in typeElem:
    255                     if (elem.tag == 'apientry'):
    256                         body += self.genOpts.apientry + noneStr(elem.tail)
    257                     else:
    258                         body += noneStr(elem.text) + noneStr(elem.tail)
    259 
    260             if body:
    261                 # Add extra newline after multi-line entries.
    262                 if '\n' in body[0:-1]:
    263                     body += '\n'
    264                 self.appendSection(section, body)
    265     #
    266     # Struct (e.g. C "struct" type) generation.
    267     # This is a special case of the <type> tag where the contents are
    268     # interpreted as a set of <member> tags instead of freeform C
    269     # C type declarations. The <member> tags are just like <param>
    270     # tags - they are a declaration of a struct or union member.
    271     # Only simple member declarations are supported (no nested
    272     # structs etc.)
    273     # If alias != None, then this struct aliases another; just
    274     #   generate a typedef of that alias.
    275     def genStruct(self, typeinfo, typeName, alias):
    276         OutputGenerator.genStruct(self, typeinfo, typeName, alias)
    277 
    278         typeElem = typeinfo.elem
    279 
    280         if alias:
    281             body = 'typedef ' + alias + ' ' + typeName + ';\n'
    282         else:
    283             body = 'typedef ' + typeElem.get('category') + ' ' + typeName + ' {\n'
    284 
    285             targetLen = 0;
    286             for member in typeElem.findall('.//member'):
    287                 targetLen = max(targetLen, self.getCParamTypeLength(member))
    288             for member in typeElem.findall('.//member'):
    289                 body += self.makeCParamDecl(member, targetLen + 4)
    290                 body += ';\n'
    291             body += '} ' + typeName + ';\n'
    292 
    293         self.appendSection('struct', body)
    294     #
    295     # Group (e.g. C "enum" type) generation.
    296     # These are concatenated together with other types.
    297     # If alias != None, it is the name of another group type
    298     #   which aliases this type; just generate that alias.
    299     def genGroup(self, groupinfo, groupName, alias = None):
    300         OutputGenerator.genGroup(self, groupinfo, groupName, alias)
    301         groupElem = groupinfo.elem
    302 
    303         if alias:
    304             # If the group name is aliased, just emit a typedef declaration
    305             # for the alias.
    306             body = 'typedef ' + alias + ' ' + groupName + ';\n'
    307         else:
    308             self.logMsg('diag', 'CGenerator.genGroup group =', groupName, 'alias =', alias)
    309 
    310             # Otherwise, emit an actual enumerated type declaration
    311             expandName = re.sub(r'([0-9a-z_])([A-Z0-9])',r'\1_\2',groupName).upper()
    312 
    313             expandPrefix = expandName
    314             expandSuffix = ''
    315             expandSuffixMatch = re.search(r'[A-Z][A-Z]+$',groupName)
    316             if expandSuffixMatch:
    317                 expandSuffix = '_' + expandSuffixMatch.group()
    318                 # Strip off the suffix from the prefix
    319                 expandPrefix = expandName.rsplit(expandSuffix, 1)[0]
    320 
    321             # Prefix
    322             body = "\ntypedef enum " + groupName + " {\n"
    323 
    324             # @@ Should use the type="bitmask" attribute instead
    325             isEnum = ('FLAG_BITS' not in expandPrefix)
    326 
    327             # Get a list of nested 'enum' tags.
    328             enums = groupElem.findall('enum')
    329 
    330             # Check for and report duplicates, and return a list with them
    331             # removed.
    332             enums = self.checkDuplicateEnums(enums)
    333 
    334             # Loop over the nested 'enum' tags. Keep track of the minimum and
    335             # maximum numeric values, if they can be determined; but only for
    336             # core API enumerants, not extension enumerants. This is inferred
    337             # by looking for 'extends' attributes.
    338             minName = None
    339 
    340             # Accumulate non-numeric enumerant values separately and append
    341             # them following the numeric values, to allow for aliases.
    342             # NOTE: this doesn't do a topological sort yet, so aliases of
    343             # aliases can still get in the wrong order.
    344             aliasText = ""
    345 
    346             for elem in enums:
    347                 # Convert the value to an integer and use that to track min/max.
    348                 (numVal,strVal) = self.enumToValue(elem, True)
    349                 name = elem.get('name')
    350 
    351                 # Extension enumerants are only included if they are required
    352                 if self.isEnumRequired(elem):
    353                     decl = "    " + name + " = " + strVal + ",\n"
    354                     if numVal != None:
    355                         body += decl
    356                     else:
    357                         aliasText += decl
    358 
    359                 # Don't track min/max for non-numbers (numVal == None)
    360                 if isEnum and numVal != None and elem.get('extends') is None:
    361                     if minName == None:
    362                         minName = maxName = name
    363                         minValue = maxValue = numVal
    364                     elif numVal < minValue:
    365                         minName = name
    366                         minValue = numVal
    367                     elif numVal > maxValue:
    368                         maxName = name
    369                         maxValue = numVal
    370 
    371             # Now append the non-numeric enumerant values
    372             body += aliasText
    373 
    374             # Generate min/max value tokens and a range-padding enum. Need some
    375             # additional padding to generate correct names...
    376             if isEnum:
    377                 body += "    " + expandPrefix + "_BEGIN_RANGE" + expandSuffix + " = " + minName + ",\n"
    378                 body += "    " + expandPrefix + "_END_RANGE" + expandSuffix + " = " + maxName + ",\n"
    379                 body += "    " + expandPrefix + "_RANGE_SIZE" + expandSuffix + " = (" + maxName + " - " + minName + " + 1),\n"
    380 
    381             body += "    " + expandPrefix + "_MAX_ENUM" + expandSuffix + " = 0x7FFFFFFF\n"
    382 
    383             # Postfix
    384             body += "} " + groupName + ";"
    385 
    386         # After either enumerated type or alias paths, add the declaration
    387         # to the appropriate section for the group being defined.
    388         if groupElem.get('type') == 'bitmask':
    389             section = 'bitmask'
    390         else:
    391             section = 'group'
    392         self.appendSection(section, body)
    393 
    394     # Enumerant generation
    395     # <enum> tags may specify their values in several ways, but are usually
    396     # just integers.
    397     def genEnum(self, enuminfo, name, alias):
    398         OutputGenerator.genEnum(self, enuminfo, name, alias)
    399         (numVal,strVal) = self.enumToValue(enuminfo.elem, False)
    400         body = '#define ' + name.ljust(33) + ' ' + strVal
    401         self.appendSection('enum', body)
    402 
    403     #
    404     # Command generation
    405     def genCmd(self, cmdinfo, name, alias):
    406         OutputGenerator.genCmd(self, cmdinfo, name, alias)
    407 
    408         # if alias:
    409         #     prefix = '// ' + name + ' is an alias of command ' + alias + '\n'
    410         # else:
    411         #     prefix = ''
    412 
    413         prefix = ''
    414         decls = self.makeCDecls(cmdinfo.elem)
    415         self.appendSection('command', prefix + decls[0] + '\n')
    416         if (self.genOpts.genFuncPointers):
    417             self.appendSection('commandPointer', decls[1])
    418