https://github.com/kwwette/swiglal
Raw File
Tip revision: 77e755f6b60cf6209af990c32b48131e8d3ff9c0 authored by Karl Wette on 16 April 2014, 19:03:41 UTC
Revert non-recursive build commits
Tip revision: 77e755f
generate_swiglal_iface.py
# generate_swiglal_iface.py - generate SWIG interface
#
# Copyright (C) 2011--2013 Karl Wette
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with with program; see the file COPYING. If not, write to the
# Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
# MA  02111-1307  USA

__author__ = 'Karl Wette <karl.wette@ligo.org>'
__copyright__ = 'Copyright (C) 2011--2013 Karl Wette'

import getopt, sys, os, re
import xml.parsers.expat

# print error message and exit
def fail(msg):
    sys.stderr.write('%s: %s\n' % (sys.argv[0], msg))
    sys.exit(1)

# parse input options
opts, args = getopt.getopt(sys.argv[1:], 'n:d:s:p:x:i:')
if len(args) > 0:
    fail('invalid arguments: %s' % ' '.join(args))
module_name = None
module_depends = None
symbol_prefixes = None
preproc_filename = None
preproc_xml_filename = None
iface_filename = None
for opt, optarg in opts:
    if opt == '-n':
        module_name = optarg
    elif opt == '-d':
        module_depends = optarg
    elif opt == '-s':
        symbol_prefixes = optarg
    elif opt == '-p':
        preproc_filename = optarg
    elif opt == '-x':
        preproc_xml_filename = optarg
    elif opt == '-i':
        iface_filename = optarg
if module_name is None:
    fail('missing argument: -n <module_name>');
if module_depends is None:
    fail('missing argument: -d <module_depends>');
if symbol_prefixes is None:
    fail('missing argument: -s <symbol_prefixes>');
if preproc_filename is None:
    fail('missing argument: -p <preproc_filename>');
if preproc_xml_filename is None:
    fail('missing argument: -x <preproc_xml_filename>');
if iface_filename is None:
    fail('missing argument: -i <iface_filename>');

# XML parser class
class XMLParser:

    # create parser
    def __init__(self):
        self.parser = xml.parsers.expat.ParserCreate()
        self.parser.StartElementHandler = self.start_element
        self.parser.EndElementHandler = self.end_element
        self.element_stack = []
        self.action_stack = []

    # parse file, starting with data and initial action
    def parse(self, filename, action):
        self.action_stack.append(action)
        file = open(filename, 'r')
        self.parser.ParseFile(file)
        file.close()

    # handler for opening element tag:
    # store current element in stack, call action, then set action
    # stack to returned action for the top 'level' number of elements
    def start_element(self, name, attrs):
        self.element_stack.append(name)
        action, level = self.action_stack[-1].start(self.element_stack, attrs)
        for _ in range(level-1):
            self.action_stack.pop()
        for _ in range(level):
            self.action_stack.append(action)

    # handler for closing element tag:
    # pop element and action stacks
    def end_element(self, name):
        assert(self.element_stack[-1] == name)
        self.action_stack[-1].end(self.element_stack)
        self.element_stack.pop()
        self.action_stack.pop()

# find the top-level <include> element:
# found if 'name' attribute equals SWIG interface name
class FindTopLevelInclude:
    def __init__(self, headers):
        self.headers = headers
    def start(self, elems, attrs):
        if len(elems) >= 3:
            if elems[-3] == 'include':
                if elems[-2] == 'attributelist':
                    if elems[-1] == 'attribute':
                        if attrs.get('name') == 'name':
                            if attrs.get('value') == preproc_filename:
                                return (FindHeaderInclude(self.headers), 3)
        return (self, 1)
    def end(self, elems):
        pass

# find <include> elements for each header:
# found if 'name' attribute equals name of header file
class FindHeaderInclude:
    def __init__(self, headers):
        self.headers = headers
    def start(self, elems, attrs):
        if len(elems) >= 3:
            if elems[-3] == 'include':
                if elems[-2] == 'attributelist':
                    if elems[-1] == 'attribute':
                        if attrs.get('name') == 'name':
                            filepath, filename = os.path.split(attrs.get('value'))
                            _, filedir = os.path.split(filepath)
                            filename = os.path.join(filedir, filename)
                            return (ParseHeader(self.headers, filename), 3)
        return (self, 1)
    def end(self, elems):
        pass

# parse header elements
class ParseHeader:
    def __init__(self, headers, filename):
        self.headers = headers
        self.filename = filename
        self.structs = []
        self.cdecls = []
    def start(self, elems, attrs):
        if len(elems) >= 1:
            if elems[-1] == 'include':
                return (FindHeaderInclude(self.headers), 1)
            elif elems[-1] == 'class':
                self.structs.append({})
                return (ParseStruct(self.structs), 1)
            elif elems[-1] == 'cdecl':
                self.cdecls.append({})
                return (ParseCDecl(self.cdecls), 1)
        return (self, 1)
    def end(self, elems):
        if len(elems) >= 1:
            if elems[-1] == 'include':
                header = {
                    'index' : len(self.headers),
                    'struct' : self.structs,
                    'cdecl' : self.cdecls
                    }
                self.headers[self.filename] = header
        pass

# parse a struct
class ParseStruct:
    def __init__(self, structs):
        self.structs = structs
    def start(self, elems, attrs):
        if len(elems) >= 3:
            if elems[-3] == 'class':
                if elems[-2] == 'attributelist':
                    if elems[-1] == 'attribute':
                        self.structs[-1][attrs.get('name')] = attrs.get('value')
        return (self, 1)
    def end(self, elems):
        pass

# parse a function prototype
class ParseCDecl:
    def __init__(self, cdecls):
        self.cdecls = cdecls
    def start(self, elems, attrs):
        if len(elems) >= 3:
            if elems[-3] == 'cdecl':
                if elems[-2] == 'attributelist':
                    if elems[-1] == 'attribute':
                        self.cdecls[-1][attrs.get('name')] = attrs.get('value')
        return (self, 1)
    def end(self, elems):
        pass

# parse SWIG parse tree of preprocessing interface file
xml_parser = XMLParser()
headers = {}
xml_parser.parse(preproc_xml_filename, FindTopLevelInclude(headers))

# always include SWIG interfaces after C headers
for header in headers:
    if header.endswith('.i'):
        headers[header]['index'] += len(headers)

# order headers so that headers appear before headers than #include them
header_files = sorted(headers, key=lambda h : headers[h]['index'])

# process symbols from interface header parse trees
functions = {}
structs = {}
tdstructs = {}
clear_macros = {}
for header in headers:
    clear_macros[header] = set()

    # C declarations
    for cdecl in headers[header]['cdecl']:
        if not 'name' in cdecl:
            fail("cdecl in header '%s' has no name" % header)
        if not 'kind' in cdecl:
            fail("cdecl '%s' in header '%s' has no kind" % (cdecl['name'], header))
        if not 'type' in cdecl or cdecl['type'] == '':
            fail("cdecl '%s' in header '%s' has no type" % (cdecl['name'], header))
        cdecl['extra_process_args'] = []

        # SWIGLAL() and SWIGLAL_CLEAR() macros
        if cdecl['name'] in ['__swiglal__', '__swiglal_clear__']:
            if not 'value' in cdecl:
                fail("cdecl '%s' in header '%s' has no value" % (cdecl['name'], header))
            macro = re.sub(r'\s', '', cdecl['value'])
            if cdecl['name'] == '__swiglal__':
                if macro in clear_macros[header]:
                    fail("duplicate definition of SWIGLAL(%s)", macro)
                else:
                    clear_macros[header].add(macro)
            else:
                if macro in clear_macros[header]:
                    clear_macros[header].remove(macro)
                else:
                    fail("cannot clear undefined macro SWIGLAL(%s)", macro)

        # functions
        elif cdecl['kind'] == 'function':
            if cdecl['name'] in functions:
                fail("duplicate function '%s' in header '%s'" % (cdecl['name'], header))
            functions[cdecl['name']] = cdecl

        # typedefs to structs
        elif cdecl['kind'] == 'typedef' and cdecl['type'].startswith('struct '):
            if cdecl['name'] in tdstructs:
                fail("duplicate struct typedef '%s' in header '%s'" % (cdecl['name'], header))
            cdecl['tagname'] = cdecl['type'][7:]
            tdstructs[cdecl['name']] = cdecl

    # structs
    for struct in headers[header]['struct']:
        if not 'name' in struct:
            fail("struct in header '%s' has no name" % header)
        if 'unnamed' in struct:
            fail("struct '%s' in header '%s' has no tag-name" % (struct['name'], header))
        if struct['name'] in structs:
            fail("duplicate struct '%s' in header %s" % (struct['name'], header))
        struct['extra_process_args'] = []
        structs[struct['name']] = struct

# look for a destructor function for each struct
dtor_name_regexp = re.compile(r'(Destroy|Free|Close)([A-Z0-9_]|$)')
dtor_decl_regexp = re.compile(r'^f\(p\.(.*)\)\.$')
for function_name in functions:

    # function must match destructor name regexp
    dtor_name_match = dtor_name_regexp.search(function_name)
    if dtor_name_match is None:
        continue

    # function must take a single pointer argument
    dtor_decl = functions[function_name]['decl']
    dtor_decl_match = dtor_decl_regexp.match(dtor_decl)
    if dtor_decl_match is None:
        continue
    dtor_struct_name = dtor_decl_match.group(1)

    # function argument must be a struct name
    if not dtor_struct_name in tdstructs:
        continue

    # function return either void or int
    if not functions[function_name]['type'] in ['void', 'int']:
        fail("destructor function '%s' has invalid return type '%s'" % (
                function_name, functions[function_name]['type'])
             )

    # struct must not already have a destructor
    if 'dtor_function' in tdstructs[dtor_struct_name]:
        fail("struct typedef '%s' has duplicate destructors '%s' and '%s'" %
             (dtor_struct_name, tdstructs[dtor_struct_name]['dtor_function'], function_name)
             )

    # save destructor name
    tdstructs[dtor_struct_name]['dtor_function'] = function_name

    # remove destructor function from interface
    functions[function_name]['feature_ignore'] = '1'

# indicate if the return type of a function is a pointer type, and matches the
# type of its first argument; many LAL functions return their first argument
# after performing some operation on it, but we want to prevent two different
# SWIG wrapping objects from owning the same LAL memory
func_retn_1starg_regexp = re.compile(r'^f\(((?:p\.)+[^,]+?)(?:,[^,]+)*\)\.\1$')
for function_name in functions:
    func_decl_type = functions[function_name]['decl'] + functions[function_name]['type']
    retn_1starg = (not func_retn_1starg_regexp.match(func_decl_type) is None)
    functions[function_name]['extra_process_args'].append('%d' % retn_1starg)

# open SWIG interface file
iface_file = open(iface_filename, 'w')
_, iface_file_basename = os.path.split(iface_filename)
iface_file.write('// %s: generated by %s\n' % (iface_file_basename, sys.argv[0]))

# define module name
iface_file.write('%%module %s;\n' % module_name)

# include common interface code
iface_file.write('%include <lal/swiglal_common.i>\n')

# import dependent modules
iface_file.write('#ifndef SWIGIMPORTED\n')
for module in module_depends.split():
    iface_file.write('%%import <lal/%sswig.i>\n' % module)
iface_file.write('#endif\n')

# include interface headers in wrapping code
iface_file.write('%header %{\n')
for header in header_files:
    iface_file.write('#include <%s>\n' % header)
iface_file.write('%}\n')

# process interface symbols, with renaming
symbol_prefix_list = symbol_prefixes.split()
symbol_prefix_list.append('')
symbols_to_process = (
    (functions, 'function', 'name'),
    (tdstructs, 'tdstruct', 'tagname')
    )
for (symbols, symbol_type, symbol_name_key) in symbols_to_process:

    # rank symbol by where their prefix comes in prefix list
    for symbol_key in symbols:
        for rank in range(len(symbol_prefix_list)):
            if symbol_key.startswith(symbol_prefix_list[rank]):
                symbols[symbol_key]['rename_rank'] = rank
                break

    # iterate over symbols in order of decreasing rank
    symbol_renames = {}
    sort_by_rank_then_name = lambda k : '%1d%s' % (symbols[k]['rename_rank'], k)
    for symbol_key in sorted(symbols, key=sort_by_rank_then_name):

        # get name of symbol
        symbol_name = symbols[symbol_key][symbol_name_key]

        # strip prefix from symbol key to get rename
        rank = symbols[symbol_key]['rename_rank']
        symbol_rename = symbol_key[len(symbol_prefix_list[rank]):]

        # ignore symbol if another symbol has been renamed to this symbol,
        # otherwise record that a symbol has been renamed to this rename
        if symbol_rename in symbol_renames:
            symbol_rename = '$ignore'
        else:
            symbol_renames[symbol_rename] = True

        #  ignore symbol if the 'feature_ignore' attribute has been set
        if 'feature_ignore' in symbols[symbol_key]:
            symbol_rename = '$ignore'

        # get macro arguments
        macro_args = [symbol_name, symbol_rename]
        macro_args.extend(symbols[symbol_key]['extra_process_args'])

        # write to interface file
        iface_file.write('%%swiglal_process_%s(%s);\n' % (symbol_type, ', '.join(macro_args)))

# include interface headers, and clear SWIGLAL() macros afterwards
for header in header_files:
    iface_file.write('%%include <%s>\n' % header)
    for macro in clear_macros[header]:
        iface_file.write('SWIGLAL_CLEAR(%s);\n' % macro)

# generate constructors and destructors for structs
for struct_name in sorted(tdstructs):
    struct_tagname = tdstructs[struct_name]['tagname']
    struct_opaque = not struct_tagname in structs
    if 'dtor_function' in tdstructs[struct_name]:
        struct_dtor_function = tdstructs[struct_name]['dtor_function']
    else:
        struct_dtor_function = ''
    iface_file.write('%%swiglal_generate_struct_cdtor(%s, %s, %i, %s)\n' % (struct_name, struct_tagname, struct_opaque, struct_dtor_function))

# close SWIG interface file
iface_file.close()
back to top