Home | History | Annotate | Download | only in registry
      1 #!/usr/bin/python3 -i
      2 #
      3 # Copyright (c) 2013-2019 The Khronos Group Inc.
      4 #
      5 # Licensed under the Apache License, Version 2.0 (the "License");
      6 # you may not use this file except in compliance with the License.
      7 # You may obtain a copy of the License at
      8 #
      9 #     http://www.apache.org/licenses/LICENSE-2.0
     10 #
     11 # Unless required by applicable law or agreed to in writing, software
     12 # distributed under the License is distributed on an "AS IS" BASIS,
     13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14 # See the License for the specific language governing permissions and
     15 # limitations under the License.
     16 
     17 from __future__ import unicode_literals
     18 import io,os,re,sys,pdb
     19 
     20 def write( *args, **kwargs ):
     21     file = kwargs.pop('file',sys.stdout)
     22     end = kwargs.pop('end','\n')
     23     file.write(' '.join([str(arg) for arg in args]))
     24     file.write(end)
     25 
     26 # noneStr - returns string argument, or "" if argument is None.
     27 # Used in converting etree Elements into text.
     28 #   str - string to convert
     29 def noneStr(str):
     30     if (str):
     31         return str
     32     else:
     33         return ""
     34 
     35 # enquote - returns string argument with surrounding quotes,
     36 #   for serialization into Python code.
     37 def enquote(str):
     38     if (str):
     39         return "'" + str + "'"
     40     else:
     41         return None
     42 
     43 # apiName - returns True if name is a Vulkan name (vk/Vk/VK prefix, or a
     44 # function pointer type), False otherwise.
     45 def apiName(str):
     46     return str[0:2].lower() == 'vk' or str[0:3] == 'PFN'
     47 
     48 # Primary sort key for regSortFeatures.
     49 # Sorts by category of the feature name string:
     50 #   Core API features (those defined with a <feature> tag)
     51 #   ARB/KHR/OES (Khronos extensions)
     52 #   other       (EXT/vendor extensions)
     53 # This will need changing for Vulkan!
     54 def regSortCategoryKey(feature):
     55     if (feature.elem.tag == 'feature'):
     56         return 0
     57     elif (feature.category == 'ARB' or
     58           feature.category == 'KHR' or
     59           feature.category == 'OES'):
     60         return 1
     61     else:
     62         return 2
     63 
     64 # Secondary sort key for regSortFeatures.
     65 # Sorts by extension name.
     66 def regSortNameKey(feature):
     67     return feature.name
     68 
     69 # Second sort key for regSortFeatures.
     70 # Sorts by feature version. <extension> elements all have version number "0"
     71 def regSortFeatureVersionKey(feature):
     72     return float(feature.versionNumber)
     73 
     74 # Tertiary sort key for regSortFeatures.
     75 # Sorts by extension number. <feature> elements all have extension number 0.
     76 def regSortExtensionNumberKey(feature):
     77     return int(feature.number)
     78 
     79 # regSortFeatures - default sort procedure for features.
     80 # Sorts by primary key of feature category ('feature' or 'extension')
     81 #   then by version number (for features)
     82 #   then by extension number (for extensions)
     83 def regSortFeatures(featureList):
     84     featureList.sort(key = regSortExtensionNumberKey)
     85     featureList.sort(key = regSortFeatureVersionKey)
     86     featureList.sort(key = regSortCategoryKey)
     87 
     88 # GeneratorOptions - base class for options used during header production
     89 # These options are target language independent, and used by
     90 # Registry.apiGen() and by base OutputGenerator objects.
     91 #
     92 # Members
     93 #   filename - basename of file to generate, or None to write to stdout.
     94 #   directory - directory in which to generate filename
     95 #   apiname - string matching <api> 'apiname' attribute, e.g. 'gl'.
     96 #   profile - string specifying API profile , e.g. 'core', or None.
     97 #   versions - regex matching API versions to process interfaces for.
     98 #     Normally '.*' or '[0-9]\.[0-9]' to match all defined versions.
     99 #   emitversions - regex matching API versions to actually emit
    100 #    interfaces for (though all requested versions are considered
    101 #    when deciding which interfaces to generate). For GL 4.3 glext.h,
    102 #    this might be '1\.[2-5]|[2-4]\.[0-9]'.
    103 #   defaultExtensions - If not None, a string which must in its
    104 #     entirety match the pattern in the "supported" attribute of
    105 #     the <extension>. Defaults to None. Usually the same as apiname.
    106 #   addExtensions - regex matching names of additional extensions
    107 #     to include. Defaults to None.
    108 #   removeExtensions - regex matching names of extensions to
    109 #     remove (after defaultExtensions and addExtensions). Defaults
    110 #     to None.
    111 #   emitExtensions - regex matching names of extensions to actually emit
    112 #     interfaces for (though all requested versions are considered when
    113 #     deciding which interfaces to generate).
    114 #   sortProcedure - takes a list of FeatureInfo objects and sorts
    115 #     them in place to a preferred order in the generated output.
    116 #     Default is core API versions, ARB/KHR/OES extensions, all
    117 #     other extensions, alphabetically within each group.
    118 # The regex patterns can be None or empty, in which case they match
    119 #   nothing.
    120 class GeneratorOptions:
    121     """Represents options during header production from an API registry"""
    122     def __init__(self,
    123                  filename = None,
    124                  directory = '.',
    125                  apiname = None,
    126                  profile = None,
    127                  versions = '.*',
    128                  emitversions = '.*',
    129                  defaultExtensions = None,
    130                  addExtensions = None,
    131                  removeExtensions = None,
    132                  emitExtensions = None,
    133                  sortProcedure = regSortFeatures):
    134         self.filename          = filename
    135         self.directory         = directory
    136         self.apiname           = apiname
    137         self.profile           = profile
    138         self.versions          = self.emptyRegex(versions)
    139         self.emitversions      = self.emptyRegex(emitversions)
    140         self.defaultExtensions = defaultExtensions
    141         self.addExtensions     = self.emptyRegex(addExtensions)
    142         self.removeExtensions  = self.emptyRegex(removeExtensions)
    143         self.emitExtensions    = self.emptyRegex(emitExtensions)
    144         self.sortProcedure     = sortProcedure
    145     #
    146     # Substitute a regular expression which matches no version
    147     # or extension names for None or the empty string.
    148     def emptyRegex(self,pat):
    149         if (pat == None or pat == ''):
    150             return '_nomatch_^'
    151         else:
    152             return pat
    153 
    154 # OutputGenerator - base class for generating API interfaces.
    155 # Manages basic logic, logging, and output file control
    156 # Derived classes actually generate formatted output.
    157 #
    158 # ---- methods ----
    159 # OutputGenerator(errFile, warnFile, diagFile)
    160 #   errFile, warnFile, diagFile - file handles to write errors,
    161 #     warnings, diagnostics to. May be None to not write.
    162 # logMsg(level, *args) - log messages of different categories
    163 #   level - 'error', 'warn', or 'diag'. 'error' will also
    164 #     raise a UserWarning exception
    165 #   *args - print()-style arguments
    166 # setExtMap(map) - specify a dictionary map from extension names to
    167 #   numbers, used in creating values for extension enumerants.
    168 # makeDir(directory) - create a directory, if not already done.
    169 #   Generally called from derived generators creating hierarchies.
    170 # beginFile(genOpts) - start a new interface file
    171 #   genOpts - GeneratorOptions controlling what's generated and how
    172 # endFile() - finish an interface file, closing it when done
    173 # beginFeature(interface, emit) - write interface for a feature
    174 # and tag generated features as having been done.
    175 #   interface - element for the <version> / <extension> to generate
    176 #   emit - actually write to the header only when True
    177 # endFeature() - finish an interface.
    178 # genType(typeinfo,name,alias) - generate interface for a type
    179 #   typeinfo - TypeInfo for a type
    180 # genStruct(typeinfo,name,alias) - generate interface for a C "struct" type.
    181 #   typeinfo - TypeInfo for a type interpreted as a struct
    182 # genGroup(groupinfo,name,alias) - generate interface for a group of enums (C "enum")
    183 #   groupinfo - GroupInfo for a group
    184 # genEnum(enuminfo,name,alias) - generate interface for an enum (constant)
    185 #   enuminfo - EnumInfo for an enum
    186 #   name - enum name
    187 # genCmd(cmdinfo,name,alias) - generate interface for a command
    188 #   cmdinfo - CmdInfo for a command
    189 # isEnumRequired(enumElem) - return True if this <enum> element is required
    190 #   elem - <enum> element to test
    191 # makeCDecls(cmd) - return C prototype and function pointer typedef for a
    192 #     <command> Element, as a list of two strings
    193 #   cmd - Element for the <command>
    194 # newline() - print a newline to the output file (utility function)
    195 #
    196 class OutputGenerator:
    197     """Generate specified API interfaces in a specific style, such as a C header"""
    198     #
    199     # categoryToPath - map XML 'category' to include file directory name
    200     categoryToPath = {
    201         'bitmask'      : 'flags',
    202         'enum'         : 'enums',
    203         'funcpointer'  : 'funcpointers',
    204         'handle'       : 'handles',
    205         'define'       : 'defines',
    206         'basetype'     : 'basetypes',
    207     }
    208     #
    209     # Constructor
    210     def __init__(self,
    211                  errFile = sys.stderr,
    212                  warnFile = sys.stderr,
    213                  diagFile = sys.stdout):
    214         self.outFile = None
    215         self.errFile = errFile
    216         self.warnFile = warnFile
    217         self.diagFile = diagFile
    218         # Internal state
    219         self.featureName = None
    220         self.genOpts = None
    221         self.registry = None
    222         # Used for extension enum value generation
    223         self.extBase      = 1000000000
    224         self.extBlockSize = 1000
    225         self.madeDirs = {}
    226     #
    227     # logMsg - write a message of different categories to different
    228     #   destinations.
    229     # level -
    230     #   'diag' (diagnostic, voluminous)
    231     #   'warn' (warning)
    232     #   'error' (fatal error - raises exception after logging)
    233     # *args - print()-style arguments to direct to corresponding log
    234     def logMsg(self, level, *args):
    235         """Log a message at the given level. Can be ignored or log to a file"""
    236         if (level == 'error'):
    237             strfile = io.StringIO()
    238             write('ERROR:', *args, file=strfile)
    239             if (self.errFile != None):
    240                 write(strfile.getvalue(), file=self.errFile)
    241             raise UserWarning(strfile.getvalue())
    242         elif (level == 'warn'):
    243             if (self.warnFile != None):
    244                 write('WARNING:', *args, file=self.warnFile)
    245         elif (level == 'diag'):
    246             if (self.diagFile != None):
    247                 write('DIAG:', *args, file=self.diagFile)
    248         else:
    249             raise UserWarning(
    250                 '*** FATAL ERROR in Generator.logMsg: unknown level:' + level)
    251     #
    252     # enumToValue - parses and converts an <enum> tag into a value.
    253     # Returns a list
    254     #   first element - integer representation of the value, or None
    255     #       if needsNum is False. The value must be a legal number
    256     #       if needsNum is True.
    257     #   second element - string representation of the value
    258     # There are several possible representations of values.
    259     #   A 'value' attribute simply contains the value.
    260     #   A 'bitpos' attribute defines a value by specifying the bit
    261     #       position which is set in that value.
    262     #   A 'offset','extbase','extends' triplet specifies a value
    263     #       as an offset to a base value defined by the specified
    264     #       'extbase' extension name, which is then cast to the
    265     #       typename specified by 'extends'. This requires probing
    266     #       the registry database, and imbeds knowledge of the
    267     #       Vulkan extension enum scheme in this function.
    268     #   A 'alias' attribute contains the name of another enum
    269     #       which this is an alias of. The other enum must be
    270     #       declared first when emitting this enum.
    271     def enumToValue(self, elem, needsNum):
    272         name = elem.get('name')
    273         numVal = None
    274         if ('value' in elem.keys()):
    275             value = elem.get('value')
    276             # print('About to translate value =', value, 'type =', type(value))
    277             if (needsNum):
    278                 numVal = int(value, 0)
    279             # If there's a non-integer, numeric 'type' attribute (e.g. 'u' or
    280             # 'ull'), append it to the string value.
    281             # t = enuminfo.elem.get('type')
    282             # if (t != None and t != '' and t != 'i' and t != 's'):
    283             #     value += enuminfo.type
    284             self.logMsg('diag', 'Enum', name, '-> value [', numVal, ',', value, ']')
    285             return [numVal, value]
    286         if ('bitpos' in elem.keys()):
    287             value = elem.get('bitpos')
    288             numVal = int(value, 0)
    289             numVal = 1 << numVal
    290             value = '0x%08x' % numVal
    291             self.logMsg('diag', 'Enum', name, '-> bitpos [', numVal, ',', value, ']')
    292             return [numVal, value]
    293         if ('offset' in elem.keys()):
    294             # Obtain values in the mapping from the attributes
    295             enumNegative = False
    296             offset = int(elem.get('offset'),0)
    297             extnumber = int(elem.get('extnumber'),0)
    298             extends = elem.get('extends')
    299             if ('dir' in elem.keys()):
    300                 enumNegative = True
    301             self.logMsg('diag', 'Enum', name, 'offset =', offset,
    302                 'extnumber =', extnumber, 'extends =', extends,
    303                 'enumNegative =', enumNegative)
    304             # Now determine the actual enumerant value, as defined
    305             # in the "Layers and Extensions" appendix of the spec.
    306             numVal = self.extBase + (extnumber - 1) * self.extBlockSize + offset
    307             if (enumNegative):
    308                 numVal = -numVal
    309             value = '%d' % numVal
    310             # More logic needed!
    311             self.logMsg('diag', 'Enum', name, '-> offset [', numVal, ',', value, ']')
    312             return [numVal, value]
    313         if 'alias' in elem.keys():
    314             return [None, elem.get('alias')]
    315         return [None, None]
    316     #
    317     # checkDuplicateEnums - sanity check for enumerated values
    318     #   enums - list of <enum> Elements
    319     #   returns the list with duplicates stripped
    320     def checkDuplicateEnums(self, enums):
    321         # Dictionaries indexed by name and numeric value.
    322         # Entries are [ Element, numVal, strVal ] matching name or value
    323 
    324         nameMap = {}
    325         valueMap = {}
    326 
    327         stripped = []
    328         for elem in enums:
    329             name = elem.get('name')
    330             (numVal, strVal) = self.enumToValue(elem, True)
    331 
    332             if name in nameMap:
    333                 # Duplicate name found; check values
    334                 (name2, numVal2, strVal2) = nameMap[name]
    335 
    336                 # Duplicate enum values for the same name are benign. This
    337                 # happens when defining the same enum conditionally in
    338                 # several extension blocks.
    339                 if (strVal2 == strVal or (numVal != None and
    340                     numVal == numVal2)):
    341                     True
    342                     # self.logMsg('info', 'checkDuplicateEnums: Duplicate enum (' + name +
    343                     #             ') found with the same value:' + strVal)
    344                 else:
    345                     self.logMsg('warn', 'checkDuplicateEnums: Duplicate enum (' + name +
    346                                 ') found with different values:' + strVal +
    347                                 ' and ' + strVal2)
    348 
    349                 # Don't add the duplicate to the returned list
    350                 continue
    351             elif numVal in valueMap:
    352                 # Duplicate value found (such as an alias); report it, but
    353                 # still add this enum to the list.
    354                 (name2, numVal2, strVal2) = valueMap[numVal]
    355 
    356                 try:
    357                     self.logMsg('warn', 'Two enums found with the same value: '
    358                              + name + ' = ' + name2.get('name') + ' = ' + strVal)
    359                 except:
    360                     pdb.set_trace()
    361 
    362             # Track this enum to detect followon duplicates
    363             nameMap[name] = [ elem, numVal, strVal ]
    364             if numVal != None:
    365                 valueMap[numVal] = [ elem, numVal, strVal ]
    366 
    367             # Add this enum to the list
    368             stripped.append(elem)
    369 
    370         # Return the list
    371         return stripped
    372     #
    373     def makeDir(self, path):
    374         self.logMsg('diag', 'OutputGenerator::makeDir(' + path + ')')
    375         if not (path in self.madeDirs.keys()):
    376             # This can get race conditions with multiple writers, see
    377             # https://stackoverflow.com/questions/273192/
    378             if not os.path.exists(path):
    379                 os.makedirs(path)
    380             self.madeDirs[path] = None
    381     #
    382     def beginFile(self, genOpts):
    383         self.genOpts = genOpts
    384         #
    385         # Open specified output file. Not done in constructor since a
    386         # Generator can be used without writing to a file.
    387         if (self.genOpts.filename != None):
    388             filename = self.genOpts.directory + '/' + self.genOpts.filename
    389             self.outFile = io.open(filename, 'w', encoding='utf-8')
    390         else:
    391             self.outFile = sys.stdout
    392     def endFile(self):
    393         self.errFile and self.errFile.flush()
    394         self.warnFile and self.warnFile.flush()
    395         self.diagFile and self.diagFile.flush()
    396         self.outFile.flush()
    397         if (self.outFile != sys.stdout and self.outFile != sys.stderr):
    398             self.outFile.close()
    399         self.genOpts = None
    400     #
    401     def beginFeature(self, interface, emit):
    402         self.emit = emit
    403         self.featureName = interface.get('name')
    404         # If there's an additional 'protect' attribute in the feature, save it
    405         self.featureExtraProtect = interface.get('protect')
    406     def endFeature(self):
    407         # Derived classes responsible for emitting feature
    408         self.featureName = None
    409         self.featureExtraProtect = None
    410     # Utility method to validate we're generating something only inside a
    411     # <feature> tag
    412     def validateFeature(self, featureType, featureName):
    413         if (self.featureName == None):
    414             raise UserWarning('Attempt to generate', featureType,
    415                     featureName, 'when not in feature')
    416     #
    417     # Type generation
    418     def genType(self, typeinfo, name, alias):
    419         self.validateFeature('type', name)
    420     #
    421     # Struct (e.g. C "struct" type) generation
    422     def genStruct(self, typeinfo, name, alias):
    423         self.validateFeature('struct', name)
    424 
    425         # The mixed-mode <member> tags may contain no-op <comment> tags.
    426         # It is convenient to remove them here where all output generators
    427         # will benefit.
    428         for member in typeinfo.elem.findall('.//member'):
    429             for comment in member.findall('comment'):
    430                 member.remove(comment)
    431     #
    432     # Group (e.g. C "enum" type) generation
    433     def genGroup(self, groupinfo, name, alias):
    434         self.validateFeature('group', name)
    435     #
    436     # Enumerant (really, constant) generation
    437     def genEnum(self, enuminfo, name, alias):
    438         self.validateFeature('enum', name)
    439     #
    440     # Command generation
    441     def genCmd(self, cmd, name, alias):
    442         self.validateFeature('command', name)
    443     #
    444     # Utility functions - turn a <proto> <name> into C-language prototype
    445     # and typedef declarations for that name.
    446     # name - contents of <name> tag
    447     # tail - whatever text follows that tag in the Element
    448     def makeProtoName(self, name, tail):
    449         return self.genOpts.apientry + name + tail
    450     def makeTypedefName(self, name, tail):
    451        return '(' + self.genOpts.apientryp + 'PFN_' + name + tail + ')'
    452     #
    453     # makeCParamDecl - return a string which is an indented, formatted
    454     # declaration for a <param> or <member> block (e.g. function parameter
    455     # or structure/union member).
    456     # param - Element (<param> or <member>) to format
    457     # aligncol - if non-zero, attempt to align the nested <name> element
    458     #   at this column
    459     def makeCParamDecl(self, param, aligncol):
    460         paramdecl = '    ' + noneStr(param.text)
    461         for elem in param:
    462             text = noneStr(elem.text)
    463             tail = noneStr(elem.tail)
    464             if (elem.tag == 'name' and aligncol > 0):
    465                 self.logMsg('diag', 'Aligning parameter', elem.text, 'to column', self.genOpts.alignFuncParam)
    466                 # Align at specified column, if possible
    467                 paramdecl = paramdecl.rstrip()
    468                 oldLen = len(paramdecl)
    469                 # This works around a problem where very long type names -
    470                 # longer than the alignment column - would run into the tail
    471                 # text.
    472                 paramdecl = paramdecl.ljust(aligncol-1) + ' '
    473                 newLen = len(paramdecl)
    474                 self.logMsg('diag', 'Adjust length of parameter decl from', oldLen, 'to', newLen, ':', paramdecl)
    475             paramdecl += text + tail
    476         return paramdecl
    477     #
    478     # getCParamTypeLength - return the length of the type field is an indented, formatted
    479     # declaration for a <param> or <member> block (e.g. function parameter
    480     # or structure/union member).
    481     # param - Element (<param> or <member>) to identify
    482     def getCParamTypeLength(self, param):
    483         paramdecl = '    ' + noneStr(param.text)
    484         for elem in param:
    485             text = noneStr(elem.text)
    486             tail = noneStr(elem.tail)
    487             if (elem.tag == 'name'):
    488                 # Align at specified column, if possible
    489                 newLen = len(paramdecl.rstrip())
    490                 self.logMsg('diag', 'Identifying length of', elem.text, 'as', newLen)
    491             paramdecl += text + tail
    492         return newLen
    493     #
    494     # isEnumRequired(elem) - return True if this <enum> element is
    495     # required, False otherwise
    496     # elem - <enum> element to test
    497     def isEnumRequired(self, elem):
    498         required = elem.get('required') != None
    499         self.logMsg('diag', 'isEnumRequired:', elem.get('name'),
    500             '->', required)
    501         return required
    502 
    503         #@@@ This code is overridden by equivalent code now run in
    504         #@@@ Registry.generateFeature
    505 
    506         required = False
    507 
    508         extname = elem.get('extname')
    509         if extname is not None:
    510             # 'supported' attribute was injected when the <enum> element was
    511             # moved into the <enums> group in Registry.parseTree()
    512             if self.genOpts.defaultExtensions == elem.get('supported'):
    513                 required = True
    514             elif re.match(self.genOpts.addExtensions, extname) is not None:
    515                 required = True
    516         elif elem.get('version') is not None:
    517             required = re.match(self.genOpts.emitversions, elem.get('version')) is not None
    518         else:
    519             required = True
    520 
    521         return required
    522 
    523     #
    524     # makeCDecls - return C prototype and function pointer typedef for a
    525     #   command, as a two-element list of strings.
    526     # cmd - Element containing a <command> tag
    527     def makeCDecls(self, cmd):
    528         """Generate C function pointer typedef for <command> Element"""
    529         proto = cmd.find('proto')
    530         params = cmd.findall('param')
    531         # Begin accumulating prototype and typedef strings
    532         pdecl = self.genOpts.apicall
    533         tdecl = 'typedef '
    534         #
    535         # Insert the function return type/name.
    536         # For prototypes, add APIENTRY macro before the name
    537         # For typedefs, add (APIENTRY *<name>) around the name and
    538         #   use the PFN_cmdnameproc naming convention.
    539         # Done by walking the tree for <proto> element by element.
    540         # etree has elem.text followed by (elem[i], elem[i].tail)
    541         #   for each child element and any following text
    542         # Leading text
    543         pdecl += noneStr(proto.text)
    544         tdecl += noneStr(proto.text)
    545         # For each child element, if it's a <name> wrap in appropriate
    546         # declaration. Otherwise append its contents and tail contents.
    547         for elem in proto:
    548             text = noneStr(elem.text)
    549             tail = noneStr(elem.tail)
    550             if (elem.tag == 'name'):
    551                 pdecl += self.makeProtoName(text, tail)
    552                 tdecl += self.makeTypedefName(text, tail)
    553             else:
    554                 pdecl += text + tail
    555                 tdecl += text + tail
    556         # Now add the parameter declaration list, which is identical
    557         # for prototypes and typedefs. Concatenate all the text from
    558         # a <param> node without the tags. No tree walking required
    559         # since all tags are ignored.
    560         # Uses: self.indentFuncProto
    561         # self.indentFuncPointer
    562         # self.alignFuncParam
    563         # Might be able to doubly-nest the joins, e.g.
    564         #   ','.join(('_'.join([l[i] for i in range(0,len(l))])
    565         n = len(params)
    566         # Indented parameters
    567         if n > 0:
    568             indentdecl = '(\n'
    569             for i in range(0,n):
    570                 paramdecl = self.makeCParamDecl(params[i], self.genOpts.alignFuncParam)
    571                 if (i < n - 1):
    572                     paramdecl += ',\n'
    573                 else:
    574                     paramdecl += ');'
    575                 indentdecl += paramdecl
    576         else:
    577             indentdecl = '(void);'
    578         # Non-indented parameters
    579         paramdecl = '('
    580         if n > 0:
    581             for i in range(0,n):
    582                 paramdecl += ''.join([t for t in params[i].itertext()])
    583                 if (i < n - 1):
    584                     paramdecl += ', '
    585         else:
    586             paramdecl += 'void'
    587         paramdecl += ");";
    588         return [ pdecl + indentdecl, tdecl + paramdecl ]
    589     #
    590     def newline(self):
    591         write('', file=self.outFile)
    592 
    593     def setRegistry(self, registry):
    594         self.registry = registry
    595         #
    596