xref: /aosp_15_r20/external/mesa3d/src/gfxstream/codegen/scripts/generator.py (revision 6104692788411f58d303aa86923a9ff6ecaded22)
1#!/usr/bin/python3 -i
2#
3# Copyright 2013-2023 The Khronos Group Inc.
4# Copyright 2023-2024 Google Inc.
5#
6# SPDX-License-Identifier: Apache-2.0
7"""Base class for source/header/doc generators, as well as some utility functions."""
8
9from __future__ import unicode_literals
10
11import io
12import os
13import re
14import shutil
15import sys
16import tempfile
17try:
18    from pathlib import Path
19except ImportError:
20    from pathlib2 import Path  # type: ignore
21
22CATEGORIES_REQUIRING_VALIDATION = set(('handle',
23                                       'enum',
24                                       'bitmask',
25                                       'basetype',
26                                       None))
27
28# These are basic C types pulled in via openxr_platform_defines.h
29TYPES_KNOWN_ALWAYS_VALID = set(('char',
30                                'float',
31                                'int8_t', 'uint8_t',
32                                'int16_t', 'uint16_t',
33                                'int32_t', 'uint32_t',
34                                'int64_t', 'uint64_t',
35                                'size_t',
36                                'intptr_t', 'uintptr_t',
37                                'int',
38                                ))
39
40def getElemName(elem, default=None):
41    """Get the name associated with an element, either a name child or name attribute."""
42    name_elem = elem.find('name')
43    if name_elem is not None:
44        return name_elem.text
45    # Fallback if there is no child.
46    return elem.get('name', default)
47
48
49def getElemType(elem, default=None):
50    """Get the type associated with an element, either a type child or type attribute."""
51    type_elem = elem.find('type')
52    if type_elem is not None:
53        return type_elem.text
54    # Fallback if there is no child.
55    return elem.get('type', default)
56
57def write(*args, **kwargs):
58    file = kwargs.pop('file', sys.stdout)
59    end = kwargs.pop('end', '\n')
60    file.write(' '.join(str(arg) for arg in args))
61    file.write(end)
62
63def category_requires_validation(category):
64    """Return True if the given type 'category' always requires validation.
65
66    Defaults to a reasonable implementation.
67
68    May override."""
69    return category in CATEGORIES_REQUIRING_VALIDATION
70
71def type_always_valid(typename):
72    """Return True if the given type name is always valid (never requires validation).
73
74    This is for things like integers.
75
76    Defaults to a reasonable implementation.
77
78    May override."""
79    return typename in TYPES_KNOWN_ALWAYS_VALID
80
81def noneStr(s):
82    """Return string argument, or "" if argument is None.
83
84    Used in converting etree Elements into text.
85    s - string to convert"""
86    if s:
87        return s
88    return ""
89
90def regSortCategoryKey(feature):
91    """Sort key for regSortFeatures.
92    Sorts by category of the feature name string:
93
94    - Core API features (those defined with a `<feature>` tag)
95        - (sort VKSC after VK - this is Vulkan-specific)
96    - ARB/KHR/OES (Khronos extensions)
97    - other       (EXT/vendor extensions)"""
98
99    if feature.elem.tag == 'feature':
100        if feature.name.startswith('VKSC'):
101            return 0.5
102        else:
103            return 0
104    if (feature.category == 'ARB'
105        or feature.category == 'KHR'
106            or feature.category == 'OES'):
107        return 1
108
109    return 2
110
111
112def regSortOrderKey(feature):
113    """Sort key for regSortFeatures - key is the sortorder attribute."""
114
115    return feature.sortorder
116
117
118def regSortNameKey(feature):
119    """Sort key for regSortFeatures - key is the extension name."""
120
121    return feature.name
122
123
124def regSortFeatureVersionKey(feature):
125    """Sort key for regSortFeatures - key is the feature version.
126    `<extension>` elements all have version number 0."""
127
128    return float(feature.versionNumber)
129
130
131def regSortExtensionNumberKey(feature):
132    """Sort key for regSortFeatures - key is the extension number.
133    `<feature>` elements all have extension number 0."""
134
135    return int(feature.number)
136
137
138def regSortFeatures(featureList):
139    """Default sort procedure for features.
140
141    - Sorts by explicit sort order (default 0) relative to other features
142    - then by feature category ('feature' or 'extension'),
143    - then by version number (for features)
144    - then by extension number (for extensions)"""
145    featureList.sort(key=regSortExtensionNumberKey)
146    featureList.sort(key=regSortFeatureVersionKey)
147    featureList.sort(key=regSortCategoryKey)
148    featureList.sort(key=regSortOrderKey)
149
150
151class MissingGeneratorOptionsError(RuntimeError):
152    """Error raised when a Generator tries to do something that requires GeneratorOptions but it is None."""
153
154    def __init__(self, msg=None):
155        full_msg = 'Missing generator options object self.genOpts'
156        if msg:
157            full_msg += ': ' + msg
158        super().__init__(full_msg)
159
160
161class MissingRegistryError(RuntimeError):
162    """Error raised when a Generator tries to do something that requires a Registry object but it is None."""
163
164    def __init__(self, msg=None):
165        full_msg = 'Missing Registry object self.registry'
166        if msg:
167            full_msg += ': ' + msg
168        super().__init__(full_msg)
169
170class GeneratorOptions:
171    """Base class for options used during header/documentation production.
172
173    These options are target language independent, and used by
174    Registry.apiGen() and by base OutputGenerator objects."""
175
176    def __init__(self,
177                 filename=None,
178                 directory='.',
179                 versions='.*',
180                 emitversions='.*',
181                 addExtensions=None,
182                 emitExtensions=None,
183                 sortProcedure=regSortFeatures,
184                ):
185        """Constructor.
186
187        Arguments:
188
189        an object that implements ConventionsBase
190        - filename - basename of file to generate, or None to write to stdout.
191        - directory - directory in which to generate filename
192        - versions - regex matching API versions to process interfaces for.
193        Normally `'.*'` or `'[0-9][.][0-9]'` to match all defined versions.
194        - emitversions - regex matching API versions to actually emit
195        interfaces for (though all requested versions are considered
196        when deciding which interfaces to generate). For GL 4.3 glext.h,
197        this might be `'1[.][2-5]|[2-4][.][0-9]'`.
198        - addExtensions - regex matching names of additional extensions
199        to include. Defaults to None.
200        - emitExtensions - regex matching names of extensions to actually emit
201        interfaces for (though all requested versions are considered when
202        deciding which interfaces to generate). Defaults to None.
203        - sortProcedure - takes a list of FeatureInfo objects and sorts
204        them in place to a preferred order in the generated output.
205
206        Default is
207          - core API versions
208          - Khronos (ARB/KHR/OES) extensions
209          - All other extensions
210          - By core API version number or extension number in each group.
211
212        The regex patterns can be None or empty, in which case they match
213        nothing."""
214
215        self.filename = filename
216        "basename of file to generate, or None to write to stdout."
217
218        self.directory = directory
219        "directory in which to generate filename"
220
221        self.versions = self.emptyRegex(versions)
222        """regex matching API versions to process interfaces for.
223        Normally `'.*'` or `'[0-9][.][0-9]'` to match all defined versions."""
224
225        self.emitversions = self.emptyRegex(emitversions)
226        """regex matching API versions to actually emit
227        interfaces for (though all requested versions are considered
228        when deciding which interfaces to generate). For GL 4.3 glext.h,
229        this might be `'1[.][2-5]|[2-4][.][0-9]'`."""
230
231        self.addExtensions = self.emptyRegex(addExtensions)
232        """regex matching names of additional extensions
233        to include. Defaults to None."""
234
235        self.emitExtensions = self.emptyRegex(emitExtensions)
236        """regex matching names of extensions to actually emit
237        interfaces for (though all requested versions are considered when
238        deciding which interfaces to generate)."""
239
240        self.sortProcedure = sortProcedure
241        """takes a list of FeatureInfo objects and sorts
242        them in place to a preferred order in the generated output.
243        Default is core API versions, ARB/KHR/OES extensions, all
244        other extensions, alphabetically within each group."""
245
246        self.registry = None
247        """Populated later with the registry object."""
248
249    def emptyRegex(self, pat):
250        """Substitute a regular expression which matches no version
251        or extension names for None or the empty string."""
252        if not pat:
253            return '_nomatch_^'
254
255        return pat
256
257
258class OutputGenerator:
259    """Generate specified API interfaces in a specific style, such as a C header.
260
261    Base class for generating API interfaces.
262    Manages basic logic, logging, and output file control.
263    Derived classes actually generate formatted output.
264    """
265
266    # categoryToPath - map XML 'category' to include file directory name
267    categoryToPath = {
268        'bitmask': 'flags',
269        'enum': 'enums',
270        'funcpointer': 'funcpointers',
271        'handle': 'handles',
272        'define': 'defines',
273        'basetype': 'basetypes',
274    }
275
276    def __init__(self, errFile=sys.stderr, warnFile=sys.stderr, diagFile=sys.stdout):
277        """Constructor
278
279        - errFile, warnFile, diagFile - file handles to write errors,
280          warnings, diagnostics to. May be None to not write."""
281        self.outFile = None
282        self.errFile = errFile
283        self.warnFile = warnFile
284        self.diagFile = diagFile
285        # Internal state
286        self.featureName = None
287        """The current feature name being generated."""
288
289        self.featureType = None
290        """The current feature type being generated."""
291
292        self.genOpts = None
293        """The GeneratorOptions subclass instance."""
294
295        self.registry = None
296        """The specification registry object."""
297
298        self.featureDictionary = {}
299        """The dictionary of dictionaries of API features."""
300
301        # Used for extension enum value generation
302        self.extBase = 1000000000
303        self.extBlockSize = 1000
304        self.madeDirs = {}
305
306        # API dictionary, which may be loaded by the beginFile method of
307        # derived generators.
308        self.apidict = None
309
310    def enumToValue(self, elem, needsNum, bitwidth = 32,
311                    forceSuffix = False, parent_for_alias_dereference=None):
312        """Parse and convert an `<enum>` tag into a value.
313
314        - elem - <enum> Element
315        - needsNum - generate a numeric representation of the element value
316        - bitwidth - size of the numeric representation in bits (32 or 64)
317        - forceSuffix - if True, always use a 'U' / 'ULL' suffix on integers
318        - parent_for_alias_dereference - if not None, an Element containing
319          the parent of elem, used to look for elements this is an alias of
320
321        Returns a list:
322
323        - first element - integer representation of the value, or None
324          if needsNum is False. The value must be a legal number
325          if needsNum is True.
326        - second element - string representation of the value
327
328        There are several possible representations of values.
329
330        - A 'value' attribute simply contains the value.
331        - A 'bitpos' attribute defines a value by specifying the bit
332          position which is set in that value.
333        - An 'offset','extbase','extends' triplet specifies a value
334          as an offset to a base value defined by the specified
335          'extbase' extension name, which is then cast to the
336          typename specified by 'extends'. This requires probing
337          the registry database, and imbeds knowledge of the
338          API extension enum scheme in this function.
339        - An 'alias' attribute contains the name of another enum
340          which this is an alias of. The other enum must be
341          declared first when emitting this enum."""
342        if self.genOpts is None:
343            raise MissingGeneratorOptionsError()
344
345        name = elem.get('name')
346        numVal = None
347        if 'value' in elem.keys():
348            value = elem.get('value')
349            # print('About to translate value =', value, 'type =', type(value))
350            if needsNum:
351                numVal = int(value, 0)
352            # If there is a non-integer, numeric 'type' attribute (e.g. 'u' or
353            # 'ull'), append it to the string value.
354            # t = enuminfo.elem.get('type')
355            # if t is not None and t != '' and t != 'i' and t != 's':
356            #     value += enuminfo.type
357            if forceSuffix:
358              if bitwidth == 64:
359                value = value + 'ULL'
360              else:
361                value = value + 'U'
362            return [numVal, value]
363        if 'bitpos' in elem.keys():
364            value = elem.get('bitpos')
365            bitpos = int(value, 0)
366            numVal = 1 << bitpos
367            value = '0x%08x' % numVal
368            if bitwidth == 64 or bitpos >= 32:
369              value = value + 'ULL'
370            elif forceSuffix:
371              value = value + 'U'
372            return [numVal, value]
373        if 'offset' in elem.keys():
374            # Obtain values in the mapping from the attributes
375            enumNegative = False
376            offset = int(elem.get('offset'), 0)
377            extnumber = int(elem.get('extnumber'), 0)
378            extends = elem.get('extends')
379            if 'dir' in elem.keys():
380                enumNegative = True
381            # Now determine the actual enumerant value, as defined
382            # in the "Layers and Extensions" appendix of the spec.
383            numVal = self.extBase + (extnumber - 1) * self.extBlockSize + offset
384            if enumNegative:
385                numVal *= -1
386            value = '%d' % numVal
387            # More logic needed!
388            return [numVal, value]
389        if 'alias' in elem.keys():
390            alias_of = elem.get('alias')
391            if parent_for_alias_dereference is None:
392                return (None, alias_of)
393            siblings = parent_for_alias_dereference.findall('enum')
394            for sib in siblings:
395                sib_name = sib.get('name')
396                if sib_name == alias_of:
397                    return self.enumToValue(sib, needsNum)
398            raise RuntimeError("Could not find the aliased enum value")
399        return [None, None]
400
401    def buildConstantCDecl(self, enuminfo, name, alias):
402        """Generate the C declaration for a constant (a single <enum>
403        value).
404
405        <enum> tags may specify their values in several ways, but are
406        usually just integers or floating-point numbers."""
407
408        (_, strVal) = self.enumToValue(enuminfo.elem, False)
409
410        if enuminfo.elem.get('type') and not alias:
411            # Generate e.g.: #define x (~0ULL)
412            typeStr = enuminfo.elem.get('type');
413            invert = '~' in strVal
414            paren = '(' in strVal
415            number = strVal.strip("()~UL")
416            if typeStr != "float":
417                if typeStr == "uint64_t":
418                    number += 'ULL'
419                else:
420                    number += 'U'
421            strVal = "~" if invert else ""
422            strVal += number
423            if paren:
424                strVal = "(" + strVal + ")";
425            body = '#define ' + name.ljust(33) + ' ' + strVal;
426        else:
427            body = '#define ' + name.ljust(33) + ' ' + strVal
428
429        return body
430
431    def beginFile(self, genOpts):
432        """Start a new interface file
433
434        - genOpts - GeneratorOptions controlling what is generated and how"""
435
436        self.genOpts = genOpts
437        if self.genOpts is None:
438            raise MissingGeneratorOptionsError()
439
440        # Open a temporary file for accumulating output.
441        if self.genOpts.filename is not None:
442            self.outFile = tempfile.NamedTemporaryFile(mode='w', encoding='utf-8', newline='\n', delete=False)
443        else:
444            self.outFile = sys.stdout
445
446    def endFile(self):
447        if self.errFile:
448            self.errFile.flush()
449        if self.warnFile:
450            self.warnFile.flush()
451        if self.diagFile:
452            self.diagFile.flush()
453        if self.outFile:
454            self.outFile.flush()
455            if self.outFile != sys.stdout and self.outFile != sys.stderr:
456                self.outFile.close()
457
458            if self.genOpts is None:
459                raise MissingGeneratorOptionsError()
460
461            # On successfully generating output, move the temporary file to the
462            # target file.
463            if self.genOpts.filename is not None:
464                if sys.platform == 'win32':
465                    directory = Path(self.genOpts.directory)
466                    if not Path.exists(directory):
467                        os.makedirs(directory)
468                shutil.copy(self.outFile.name, self.genOpts.directory + '/' + self.genOpts.filename)
469                os.remove(self.outFile.name)
470        self.genOpts = None
471
472    def beginFeature(self, interface, emit):
473        """Write interface for a feature and tag generated features as having been done.
474
475        - interface - element for the `<version>` / `<extension>` to generate
476        - emit - actually write to the header only when True"""
477        self.emit = emit
478        self.featureName = interface.get('name')
479        self.featureType = interface.get('type')
480
481    def endFeature(self):
482        """Finish an interface file, closing it when done.
483
484        Derived classes responsible for emitting feature"""
485        self.featureName = None
486        self.featureType = None
487
488    def validateFeature(self, featureType, featureName):
489        """Validate we are generating something only inside a `<feature>` tag"""
490        if self.featureName is None:
491            raise UserWarning('Attempt to generate', featureType,
492                              featureName, 'when not in feature')
493
494    def genType(self, typeinfo, name, alias):
495        """Generate interface for a type
496
497        - typeinfo - TypeInfo for a type
498
499        Extend to generate as desired in your derived class."""
500        self.validateFeature('type', name)
501
502    def genStruct(self, typeinfo, typeName, alias):
503        """Generate interface for a C "struct" type.
504
505        - typeinfo - TypeInfo for a type interpreted as a struct
506
507        Extend to generate as desired in your derived class."""
508        self.validateFeature('struct', typeName)
509
510        # The mixed-mode <member> tags may contain no-op <comment> tags.
511        # It is convenient to remove them here where all output generators
512        # will benefit.
513        for member in typeinfo.elem.findall('.//member'):
514            for comment in member.findall('comment'):
515                member.remove(comment)
516
517    def genGroup(self, groupinfo, groupName, alias):
518        """Generate interface for a group of enums (C "enum")
519
520        - groupinfo - GroupInfo for a group.
521
522        Extend to generate as desired in your derived class."""
523
524        self.validateFeature('group', groupName)
525
526    def genEnum(self, enuminfo, typeName, alias):
527        """Generate interface for an enum (constant).
528
529        - enuminfo - EnumInfo for an enum
530        - name - enum name
531
532        Extend to generate as desired in your derived class."""
533        self.validateFeature('enum', typeName)
534
535    def genCmd(self, cmd, cmdinfo, alias):
536        """Generate interface for a command.
537
538        - cmdinfo - CmdInfo for a command
539
540        Extend to generate as desired in your derived class."""
541        self.validateFeature('command', cmdinfo)
542
543    def makeProtoName(self, name, tail):
544        """Turn a `<proto>` `<name>` into C-language prototype
545        and typedef declarations for that name.
546
547        - name - contents of `<name>` tag
548        - tail - whatever text follows that tag in the Element"""
549        if self.genOpts is None:
550            raise MissingGeneratorOptionsError()
551        return self.genOpts.apientry + name + tail
552
553    def makeTypedefName(self, name, tail):
554        """Make the function-pointer typedef name for a command."""
555        if self.genOpts is None:
556            raise MissingGeneratorOptionsError()
557        return '(' + self.genOpts.apientryp + 'PFN_' + name + tail + ')'
558
559    def makeCParamDecl(self, param, aligncol):
560        """Return a string which is an indented, formatted
561        declaration for a `<param>` or `<member>` block (e.g. function parameter
562        or structure/union member).
563
564        - param - Element (`<param>` or `<member>`) to format
565        - aligncol - if non-zero, attempt to align the nested `<name>` element
566          at this column"""
567        if self.genOpts is None:
568            raise MissingGeneratorOptionsError()
569        indent = '    '
570        paramdecl = indent
571        prefix = noneStr(param.text)
572
573        for elem in param:
574            text = noneStr(elem.text)
575            tail = noneStr(elem.tail)
576
577            if elem.tag == 'name' and aligncol > 0:
578                # Align at specified column, if possible
579                paramdecl = paramdecl.rstrip()
580                oldLen = len(paramdecl)
581                # This works around a problem where very long type names -
582                # longer than the alignment column - would run into the tail
583                # text.
584                paramdecl = paramdecl.ljust(aligncol - 1) + ' '
585                newLen = len(paramdecl)
586
587            paramdecl += prefix + text + tail
588
589            # Clear prefix for subsequent iterations
590            prefix = ''
591
592        paramdecl = paramdecl + prefix
593
594        if aligncol == 0:
595            # Squeeze out multiple spaces other than the indentation
596            paramdecl = indent + ' '.join(paramdecl.split())
597        return paramdecl
598
599    def getCParamTypeLength(self, param):
600        """Return the length of the type field is an indented, formatted
601        declaration for a `<param>` or `<member>` block (e.g. function parameter
602        or structure/union member).
603
604        - param - Element (`<param>` or `<member>`) to identify"""
605        if self.genOpts is None:
606            raise MissingGeneratorOptionsError()
607
608        # Allow for missing <name> tag
609        newLen = 0
610        paramdecl = '    ' + noneStr(param.text)
611        for elem in param:
612            text = noneStr(elem.text)
613            tail = noneStr(elem.tail)
614
615            if elem.tag == 'name':
616                # Align at specified column, if possible
617                newLen = len(paramdecl.rstrip())
618            paramdecl += text + tail
619
620        return newLen
621
622    def getMaxCParamTypeLength(self, info):
623        """Return the length of the longest type field for a member/parameter.
624        - info - TypeInfo or CommandInfo.
625        """
626        lengths = (self.getCParamTypeLength(member)
627                   for member in info.getMembers())
628        return max(lengths)
629
630    def getTypeCategory(self, typename):
631        """Get the category of a type."""
632        if self.registry is None:
633            raise MissingRegistryError()
634
635        info = self.registry.typedict.get(typename)
636        if info is None:
637            return None
638
639        elem = info.elem
640        if elem is not None:
641            return elem.get('category')
642        return None
643
644    def isStructAlwaysValid(self, structname):
645        """Try to do check if a structure is always considered valid (i.e. there is no rules to its acceptance)."""
646        # A conventions object is required for this call.
647        if self.registry is None:
648            raise MissingRegistryError()
649
650        if type_always_valid(structname):
651            return True
652
653        category = self.getTypeCategory(structname)
654        if category_requires_validation(category):
655            return False
656
657        info = self.registry.typedict.get(structname)
658        members = info.getMembers()
659
660        for member in members:
661            member_name = getElemName(member)
662            if member_name in ('sType', 'pNext'):
663                return False
664
665            if member.get('noautovalidity'):
666                return False
667
668            member_type = getElemType(member)
669
670            if member_type in ('void', 'char') or self.paramIsArray(member) or self.paramIsPointer(member):
671                return False
672
673            if type_always_valid(member_type):
674                continue
675
676            member_category = self.getTypeCategory(member_type)
677
678            if category_requires_validation(member_category):
679                return False
680
681            if member_category in ('struct', 'union'):
682                if self.isStructAlwaysValid(member_type) is False:
683                    return False
684
685        return True
686
687    def paramIsArray(self, param):
688        """Check if the parameter passed in is a pointer to an array.
689
690        param           the XML information for the param
691        """
692        return param.get('len') is not None
693
694    def paramIsPointer(self, param):
695        """Check if the parameter passed in is a pointer.
696
697        param           the XML information for the param
698        """
699        tail = param.find('type').tail
700        return tail is not None and '*' in tail
701
702    def isEnumRequired(self, elem):
703        """Return True if this `<enum>` element is
704        required, False otherwise
705
706        - elem - `<enum>` element to test"""
707        required = elem.get('required') is not None
708        return required
709
710        # @@@ This code is overridden by equivalent code now run in
711        # @@@ Registry.generateFeature
712
713        required = False
714
715        extname = elem.get('extname')
716        if extname is not None:
717            # 'supported' attribute was injected when the <enum> element was
718            # moved into the <enums> group in Registry.parseTree()
719            if 'vulkan' == elem.get('supported'):
720                required = True
721            elif re.match(self.genOpts.addExtensions, extname) is not None:
722                required = True
723        elif elem.get('version') is not None:
724            required = re.match(self.genOpts.emitversions, elem.get('version')) is not None
725        else:
726            required = True
727
728        return required
729
730    def makeCDecls(self, cmd):
731        """Return C prototype and function pointer typedef for a
732        `<command>` Element, as a two-element list of strings.
733
734        - cmd - Element containing a `<command>` tag"""
735        if self.genOpts is None:
736            raise MissingGeneratorOptionsError()
737        proto = cmd.find('proto')
738        params = cmd.findall('param')
739        # Begin accumulating prototype and typedef strings
740        pdecl = 'VKAPI_ATTR '
741        tdecl = 'typedef '
742
743        # Insert the function return type/name.
744        # For prototypes, add APIENTRY macro before the name
745        # For typedefs, add (APIENTRY *<name>) around the name and
746        #   use the PFN_cmdnameproc naming convention.
747        # Done by walking the tree for <proto> element by element.
748        # etree has elem.text followed by (elem[i], elem[i].tail)
749        #   for each child element and any following text
750        # Leading text
751        pdecl += noneStr(proto.text)
752        tdecl += noneStr(proto.text)
753        # For each child element, if it is a <name> wrap in appropriate
754        # declaration. Otherwise append its contents and tail contents.
755        for elem in proto:
756            text = noneStr(elem.text)
757            tail = noneStr(elem.tail)
758            if elem.tag == 'name':
759                pdecl += self.makeProtoName(text, tail)
760                tdecl += self.makeTypedefName(text, tail)
761            else:
762                pdecl += text + tail
763                tdecl += text + tail
764
765        if self.genOpts.alignFuncParam == 0:
766            # Squeeze out multiple spaces - there is no indentation
767            pdecl = ' '.join(pdecl.split())
768            tdecl = ' '.join(tdecl.split())
769
770        # Now add the parameter declaration list, which is identical
771        # for prototypes and typedefs. Concatenate all the text from
772        # a <param> node without the tags. No tree walking required
773        # since all tags are ignored.
774        # Uses: self.indentFuncProto
775        # self.indentFuncPointer
776        # self.alignFuncParam
777        n = len(params)
778        # Indented parameters
779        if n > 0:
780            indentdecl = '(\n'
781            indentdecl += ',\n'.join(self.makeCParamDecl(p, self.genOpts.alignFuncParam)
782                                     for p in params)
783            indentdecl += ');'
784        else:
785            indentdecl = '(void);'
786        # Non-indented parameters
787        paramdecl = '('
788        if n > 0:
789            paramnames = []
790            paramnames = (''.join(t for t in p.itertext())
791                          for p in params)
792            paramdecl += ', '.join(paramnames)
793        else:
794            paramdecl += 'void'
795        paramdecl += ");"
796        return [pdecl + indentdecl, tdecl + paramdecl]
797
798    def newline(self):
799        """Print a newline to the output file (utility function)"""
800        write('', file=self.outFile)
801
802    def setRegistry(self, registry):
803        self.registry = registry
804