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