jameskermode / f90wrap

F90 to Python interface generator with derived type support

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

--move-methods causes wrong "use" statements

perrette opened this issue · comments

Coming back to f90wrap. Renewed congrats.
I really like the convenience --move-methods options, but apparently it assumes that all methods with the type as first argument come from the same module as the type in question. Here a case where it will cause an error:

debug_move_methods.f90:

module basemod
	!! base module, where the type is defined
	implicit none
	type param_t
		integer :: a
	end type
contains
	subroutine increment_a(p)
		type(param_t), intent(inout) :: p
		p%a = p%a + 1
	end subroutine
end module

module extmod
	!! extension module, adding non essential functionality
	implicit none
	use basemod, only: param_t
contains	
	subroutine print_a(p)
		type(param_t), intent(in) :: p
		print*, p%a
	end subroutine
end module

The standard procedure works like a charm:

gfortran -c debug_move_methods.f90 -fPIC
f90wrap -m debug debug_move_methods.f90
f2py-f90wrap -c -m _debug debug_move_methods.o f90wrap_debug_move_methods.f90

But the --move-methods variant fails:

f90wrap -m debug debug_move_methods.f90 --move-methods
f2py-f90wrap -c -m _debug debug_move_methods.o f90wrap_debug_move_methods.f90  # FAILS

because of unified (wrong) module use in f90wrap_debug_move_methods.f90

use basemod, only: param_t, print_a

Whereas print_a is defined in the extension module extmod, not in basemod.

I am not saying my design is the best ever (maybe the use of new submodule feature might achieve something similar -- but is it supported by f90wrap?), but I assume this bug should easy to fix? Thanks in any case.

Here is a reasonable workaround for my use case: just create a dummy module with all imports and replace any "use ..." statements in f90wra_*.f90 files with "use dummy ...".

Here a python script to do this on the fly (e.g. to be called from a Makefile just after f90wrap):

"""module {dummy}
!
! Fix cross-dependency bug associated with --move-methods flag to f90wrap.
! 
! The approach taken here is to create a dummy module which contains all needed module
! imports, and replace all "use {prefix}... " statements with "use {dummy}"
! It parses generated f90wrap_*.f90 code to create {dummy} sequentially.
!
! This module needs to be manually compiled and passed to f2py[-f90wrap] as object.

{statements}

contains

end module {dummy}
"""
from __future__ import print_function, absolute_import
import os, logging
logging.basicConfig(level=logging.INFO)

def parse_line(line):
    "use mod , only : name1, name2"
    if not ':' in line:
        usemod, names = line, ""
    else:
        useonly, names = line.split(':')
        usemod, _ = useonly.split(',')
    _, mod = usemod.strip().split()
    assert _.strip() == 'use'
    return mod, [nm.strip() for nm in names.split(',')]

class DummyModule(object):
    def __init__(self, name, prefix):
        self.name = name
        self.prefix = prefix
        self.statements = []

    def parse_line(self, line):
        if line.strip().startswith('use {}'.format(self.prefix)):
            mod, names = parse_line(line)
            assert mod != self.name, 'already parsed + prefix == dummy'

            # append use statement in dummy module
            self.append(mod)

            # replace original line
            line = "    use {}, only: {}".format(self.name, ", ".join(names))

        elif line.strip().startswith('use {}'.format(self.name)):
            raise ValueError('already parsed')

        return line

    def append(self, mod):
        statement = "    use "+mod
        if statement not in self.statements:
            logging.info("generate_dummy:: append module: "+mod)
            self.statements.append(statement)


    def format(self):
        return __doc__.format(dummy=self.name, prefix=self.prefix, statements="\n".join(self.statements))


def main():
    import argparse
    parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
    parser.add_argument('files', nargs='*', default=[], help='f90wrap_*.f90 files')
    parser.add_argument('--dummy', required=True, 
                        help='name of dummy module to create, e.g. module_dummy_')
    parser.add_argument('--prefix', required=True, help='prefix of modules to replace, e.g. module_')
    parser.add_argument('--dry-run', action='store_true', help='only print dummy module, do not modify any file')
    parser.add_argument('--add-modules', nargs='*', default=[], help='add additional modules (typically the extension modules erroneously removed by f90wrap with --move-method option activated)')
    args = parser.parse_args()

    dummy = DummyModule(args.dummy, args.prefix)

    for fname in args.files:
        logging.info("generate_dummy:: visit file: "+fname)

        txt = open(fname).read()

        lines = txt.splitlines()
        for i, line in enumerate(lines):
            lines[i] = dummy.parse_line(line)

        newtxt = "\n".join(lines)

        # replace file if modified
        if txt != newtxt and not args.dry_run:
            logging.info("generate_dummy:: update file: "+fname)
            with open(fname, 'w') as f:
                f.write(newtxt)

    # append additional modules
    for mod in args.add_modules:
        dummy.append(mod)

    print(dummy.format())

if __name__ == "__main__":
    main()