• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/python3 -i
2#
3# Copyright 2013-2023 The Khronos Group Inc.
4#
5# SPDX-License-Identifier: Apache-2.0
6"""Base class for source/header/doc generators, as well as some utility functions."""
7
8from __future__ import unicode_literals
9
10import io
11import os
12import pdb
13import re
14import shutil
15import sys
16import tempfile
17try:
18    from pathlib import Path
19except ImportError:
20    from pathlib2 import Path  # type: ignore
21
22from spec_tools.util import getElemName, getElemType
23
24
25def write(*args, **kwargs):
26    file = kwargs.pop('file', sys.stdout)
27    end = kwargs.pop('end', '\n')
28    file.write(' '.join(str(arg) for arg in args))
29    file.write(end)
30
31
32def noneStr(s):
33    """Return string argument, or "" if argument is None.
34
35    Used in converting etree Elements into text.
36    s - string to convert"""
37    if s:
38        return s
39    return ""
40
41
42def enquote(s):
43    """Return string argument with surrounding quotes,
44      for serialization into Python code."""
45    if s:
46        if isinstance(s, str):
47            return f"'{s}'"
48        else:
49            return s
50    return None
51
52
53def regSortCategoryKey(feature):
54    """Sort key for regSortFeatures.
55    Sorts by category of the feature name string:
56
57    - Core API features (those defined with a `<feature>` tag)
58        - (sort VKSC after VK - this is Vulkan-specific)
59    - ARB/KHR/OES (Khronos extensions)
60    - other       (EXT/vendor extensions)"""
61
62    if feature.elem.tag == 'feature':
63        if feature.name.startswith('VKSC'):
64            return 0.5
65        else:
66            return 0
67    if (feature.category == 'ARB'
68        or feature.category == 'KHR'
69            or feature.category == 'OES'):
70        return 1
71
72    return 2
73
74
75def regSortOrderKey(feature):
76    """Sort key for regSortFeatures - key is the sortorder attribute."""
77
78    return feature.sortorder
79
80
81def regSortNameKey(feature):
82    """Sort key for regSortFeatures - key is the extension name."""
83
84    return feature.name
85
86
87def regSortFeatureVersionKey(feature):
88    """Sort key for regSortFeatures - key is the feature version.
89    `<extension>` elements all have version number 0."""
90
91    return float(feature.versionNumber)
92
93
94def regSortExtensionNumberKey(feature):
95    """Sort key for regSortFeatures - key is the extension number.
96    `<feature>` elements all have extension number 0."""
97
98    return int(feature.number)
99
100
101def regSortFeatures(featureList):
102    """Default sort procedure for features.
103
104    - Sorts by explicit sort order (default 0) relative to other features
105    - then by feature category ('feature' or 'extension'),
106    - then by version number (for features)
107    - then by extension number (for extensions)"""
108    featureList.sort(key=regSortExtensionNumberKey)
109    featureList.sort(key=regSortFeatureVersionKey)
110    featureList.sort(key=regSortCategoryKey)
111    featureList.sort(key=regSortOrderKey)
112
113
114class MissingGeneratorOptionsError(RuntimeError):
115    """Error raised when a Generator tries to do something that requires GeneratorOptions but it is None."""
116
117    def __init__(self, msg=None):
118        full_msg = 'Missing generator options object self.genOpts'
119        if msg:
120            full_msg += ': ' + msg
121        super().__init__(full_msg)
122
123
124class MissingRegistryError(RuntimeError):
125    """Error raised when a Generator tries to do something that requires a Registry object but it is None."""
126
127    def __init__(self, msg=None):
128        full_msg = 'Missing Registry object self.registry'
129        if msg:
130            full_msg += ': ' + msg
131        super().__init__(full_msg)
132
133
134class MissingGeneratorOptionsConventionsError(RuntimeError):
135    """Error raised when a Generator tries to do something that requires a Conventions object but it is None."""
136
137    def __init__(self, msg=None):
138        full_msg = 'Missing Conventions object self.genOpts.conventions'
139        if msg:
140            full_msg += ': ' + msg
141        super().__init__(full_msg)
142
143
144class GeneratorOptions:
145    """Base class for options used during header/documentation production.
146
147    These options are target language independent, and used by
148    Registry.apiGen() and by base OutputGenerator objects."""
149
150    def __init__(self,
151                 conventions=None,
152                 filename=None,
153                 directory='.',
154                 genpath=None,
155                 apiname=None,
156                 mergeApiNames=None,
157                 profile=None,
158                 versions='.*',
159                 emitversions='.*',
160                 defaultExtensions=None,
161                 addExtensions=None,
162                 removeExtensions=None,
163                 emitExtensions=None,
164                 emitSpirv=None,
165                 emitFormats=None,
166                 reparentEnums=True,
167                 sortProcedure=regSortFeatures,
168                 requireCommandAliases=False,
169                 requireDepends=True,
170                ):
171        """Constructor.
172
173        Arguments:
174
175        - conventions - may be mandatory for some generators:
176        an object that implements ConventionsBase
177        - filename - basename of file to generate, or None to write to stdout.
178        - directory - directory in which to generate filename
179        - genpath - path to previously generated files, such as apimap.py
180        - apiname - string matching `<api>` 'apiname' attribute, e.g. 'gl'.
181        - mergeApiNames - If not None, a comma separated list of API names
182          to merge into the API specified by 'apiname'
183        - profile - string specifying API profile , e.g. 'core', or None.
184        - versions - regex matching API versions to process interfaces for.
185        Normally `'.*'` or `'[0-9][.][0-9]'` to match all defined versions.
186        - emitversions - regex matching API versions to actually emit
187        interfaces for (though all requested versions are considered
188        when deciding which interfaces to generate). For GL 4.3 glext.h,
189        this might be `'1[.][2-5]|[2-4][.][0-9]'`.
190        - defaultExtensions - If not None, a string which must in its
191        entirety match the pattern in the "supported" attribute of
192        the `<extension>`. Defaults to None. Usually the same as apiname.
193        - addExtensions - regex matching names of additional extensions
194        to include. Defaults to None.
195        - removeExtensions - regex matching names of extensions to
196        remove (after defaultExtensions and addExtensions). Defaults
197        to None.
198        - emitExtensions - regex matching names of extensions to actually emit
199        interfaces for (though all requested versions are considered when
200        deciding which interfaces to generate). Defaults to None.
201        - emitSpirv - regex matching names of extensions and capabilities
202        to actually emit interfaces for.
203        - emitFormats - regex matching names of formats to actually emit
204        interfaces for.
205        - reparentEnums - move <enum> elements which extend an enumerated
206        type from <feature> or <extension> elements to the target <enums>
207        element. This is required for almost all purposes, but the
208        InterfaceGenerator relies on the list of interfaces in the <feature>
209        or <extension> being complete. Defaults to True.
210        - sortProcedure - takes a list of FeatureInfo objects and sorts
211        them in place to a preferred order in the generated output.
212        - requireCommandAliases - if True, treat command aliases
213        as required dependencies.
214        - requireDepends - whether to follow API dependencies when emitting
215        APIs.
216
217        Default is
218          - core API versions
219          - Khronos (ARB/KHR/OES) extensions
220          - All other extensions
221          - By core API version number or extension number in each group.
222
223        The regex patterns can be None or empty, in which case they match
224        nothing."""
225        self.conventions = conventions
226        """may be mandatory for some generators:
227        an object that implements ConventionsBase"""
228
229        self.filename = filename
230        "basename of file to generate, or None to write to stdout."
231
232        self.genpath = genpath
233        """path to previously generated files, such as apimap.py"""
234
235        self.directory = directory
236        "directory in which to generate filename"
237
238        self.apiname = apiname
239        "string matching `<api>` 'apiname' attribute, e.g. 'gl'."
240
241        self.mergeApiNames = mergeApiNames
242        "comma separated list of API names to merge into the API specified by 'apiname'"
243
244        self.profile = profile
245        "string specifying API profile , e.g. 'core', or None."
246
247        self.versions = self.emptyRegex(versions)
248        """regex matching API versions to process interfaces for.
249        Normally `'.*'` or `'[0-9][.][0-9]'` to match all defined versions."""
250
251        self.emitversions = self.emptyRegex(emitversions)
252        """regex matching API versions to actually emit
253        interfaces for (though all requested versions are considered
254        when deciding which interfaces to generate). For GL 4.3 glext.h,
255        this might be `'1[.][2-5]|[2-4][.][0-9]'`."""
256
257        self.defaultExtensions = defaultExtensions
258        """If not None, a string which must in its
259        entirety match the pattern in the "supported" attribute of
260        the `<extension>`. Defaults to None. Usually the same as apiname."""
261
262        self.addExtensions = self.emptyRegex(addExtensions)
263        """regex matching names of additional extensions
264        to include. Defaults to None."""
265
266        self.removeExtensions = self.emptyRegex(removeExtensions)
267        """regex matching names of extensions to
268        remove (after defaultExtensions and addExtensions). Defaults
269        to None."""
270
271        self.emitExtensions = self.emptyRegex(emitExtensions)
272        """regex matching names of extensions to actually emit
273        interfaces for (though all requested versions are considered when
274        deciding which interfaces to generate)."""
275
276        self.emitSpirv = self.emptyRegex(emitSpirv)
277        """regex matching names of extensions and capabilities
278        to actually emit interfaces for."""
279
280        self.emitFormats = self.emptyRegex(emitFormats)
281        """regex matching names of formats
282        to actually emit interfaces for."""
283
284        self.reparentEnums = reparentEnums
285        """boolean specifying whether to remove <enum> elements from
286        <feature> or <extension> when extending an <enums> type."""
287
288        self.sortProcedure = sortProcedure
289        """takes a list of FeatureInfo objects and sorts
290        them in place to a preferred order in the generated output.
291        Default is core API versions, ARB/KHR/OES extensions, all
292        other extensions, alphabetically within each group."""
293
294        self.codeGenerator = False
295        """True if this generator makes compilable code"""
296
297        self.registry = None
298        """Populated later with the registry object."""
299
300        self.requireCommandAliases = requireCommandAliases
301        """True if alias= attributes of <command> tags are transitively
302        required."""
303
304        self.requireDepends = requireDepends
305        """True if dependencies of API tags are transitively required."""
306
307    def emptyRegex(self, pat):
308        """Substitute a regular expression which matches no version
309        or extension names for None or the empty string."""
310        if not pat:
311            return '_nomatch_^'
312
313        return pat
314
315
316class OutputGenerator:
317    """Generate specified API interfaces in a specific style, such as a C header.
318
319    Base class for generating API interfaces.
320    Manages basic logic, logging, and output file control.
321    Derived classes actually generate formatted output.
322    """
323
324    # categoryToPath - map XML 'category' to include file directory name
325    categoryToPath = {
326        'bitmask': 'flags',
327        'enum': 'enums',
328        'funcpointer': 'funcpointers',
329        'handle': 'handles',
330        'define': 'defines',
331        'basetype': 'basetypes',
332    }
333
334    def breakName(self, name, msg):
335        """Break into debugger if this is a special name"""
336
337        # List of string names to break on
338        bad = (
339        )
340
341        if name in bad and True:
342            print('breakName {}: {}'.format(name, msg))
343            pdb.set_trace()
344
345    def __init__(self, errFile=sys.stderr, warnFile=sys.stderr, diagFile=sys.stdout):
346        """Constructor
347
348        - errFile, warnFile, diagFile - file handles to write errors,
349          warnings, diagnostics to. May be None to not write."""
350        self.outFile = None
351        self.errFile = errFile
352        self.warnFile = warnFile
353        self.diagFile = diagFile
354        # Internal state
355        self.featureName = None
356        """The current feature name being generated."""
357
358        self.featureType = None
359        """The current feature type being generated."""
360
361        self.genOpts = None
362        """The GeneratorOptions subclass instance."""
363
364        self.registry = None
365        """The specification registry object."""
366
367        self.featureDictionary = {}
368        """The dictionary of dictionaries of API features."""
369
370        # Used for extension enum value generation
371        self.extBase = 1000000000
372        self.extBlockSize = 1000
373        self.madeDirs = {}
374
375        # API dictionary, which may be loaded by the beginFile method of
376        # derived generators.
377        self.apidict = None
378
379        # File suffix for generated files, set in beginFile below.
380        self.file_suffix = ''
381
382    def logMsg(self, level, *args):
383        """Write a message of different categories to different
384        destinations.
385
386        - `level`
387          - 'diag' (diagnostic, voluminous)
388          - 'warn' (warning)
389          - 'error' (fatal error - raises exception after logging)
390
391        - `*args` - print()-style arguments to direct to corresponding log"""
392        if level == 'error':
393            strfile = io.StringIO()
394            write('ERROR:', *args, file=strfile)
395            if self.errFile is not None:
396                write(strfile.getvalue(), file=self.errFile)
397            raise UserWarning(strfile.getvalue())
398        elif level == 'warn':
399            if self.warnFile is not None:
400                write('WARNING:', *args, file=self.warnFile)
401        elif level == 'diag':
402            if self.diagFile is not None:
403                write('DIAG:', *args, file=self.diagFile)
404        else:
405            raise UserWarning(
406                '*** FATAL ERROR in Generator.logMsg: unknown level:' + level)
407
408    def enumToValue(self, elem, needsNum, bitwidth = 32,
409                    forceSuffix = False, parent_for_alias_dereference=None):
410        """Parse and convert an `<enum>` tag into a value.
411
412        - elem - <enum> Element
413        - needsNum - generate a numeric representation of the element value
414        - bitwidth - size of the numeric representation in bits (32 or 64)
415        - forceSuffix - if True, always use a 'U' / 'ULL' suffix on integers
416        - parent_for_alias_dereference - if not None, an Element containing
417          the parent of elem, used to look for elements this is an alias of
418
419        Returns a list:
420
421        - first element - integer representation of the value, or None
422          if needsNum is False. The value must be a legal number
423          if needsNum is True.
424        - second element - string representation of the value
425
426        There are several possible representations of values.
427
428        - A 'value' attribute simply contains the value.
429        - A 'bitpos' attribute defines a value by specifying the bit
430          position which is set in that value.
431        - An 'offset','extbase','extends' triplet specifies a value
432          as an offset to a base value defined by the specified
433          'extbase' extension name, which is then cast to the
434          typename specified by 'extends'. This requires probing
435          the registry database, and imbeds knowledge of the
436          API extension enum scheme in this function.
437        - An 'alias' attribute contains the name of another enum
438          which this is an alias of. The other enum must be
439          declared first when emitting this enum."""
440        if self.genOpts is None:
441            raise MissingGeneratorOptionsError()
442        if self.genOpts.conventions is None:
443            raise MissingGeneratorOptionsConventionsError()
444
445        name = elem.get('name')
446        numVal = None
447        if 'value' in elem.keys():
448            value = elem.get('value')
449            # print('About to translate value =', value, 'type =', type(value))
450            if needsNum:
451                numVal = int(value, 0)
452            # If there is a non-integer, numeric 'type' attribute (e.g. 'u' or
453            # 'ull'), append it to the string value.
454            # t = enuminfo.elem.get('type')
455            # if t is not None and t != '' and t != 'i' and t != 's':
456            #     value += enuminfo.type
457            if forceSuffix:
458              if bitwidth == 64:
459                value = value + 'ULL'
460              else:
461                value = value + 'U'
462            self.logMsg('diag', 'Enum', name, '-> value [', numVal, ',', value, ']')
463            return [numVal, value]
464        if 'bitpos' in elem.keys():
465            value = elem.get('bitpos')
466            bitpos = int(value, 0)
467            numVal = 1 << bitpos
468            value = '0x%08x' % numVal
469            if bitwidth == 64 or bitpos >= 32:
470              value = value + 'ULL'
471            elif forceSuffix:
472              value = value + 'U'
473            self.logMsg('diag', 'Enum', name, '-> bitpos [', numVal, ',', value, ']')
474            return [numVal, value]
475        if 'offset' in elem.keys():
476            # Obtain values in the mapping from the attributes
477            enumNegative = False
478            offset = int(elem.get('offset'), 0)
479            extnumber = int(elem.get('extnumber'), 0)
480            extends = elem.get('extends')
481            if 'dir' in elem.keys():
482                enumNegative = True
483            self.logMsg('diag', 'Enum', name, 'offset =', offset,
484                        'extnumber =', extnumber, 'extends =', extends,
485                        'enumNegative =', enumNegative)
486            # Now determine the actual enumerant value, as defined
487            # in the "Layers and Extensions" appendix of the spec.
488            numVal = self.extBase + (extnumber - 1) * self.extBlockSize + offset
489            if enumNegative:
490                numVal *= -1
491            value = '%d' % numVal
492            # More logic needed!
493            self.logMsg('diag', 'Enum', name, '-> offset [', numVal, ',', value, ']')
494            return [numVal, value]
495        if 'alias' in elem.keys():
496            alias_of = elem.get('alias')
497            if parent_for_alias_dereference is None:
498                return (None, alias_of)
499            siblings = parent_for_alias_dereference.findall('enum')
500            for sib in siblings:
501                sib_name = sib.get('name')
502                if sib_name == alias_of:
503                    return self.enumToValue(sib, needsNum)
504            raise RuntimeError("Could not find the aliased enum value")
505        return [None, None]
506
507    def checkDuplicateEnums(self, enums):
508        """Check enumerated values for duplicates.
509
510        -  enums - list of `<enum>` Elements
511
512        returns the list with duplicates stripped"""
513        # Dictionaries indexed by name and numeric value.
514        # Entries are [ Element, numVal, strVal ] matching name or value
515
516        nameMap = {}
517        valueMap = {}
518
519        stripped = []
520        for elem in enums:
521            name = elem.get('name')
522            (numVal, strVal) = self.enumToValue(elem, True)
523
524            if name in nameMap:
525                # Duplicate name found; check values
526                (name2, numVal2, strVal2) = nameMap[name]
527
528                # Duplicate enum values for the same name are benign. This
529                # happens when defining the same enum conditionally in
530                # several extension blocks.
531                if (strVal2 == strVal or (numVal is not None
532                                          and numVal == numVal2)):
533                    True
534                    # self.logMsg('info', 'checkDuplicateEnums: Duplicate enum (' + name +
535                    #             ') found with the same value:' + strVal)
536                else:
537                    self.logMsg('warn', 'checkDuplicateEnums: Duplicate enum (' + name
538                                + ') found with different values:' + strVal
539                                + ' and ' + strVal2)
540
541                # Do not add the duplicate to the returned list
542                continue
543            elif numVal in valueMap:
544                # Duplicate value found (such as an alias); report it, but
545                # still add this enum to the list.
546                (name2, numVal2, strVal2) = valueMap[numVal]
547
548                msg = 'Two enums found with the same value: {} = {} = {}'.format(
549                    name, name2.get('name'), strVal)
550                self.logMsg('error', msg)
551
552            # Track this enum to detect followon duplicates
553            nameMap[name] = [elem, numVal, strVal]
554            if numVal is not None:
555                valueMap[numVal] = [elem, numVal, strVal]
556
557            # Add this enum to the list
558            stripped.append(elem)
559
560        # Return the list
561        return stripped
562
563    def misracstyle(self):
564        return False;
565
566    def misracppstyle(self):
567        return False;
568
569    def buildEnumCDecl(self, expand, groupinfo, groupName):
570        """Generate the C declaration for an enum"""
571        if self.genOpts is None:
572            raise MissingGeneratorOptionsError()
573        if self.genOpts.conventions is None:
574            raise MissingGeneratorOptionsConventionsError()
575
576        groupElem = groupinfo.elem
577
578        # Determine the required bit width for the enum group.
579        # 32 is the default, which generates C enum types for the values.
580        bitwidth = 32
581
582        # If the constFlagBits preference is set, 64 is the default for bitmasks
583        if self.genOpts.conventions.constFlagBits and groupElem.get('type') == 'bitmask':
584            bitwidth = 64
585
586        # Check for an explicitly defined bitwidth, which will override any defaults.
587        if groupElem.get('bitwidth'):
588            try:
589                bitwidth = int(groupElem.get('bitwidth'))
590            except ValueError as ve:
591                self.logMsg('error', 'Invalid value for bitwidth attribute (', groupElem.get('bitwidth'), ') for ', groupName, ' - must be an integer value\n')
592                exit(1)
593
594        usebitmask = False
595        usedefine = False
596
597        # Bitmask flags can be generated as either "static const uint{32,64}_t" values,
598        # or as 32-bit C enums. 64-bit types must use uint64_t values.
599        if groupElem.get('type') == 'bitmask':
600            if bitwidth > 32 or self.misracppstyle():
601                usebitmask = True
602            if self.misracstyle():
603                usedefine = True
604
605        if usedefine or usebitmask:
606            # Validate the bitwidth and generate values appropriately
607            if bitwidth > 64:
608                self.logMsg('error', 'Invalid value for bitwidth attribute (', groupElem.get('bitwidth'), ') for bitmask type ', groupName, ' - must be less than or equal to 64\n')
609                exit(1)
610            else:
611                return self.buildEnumCDecl_BitmaskOrDefine(groupinfo, groupName, bitwidth, usedefine)
612        else:
613            # Validate the bitwidth and generate values appropriately
614            if bitwidth > 32:
615                self.logMsg('error', 'Invalid value for bitwidth attribute (', groupElem.get('bitwidth'), ') for enum type ', groupName, ' - must be less than or equal to 32\n')
616                exit(1)
617            else:
618                return self.buildEnumCDecl_Enum(expand, groupinfo, groupName)
619
620    def buildEnumCDecl_BitmaskOrDefine(self, groupinfo, groupName, bitwidth, usedefine):
621        """Generate the C declaration for an "enum" that is actually a
622        set of flag bits"""
623        groupElem = groupinfo.elem
624        flagTypeName = groupElem.get('name')
625
626        # Prefix
627        body = "// Flag bits for " + flagTypeName + "\n"
628
629        if bitwidth == 64:
630            body += "typedef VkFlags64 %s;\n" % flagTypeName;
631        else:
632            body += "typedef VkFlags %s;\n" % flagTypeName;
633
634        # Maximum allowable value for a flag (unsigned 64-bit integer)
635        maxValidValue = 2**(64) - 1
636        minValidValue = 0
637
638        # Get a list of nested 'enum' tags.
639        enums = groupElem.findall('enum')
640
641        # Check for and report duplicates, and return a list with them
642        # removed.
643        enums = self.checkDuplicateEnums(enums)
644
645        # Accumulate non-numeric enumerant values separately and append
646        # them following the numeric values, to allow for aliases.
647        # NOTE: this does not do a topological sort yet, so aliases of
648        # aliases can still get in the wrong order.
649        aliasText = ''
650
651        # Loop over the nested 'enum' tags.
652        for elem in enums:
653            # Convert the value to an integer and use that to track min/max.
654            # Values of form -(number) are accepted but nothing more complex.
655            # Should catch exceptions here for more complex constructs. Not yet.
656            (numVal, strVal) = self.enumToValue(elem, True, bitwidth, True)
657            name = elem.get('name')
658
659            # Range check for the enum value
660            if numVal is not None and (numVal > maxValidValue or numVal < minValidValue):
661                self.logMsg('error', 'Allowable range for flag types in C is [', minValidValue, ',', maxValidValue, '], but', name, 'flag has a value outside of this (', strVal, ')\n')
662                exit(1)
663
664            decl = self.genRequirements(name, mustBeFound = False)
665
666            if self.isEnumRequired(elem):
667                protect = elem.get('protect')
668                if protect is not None:
669                    body += '#ifdef {}\n'.format(protect)
670
671                if usedefine:
672                    decl += "#define {} {}\n".format(name, strVal)
673                elif self.misracppstyle():
674                    decl += "static constexpr {} {} {{{}}};\n".format(flagTypeName, name, strVal)
675                else:
676                    # Some C compilers only allow initializing a 'static const' variable with a literal value.
677                    # So initializing an alias from another 'static const' value would fail to compile.
678                    # Work around this by chasing the aliases to get the actual value.
679                    while numVal is None:
680                        alias = self.registry.tree.find("enums/enum[@name='" + strVal + "']")
681                        if alias is not None:
682                            (numVal, strVal) = self.enumToValue(alias, True, bitwidth, True)
683                        else:
684                            self.logMsg('error', 'No such alias {} for enum {}'.format(strVal, name))
685                    decl += "static const {} {} = {};\n".format(flagTypeName, name, strVal)
686
687                if numVal is not None:
688                    body += decl
689                else:
690                    aliasText += decl
691
692                if protect is not None:
693                    body += '#endif\n'
694
695        # Now append the non-numeric enumerant values
696        body += aliasText
697
698        # Postfix
699
700        return ("bitmask", body)
701
702    def buildEnumCDecl_Enum(self, expand, groupinfo, groupName):
703        """Generate the C declaration for an enumerated type"""
704        groupElem = groupinfo.elem
705
706        # Break the group name into prefix and suffix portions for range
707        # enum generation
708        expandName = re.sub(r'([0-9]+|[a-z_])([A-Z0-9])', r'\1_\2', groupName).upper()
709        expandPrefix = expandName
710        expandSuffix = ''
711        expandSuffixMatch = re.search(r'[A-Z][A-Z]+$', groupName)
712        if expandSuffixMatch:
713            expandSuffix = '_' + expandSuffixMatch.group()
714            # Strip off the suffix from the prefix
715            expandPrefix = expandName.rsplit(expandSuffix, 1)[0]
716
717        # Prefix
718        body = ["typedef enum %s {" % groupName]
719
720        # @@ Should use the type="bitmask" attribute instead
721        isEnum = ('FLAG_BITS' not in expandPrefix)
722
723        # Allowable range for a C enum - which is that of a signed 32-bit integer
724        maxValidValue = 2**(32 - 1) - 1
725        minValidValue = (maxValidValue * -1) - 1
726
727        # Get a list of nested 'enum' tags.
728        enums = groupElem.findall('enum')
729
730        # Check for and report duplicates, and return a list with them
731        # removed.
732        enums = self.checkDuplicateEnums(enums)
733
734        # Loop over the nested 'enum' tags. Keep track of the minimum and
735        # maximum numeric values, if they can be determined; but only for
736        # core API enumerants, not extension enumerants. This is inferred
737        # by looking for 'extends' attributes.
738        minName = None
739
740        # Accumulate non-numeric enumerant values separately and append
741        # them following the numeric values, to allow for aliases.
742        # NOTE: this does not do a topological sort yet, so aliases of
743        # aliases can still get in the wrong order.
744        aliasText = []
745
746        maxName = None
747        minValue = None
748        maxValue = None
749        for elem in enums:
750            # Convert the value to an integer and use that to track min/max.
751            # Values of form -(number) are accepted but nothing more complex.
752            # Should catch exceptions here for more complex constructs. Not yet.
753            (numVal, strVal) = self.enumToValue(elem, True)
754            name = elem.get('name')
755
756            # Extension enumerants are only included if they are required
757            if self.isEnumRequired(elem):
758                decl = ''
759
760                protect = elem.get('protect')
761                if protect is not None:
762                    decl += '#ifdef {}\n'.format(protect)
763
764                # Indent requirements comment, if there is one
765                requirements = self.genRequirements(name, mustBeFound = False)
766                if requirements != '':
767                    requirements = '  ' + requirements
768                decl += requirements
769                decl += '    {} = {},'.format(name, strVal)
770
771                if protect is not None:
772                    decl += '\n#endif'
773
774                if numVal is not None:
775                    body.append(decl)
776                else:
777                    aliasText.append(decl)
778
779            # Range check for the enum value
780            if numVal is not None and (numVal > maxValidValue or numVal < minValidValue):
781                self.logMsg('error', 'Allowable range for C enum types is [', minValidValue, ',', maxValidValue, '], but', name, 'has a value outside of this (', strVal, ')\n')
782                exit(1)
783
784            # Do not track min/max for non-numbers (numVal is None)
785            if isEnum and numVal is not None and elem.get('extends') is None:
786                if minName is None:
787                    minName = maxName = name
788                    minValue = maxValue = numVal
789                elif minValue is None or numVal < minValue:
790                    minName = name
791                    minValue = numVal
792                elif maxValue is None or numVal > maxValue:
793                    maxName = name
794                    maxValue = numVal
795
796        # Now append the non-numeric enumerant values
797        body.extend(aliasText)
798
799        # Generate min/max value tokens - legacy use case.
800        if isEnum and expand:
801            body.extend((f'    {expandPrefix}_BEGIN_RANGE{expandSuffix} = {minName},',
802                         f'    {expandPrefix}_END_RANGE{expandSuffix} = {maxName},',
803                         f'    {expandPrefix}_RANGE_SIZE{expandSuffix} = ({maxName} - {minName} + 1),'))
804
805        # Generate a range-padding value to ensure the enum is 32 bits, but
806        # only in code generators, so it does not appear in documentation
807        if (self.genOpts.codeGenerator or
808            self.conventions.generate_max_enum_in_docs):
809            body.append(f'    {expandPrefix}_MAX_ENUM{expandSuffix} = 0x7FFFFFFF')
810
811        # Postfix
812        body.append("} %s;" % groupName)
813
814        # Determine appropriate section for this declaration
815        if groupElem.get('type') == 'bitmask':
816            section = 'bitmask'
817        else:
818            section = 'group'
819
820        return (section, '\n'.join(body))
821
822    def buildConstantCDecl(self, enuminfo, name, alias):
823        """Generate the C declaration for a constant (a single <enum>
824        value).
825
826        <enum> tags may specify their values in several ways, but are
827        usually just integers or floating-point numbers."""
828
829        (_, strVal) = self.enumToValue(enuminfo.elem, False)
830
831        if self.misracppstyle() and enuminfo.elem.get('type') and not alias:
832            # Generate e.g.: static constexpr uint32_t x = ~static_cast<uint32_t>(1U);
833            # This appeases MISRA "underlying type" rules.
834            typeStr = enuminfo.elem.get('type');
835            invert = '~' in strVal
836            number = strVal.strip("()~UL")
837            if typeStr != "float":
838                number += 'U'
839            strVal = "~" if invert else ""
840            strVal += "static_cast<" + typeStr + ">(" + number + ")"
841            body = 'static constexpr ' + typeStr.ljust(9) + name.ljust(33) + ' {' + strVal + '};'
842        elif enuminfo.elem.get('type') and not alias:
843            # Generate e.g.: #define x (~0ULL)
844            typeStr = enuminfo.elem.get('type');
845            invert = '~' in strVal
846            paren = '(' in strVal
847            number = strVal.strip("()~UL")
848            if typeStr != "float":
849                if typeStr == "uint64_t":
850                    number += 'ULL'
851                else:
852                    number += 'U'
853            strVal = "~" if invert else ""
854            strVal += number
855            if paren:
856                strVal = "(" + strVal + ")";
857            body = '#define ' + name.ljust(33) + ' ' + strVal;
858        else:
859            body = '#define ' + name.ljust(33) + ' ' + strVal
860
861        return body
862
863    def makeDir(self, path):
864        """Create a directory, if not already done.
865
866        Generally called from derived generators creating hierarchies."""
867        self.logMsg('diag', 'OutputGenerator::makeDir(' + path + ')')
868        if path not in self.madeDirs:
869            # This can get race conditions with multiple writers, see
870            # https://stackoverflow.com/questions/273192/
871            if not os.path.exists(path):
872                os.makedirs(path)
873            self.madeDirs[path] = None
874
875    def beginFile(self, genOpts):
876        """Start a new interface file
877
878        - genOpts - GeneratorOptions controlling what is generated and how"""
879
880        self.genOpts = genOpts
881        if self.genOpts is None:
882            raise MissingGeneratorOptionsError()
883        if self.genOpts.conventions is None:
884            raise MissingGeneratorOptionsConventionsError()
885        self.should_insert_may_alias_macro = \
886            self.genOpts.conventions.should_insert_may_alias_macro(self.genOpts)
887        self.file_suffix = self.genOpts.conventions.file_suffix
888
889        # Try to import the API dictionary, apimap.py, if it exists. Nothing
890        # in apimap.py cannot be extracted directly from the XML, and in the
891        # future we should do that.
892        if self.genOpts.genpath is not None:
893            try:
894                sys.path.insert(0, self.genOpts.genpath)
895                import apimap
896                self.apidict = apimap
897            except ImportError:
898                self.apidict = None
899
900        self.conventions = genOpts.conventions
901
902        # Open a temporary file for accumulating output.
903        if self.genOpts.filename is not None:
904            self.outFile = tempfile.NamedTemporaryFile(mode='w', encoding='utf-8', newline='\n', delete=False)
905        else:
906            self.outFile = sys.stdout
907
908    def endFile(self):
909        if self.errFile:
910            self.errFile.flush()
911        if self.warnFile:
912            self.warnFile.flush()
913        if self.diagFile:
914            self.diagFile.flush()
915        if self.outFile:
916            self.outFile.flush()
917            if self.outFile != sys.stdout and self.outFile != sys.stderr:
918                self.outFile.close()
919
920            if self.genOpts is None:
921                raise MissingGeneratorOptionsError()
922
923            # On successfully generating output, move the temporary file to the
924            # target file.
925            if self.genOpts.filename is not None:
926                if sys.platform == 'win32':
927                    directory = Path(self.genOpts.directory)
928                    if not Path.exists(directory):
929                        os.makedirs(directory)
930                shutil.copy(self.outFile.name, self.genOpts.directory + '/' + self.genOpts.filename)
931                os.remove(self.outFile.name)
932        self.genOpts = None
933
934    def beginFeature(self, interface, emit):
935        """Write interface for a feature and tag generated features as having been done.
936
937        - interface - element for the `<version>` / `<extension>` to generate
938        - emit - actually write to the header only when True"""
939        self.emit = emit
940        self.featureName = interface.get('name')
941        self.featureType = interface.get('type')
942        # If there is an additional 'protect' attribute in the feature, save it
943        self.featureExtraProtect = interface.get('protect')
944
945    def endFeature(self):
946        """Finish an interface file, closing it when done.
947
948        Derived classes responsible for emitting feature"""
949        self.featureName = None
950        self.featureType = None
951        self.featureExtraProtect = None
952
953    def genRequirements(self, name, mustBeFound = True):
954        """Generate text showing what core versions and extensions introduce
955        an API. This exists in the base Generator class because it is used by
956        the shared enumerant-generating interfaces (buildEnumCDecl, etc.).
957        Here it returns an empty string for most generators, but can be
958        overridden by e.g. DocGenerator.
959
960        - name - name of the API
961        - mustBeFound - If True, when requirements for 'name' cannot be
962          determined, a warning comment is generated.
963        """
964
965        return ''
966
967    def validateFeature(self, featureType, featureName):
968        """Validate we are generating something only inside a `<feature>` tag"""
969        if self.featureName is None:
970            raise UserWarning('Attempt to generate', featureType,
971                              featureName, 'when not in feature')
972
973    def genType(self, typeinfo, name, alias):
974        """Generate interface for a type
975
976        - typeinfo - TypeInfo for a type
977
978        Extend to generate as desired in your derived class."""
979        self.validateFeature('type', name)
980
981    def genStruct(self, typeinfo, typeName, alias):
982        """Generate interface for a C "struct" type.
983
984        - typeinfo - TypeInfo for a type interpreted as a struct
985
986        Extend to generate as desired in your derived class."""
987        self.validateFeature('struct', typeName)
988
989        # The mixed-mode <member> tags may contain no-op <comment> tags.
990        # It is convenient to remove them here where all output generators
991        # will benefit.
992        for member in typeinfo.elem.findall('.//member'):
993            for comment in member.findall('comment'):
994                member.remove(comment)
995
996    def genGroup(self, groupinfo, groupName, alias):
997        """Generate interface for a group of enums (C "enum")
998
999        - groupinfo - GroupInfo for a group.
1000
1001        Extend to generate as desired in your derived class."""
1002
1003        self.validateFeature('group', groupName)
1004
1005    def genEnum(self, enuminfo, typeName, alias):
1006        """Generate interface for an enum (constant).
1007
1008        - enuminfo - EnumInfo for an enum
1009        - name - enum name
1010
1011        Extend to generate as desired in your derived class."""
1012        self.validateFeature('enum', typeName)
1013
1014    def genCmd(self, cmd, cmdinfo, alias):
1015        """Generate interface for a command.
1016
1017        - cmdinfo - CmdInfo for a command
1018
1019        Extend to generate as desired in your derived class."""
1020        self.validateFeature('command', cmdinfo)
1021
1022    def genSpirv(self, spirv, spirvinfo, alias):
1023        """Generate interface for a spirv element.
1024
1025        - spirvinfo - SpirvInfo for a command
1026
1027        Extend to generate as desired in your derived class."""
1028        return
1029
1030    def genFormat(self, format, formatinfo, alias):
1031        """Generate interface for a format element.
1032
1033        - formatinfo - FormatInfo
1034
1035        Extend to generate as desired in your derived class."""
1036        return
1037
1038    def genSyncStage(self, stageinfo):
1039        """Generate interface for a sync stage element.
1040
1041        - stageinfo - SyncStageInfo
1042
1043        Extend to generate as desired in your derived class."""
1044        return
1045
1046    def genSyncAccess(self, accessinfo):
1047        """Generate interface for a sync stage element.
1048
1049        - accessinfo - AccessInfo
1050
1051        Extend to generate as desired in your derived class."""
1052        return
1053
1054    def genSyncPipeline(self, pipelineinfo):
1055        """Generate interface for a sync stage element.
1056
1057        - pipelineinfo - SyncPipelineInfo
1058
1059        Extend to generate as desired in your derived class."""
1060        return
1061
1062    def makeProtoName(self, name, tail):
1063        """Turn a `<proto>` `<name>` into C-language prototype
1064        and typedef declarations for that name.
1065
1066        - name - contents of `<name>` tag
1067        - tail - whatever text follows that tag in the Element"""
1068        if self.genOpts is None:
1069            raise MissingGeneratorOptionsError()
1070        return self.genOpts.apientry + name + tail
1071
1072    def makeTypedefName(self, name, tail):
1073        """Make the function-pointer typedef name for a command."""
1074        if self.genOpts is None:
1075            raise MissingGeneratorOptionsError()
1076        return '(' + self.genOpts.apientryp + 'PFN_' + name + tail + ')'
1077
1078    def makeCParamDecl(self, param, aligncol):
1079        """Return a string which is an indented, formatted
1080        declaration for a `<param>` or `<member>` block (e.g. function parameter
1081        or structure/union member).
1082
1083        - param - Element (`<param>` or `<member>`) to format
1084        - aligncol - if non-zero, attempt to align the nested `<name>` element
1085          at this column"""
1086        if self.genOpts is None:
1087            raise MissingGeneratorOptionsError()
1088        if self.genOpts.conventions is None:
1089            raise MissingGeneratorOptionsConventionsError()
1090        indent = '    '
1091        paramdecl = indent
1092        prefix = noneStr(param.text)
1093
1094        for elem in param:
1095            text = noneStr(elem.text)
1096            tail = noneStr(elem.tail)
1097
1098            if self.should_insert_may_alias_macro and self.genOpts.conventions.is_voidpointer_alias(elem.tag, text, tail):
1099                # OpenXR-specific macro insertion - but not in apiinc for the spec
1100                tail = self.genOpts.conventions.make_voidpointer_alias(tail)
1101            if elem.tag == 'name' and aligncol > 0:
1102                self.logMsg('diag', 'Aligning parameter', elem.text, 'to column', self.genOpts.alignFuncParam)
1103                # Align at specified column, if possible
1104                paramdecl = paramdecl.rstrip()
1105                oldLen = len(paramdecl)
1106                # This works around a problem where very long type names -
1107                # longer than the alignment column - would run into the tail
1108                # text.
1109                paramdecl = paramdecl.ljust(aligncol - 1) + ' '
1110                newLen = len(paramdecl)
1111                self.logMsg('diag', 'Adjust length of parameter decl from', oldLen, 'to', newLen, ':', paramdecl)
1112
1113            if (self.misracppstyle() and prefix.find('const ') != -1):
1114                # Change pointer type order from e.g. "const void *" to "void const *".
1115                # If the string starts with 'const', reorder it to be after the first type.
1116                paramdecl += prefix.replace('const ', '') + text + ' const' + tail
1117            else:
1118                paramdecl += prefix + text + tail
1119
1120            # Clear prefix for subsequent iterations
1121            prefix = ''
1122
1123        paramdecl = paramdecl + prefix
1124
1125        if aligncol == 0:
1126            # Squeeze out multiple spaces other than the indentation
1127            paramdecl = indent + ' '.join(paramdecl.split())
1128        return paramdecl
1129
1130    def getCParamTypeLength(self, param):
1131        """Return the length of the type field is an indented, formatted
1132        declaration for a `<param>` or `<member>` block (e.g. function parameter
1133        or structure/union member).
1134
1135        - param - Element (`<param>` or `<member>`) to identify"""
1136        if self.genOpts is None:
1137            raise MissingGeneratorOptionsError()
1138        if self.genOpts.conventions is None:
1139            raise MissingGeneratorOptionsConventionsError()
1140
1141        # Allow for missing <name> tag
1142        newLen = 0
1143        paramdecl = '    ' + noneStr(param.text)
1144        for elem in param:
1145            text = noneStr(elem.text)
1146            tail = noneStr(elem.tail)
1147
1148            if self.should_insert_may_alias_macro and self.genOpts.conventions.is_voidpointer_alias(elem.tag, text, tail):
1149                # OpenXR-specific macro insertion
1150                tail = self.genOpts.conventions.make_voidpointer_alias(tail)
1151            if elem.tag == 'name':
1152                # Align at specified column, if possible
1153                newLen = len(paramdecl.rstrip())
1154                self.logMsg('diag', 'Identifying length of', elem.text, 'as', newLen)
1155            paramdecl += text + tail
1156
1157        return newLen
1158
1159    def getMaxCParamTypeLength(self, info):
1160        """Return the length of the longest type field for a member/parameter.
1161
1162        - info - TypeInfo or CommandInfo.
1163        """
1164        lengths = (self.getCParamTypeLength(member)
1165                   for member in info.getMembers())
1166        return max(lengths)
1167
1168    def getHandleParent(self, typename):
1169        """Get the parent of a handle object."""
1170        if self.registry is None:
1171            raise MissingRegistryError()
1172
1173        info = self.registry.typedict.get(typename)
1174        if info is None:
1175            return None
1176
1177        elem = info.elem
1178        if elem is not None:
1179            return elem.get('parent')
1180
1181        return None
1182
1183    def iterateHandleAncestors(self, typename):
1184        """Iterate through the ancestors of a handle type."""
1185        current = self.getHandleParent(typename)
1186        while current is not None:
1187            yield current
1188            current = self.getHandleParent(current)
1189
1190    def getHandleAncestors(self, typename):
1191        """Get the ancestors of a handle object."""
1192        return list(self.iterateHandleAncestors(typename))
1193
1194    def getTypeCategory(self, typename):
1195        """Get the category of a type."""
1196        if self.registry is None:
1197            raise MissingRegistryError()
1198
1199        info = self.registry.typedict.get(typename)
1200        if info is None:
1201            return None
1202
1203        elem = info.elem
1204        if elem is not None:
1205            return elem.get('category')
1206        return None
1207
1208    def isStructAlwaysValid(self, structname):
1209        """Try to do check if a structure is always considered valid (i.e. there is no rules to its acceptance)."""
1210        # A conventions object is required for this call.
1211        if not self.conventions:
1212            raise RuntimeError("To use isStructAlwaysValid, be sure your options include a Conventions object.")
1213        if self.registry is None:
1214            raise MissingRegistryError()
1215
1216        if self.conventions.type_always_valid(structname):
1217            return True
1218
1219        category = self.getTypeCategory(structname)
1220        if self.conventions.category_requires_validation(category):
1221            return False
1222
1223        info = self.registry.typedict.get(structname)
1224        if info is None:
1225            self.logMsg('error', f'isStructAlwaysValid({structname}) - structure not found in typedict')
1226
1227        members = info.getMembers()
1228
1229        for member in members:
1230            member_name = getElemName(member)
1231            if member_name in (self.conventions.structtype_member_name,
1232                               self.conventions.nextpointer_member_name):
1233                return False
1234
1235            if member.get('noautovalidity'):
1236                return False
1237
1238            member_type = getElemType(member)
1239
1240            if member_type in ('void', 'char') or self.paramIsArray(member) or self.paramIsPointer(member):
1241                return False
1242
1243            if self.conventions.type_always_valid(member_type):
1244                continue
1245
1246            member_category = self.getTypeCategory(member_type)
1247
1248            if self.conventions.category_requires_validation(member_category):
1249                return False
1250
1251            if member_category in ('struct', 'union'):
1252                if self.isStructAlwaysValid(member_type) is False:
1253                    return False
1254
1255        return True
1256
1257    def paramIsArray(self, param):
1258        """Check if the parameter passed in is a pointer to an array.
1259
1260        param           the XML information for the param
1261        """
1262        return param.get('len') is not None
1263
1264    def paramIsPointer(self, param):
1265        """Check if the parameter passed in is a pointer.
1266
1267        param           the XML information for the param
1268        """
1269        tail = param.find('type').tail
1270        return tail is not None and '*' in tail
1271
1272    def isEnumRequired(self, elem):
1273        """Return True if this `<enum>` element is
1274        required, False otherwise
1275
1276        - elem - `<enum>` element to test"""
1277        required = elem.get('required') is not None
1278        self.logMsg('diag', 'isEnumRequired:', elem.get('name'),
1279                    '->', required)
1280        return required
1281
1282        # @@@ This code is overridden by equivalent code now run in
1283        # @@@ Registry.generateFeature
1284
1285        required = False
1286
1287        extname = elem.get('extname')
1288        if extname is not None:
1289            # 'supported' attribute was injected when the <enum> element was
1290            # moved into the <enums> group in Registry.parseTree()
1291            if self.genOpts.defaultExtensions == elem.get('supported'):
1292                required = True
1293            elif re.match(self.genOpts.addExtensions, extname) is not None:
1294                required = True
1295        elif elem.get('version') is not None:
1296            required = re.match(self.genOpts.emitversions, elem.get('version')) is not None
1297        else:
1298            required = True
1299
1300        return required
1301
1302    def makeCDecls(self, cmd):
1303        """Return C prototype and function pointer typedef for a
1304        `<command>` Element, as a two-element list of strings.
1305
1306        - cmd - Element containing a `<command>` tag"""
1307        if self.genOpts is None:
1308            raise MissingGeneratorOptionsError()
1309        proto = cmd.find('proto')
1310        params = cmd.findall('param')
1311        # Begin accumulating prototype and typedef strings
1312        pdecl = self.genOpts.apicall
1313        tdecl = 'typedef '
1314
1315        # Insert the function return type/name.
1316        # For prototypes, add APIENTRY macro before the name
1317        # For typedefs, add (APIENTRY *<name>) around the name and
1318        #   use the PFN_cmdnameproc naming convention.
1319        # Done by walking the tree for <proto> element by element.
1320        # etree has elem.text followed by (elem[i], elem[i].tail)
1321        #   for each child element and any following text
1322        # Leading text
1323        pdecl += noneStr(proto.text)
1324        tdecl += noneStr(proto.text)
1325        # For each child element, if it is a <name> wrap in appropriate
1326        # declaration. Otherwise append its contents and tail contents.
1327        for elem in proto:
1328            text = noneStr(elem.text)
1329            tail = noneStr(elem.tail)
1330            if elem.tag == 'name':
1331                pdecl += self.makeProtoName(text, tail)
1332                tdecl += self.makeTypedefName(text, tail)
1333            else:
1334                pdecl += text + tail
1335                tdecl += text + tail
1336
1337        if self.genOpts.alignFuncParam == 0:
1338            # Squeeze out multiple spaces - there is no indentation
1339            pdecl = ' '.join(pdecl.split())
1340            tdecl = ' '.join(tdecl.split())
1341
1342        # Now add the parameter declaration list, which is identical
1343        # for prototypes and typedefs. Concatenate all the text from
1344        # a <param> node without the tags. No tree walking required
1345        # since all tags are ignored.
1346        # Uses: self.indentFuncProto
1347        # self.indentFuncPointer
1348        # self.alignFuncParam
1349        n = len(params)
1350        # Indented parameters
1351        if n > 0:
1352            indentdecl = '(\n'
1353            indentdecl += ',\n'.join(self.makeCParamDecl(p, self.genOpts.alignFuncParam)
1354                                     for p in params)
1355            indentdecl += ');'
1356        else:
1357            indentdecl = '(void);'
1358        # Non-indented parameters
1359        paramdecl = '('
1360        if n > 0:
1361            paramnames = []
1362            if self.misracppstyle():
1363                for p in params:
1364                    param = ''
1365                    firstIter = True;
1366                    for t in p.itertext():
1367                        if (firstIter):
1368                            prefix = t
1369                            firstIter = False
1370                        else:
1371                            # Change pointer type order from e.g. "const void *" to "void const *".
1372                            # If the string starts with 'const', reorder it to be after the first type.
1373                            if (prefix.find('const ') != -1):
1374                                param += prefix.replace('const ', '') + t + ' const '
1375                            else:
1376                                param += prefix + t
1377                            # Clear prefix for subsequent iterations
1378                            prefix = ''
1379                    paramnames.append(param);
1380            else:
1381                paramnames = (''.join(t for t in p.itertext())
1382                              for p in params)
1383            paramdecl += ', '.join(paramnames)
1384        else:
1385            paramdecl += 'void'
1386        paramdecl += ");"
1387        return [pdecl + indentdecl, tdecl + paramdecl]
1388
1389    def newline(self):
1390        """Print a newline to the output file (utility function)"""
1391        write('', file=self.outFile)
1392
1393    def setRegistry(self, registry):
1394        self.registry = registry
1395