/ scripts / generator.py
generator.py
  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  from __future__ import unicode_literals
 18  
 19  import io
 20  import os
 21  import re
 22  import pdb
 23  import sys
 24  from pathlib import Path
 25  
 26  def write( *args, **kwargs ):
 27      file = kwargs.pop('file',sys.stdout)
 28      end = kwargs.pop('end','\n')
 29      file.write(' '.join(str(arg) for arg in args))
 30      file.write(end)
 31  
 32  # noneStr - returns string argument, or "" if argument is None.
 33  # Used in converting etree Elements into text.
 34  #   s - string to convert
 35  def noneStr(s):
 36      if s:
 37          return s
 38      return ""
 39  
 40  # enquote - returns string argument with surrounding quotes,
 41  #   for serialization into Python code.
 42  def enquote(s):
 43      if s:
 44          return "'{}'".format(s)
 45      return None
 46  
 47  # Primary sort key for regSortFeatures.
 48  # Sorts by category of the feature name string:
 49  #   Core API features (those defined with a <feature> tag)
 50  #   ARB/KHR/OES (Khronos extensions)
 51  #   other       (EXT/vendor extensions)
 52  # This will need changing for Vulkan!
 53  def regSortCategoryKey(feature):
 54      if feature.elem.tag == 'feature':
 55          return 0
 56      if (feature.category == 'ARB' or
 57          feature.category == 'KHR' or
 58              feature.category == 'OES'):
 59          return 1
 60  
 61      return 2
 62  
 63  # Secondary sort key for regSortFeatures.
 64  # Sorts by extension name.
 65  def regSortNameKey(feature):
 66      return feature.name
 67  
 68  # Second sort key for regSortFeatures.
 69  # Sorts by feature version. <extension> elements all have version number "0"
 70  def regSortFeatureVersionKey(feature):
 71      return float(feature.versionNumber)
 72  
 73  # Tertiary sort key for regSortFeatures.
 74  # Sorts by extension number. <feature> elements all have extension number 0.
 75  def regSortExtensionNumberKey(feature):
 76      return int(feature.number)
 77  
 78  # regSortFeatures - default sort procedure for features.
 79  # Sorts by primary key of feature category ('feature' or 'extension')
 80  #   then by version number (for features)
 81  #   then by extension number (for extensions)
 82  def regSortFeatures(featureList):
 83      featureList.sort(key = regSortExtensionNumberKey)
 84      featureList.sort(key = regSortFeatureVersionKey)
 85      featureList.sort(key = regSortCategoryKey)
 86  
 87  # GeneratorOptions - base class for options used during header production
 88  # These options are target language independent, and used by
 89  # Registry.apiGen() and by base OutputGenerator objects.
 90  #
 91  # Members
 92  #   conventions - may be mandatory for some generators:
 93  #     an object that implements ConventionsBase
 94  #   filename - basename of file to generate, or None to write to stdout.
 95  #   directory - directory in which to generate filename
 96  #   apiname - string matching <api> 'apiname' attribute, e.g. 'gl'.
 97  #   profile - string specifying API profile , e.g. 'core', or None.
 98  #   versions - regex matching API versions to process interfaces for.
 99  #     Normally '.*' or '[0-9]\.[0-9]' to match all defined versions.
100  #   emitversions - regex matching API versions to actually emit
101  #    interfaces for (though all requested versions are considered
102  #    when deciding which interfaces to generate). For GL 4.3 glext.h,
103  #    this might be '1\.[2-5]|[2-4]\.[0-9]'.
104  #   defaultExtensions - If not None, a string which must in its
105  #     entirety match the pattern in the "supported" attribute of
106  #     the <extension>. Defaults to None. Usually the same as apiname.
107  #   addExtensions - regex matching names of additional extensions
108  #     to include. Defaults to None.
109  #   removeExtensions - regex matching names of extensions to
110  #     remove (after defaultExtensions and addExtensions). Defaults
111  #     to None.
112  #   emitExtensions - regex matching names of extensions to actually emit
113  #     interfaces for (though all requested versions are considered when
114  #     deciding which interfaces to generate).
115  #   sortProcedure - takes a list of FeatureInfo objects and sorts
116  #     them in place to a preferred order in the generated output.
117  #     Default is core API versions, ARB/KHR/OES extensions, all
118  #     other extensions, alphabetically within each group.
119  # The regex patterns can be None or empty, in which case they match
120  #   nothing.
121  class GeneratorOptions:
122      """Represents options during header production from an API registry"""
123  
124      def __init__(self,
125                   conventions = None,
126                   filename = None,
127                   directory = '.',
128                   apiname = None,
129                   profile = None,
130                   versions = '.*',
131                   emitversions = '.*',
132                   defaultExtensions = None,
133                   addExtensions = None,
134                   removeExtensions = None,
135                   emitExtensions = None,
136                   sortProcedure = regSortFeatures):
137          self.conventions       = conventions
138          self.filename          = filename
139          self.directory         = directory
140          self.apiname           = apiname
141          self.profile           = profile
142          self.versions          = self.emptyRegex(versions)
143          self.emitversions      = self.emptyRegex(emitversions)
144          self.defaultExtensions = defaultExtensions
145          self.addExtensions     = self.emptyRegex(addExtensions)
146          self.removeExtensions  = self.emptyRegex(removeExtensions)
147          self.emitExtensions    = self.emptyRegex(emitExtensions)
148          self.sortProcedure     = sortProcedure
149  
150      # Substitute a regular expression which matches no version
151      # or extension names for None or the empty string.
152      def emptyRegex(self, pat):
153          if pat is None or pat == '':
154              return '_nomatch_^'
155  
156          return pat
157  
158  # OutputGenerator - base class for generating API interfaces.
159  # Manages basic logic, logging, and output file control
160  # Derived classes actually generate formatted output.
161  #
162  # ---- methods ----
163  # OutputGenerator(errFile, warnFile, diagFile)
164  #   errFile, warnFile, diagFile - file handles to write errors,
165  #     warnings, diagnostics to. May be None to not write.
166  # logMsg(level, *args) - log messages of different categories
167  #   level - 'error', 'warn', or 'diag'. 'error' will also
168  #     raise a UserWarning exception
169  #   *args - print()-style arguments
170  # setExtMap(map) - specify a dictionary map from extension names to
171  #   numbers, used in creating values for extension enumerants.
172  # makeDir(directory) - create a directory, if not already done.
173  #   Generally called from derived generators creating hierarchies.
174  # beginFile(genOpts) - start a new interface file
175  #   genOpts - GeneratorOptions controlling what's generated and how
176  # endFile() - finish an interface file, closing it when done
177  # beginFeature(interface, emit) - write interface for a feature
178  # and tag generated features as having been done.
179  #   interface - element for the <version> / <extension> to generate
180  #   emit - actually write to the header only when True
181  # endFeature() - finish an interface.
182  # genType(typeinfo,name,alias) - generate interface for a type
183  #   typeinfo - TypeInfo for a type
184  # genStruct(typeinfo,name,alias) - generate interface for a C "struct" type.
185  #   typeinfo - TypeInfo for a type interpreted as a struct
186  # genGroup(groupinfo,name,alias) - generate interface for a group of enums (C "enum")
187  #   groupinfo - GroupInfo for a group
188  # genEnum(enuminfo,name,alias) - generate interface for an enum (constant)
189  #   enuminfo - EnumInfo for an enum
190  #   name - enum name
191  # genCmd(cmdinfo,name,alias) - generate interface for a command
192  #   cmdinfo - CmdInfo for a command
193  # isEnumRequired(enumElem) - return True if this <enum> element is required
194  #   elem - <enum> element to test
195  # makeCDecls(cmd) - return C prototype and function pointer typedef for a
196  #     <command> Element, as a list of two strings
197  #   cmd - Element for the <command>
198  # newline() - print a newline to the output file (utility function)
199  #
200  class OutputGenerator:
201      """Generate specified API interfaces in a specific style, such as a C header"""
202  
203      # categoryToPath - map XML 'category' to include file directory name
204      categoryToPath = {
205          'bitmask'      : 'flags',
206          'enum'         : 'enums',
207          'funcpointer'  : 'funcpointers',
208          'handle'       : 'handles',
209          'define'       : 'defines',
210          'basetype'     : 'basetypes',
211      }
212  
213      # Constructor
214      def __init__(self,
215                   errFile = sys.stderr,
216                   warnFile = sys.stderr,
217                   diagFile = sys.stdout):
218          self.outFile = None
219          self.errFile = errFile
220          self.warnFile = warnFile
221          self.diagFile = diagFile
222          # Internal state
223          self.featureName = None
224          self.genOpts = None
225          self.registry = None
226          # Used for extension enum value generation
227          self.extBase      = 1000000000
228          self.extBlockSize = 1000
229          self.madeDirs = {}
230  
231      # logMsg - write a message of different categories to different
232      #   destinations.
233      # level -
234      #   'diag' (diagnostic, voluminous)
235      #   'warn' (warning)
236      #   'error' (fatal error - raises exception after logging)
237      # *args - print()-style arguments to direct to corresponding log
238      def logMsg(self, level, *args):
239          """Log a message at the given level. Can be ignored or log to a file"""
240          if level == 'error':
241              strfile = io.StringIO()
242              write('ERROR:', *args, file=strfile)
243              if self.errFile is not None:
244                  write(strfile.getvalue(), file=self.errFile)
245              raise UserWarning(strfile.getvalue())
246          elif level == 'warn':
247              if self.warnFile is not None:
248                  write('WARNING:', *args, file=self.warnFile)
249          elif level == 'diag':
250              if self.diagFile is not None:
251                  write('DIAG:', *args, file=self.diagFile)
252          else:
253              raise UserWarning(
254                  '*** FATAL ERROR in Generator.logMsg: unknown level:' + level)
255  
256      # enumToValue - parses and converts an <enum> tag into a value.
257      # Returns a list
258      #   first element - integer representation of the value, or None
259      #       if needsNum is False. The value must be a legal number
260      #       if needsNum is True.
261      #   second element - string representation of the value
262      # There are several possible representations of values.
263      #   A 'value' attribute simply contains the value.
264      #   A 'bitpos' attribute defines a value by specifying the bit
265      #       position which is set in that value.
266      #   A 'offset','extbase','extends' triplet specifies a value
267      #       as an offset to a base value defined by the specified
268      #       'extbase' extension name, which is then cast to the
269      #       typename specified by 'extends'. This requires probing
270      #       the registry database, and imbeds knowledge of the
271      #       API extension enum scheme in this function.
272      #   A 'alias' attribute contains the name of another enum
273      #       which this is an alias of. The other enum must be
274      #       declared first when emitting this enum.
275      def enumToValue(self, elem, needsNum):
276          name = elem.get('name')
277          numVal = None
278          if 'value' in elem.keys():
279              value = elem.get('value')
280              # print('About to translate value =', value, 'type =', type(value))
281              if needsNum:
282                  numVal = int(value, 0)
283              # If there's a non-integer, numeric 'type' attribute (e.g. 'u' or
284              # 'ull'), append it to the string value.
285              # t = enuminfo.elem.get('type')
286              # if t is not None and t != '' and t != 'i' and t != 's':
287              #     value += enuminfo.type
288              self.logMsg('diag', 'Enum', name, '-> value [', numVal, ',', value, ']')
289              return [numVal, value]
290          if 'bitpos' in elem.keys():
291              value = elem.get('bitpos')
292              bitpos = int(value, 0)
293              numVal = 1 << bitpos
294              value = '0x%08x' % numVal
295              if( bitpos >= 32 ):
296                  value = value + 'ULL'
297              self.logMsg('diag', 'Enum', name, '-> bitpos [', numVal, ',', value, ']')
298              return [numVal, value]
299          if 'offset' in elem.keys():
300              # Obtain values in the mapping from the attributes
301              enumNegative = False
302              offset = int(elem.get('offset'),0)
303              extnumber = int(elem.get('extnumber'),0)
304              extends = elem.get('extends')
305              if 'dir' in elem.keys():
306                  enumNegative = True
307              self.logMsg('diag', 'Enum', name, 'offset =', offset,
308                  'extnumber =', extnumber, 'extends =', extends,
309                  'enumNegative =', enumNegative)
310              # Now determine the actual enumerant value, as defined
311              # in the "Layers and Extensions" appendix of the spec.
312              numVal = self.extBase + (extnumber - 1) * self.extBlockSize + offset
313              if enumNegative:
314                  numVal *= -1
315              value = '%d' % numVal
316              # More logic needed!
317              self.logMsg('diag', 'Enum', name, '-> offset [', numVal, ',', value, ']')
318              return [numVal, value]
319          if 'alias' in elem.keys():
320              return [None, elem.get('alias')]
321          return [None, None]
322  
323      # checkDuplicateEnums - sanity check for enumerated values
324      #   enums - list of <enum> Elements
325      #   returns the list with duplicates stripped
326      def checkDuplicateEnums(self, enums):
327          # Dictionaries indexed by name and numeric value.
328          # Entries are [ Element, numVal, strVal ] matching name or value
329  
330          nameMap = {}
331          valueMap = {}
332  
333          stripped = []
334          for elem in enums:
335              name = elem.get('name')
336              (numVal, strVal) = self.enumToValue(elem, True)
337  
338              if name in nameMap:
339                  # Duplicate name found; check values
340                  (name2, numVal2, strVal2) = nameMap[name]
341  
342                  # Duplicate enum values for the same name are benign. This
343                  # happens when defining the same enum conditionally in
344                  # several extension blocks.
345                  if (strVal2 == strVal or (numVal is not None and
346                      numVal == numVal2)):
347                      True
348                      # self.logMsg('info', 'checkDuplicateEnums: Duplicate enum (' + name +
349                      #             ') found with the same value:' + strVal)
350                  else:
351                      self.logMsg('warn', 'checkDuplicateEnums: Duplicate enum (' + name +
352                                  ') found with different values:' + strVal +
353                                  ' and ' + strVal2)
354  
355                  # Don't add the duplicate to the returned list
356                  continue
357              elif numVal in valueMap:
358                  # Duplicate value found (such as an alias); report it, but
359                  # still add this enum to the list.
360                  (name2, numVal2, strVal2) = valueMap[numVal]
361  
362                  try:
363                      self.logMsg('warn', 'Two enums found with the same value: '
364                               + name + ' = ' + name2.get('name') + ' = ' + strVal)
365                  except:
366                      pdb.set_trace()
367  
368              # Track this enum to detect followon duplicates
369              nameMap[name] = [ elem, numVal, strVal ]
370              if numVal is not None:
371                  valueMap[numVal] = [ elem, numVal, strVal ]
372  
373              # Add this enum to the list
374              stripped.append(elem)
375  
376          # Return the list
377          return stripped
378  
379      # buildEnumCDecl
380      # Generates the C declaration for an enum
381      def buildEnumCDecl(self, expand, groupinfo, groupName):
382          groupElem = groupinfo.elem
383  
384          if self.genOpts.conventions.constFlagBits and groupElem.get('type') == 'bitmask':
385              return self.buildEnumCDecl_Bitmask( groupinfo, groupName)
386          else:
387              return self.buildEnumCDecl_Enum(expand, groupinfo, groupName)
388  
389      # buildEnumCDecl_Bitmask
390      # Generates the C declaration for an "enum" that is actually a
391      # set of flag bits
392      def buildEnumCDecl_Bitmask(self, groupinfo, groupName):
393          groupElem = groupinfo.elem
394          flagTypeName = groupinfo.flagType.elem.get('name')
395  
396          # Prefix
397          body = "// Flag bits for " + flagTypeName + "\n"
398  
399          # Loop over the nested 'enum' tags.
400          for elem in groupElem.findall('enum'):
401              # Convert the value to an integer and use that to track min/max.
402              # Values of form -(number) are accepted but nothing more complex.
403              # Should catch exceptions here for more complex constructs. Not yet.
404              (_, strVal) = self.enumToValue(elem, True)
405              name = elem.get('name')
406              body += "static const " + flagTypeName + " " + name + " = " + strVal + ";\n"
407  
408          # Postfix
409  
410          return ("bitmask", body)
411  
412      # Generates the C declaration for an enumerated type
413      def buildEnumCDecl_Enum(self, expand, groupinfo, groupName):
414          groupElem = groupinfo.elem
415  
416          # Break the group name into prefix and suffix portions for range
417          # enum generation
418          expandName = re.sub(r'([0-9a-z_])([A-Z0-9])',r'\1_\2',groupName).upper()
419          expandPrefix = expandName
420          expandSuffix = ''
421          expandSuffixMatch = re.search(r'[A-Z][A-Z]+$',groupName)
422          if expandSuffixMatch:
423              expandSuffix = '_' + expandSuffixMatch.group()
424              # Strip off the suffix from the prefix
425              expandPrefix = expandName.rsplit(expandSuffix, 1)[0]
426  
427          # Prefix
428          body = "typedef enum " + groupName + " {\n"
429  
430          # @@ Should use the type="bitmask" attribute instead
431          isEnum = ('FLAG_BITS' not in expandPrefix)
432  
433          # Get a list of nested 'enum' tags.
434          enums = groupElem.findall('enum')
435  
436          # Check for and report duplicates, and return a list with them
437          # removed.
438          enums = self.checkDuplicateEnums(enums)
439  
440          # Loop over the nested 'enum' tags. Keep track of the minimum and
441          # maximum numeric values, if they can be determined; but only for
442          # core API enumerants, not extension enumerants. This is inferred
443          # by looking for 'extends' attributes.
444          minName = None
445  
446          # Accumulate non-numeric enumerant values separately and append
447          # them following the numeric values, to allow for aliases.
448          # NOTE: this doesn't do a topological sort yet, so aliases of
449          # aliases can still get in the wrong order.
450          aliasText = ""
451  
452          for elem in enums:
453              # Convert the value to an integer and use that to track min/max.
454              # Values of form -(number) are accepted but nothing more complex.
455              # Should catch exceptions here for more complex constructs. Not yet.
456              (numVal,strVal) = self.enumToValue(elem, True)
457              name = elem.get('name')
458  
459              # Extension enumerants are only included if they are required
460              if self.isEnumRequired(elem):
461                  decl = "    " + name + " = " + strVal + ",\n"
462                  if numVal is not None:
463                      body += decl
464                  else:
465                      aliasText += decl
466  
467              # Don't track min/max for non-numbers (numVal is None)
468              if isEnum and numVal is not None and elem.get('extends') is None:
469                  if minName is None:
470                      minName = maxName = name
471                      minValue = maxValue = numVal
472                  elif numVal < minValue:
473                      minName = name
474                      minValue = numVal
475                  elif numVal > maxValue:
476                      maxName = name
477                      maxValue = numVal
478  
479          # Now append the non-numeric enumerant values
480          body += aliasText
481  
482          # Generate min/max value tokens and a range-padding enum. Need some
483          # additional padding to generate correct names...
484          if isEnum and expand:
485              body += "    " + expandPrefix + "_BEGIN_RANGE" + expandSuffix + " = " + minName + ",\n"
486              body += "    " + expandPrefix + "_END_RANGE" + expandSuffix + " = " + maxName + ",\n"
487              body += "    " + expandPrefix + "_RANGE_SIZE" + expandSuffix + " = (" + maxName + " - " + minName + " + 1),\n"
488  
489          # Always generate this to make sure the enumerated type is 32 bits
490          body += "    " + expandPrefix + "_MAX_ENUM" + expandSuffix + " = 0x7FFFFFFF\n"
491  
492          # Postfix
493          body += "} " + groupName + ";"
494  
495          # Determine appropriate section for this declaration
496          if groupElem.get('type') == 'bitmask':
497              section = 'bitmask'
498          else:
499              section = 'group'
500  
501          return (section, body)
502  
503      def makeDir(self, path):
504          self.logMsg('diag', 'OutputGenerator::makeDir(' + path + ')')
505          if path not in self.madeDirs:
506              # This can get race conditions with multiple writers, see
507              # https://stackoverflow.com/questions/273192/
508              if not os.path.exists(path):
509                  os.makedirs(path)
510              self.madeDirs[path] = None
511  
512      def beginFile(self, genOpts):
513          self.genOpts = genOpts
514  
515          # Open specified output file. Not done in constructor since a
516          # Generator can be used without writing to a file.
517          if self.genOpts.filename is not None:
518              if sys.platform == 'win32':
519                  directory = Path(self.genOpts.directory)
520                  if not Path.exists(directory):
521                      os.makedirs(directory)
522                  self.outFile = (directory / self.genOpts.filename).open('w', encoding='utf-8')
523              else:
524                  filename = self.genOpts.directory + '/' + self.genOpts.filename
525                  self.outFile = io.open(filename, 'w', encoding='utf-8')
526          else:
527              self.outFile = sys.stdout
528  
529      def endFile(self):
530          if self.errFile:
531              self.errFile.flush()
532          if self.warnFile:
533              self.warnFile.flush()
534          if self.diagFile:
535              self.diagFile.flush()
536          self.outFile.flush()
537          if self.outFile != sys.stdout and self.outFile != sys.stderr:
538              self.outFile.close()
539          self.genOpts = None
540  
541      def beginFeature(self, interface, emit):
542          self.emit = emit
543          self.featureName = interface.get('name')
544          # If there's an additional 'protect' attribute in the feature, save it
545          self.featureExtraProtect = interface.get('protect')
546  
547      def endFeature(self):
548          # Derived classes responsible for emitting feature
549          self.featureName = None
550          self.featureExtraProtect = None
551  
552      # Utility method to validate we're generating something only inside a
553      # <feature> tag
554      def validateFeature(self, featureType, featureName):
555          if self.featureName is None:
556              raise UserWarning('Attempt to generate', featureType,
557                                featureName, 'when not in feature')
558  
559      # Type generation
560      def genType(self, typeinfo, name, alias):
561          self.validateFeature('type', name)
562  
563      # Struct (e.g. C "struct" type) generation
564      def genStruct(self, typeinfo, typeName, alias):
565          self.validateFeature('struct', typeName)
566  
567          # The mixed-mode <member> tags may contain no-op <comment> tags.
568          # It is convenient to remove them here where all output generators
569          # will benefit.
570          for member in typeinfo.elem.findall('.//member'):
571              for comment in member.findall('comment'):
572                  member.remove(comment)
573  
574      # Group (e.g. C "enum" type) generation
575      def genGroup(self, groupinfo, groupName, alias):
576          self.validateFeature('group', groupName)
577  
578      # Enumerant (really, constant) generation
579      def genEnum(self, enuminfo, typeName, alias):
580          self.validateFeature('enum', typeName)
581  
582      # Command generation
583      def genCmd(self, cmd, cmdinfo, alias):
584          self.validateFeature('command', cmdinfo)
585  
586      # Utility functions - turn a <proto> <name> into C-language prototype
587      # and typedef declarations for that name.
588      # name - contents of <name> tag
589      # tail - whatever text follows that tag in the Element
590      def makeProtoName(self, name, tail):
591          return self.genOpts.apientry + name + tail
592  
593      def makeTypedefName(self, name, tail):
594          return '(' + self.genOpts.apientryp + 'PFN_' + name + tail + ')'
595  
596      # makeCParamDecl - return a string which is an indented, formatted
597      # declaration for a <param> or <member> block (e.g. function parameter
598      # or structure/union member).
599      # param - Element (<param> or <member>) to format
600      # aligncol - if non-zero, attempt to align the nested <name> element
601      #   at this column
602      def makeCParamDecl(self, param, aligncol):
603          paramdecl = '    ' + noneStr(param.text)
604          for elem in param:
605              text = noneStr(elem.text)
606              tail = noneStr(elem.tail)
607  
608              if self.genOpts.conventions.is_voidpointer_alias(elem.tag, text, tail):
609                  # OpenXR-specific macro insertion
610                  tail = self.genOpts.conventions.make_voidpointer_alias(tail)
611              if elem.tag == 'name' and aligncol > 0:
612                  self.logMsg('diag', 'Aligning parameter', elem.text, 'to column', self.genOpts.alignFuncParam)
613                  # Align at specified column, if possible
614                  paramdecl = paramdecl.rstrip()
615                  oldLen = len(paramdecl)
616                  # This works around a problem where very long type names -
617                  # longer than the alignment column - would run into the tail
618                  # text.
619                  paramdecl = paramdecl.ljust(aligncol-1) + ' '
620                  newLen = len(paramdecl)
621                  self.logMsg('diag', 'Adjust length of parameter decl from', oldLen, 'to', newLen, ':', paramdecl)
622              paramdecl += text + tail
623          return paramdecl
624  
625      # getCParamTypeLength - return the length of the type field is an indented, formatted
626      # declaration for a <param> or <member> block (e.g. function parameter
627      # or structure/union member).
628      # param - Element (<param> or <member>) to identify
629      def getCParamTypeLength(self, param):
630          paramdecl = '    ' + noneStr(param.text)
631          for elem in param:
632              text = noneStr(elem.text)
633              tail = noneStr(elem.tail)
634  
635              if self.genOpts.conventions.is_voidpointer_alias(elem.tag, text, tail):
636                  # OpenXR-specific macro insertion
637                  tail = self.genOpts.conventions.make_voidpointer_alias(tail)
638              if elem.tag == 'name':
639                  # Align at specified column, if possible
640                  newLen = len(paramdecl.rstrip())
641                  self.logMsg('diag', 'Identifying length of', elem.text, 'as', newLen)
642              paramdecl += text + tail
643  
644          return newLen
645  
646      # isEnumRequired(elem) - return True if this <enum> element is
647      # required, False otherwise
648      # elem - <enum> element to test
649      def isEnumRequired(self, elem):
650          required = elem.get('required') is not None
651          self.logMsg('diag', 'isEnumRequired:', elem.get('name'),
652              '->', required)
653          return required
654  
655          #@@@ This code is overridden by equivalent code now run in
656          #@@@ Registry.generateFeature
657  
658          required = False
659  
660          extname = elem.get('extname')
661          if extname is not None:
662              # 'supported' attribute was injected when the <enum> element was
663              # moved into the <enums> group in Registry.parseTree()
664              if self.genOpts.defaultExtensions == elem.get('supported'):
665                  required = True
666              elif re.match(self.genOpts.addExtensions, extname) is not None:
667                  required = True
668          elif elem.get('version') is not None:
669              required = re.match(self.genOpts.emitversions, elem.get('version')) is not None
670          else:
671              required = True
672  
673          return required
674  
675      # makeCDecls - return C prototype and function pointer typedef for a
676      #   command, as a two-element list of strings.
677      # cmd - Element containing a <command> tag
678      def makeCDecls(self, cmd):
679          """Generate C function pointer typedef for <command> Element"""
680          proto = cmd.find('proto')
681          params = cmd.findall('param')
682          # Begin accumulating prototype and typedef strings
683          pdecl = self.genOpts.apicall
684          tdecl = 'typedef '
685  
686          # Insert the function return type/name.
687          # For prototypes, add APIENTRY macro before the name
688          # For typedefs, add (APIENTRY *<name>) around the name and
689          #   use the PFN_cmdnameproc naming convention.
690          # Done by walking the tree for <proto> element by element.
691          # etree has elem.text followed by (elem[i], elem[i].tail)
692          #   for each child element and any following text
693          # Leading text
694          pdecl += noneStr(proto.text)
695          tdecl += noneStr(proto.text)
696          # For each child element, if it's a <name> wrap in appropriate
697          # declaration. Otherwise append its contents and tail contents.
698          for elem in proto:
699              text = noneStr(elem.text)
700              tail = noneStr(elem.tail)
701              if elem.tag == 'name':
702                  pdecl += self.makeProtoName(text, tail)
703                  tdecl += self.makeTypedefName(text, tail)
704              else:
705                  pdecl += text + tail
706                  tdecl += text + tail
707          # Now add the parameter declaration list, which is identical
708          # for prototypes and typedefs. Concatenate all the text from
709          # a <param> node without the tags. No tree walking required
710          # since all tags are ignored.
711          # Uses: self.indentFuncProto
712          # self.indentFuncPointer
713          # self.alignFuncParam
714          n = len(params)
715          # Indented parameters
716          if n > 0:
717              indentdecl = '(\n'
718              indentdecl += ',\n'.join(self.makeCParamDecl(p, self.genOpts.alignFuncParam)
719                                       for p in params)
720              indentdecl += ');'
721          else:
722              indentdecl = '(void);'
723          # Non-indented parameters
724          paramdecl = '('
725          if n > 0:
726              paramnames = (''.join(t for t in p.itertext())
727                            for p in params)
728              paramdecl += ', '.join(paramnames)
729          else:
730              paramdecl += 'void'
731          paramdecl += ");"
732          return [ pdecl + indentdecl, tdecl + paramdecl ]
733  
734      def newline(self):
735          write('', file=self.outFile)
736  
737      def setRegistry(self, registry):
738          self.registry = registry