From 586454bda46228da88516e2d37f2bef7f98d76de Mon Sep 17 00:00:00 2001
From: Mathieu Faverge <mathieu.faverge@inria.fr>
Date: Mon, 14 May 2018 11:51:26 +0200
Subject: [PATCH] Add scripts to generate wrappers

---
 tools/gen_wrappers.py          | 543 +++++++++++++++++++++++++++++++++
 tools/wrappers/__init__.py     |  41 +++
 tools/wrappers/wrap_fortran.py | 474 ++++++++++++++++++++++++++++
 tools/wrappers/wrap_python.py  | 518 +++++++++++++++++++++++++++++++
 4 files changed, 1576 insertions(+)
 create mode 100755 tools/gen_wrappers.py
 create mode 100644 tools/wrappers/__init__.py
 create mode 100644 tools/wrappers/wrap_fortran.py
 create mode 100644 tools/wrappers/wrap_python.py

diff --git a/tools/gen_wrappers.py b/tools/gen_wrappers.py
new file mode 100755
index 00000000..841177e1
--- /dev/null
+++ b/tools/gen_wrappers.py
@@ -0,0 +1,543 @@
+#!/usr/bin/env python
+"""
+ @file gen_wrappers.py
+
+ Python and Fortran 90 wrapper generator for some of the solverstack
+ libraries, inspired from the PLASMA-OMP fortran generator.
+
+ @copyright 2016-2017 University of Tennessee, US, University of
+                      Manchester, UK. All rights reserved.
+ @copyright 2017-2018 Bordeaux INP, CNRS (LaBRI UMR 5800), Inria,
+                      Univ. Bordeaux. All rights reserved.
+
+ @version 6.0.0
+ @author Pierre Ramet
+ @author Mathieu Faverge
+ @date 2017-05-04
+
+"""
+import os
+import re
+import argparse
+import wrappers
+
+description = '''\
+Generates Fortran 90 and Python wrappers from the spm header files.'''
+
+help = '''\
+----------------------------------------------------------------------
+Example uses:
+
+   $SPM_SRC_DIR/gen_wrappers.py
+
+----------------------------------------------------------------------
+'''
+
+# ------------------------------------------------------------
+# command line options
+parser = argparse.ArgumentParser(
+    formatter_class=argparse.RawDescriptionHelpFormatter,
+    description=description,
+    epilog=help )
+parser.add_argument('--prefix',        action='store', help='Prefix for variables in Makefile', default='./')
+parser.add_argument('args', nargs='*', action='store', help='Files to process')
+opts = parser.parse_args()
+
+# exclude inline functions from the interface
+exclude_list = [ "inline", "spmIntSort" ]
+
+def polish_file(whole_file):
+    """Preprocessing and cleaning of the header file.
+       Do not change the order of the regular expressions !
+       Works with a long string."""
+
+    clean_file = whole_file
+
+    # borrowed from cfwrapper.py
+    # Remove C comments:
+    clean_file = re.sub(r"(?s)/\*.*?\*/", "", clean_file)
+    clean_file = re.sub(r"//.*", "", clean_file)
+    # Remove C directives (multilines then monoline):
+    clean_file = re.sub(r"(?m)^#(.*[\\][\n])+.*?$", "", clean_file)
+    clean_file = re.sub("(?m)^#.*$", "", clean_file)
+    clean_file = re.sub("(?m)#.*", "", clean_file)
+    # Remove TABs and overnumerous spaces:
+    clean_file = clean_file.replace("\t", " ")
+    clean_file = re.sub("[ ]{2,}", " ", clean_file)
+    # Remove extern C statement:
+    clean_file = re.sub("(?m)^(extern).*$", "", clean_file)
+    # Remove empty lines:
+    clean_file = re.sub(r"(?m)^\n$", "", clean_file)
+    # Merge structs
+    clean_file = re.sub(r"(?m)$", "", clean_file)
+
+    # Merge string into single line
+    clean_file = re.sub(r"\n", "", clean_file)
+
+    # Split the line based on ";" and "}"
+    clean_file = re.sub(r";", "\n", clean_file)
+    clean_file = re.sub(r"}", "}\n", clean_file)
+
+    return clean_file
+
+def preprocess_list(initial_list):
+    """Preprocessing and cleaning of the header file.
+       Works with a list of strings.
+       Produces a new list in which each function, enum or struct
+       corresponds to a single item."""
+
+    # merge braces
+    list1 = []
+    merged_line = ""
+    nopen = 0
+    inStruct = False
+    for line in initial_list:
+
+        if (line.find("struct") > -1):
+            inStruct = True
+
+        if (inStruct):
+            split_character = ","
+        else:
+            split_character = ""
+
+        nopen += line.count("{") - line.count("}")
+        merged_line += line + split_character
+
+        if (nopen <= 0):
+            list1.append(merged_line)
+            merged_line = ""
+            isOpen   = False
+            inStruct = False
+            nopen = 0
+
+    # merge structs
+    list2 = []
+    merged_line = ""
+    for line in list1:
+
+        merged_line += line
+
+        if (line.find("struct") == -1):
+            list2.append(merged_line)
+            merged_line = ""
+
+    # clean orphan braces
+    list3 = []
+    for line in list2:
+
+        if (line.strip() != "}"):
+            list3.append(line)
+
+    #print '\n\n'.join(list3)
+
+    return list3
+
+def parse_triple(string):
+    """Parse string of
+       type (*)name
+       into triple of [type, pointer, name, const]"""
+
+    if "const" in string:
+        const=1
+        string = re.sub(r"const", "", string)
+    else:
+        const=0
+
+    parts = string.split()
+    if (len(parts) < 2 or len(parts) > 3):
+        print("Error: Cannot detect type for ", string)
+
+    type_part = str.strip(parts[0])
+
+    if (len(parts) == 2):
+        name_with_pointer = str.strip(parts[1])
+        if (name_with_pointer.find("**") > -1):
+            pointer_part = "**"
+            name_part = name_with_pointer.replace("**", "")
+        elif (name_with_pointer.find("*") > -1):
+            pointer_part = "*"
+            name_part    = name_with_pointer.replace("*", "")
+        else:
+            pointer_part = ""
+            name_part    = name_with_pointer
+
+    elif (len(parts) == 3):
+        if (str.strip(parts[1]) == "**"):
+            pointer_part = "**"
+            name_part    = str.strip(parts[2])
+        elif (str.strip(parts[1]) == "*"):
+            pointer_part = "*"
+            name_part    = str.strip(parts[2])
+        else:
+            print("Error: Too many parts for ", string)
+
+    name_part = name_part.strip()
+
+    return [type_part, pointer_part, name_part, const]
+
+
+def parse_enums(preprocessed_list):
+    """Each enum will be parsed into a list of its arguments."""
+
+    enum_list = []
+    for proto in preprocessed_list:
+
+        # extract the part of the function from the prototype
+        fun_parts = proto.split("{")
+
+        split_fun = fun_parts[0].strip().split()
+        if len(split_fun) == 0:
+            continue
+
+        if ((split_fun[0] == "enum") or
+            (split_fun[0] == "typedef" and split_fun[1] == "enum")):
+
+
+            if split_fun[0] == "enum":
+                enumname = split_fun[1]
+            else:
+                enumname = split_fun[2]
+            enumname = re.sub(r"_e$", "", enumname)
+            enumname = re.sub(r"^pastix_", "", enumname)
+
+            args_string = fun_parts[1];
+            args_string = re.sub(r"}", "", args_string)
+            args_list = args_string.split(",")
+            params = [];
+            for args in args_list:
+                args = args.strip();
+                if (args != ""):
+                    values = args.split("=")
+
+                    name = values[0].strip()
+                    if (len(values) > 1):
+                        value = values[1].strip()
+                    else:
+                        if (len(params) > 0):
+                            value = params[len(params)-1][1] + 1
+                        else:
+                            value = 0
+
+                    params.append([name, value])
+
+            enum_list.append([enumname, params])
+
+    return enum_list
+
+
+def parse_structs(preprocessed_list):
+    """Each struct will be parsed into a list of its arguments."""
+
+    struct_list = []
+    for proto in preprocessed_list:
+
+        # extract the part of the function from the prototype
+        fun_parts = proto.split("{")
+
+        if (fun_parts[0].find("struct") > -1):
+            args_string = fun_parts[1]
+            parts = args_string.split("}")
+            args_string = parts[0].strip()
+            args_string = re.sub(r"volatile", "", args_string)
+            if (len(parts) > 1):
+                name_string = parts[1]
+                name_string = re.sub(r"(?m),", "", name_string)
+                name_string = name_string.strip()
+            else:
+                print("Error: Cannot detect name for ", proto)
+                name_string = "name_not_recognized"
+
+            args_list = args_string.split(",")
+            params = [];
+            params.append(["struct","",name_string])
+            for arg in args_list:
+                if (not (arg == "" or arg == " ")):
+                    params.append(parse_triple(arg))
+
+            struct_list.append(params)
+            wrappers.derived_types.append(name_string)
+
+    # reorder the list so that only defined types are exported
+    goAgain = True
+    while (goAgain):
+        goAgain = False
+        for istruct in range(0,len(struct_list)-1):
+            struct = struct_list[istruct]
+            for j in range(1,len(struct)-1):
+                type_name = struct_list[istruct][j][0]
+
+                if (type_name in wrappers.derived_types):
+
+                    # try to find the name in the registered types
+                    definedEarlier = False
+                    for jstruct in range(0,istruct):
+                        struct2 = struct_list[jstruct]
+                        that_name = struct2[0][2]
+                        if (that_name == type_name):
+                            definedEarlier = True
+
+                    # if not found, try to find it behind
+                    if (not definedEarlier):
+                        definedLater = False
+                        for jstruct in range(istruct+1,len(struct_list)-1):
+                            struct2 = struct_list[jstruct]
+                            that_name = struct2[0][2]
+                            if (that_name == type_name):
+                                index = jstruct
+                                definedLater = True
+
+                        # swap the entries
+                        if (definedLater):
+                            print("Swapping " + struct_list[istruct][0][2] + " and " + struct_list[index][0][2])
+                            tmp = struct_list[index]
+                            struct_list[index] = struct_list[istruct]
+                            struct_list[istruct] = tmp
+                            goAgain = True
+                        else:
+                            print("Error: Cannot find a derived type " + type_name + " in imported structs.")
+
+    return struct_list
+
+
+def parse_prototypes(preprocessed_list):
+    """Each prototype will be parsed into a list of its arguments."""
+
+    function_list = []
+    for proto in preprocessed_list:
+
+        if (proto.find("(") == -1):
+            continue
+
+        # extract the part of the function from the prototype
+        fun_parts = proto.split("(")
+        fun_def  = str.strip(fun_parts[0])
+
+        exclude_this_function = False
+        for exclude in exclude_list:
+            if (fun_def.find(exclude) != -1):
+                exclude_this_function = True
+
+        if (exclude_this_function):
+            continue
+
+        # clean keywords
+        fun_def = fun_def.replace("static", "")
+
+        # extract arguments from the prototype and make a list from them
+        if (len(fun_parts) > 1):
+            fun_args = fun_parts[1]
+        else:
+            fun_args = ""
+
+        fun_args = fun_args.split(")")[0]
+        fun_args = fun_args.replace(";", "")
+        fun_args = re.sub(r"volatile", "", fun_args)
+        fun_args = fun_args.replace("\n", "")
+        fun_args_list = fun_args.split(",")
+
+        # generate argument list
+        argument_list = []
+        # function itself on the first position
+        argument_list.append(parse_triple(fun_def))
+        # append arguments
+        for arg in fun_args_list:
+            if (not (arg == "" or arg == " ")):
+                argument_list.append(parse_triple(arg))
+
+        # add it only if there is no duplicity with previous one
+        is_function_already_present = False
+        fun_name = argument_list[0][2]
+        for fun in function_list:
+            if (fun_name == fun[0][2]):
+                is_function_already_present = True
+
+        if (not is_function_already_present):
+            function_list.append(argument_list)
+
+    return function_list
+
+def write_module(f, wrapper, generator, enum_list, struct_list, function_list):
+    """Generate a single Fortran module. Its structure will be:
+       enums converted to constants
+       structs converted to derived types
+       interfaces of all C functions
+       Fortran wrappers of the C functions"""
+
+    modulefile = open( f['filename'], "w" )
+
+    f_interface = generator.header( f )
+    modulefile.write(f_interface + "\n")
+
+    # enums
+    if (len(enum_list) > 0):
+        for enum in enum_list:
+            f_interface = generator.enum( f, enum )
+            modulefile.write(f_interface + "\n")
+
+    # derived types
+    if (len(struct_list) > 0):
+        for struct in struct_list:
+            f_interface = generator.struct(struct)
+            modulefile.write(f_interface + "\n")
+
+    # functions
+    if (len(function_list) > 0):
+        for function in function_list:
+            f_interface = generator.function(function)
+            modulefile.write(f_interface + "\n")
+
+        if wrapper:
+            modulefile.write("contains\n\n")
+            modulefile.write("  " + "! Wrappers of the C functions.\n")
+
+            for function in function_list:
+                f_wrapper = generator.wrapper(function)
+                modulefile.write(f_wrapper + "\n")
+
+    footer=generator.footer( f )
+    modulefile.write(footer + "\n")
+
+    modulefile.close()
+
+    return f['filename']
+
+
+enums_python_coeftype='''
+    @staticmethod
+    def getptype ( dtype ):
+        np_dict = {
+            np.dtype('float32')    : coeftype.Float,
+            np.dtype('float64')    : coeftype.Double,
+            np.dtype('complex64')  : coeftype.Complex32,
+            np.dtype('complex128') : coeftype.Complex64,
+        }
+        if dtype in np_dict:
+            return np_dict[dtype]
+        else:
+            return -1
+
+    @staticmethod
+    def getctype ( flttype ):
+        class c_float_complex(Structure):
+            _fields_ = [("r",c_float),("i", c_float)]
+        class c_double_complex(Structure):
+            _fields_ = [("r",c_double),("i", c_double)]
+
+        np_dict = {
+            coeftype.Float     : c_float,
+            coeftype.Double    : c_double,
+            coeftype.Complex32 : c_float_complex,
+            coeftype.Complex64 : c_double_complex
+        }
+        if flttype in np_dict:
+            return np_dict[flttype]
+        else:
+            return -1
+
+    @staticmethod
+    def getnptype ( flttype ):
+        np_dict = {
+            coeftype.Float     : np.dtype('float32'),
+            coeftype.Double    : np.dtype('float64'),
+            coeftype.Complex32 : np.dtype('complex64'),
+            coeftype.Complex64 : np.dtype('complex128')
+        }
+        if flttype in np_dict:
+            return np_dict[flttype]
+        else:
+            return -1
+'''
+
+enums_fortran_footer='''
+  integer, parameter :: spm_int_t = SPM_INT_KIND
+
+contains
+
+  function spm_getintsize()
+    integer :: spm_getintsize
+    spm_getintsize = SPM_INT_KIND
+    return
+  end function spm_getintsize
+'''
+
+spm_enums = {
+    'filename' : [ "include/spm_const.h" ],
+    'python'   : { 'filename'    : "wrappers/python/spm/enum.py.in",
+                   'description' : "SPM python wrapper to define enums and datatypes",
+                   'header'      : "spm_int = @SPM_PYTHON_INTEGER@",
+                   'footer'      : "",
+                   'enums'       : { 'coeftype' : enums_python_coeftype }
+    },
+    'fortran'  : { 'filename'    : "wrappers/fortran90/src/spm_enums.F90",
+                   'description' : "SPM fortran 90 wrapper to define enums and datatypes",
+                   'header'      : "  implicit none\n",
+                   'footer'      : enums_fortran_footer,
+                   'enums'       : {}
+    },
+}
+
+spm = {
+    'filename' : [ "include/spm.h" ],
+    'python'   : { 'filename'    : "wrappers/python/spm/__spm__.py",
+                   'description' : "SPM python wrapper",
+                   'header'      : "from . import libspm\nfrom .enum import spm_int\n",
+                   'footer'      : "",
+                   'enums'       : {}
+    },
+    'fortran'  : { 'filename'    : "wrappers/fortran90/src/spmf.f90",
+                   'description' : "SPM Fortran 90 wrapper",
+                   'header'      : "  use spm_enums\n  implicit none\n",
+                   'footer'      : "",
+                   'enums'       : {}
+    },
+}
+
+def main():
+
+    # common cleaned header files
+    preprocessed_list = []
+
+    # source header files
+    for f in [ spm_enums, spm ]:
+        preprocessed_list = []
+        for filename in f['filename']:
+
+            # source a header file
+            c_header_file = open(filename, 'r').read()
+
+            # clean the string (remove comments, macros, etc.)
+            clean_file = polish_file(c_header_file)
+
+            # convert the string to a list of strings
+            initial_list = clean_file.split("\n")
+
+            # process the list so that each enum, struct or function
+            # are just one item
+            nice_list = preprocess_list(initial_list)
+
+            # compose all files into one big list
+            preprocessed_list += nice_list
+
+            # register all enums
+            enum_list = parse_enums(preprocessed_list)
+
+            # register all structs
+            struct_list = parse_structs(preprocessed_list)
+
+            # register all individual functions and their signatures
+            function_list = parse_prototypes(preprocessed_list)
+
+        # export the module
+        modulefilename = write_module( f['fortran'], True,
+                                       wrappers.wrap_fortran,
+                                       enum_list, struct_list, function_list)
+        print( "Exported file: " + modulefilename )
+
+        modulefilename = write_module( f['python'], False,
+                                       wrappers.wrap_python,
+                                       enum_list, struct_list, function_list)
+        print( "Exported file: " + modulefilename )
+
+# execute the program
+main()
diff --git a/tools/wrappers/__init__.py b/tools/wrappers/__init__.py
new file mode 100644
index 00000000..249cb8e5
--- /dev/null
+++ b/tools/wrappers/__init__.py
@@ -0,0 +1,41 @@
+"""
+Wrappers
+========
+
+ @file wrappers/__init__.py
+
+ PaStiX wrapper generators module intialization
+
+ @copyright 2017-2018 Bordeaux INP, CNRS (LaBRI UMR 5800), Inria,
+                      Univ. Bordeaux. All rights reserved.
+
+ @version 6.0.0
+ @author Mathieu Faverge
+ @date 2017-05-04
+
+"""
+
+# translation_table with names of auxiliary variables
+return_variables_dict = {
+    "double":            ("value"),
+    "float":             ("value"),
+    "pastix_int_t":      ("value"),
+    "pastix_spm_t":      ("spmo"),
+    "pastix_order_t":    ("order"),
+    "spm_int_t"   :      ("value"),
+    "spmatrix_t":        ("spmo"),
+}
+
+# global list used to determine derived types
+derived_types = [ 'pastix_int_t', 'pastix_data_t', 'pastix_order_t']
+
+# name arrays which will be translated to assumed-size arrays, e.g. pA(*)
+arrays_names_2D = ["pA", "pB", "pC", "pAB", "pQ", "pX", "pAs"]
+arrays_names_1D = ["colptr", "rowptr", "loc2glob", "dofs", "row",
+                   "iparm", "dparm", "bindtab", "perm", "invp", "schur_list",
+                   "rang", "tree" ]
+
+__all__ = [ 'return_variables_dict', 'derived_types', 'arrays_names_1D', 'arrays_names_2D' ]
+
+from .wrap_python  import *
+from .wrap_fortran import *
diff --git a/tools/wrappers/wrap_fortran.py b/tools/wrappers/wrap_fortran.py
new file mode 100644
index 00000000..e4250f83
--- /dev/null
+++ b/tools/wrappers/wrap_fortran.py
@@ -0,0 +1,474 @@
+#!/usr/bin/env python
+"""
+Wrapper Fortran 90
+==================
+
+ @file wrappers/wrap_fortran.py
+
+ PaStiX generator for the Fortran 90 wrapper
+
+ @copyright 2017-2017 Bordeaux INP, CNRS (LaBRI UMR 5800), Inria,
+                      Univ. Bordeaux. All rights reserved.
+
+ @version 6.0.0
+ @author Mathieu Faverge
+ @date 2017-05-04
+
+"""
+import os
+import re
+import argparse
+from . import *
+
+# set indentation in the f90 file
+tab = "  "
+indent = "   "
+
+itab=2
+iindent=3
+
+# translation_table of types
+types_dict = {
+    "int":            ("integer(kind=c_int)"),
+    "spm_coeftype_t": ("integer(c_int)"),
+    "spm_dir_t":      ("integer(c_int)"),
+    "spm_trans_t":    ("integer(c_int)"),
+    "spm_uplo_t":     ("integer(c_int)"),
+    "spm_diag_t":     ("integer(c_int)"),
+    "spm_side_t":     ("integer(c_int)"),
+    "spm_driver_t":   ("integer(c_int)"),
+    "spm_fmttype_t":  ("integer(c_int)"),
+    "spm_layout_t":   ("integer(c_int)"),
+    "spm_normtype_t": ("integer(c_int)"),
+    "spm_rhstype_t":  ("integer(c_int)"),
+    "spm_mtxtype_t":  ("integer(c_int)"),
+    "spmatrix_t":     ("type(spmatrix_t)"),
+    "spm_int_t":      ("integer(kind=spm_int_t)"),
+    "size_t":         ("integer(kind=c_size_t)"),
+    "char":           ("character(kind=c_char)"),
+    "double":         ("real(kind=c_double)"),
+    "float":          ("real(kind=c_float)"),
+    "spm_complex64_t":("complex(kind=c_double_complex)"),
+    "spm_complex32_t":("complex(kind=c_float_complex)"),
+    "void":           ("type(c_ptr)"),
+    "MPI_Comm":       ("integer(kind=c_int)"),
+    "FILE":           ("type(c_ptr)"),
+}
+
+def iso_c_interface_type(arg, return_value, list):
+    """Generate a declaration for a variable in the interface."""
+
+    if (arg[1] == "*" or arg[1] == "**"):
+        is_pointer = True
+    else:
+        is_pointer = False
+
+    if (is_pointer):
+        f_type = "type(c_ptr)"
+    else:
+        f_type = types_dict[arg[0]]
+
+    if (not return_value and arg[1] != "**"):
+        f_type += ", "
+        f_pointer = "value"
+    else:
+        f_pointer = ""
+
+    f_name = arg[2]
+
+    list.append( [ f_type, f_pointer, f_name ] );
+    return len(f_type + f_pointer)
+
+def iso_c_wrapper_type(arg, args_list, args_size):
+    """Generate a declaration for a variable in the Fortran wrapper."""
+
+    if (arg[1] == "*" or arg[1] == "**"):
+        is_pointer = True
+    else:
+        is_pointer = False
+
+    if (is_pointer and arg[0].strip() == "void"):
+        f_type = "type(c_ptr), "
+    else:
+        f_type = types_dict[arg[0]] + ", "
+
+    if is_pointer and not arg[3]:
+        f_intent = "intent(inout)"
+    else:
+        f_intent = "intent(in)"
+
+    if (is_pointer):
+        f_intent += ", "
+        if (arg[1] == "*"):
+           f_target = "target"
+        else:
+           f_target = "pointer"
+    else:
+        f_target = ""
+
+    f_name = arg[2]
+
+    # Force case of myorder
+    if f_name == "myorder":
+        f_intent = "intent(in), "
+        f_target = "pointer"
+
+    # detect array argument
+    if   (is_pointer and f_name in arrays_names_2D):
+        f_array = "(:,:)"
+    elif (is_pointer and f_name in arrays_names_1D):
+        f_array = "(:)"
+    else:
+        f_array = ""
+
+    f_line = f_type + f_intent + f_target + " :: " + f_name + f_array
+
+    args_size[0] = max(args_size[0], len(f_type))
+    args_size[1] = max(args_size[1], len(f_intent))
+    args_size[2] = max(args_size[2], len(f_target))
+    args_list.append( (f_type, f_intent, f_target, f_name, f_array ) )
+    return f_line
+
+class wrap_fortran:
+
+    @staticmethod
+    def header( f ):
+        filename = os.path.basename( f['filename'] )
+        modname = re.sub(r".f90", "", filename, flags=re.IGNORECASE)
+        header = '''
+!
+! @file '''+ filename +'''
+!
+! ''' + f['description'] + '''
+!
+! @copyright 2017      Bordeaux INP, CNRS (LaBRI UMR 5800), Inria,
+!                      Univ. Bordeaux. All rights reserved.
+!
+! @version 6.0.0
+! @author Mathieu Faverge
+! @date 2017-01-01
+!
+! This file has been automatically generated with gen_wrappers.py
+!
+module ''' + modname + '''
+  use iso_c_binding
+'''
+
+        if f['header'] != "":
+            header += f['header']
+
+        return header
+
+    @staticmethod
+    def footer( f ):
+        filename = os.path.basename( f['filename'] )
+        modname = re.sub(r".f90", "", filename, flags=re.IGNORECASE)
+        footer = f['footer'] + '''
+end module ''' + modname
+        return footer
+
+    @staticmethod
+    def enum( f, enum ):
+        """Generate an interface for an enum.
+           Translate it into constants."""
+
+        ename  = enum[0]
+        params = enum[1]
+
+        # initialize a string with the fortran interface
+        f_interface  = tab + "! enum " + ename + "\n"
+        f_interface += tab + "enum, bind(C)\n"
+
+        # loop over the arguments of the enum to get max param length
+        length=0
+        for param in params:
+            length= max( length, len(param[0]) )
+        fmt="%-"+ str(length) + "s"
+
+        # Increment for index array enums
+        inc = 0
+        if ename[1:5] == "parm":
+            inc=1
+
+        # loop over the arguments of the enum
+        for param in params:
+            name  = param[0]
+            if isinstance(param[1],int):
+                if name[1:10] == "PARM_SIZE":
+                    value = str(param[1])
+                else:
+                    value = str(param[1] + inc)
+            else:
+                value = str(param[1])
+            f_interface += tab + "   enumerator :: " + format(fmt % name) + " = " + value + "\n"
+
+        f_interface += tab + "end enum\n"
+        return f_interface
+
+    @staticmethod
+    def struct(struct):
+        """Generate an interface for a struct.
+           Translate it into a derived type."""
+
+        # initialize a string with the fortran interface
+        f_interface = ""
+
+        s = itab
+        name = struct[0][2]
+        f_interface += s*" " + "type, bind(c) :: " + name + "\n"
+
+        # loop over the arguments of the struct to get the length
+        s += iindent
+        slist = []
+        length= 0
+        for j in range(1,len(struct)):
+            length = max( length, iso_c_interface_type(struct[j], True, slist) )
+        fmt = s*" " + "%-"+ str(length) + "s :: %s\n"
+
+        # loop over the arguments of the struct
+        for j in range(0,len(slist)):
+            f_interface += format( fmt % (slist[j][0], slist[j][2]) )
+
+        s -= iindent
+        f_interface += s*" " + "end type " + name + "\n"
+
+        return f_interface
+
+    @staticmethod
+    def function(function):
+        """Generate an interface for a function."""
+
+        # is it a function or a subroutine
+        if (function[0][0] == "void"):
+            is_function = False
+            symbol="subroutine"
+        else:
+            is_function = True
+            symbol="function"
+
+        c_symbol = function[0][2]
+        f_symbol = function[0][2] + "_c"
+
+        used_derived_types = set([])
+        for arg in function:
+            type_name = arg[0]
+            if (type_name in derived_types):
+                used_derived_types.add(type_name)
+
+        # initialize a string with the fortran interface
+        s = itab
+        f_interface = s*" " + "interface\n"
+        s += iindent
+
+        f_interface += s*" " + symbol + " " + f_symbol + "("
+        s += itab
+
+        initial_len = s + len(symbol + " " + f_symbol + "(" )
+
+        # loop over the arguments to compose the first line
+        s += iindent
+        for j in range(1,len(function)):
+            if (j != 1):
+                f_interface += ", "
+                initial_len += 2
+
+            l = len(function[j][2])
+            if ((initial_len + l) > 77):
+                f_interface += "&\n" + s*" "
+                initial_len = s
+
+            f_interface += function[j][2]
+            initial_len += l
+
+        f_interface += ") &\n"
+        f_interface += s*" " + "bind(c, name='" + c_symbol +"')\n"
+        s -= iindent
+
+        # add common header
+        f_interface += s*" " + "use iso_c_binding\n"
+        # import derived types
+        for derived_type in used_derived_types:
+            f_interface += s*" " + "import " + derived_type +"\n"
+        f_interface += s*" " + "implicit none\n"
+
+        plist = []
+        length = 0
+        # add the return value of the function
+        if (is_function):
+            l = iso_c_interface_type(function[0], True, plist ) + 2
+            length = max( length, l );
+            plist[0][2] += "_c"
+
+        # loop over the arguments to describe them
+        for j in range(1,len(function)):
+            length = max( length, iso_c_interface_type(function[j], False, plist ) )
+
+        # loop over the parameters
+        for j in range(0,len(plist)):
+            fmt = s*" " + "%s%" + str(length-len(plist[j][0])) + "s :: %s\n"
+            f_interface += format( fmt % (plist[j][0], plist[j][1], plist[j][2]) )
+
+        s -= itab
+        f_interface += s*" " + "end " + symbol + " " + f_symbol + "\n"
+
+        s -= iindent
+        f_interface += s*" " + "end interface\n"
+
+        return f_interface
+
+    @staticmethod
+    def wrapper(function):
+        """Generate a wrapper for a function.
+           void functions in C will be called as subroutines,
+           functions in C will be turned to subroutines by appending
+           the return value as the last argument."""
+
+        return_type = function[0][0]
+        return_pointer = function[0][1]
+        return_var = ""
+
+        # is it a function or a subroutine
+        if (return_type == "void"):
+            is_function = False
+            f_return = "call "
+        else:
+            is_function = True
+            if (return_type == "int"):
+                return_var = "info"
+            else:
+                return_var = return_variables_dict[return_type]
+            f_return = return_var + " = "
+
+        c_symbol = function[0][2]
+        f_symbol = c_symbol + "_c"
+
+        # loop over the arguments to compose the first line and call line
+        s = itab
+        signature_line = s*" " + "subroutine " + c_symbol + "("
+        call_line      = ""
+        signature_line_length = len(signature_line)
+        call_line_length      = s + itab + len(f_return + f_symbol + "(" )
+        double_pointers = []
+        args_list = []
+        args_size = [ 0, 0, 0 ]
+        length = 0
+        for j in range(1,len(function)):
+            # pointers
+            arg_type    = function[j][0]
+            arg_pointer = function[j][1]
+            arg_name    = function[j][2]
+
+            isfile = (arg_type == "FILE")
+
+            if (j != 1):
+                if not isfile:
+                    signature_line += ", "
+                    signature_line_length += 2
+                call_line += ", "
+                call_line_length += 2
+
+            # Signature line (do not make the argument list too long)
+            if not isfile:
+                line = iso_c_wrapper_type(function[j], args_list, args_size)
+
+                l = len(arg_name)
+                if ((signature_line_length + l) > 77):
+                    signature_line_length = s+itab+iindent
+                    signature_line += "&\n" + signature_line_length*" "
+                signature_line += arg_name
+                signature_line_length += l
+
+            # Call line
+            if isfile:
+                call_param = "c_null_ptr"
+            elif (arg_pointer == "**"):
+                aux_name = arg_name + "_aux"
+                call_param = aux_name
+                double_pointers.append(arg_name)
+            elif (arg_pointer == "*") and (not "type(c_ptr), " in args_list[len(args_list)-1]):
+                call_param = "c_loc(" + arg_name + ")"
+            else:
+                call_param = arg_name
+
+            # Do not make the call line too long
+            l = len(call_param)
+            if ((call_line_length + l) > 77):
+                call_line_length = s+itab+iindent+itab
+                call_line += "&\n" + call_line_length*" "
+            call_line += call_param
+            call_line_length += l
+
+
+        # initialize a string with the fortran interface
+        f_wrapper = signature_line
+        if (is_function):
+            if (len(function) > 1):
+                f_wrapper += ", "
+                signature_line_length += 2
+
+            return_type = function[0][0]
+            return_pointer = function[0][1]
+
+            l = len(return_var)
+            if ((signature_line_length + l) > 78):
+                signature_line_length = s+itab+iindent
+                f_wrapper += "&\n" + signature_line_length*" "
+
+            f_wrapper += return_var
+
+        f_wrapper += ")\n"
+
+        s += itab
+        # add common header
+        f_wrapper += s*" " + "use iso_c_binding\n"
+        f_wrapper += s*" " + "implicit none\n"
+
+        # add the return info value of the function
+        if (is_function):
+            f_type = types_dict[return_type] + ", "
+            f_intent = "intent(out)"
+            if (function[0][1] == "*"):
+                f_intent += ", "
+                f_target = "pointer"
+            else:
+                f_target = ""
+
+            args_size[0] = max(args_size[0], len(f_type))
+            args_size[1] = max(args_size[1], len(f_intent))
+            args_size[2] = max(args_size[2], len(f_target))
+            args_list.append( (f_type, f_intent, f_target, return_var, "" ) )
+
+        # loop over potential double pointers and generate auxiliary variables for them
+        init_line = ""
+        for double_pointer in double_pointers:
+            aux_name = double_pointer + "_aux"
+
+            args_size[0] = max(args_size[0], len("type(c_ptr)"))
+            args_list.append( ("type(c_ptr)", "", "", aux_name, "") )
+            init_line += s*" " + aux_name + " = c_loc(" + double_pointer + ")\n"
+
+        # loop over the arguments to describe them
+        fmt = s*" " + "%-" + str(args_size[0]) + "s%-" + str(args_size[1]) + "s%-" + str(args_size[2]) + "s :: %s%s\n"
+        for j in range(0,len(args_list)):
+            f_wrapper += format( fmt % args_list[j] )
+
+        f_wrapper += "\n"
+        f_wrapper += init_line
+        if init_line != "":
+            f_wrapper += "\n"
+
+        # generate the call to the C function
+        if (is_function and return_pointer == "*"):
+            f_wrapper += s*" " + "call c_f_pointer(" + f_symbol + "(" + call_line + "), " + return_var + ")\n"
+        else:
+            f_wrapper += s*" " + f_return + f_symbol + "(" + call_line + ")\n"
+
+        # loop over potential double pointers and translate them to Fortran pointers
+        for double_pointer in double_pointers:
+            aux_name = double_pointer + "_aux"
+            f_wrapper += s*" " + "call c_f_pointer(" + aux_name + ", " + double_pointer + ")\n"
+
+        s -= itab
+        f_wrapper += s*" " + "end subroutine " + c_symbol + "\n"
+
+        return f_wrapper
diff --git a/tools/wrappers/wrap_python.py b/tools/wrappers/wrap_python.py
new file mode 100644
index 00000000..ed5b79b4
--- /dev/null
+++ b/tools/wrappers/wrap_python.py
@@ -0,0 +1,518 @@
+#!/usr/bin/env python
+"""
+Wrapper Python
+==============
+
+ @file wrappers/wrap_python.py
+
+ PaStiX generator for the python wrapper
+
+ @copyright 2017-2017 Bordeaux INP, CNRS (LaBRI UMR 5800), Inria,
+                      Univ. Bordeaux. All rights reserved.
+
+ @version 6.0.0
+ @author Mathieu Faverge
+ @date 2017-05-04
+
+"""
+import os
+import re
+import argparse
+from . import *
+
+indent="    "
+iindent=4
+
+# translation_table of types
+types_dict = {
+    "int":            ("c_int"),
+    "spm_coeftype_t": ("c_int"),
+    "spm_dir_t":      ("c_int"),
+    "spm_trans_t":    ("c_int"),
+    "spm_uplo_t":     ("c_int"),
+    "spm_diag_t":     ("c_int"),
+    "spm_side_t":     ("c_int"),
+    "spm_driver_t":   ("c_int"),
+    "spm_fmttype_t":  ("c_int"),
+    "spm_layout_t":   ("c_int"),
+    "spm_normtype_t": ("c_int"),
+    "spm_rhstype_t":  ("c_int"),
+    "spm_mtxtype_t":  ("c_int"),
+    "spm_int_t":      ("pastix_int"),
+    "spmatrix_t":     ("pyspmatrix_t"),
+    "size_t":         ("c_size_t"),
+    "char":           ("c_char"),
+    "double":         ("c_double"),
+    "float":          ("c_float"),
+    "void":           ("c_void"),
+    "MPI_Comm":       ("c_int"),
+    "FILE":           ("c_void"),
+}
+
+def iso_c_interface_type(arg, return_value, args_list, args_size):
+    """Generate a declaration for a variable in the interface."""
+
+    if (arg[1] == "*" or arg[1] == "**"):
+        is_pointer = True
+    else:
+        is_pointer = False
+
+    f_type = types_dict[arg[0]]
+    if is_pointer:
+        if f_type == "c_void":
+            f_type = "c_void_p"
+        elif f_type == "c_char":
+            f_type = "c_char_p"
+        elif f_type == "c_int":
+            f_type = "c_int_p"
+        else:
+            f_type = "POINTER("+f_type+")"
+
+    if (not return_value and arg[1] != "**"):
+        f_pointer = "value"
+    else:
+        f_pointer = ""
+
+    f_name = format("\"%s\", " % arg[2] )
+
+    args_size[0] = max(args_size[0], len(f_name))
+    args_size[1] = max(args_size[1], len(f_type))
+    args_list.append( [ f_name, f_type ] );
+
+def iso_c_wrapper_type(arg, args_list, args_size):
+    """Generate a declaration for a variable in the Fortran wrapper."""
+
+    if (arg[1] == "*" or arg[1] == "**"):
+        is_pointer = True
+    else:
+        is_pointer = False
+
+    isfile = (arg[0] == "FILE")
+    f_type = types_dict[arg[0]]
+
+    if is_pointer:
+        if f_type == "c_void":
+            f_type = "c_void_p"
+        elif f_type == "c_char":
+            f_type = "c_char_p"
+        elif f_type == "c_int":
+            f_type = "c_int_p"
+        else:
+            f_type = "POINTER("+f_type+")"
+
+    f_name = arg[2]
+    f_call = f_name
+
+    if isfile:
+        f_name = ""
+        f_call = "None"
+
+    # detect array argument
+    if (is_pointer and f_name in arrays_names_2D):
+        f_call = f_name + ".ctypes.data_as( " + f_type + " )"
+    elif (is_pointer and f_name in arrays_names_1D):
+        f_call = f_name + ".ctypes.data_as( " + f_type + " )"
+
+    if arg[1] == "**":
+        f_call = "pointer( " + f_call + " )"
+
+    args_size[0] = max(args_size[0], len(f_name))
+    args_size[1] = max(args_size[1], len(f_type))
+    args_size[2] = max(args_size[2], len(f_call))
+    args_list.append( [f_name, f_type, f_call ] )
+
+class wrap_python:
+
+    @staticmethod
+    def header( f ):
+        filename = os.path.basename( f['filename'] )
+        filename = re.sub(r"\.in", "", filename)
+        header = '''"""
+
+ @file ''' + filename + '''
+
+ ''' + f['description'] + '''
+
+ @copyright 2017-2017 Bordeaux INP, CNRS (LaBRI UMR 5800), Inria,
+                      Univ. Bordeaux. All rights reserved.
+
+ @version 6.0.0
+ @author Pierre Ramet
+ @author Mathieu Faverge
+ @author Louis Poirel
+ @date 2017-05-04
+
+This file has been automatically generated with gen_wrappers.py
+
+"""
+from ctypes import *
+import numpy as np
+'''
+        if f['header'] != "":
+            header += "\n" + f['header']
+        return header;
+
+    @staticmethod
+    def footer( f ):
+        filename = os.path.basename( f['filename'] )
+        modname = re.sub(r".f90", "", filename, flags=re.IGNORECASE)
+        footer = f['footer']
+        return footer
+
+    @staticmethod
+    def enum( f, enum ):
+        """Generate an interface for an enum.
+           Translate it into constants."""
+
+        ename  = enum[0]
+        params = enum[1]
+
+        # initialize a string with the fortran interface
+        py_interface = "class " + ename + ":\n"
+
+        # loop over the arguments of the enum to get max param length
+        length=0
+        for param in params:
+            # Convert IPARM/DPARM to lower case
+            if param[0][1:5] == "PARM":
+                param[0] = re.sub(r"[ID]PARM_", "", param[0])
+                param[0] = param[0].lower()
+
+            # Remove Pastix from everything
+            param[0] = re.sub(r"Pastix", "", param[0])
+
+            if ename == "error":
+                param[0] = re.sub(r"PASTIX_", "", param[0])
+                param[0] = re.sub(r"ERR_", "", param[0])
+            elif ename == "fact_mode" or ename == "factotype" or ename == "solv_mode":
+                param[0] = re.sub(r"Fact", "", param[0])
+                param[0] = re.sub(r"Solv", "", param[0])
+                param[0] = re.sub(r"Mode", "", param[0])
+            elif ename == "scheduler":
+                param[0] = re.sub(r"Sched", "", param[0])
+            elif ename == "threadmode":
+                param[0] = re.sub(r"Thread", "", param[0])
+            elif ename[0:8] == "compress":
+                param[0] = re.sub(r"Compress", "", param[0])
+                param[0] = re.sub(r"When", "", param[0])
+                param[0] = re.sub(r"Method", "", param[0])
+            elif ename == "rhstype":
+                param[0] = re.sub(r"Rhs", "", param[0])
+            elif ename == "trans":
+                param[0] = param[0]
+            elif ename == "mtxtype":
+                param[1] = re.sub(r"Pastix", "trans.", param[1])
+            elif ename == "normtype":
+                param[0] = re.sub(r"Norm", "", param[0])
+            else:
+                param[0] = re.sub(ename, "", param[0], flags=re.IGNORECASE)
+            length = max( length, len(param[0]))
+        fmt="%-"+ str(length) + "s"
+
+        # loop over the arguments of the enum
+        for param in params:
+            name  = param[0]
+            value = str(param[1])
+
+            py_interface += indent + format(fmt % name) + " = " + value + "\n"
+
+        if ename in f['enums']:
+            py_interface += f['enums'][ename]
+
+        return py_interface
+
+    @staticmethod
+    def struct(struct):
+        """Generate an interface for a struct.
+           Translate it into a derived type."""
+
+        # initialize a string with the fortran interface
+        py_interface = ""
+
+        s = 0
+        name = struct[0][2]
+        name = re.sub(r"pastix_", "", name)
+        py_interface +=  "class pypastix_" + name + "(Structure):\n"
+
+        s = iindent
+        py_interface +=  s*" " + "_fields_ = ["
+        headline = (s+len("_fields_ = ["))*" "
+
+        slist = []
+        ssize = [ 0, 0 ]
+
+        # loop over the arguments of the enum
+        for j in range(1,len(struct)):
+            iso_c_interface_type(struct[j], True, slist, ssize)
+
+        s += iindent
+        fmt = "(%-"+ str(ssize[0]) + "s %-"+ str(ssize[1]) +"s)"
+
+        for j in range(0,len(slist)):
+            if (j > 0):
+                py_interface += ",\n" + headline
+
+            py_interface += format( fmt % (slist[j][0], slist[j][1]) )
+
+        py_interface += " ]\n"
+
+        return py_interface
+
+    @staticmethod
+    def function(function):
+        """Generate an interface for a function."""
+
+        return_type    = function[0][0]
+        return_pointer = function[0][1]
+
+        # is it a function or a subroutine
+        if (return_type == "void"):
+            is_function = False
+        else:
+            is_function = True
+
+        c_symbol = function[0][2]
+        if "pastix" in c_symbol:
+            libname = "libpastix"
+            prefix  = "pypastix_"
+        elif "spm" in c_symbol:
+            libname = "libspm"
+            prefix  = "pyspm_"
+        else:
+            print("ERROR: function name without pastix nor spm")
+            return
+
+        # loop over the arguments to compose the different call lines
+        func_line = "def " + prefix + c_symbol + "("
+        func_line_length = len(func_line)
+        func_line_indent = len(func_line)
+
+        args_line = iindent*" " + libname + "." + c_symbol + ".argtypes = ["
+        args_line_length = len(args_line)
+        args_line_indent = len(args_line)
+
+        if is_function:
+            call_line = iindent*" " + "return " + libname + "." + c_symbol + "("
+
+            py_type = types_dict[return_type]
+            if return_pointer == "*":
+                if py_type == "c_void":
+                    py_type = "c_void_p"
+                else:
+                    py_type = "POINTER("+py_type+")"
+            elif  return_pointer == "**":
+                py_type = "c_void_p"
+
+            retv_line = iindent*" " + libname + "." + c_symbol + ".restype = " + py_type
+        else:
+            call_line = iindent*" " + libname + "." + c_symbol + "("
+            retv_line = ""
+        call_line_length = len(call_line)
+        call_line_indent = len(call_line)
+
+        args_list = []
+        args_size = [ 0, 0, 0 ]
+
+        # loop over the arguments to compose the first line
+        for j in range(1,len(function)):
+            iso_c_wrapper_type(function[j], args_list, args_size)
+
+        for j in range(0, len(args_list)):
+            # pointers
+            arg_name = args_list[j][0]
+            arg_type = args_list[j][1]
+            arg_call = args_list[j][2]
+
+            isfile = (len(arg_name) == 0)
+
+            if j > 0:
+                if not isfile:
+                    func_line += ","
+                    func_line_length += 2
+                args_line += ","
+                args_line_length += 2
+                call_line += ","
+                call_line_length += 2
+
+            # func_line
+            l = len(arg_name)
+            if not isfile:
+                if ((func_line_length + l) > 78):
+                    func_line_length = func_line_indent
+                    func_line += "\n" + func_line_indent*" "
+                func_line += " " + arg_name
+                func_line_length += l
+
+            # args_line
+            l = len(arg_type)
+            if ((args_line_length + l) > 78):
+                args_line_length = args_line_indent
+                args_line += "\n" + args_line_indent*" "
+            args_line += " " + arg_type
+            args_line_length += l
+
+            # call_line
+            l = len(arg_call)
+            if ((call_line_length + l) > 78):
+                call_line_length = call_line_indent
+                call_line += "\n" + call_line_indent*" "
+            call_line += " " + arg_call
+            call_line_length += l
+
+        py_interface  = func_line + " ):\n"
+        py_interface += args_line + " ]\n"
+        if len(retv_line) > 0:
+            py_interface += retv_line + "\n"
+        py_interface += call_line + " )\n"
+
+        # # add common header
+        # py_interface += indent + 2*indent + "use iso_c_binding\n"
+        # # import derived types
+        # for derived_type in used_derived_types:
+        #     py_interface += indent + 2*indent + "import " + derived_type +"\n"
+        # py_interface += indent + 2*indent + "implicit none\n"
+
+
+        # # add the return value of the function
+        # if (is_function):
+        #     py_interface +=  indent + 2*indent + iso_c_interface_type(function[0], True) + "_c"
+        #     py_interface += "\n"
+
+        # # loop over the arguments to describe them
+        # for j in range(1,len(function)):
+        #     py_interface += indent + 2*indent + iso_c_interface_type(function[j], False)
+        #     py_interface += "\n"
+
+        # if (is_function):
+        #     py_interface += indent + indent + "end function\n"
+        # else:
+        #     py_interface += indent + indent + "end subroutine\n"
+
+        # py_interface += indent + "end interface\n"
+
+        return py_interface
+
+    @staticmethod
+    def wrapper(function):
+        """Generate a wrapper for a function.
+           void functions in C will be called as subroutines,
+           functions in C will be turned to subroutines by appending
+           the return value as the last argument."""
+
+        # is it a function or a subroutine
+        if (function[0][0] == "void"):
+            is_function = False
+        else:
+            is_function = True
+
+        c_symbol = function[0][2]
+        f_symbol = c_symbol + "_c"
+
+        if (is_function):
+            initial_indent_signature = len(indent + "subroutine " + c_symbol + "(") * " "
+            initial_indent_call      = len(indent + indent + "info = " + f_symbol + "(") * " "
+        else:
+            initial_indent_signature = len(indent + "subroutine " + c_symbol + "(") * " "
+            initial_indent_call      = len(indent + indent + "call " + f_symbol + "(") * " "
+
+        # loop over the arguments to compose the first line and call line
+        signature_line = ""
+        call_line = ""
+        double_pointers = []
+        for j in range(1,len(function)):
+            if (j != 1):
+                signature_line += ", "
+                call_line += ", "
+
+            # do not make the argument list too long
+            if (j%9 == 0):
+                call_line      += "&\n" + initial_indent_call
+                signature_line += "&\n" + initial_indent_signature
+
+            # pointers
+            arg_type    = function[j][0]
+            arg_pointer = function[j][1]
+            arg_name    = function[j][2]
+
+            signature_line += arg_name
+            if (arg_pointer == "**"):
+                aux_name = arg_name + "_aux"
+                call_line += aux_name
+                double_pointers.append(arg_name)
+            elif (arg_pointer == "*"):
+                call_line += "c_loc(" + arg_name + ")"
+            else:
+                call_line += arg_name
+
+        contains_derived_types = False
+        for arg in function:
+            if (arg[0] in derived_types):
+                contains_derived_types = True
+
+        # initialize a string with the fortran interface
+        f_wrapper = ""
+        f_wrapper += indent + "subroutine "
+        f_wrapper += c_symbol + "("
+
+        # add the info argument at the end
+        f_wrapper += signature_line
+        if (is_function):
+            if (len(function) > 1):
+                f_wrapper += ", "
+
+            return_type = function[0][0]
+            return_pointer = function[0][1]
+            if (return_type == "int"):
+                return_var = "info"
+            else:
+                return_var = return_variables_dict[return_type]
+
+            f_wrapper += return_var
+
+        f_wrapper += ")\n"
+
+        # add common header
+        f_wrapper += indent + indent + "use iso_c_binding\n"
+        f_wrapper += indent + indent + "implicit none\n"
+
+        # loop over the arguments to describe them
+        for j in range(1,len(function)):
+            f_wrapper += indent + indent + iso_c_wrapper_type(function[j]) + "\n"
+
+        # add the return info value of the function
+        if (is_function):
+            if (function[0][1] == "*"):
+                f_target = ", pointer"
+            else:
+                f_target = ""
+
+            f_wrapper += indent + indent + types_dict[return_type] + ", intent(out)" + f_target + " :: " + return_var + "\n"
+
+        f_wrapper += "\n"
+
+        # loop over potential double pointers and generate auxiliary variables for them
+        for double_pointer in double_pointers:
+            aux_name = double_pointer + "_aux"
+            f_wrapper += indent + indent + "type(c_ptr) :: " + aux_name + "\n"
+            f_wrapper += "\n"
+
+        if (is_function):
+            f_return = return_var
+            f_return += " = "
+        else:
+            f_return = "call "
+
+        # generate the call to the C function
+        if (is_function and return_pointer == "*"):
+            f_wrapper += indent + indent + "call c_f_pointer(" + f_symbol + "(" + call_line + "), " + return_var + ")\n"
+        else:
+            f_wrapper += indent + indent + f_return + f_symbol + "(" + call_line + ")\n"
+
+        # loop over potential double pointers and translate them to Fortran pointers
+        for double_pointer in double_pointers:
+            aux_name = double_pointer + "_aux"
+            f_wrapper += indent + indent + "call c_f_pointer(" + aux_name + ", " + double_pointer + ")\n"
+
+        f_wrapper += indent + "end subroutine\n"
+
+        return f_wrapper
-- 
GitLab