Repository: numpy/numpy

Issue:
f2py fails creating an interface file
Somewhere in the code a wrong variable is passed down to an exec statement. This is not a pull request as I believe someone should look into it and potentially also replace the actual `exec()` call.

In the meanwhile, this ugly hack fixes it:

``` diff
--- a/numpy/f2py/crackfortran.py
+++ b/numpy/f2py/crackfortran.py
@@ -2606,7 +2606,10 @@
     ret = []
     for intent in lst:
         try:
-            exec('c = isintent_%s(var)' % intent)
+            # Somewhere in the code the intent item of var['intent'] is set to
+            # "in out" instead of "inout". This ugly fix works but is likely
+            # not an ideal solution.
+            exec('c = isintent_%s(var)' % intent.replace(" ", ""))
         except NameError:
             c = 0
         if c:
```
### Additional information
#### Numpy Version

``` python
>>> np.__version__
1.6.2
```
#### Library

Used on [mtspec](http://wwwprof.uniandes.edu.co/~gprieto/software/mwlib.html) library v3.1 if anyone wants to reproduce.
#### Call and Traceback

``` bash
$ f2py -m mtspec -h mtspec.pyf *.f90

...

Traceback (most recent call last):
      File "/home/krischer/local/virtual_envs/py2.7/bin/f2py", line 24, in <module>
          main()
        File "/home/krischer/local/virtual_envs/py2.7/lib/python2.7/site-packages/numpy/f2py/f2py2e.py", line 590, in main
          run_main(sys.argv[1:])
        File "/home/krischer/local/virtual_envs/py2.7/lib/python2.7/site-packages/numpy/f2py/f2py2e.py", line 361, in run_main
          postlist=callcrackfortran(files,options)
        File "/home/krischer/local/virtual_envs/py2.7/lib/python2.7/site-packages/numpy/f2py/f2py2e.py", line 286, in callcrackfortran
          pyf=crackfortran.crack2fortran(postlist)
        File "/home/krischer/local/virtual_envs/py2.7/lib/python2.7/site-packages/numpy/f2py/crackfortran.py", line 2749, in crack2fortran
          pyf=crack2fortrangen(block)+'\n'
        File "/home/krischer/local/virtual_envs/py2.7/lib/python2.7/site-packages/numpy/f2py/crackfortran.py", line 2517, in crack2fortrangen
          ret=ret+crack2fortrangen(g,tab,as_interface=as_interface)
        File "/home/krischer/local/virtual_envs/py2.7/lib/python2.7/site-packages/numpy/f2py/crackfortran.py", line 2560, in crack2fortrangen
          body=crack2fortrangen(block['body'],tab+tabchar)
        File "/home/krischer/local/virtual_envs/py2.7/lib/python2.7/site-packages/numpy/f2py/crackfortran.py", line 2517, in crack2fortrangen
          ret=ret+crack2fortrangen(g,tab,as_interface=as_interface)
        File "/home/krischer/local/virtual_envs/py2.7/lib/python2.7/site-packages/numpy/f2py/crackfortran.py", line 2560, in crack2fortrangen
          body=crack2fortrangen(block['body'],tab+tabchar)
        File "/home/krischer/local/virtual_envs/py2.7/lib/python2.7/site-packages/numpy/f2py/crackfortran.py", line 2517, in crack2fortrangen
          ret=ret+crack2fortrangen(g,tab,as_interface=as_interface)
        File "/home/krischer/local/virtual_envs/py2.7/lib/python2.7/site-packages/numpy/f2py/crackfortran.py", line 2561, in crack2fortrangen
          vars=vars2fortran(block,block['vars'],argsl,tab+tabchar, as_interface=as_interface)
        File "/home/krischer/local/virtual_envs/py2.7/lib/python2.7/site-packages/numpy/f2py/crackfortran.py", line 2711, in vars2fortran
          lst = true_intent_list(vars[a])
        File "/home/krischer/local/virtual_envs/py2.7/lib/python2.7/site-packages/numpy/f2py/crackfortran.py", line 2609, in true_intent_list
          exec('c = isintent_%s(var)' % intent)
        File "<string>", line 1
          c = isintent_in out(var)
                            ^
      SyntaxError: invalid syntax
```



Discussion:
Could you provide a small program that exhibits the problem? f2py is a tangle and the problem isn't easy to find.

Here's a test case.  Note the space in `in out`.

**foo.f90**

```
subroutine foo(x)
implicit none
real(4), intent(in out) :: x
return
end
```

Here's the error:

```
$ f2py -m foo -h foo.pyf foo.f90 
Reading fortran codes...
    Reading file 'foo.f90' (format:free)
Post-processing...
    Block: foo
            Block: foo
Post-processing (stage 2)...
Saving signatures to file "./foo.pyf"
Traceback (most recent call last):
  File "/Users/warren/anaconda/bin/f2py", line 24, in <module>
    main()
  File "/Users/warren/local_scipy/lib/python2.7/site-packages/numpy/f2py/f2py2e.py", line 592, in main
    run_main(sys.argv[1:])
  File "/Users/warren/local_scipy/lib/python2.7/site-packages/numpy/f2py/f2py2e.py", line 357, in run_main
    postlist=callcrackfortran(files, options)
  File "/Users/warren/local_scipy/lib/python2.7/site-packages/numpy/f2py/f2py2e.py", line 286, in callcrackfortran
    pyf=crackfortran.crack2fortran(postlist)
  File "/Users/warren/local_scipy/lib/python2.7/site-packages/numpy/f2py/crackfortran.py", line 2789, in crack2fortran
    pyf=crack2fortrangen(block)+'\n'
  File "/Users/warren/local_scipy/lib/python2.7/site-packages/numpy/f2py/crackfortran.py", line 2557, in crack2fortrangen
    ret=ret+crack2fortrangen(g, tab, as_interface=as_interface)
  File "/Users/warren/local_scipy/lib/python2.7/site-packages/numpy/f2py/crackfortran.py", line 2600, in crack2fortrangen
    body=crack2fortrangen(block['body'], tab+tabchar)
  File "/Users/warren/local_scipy/lib/python2.7/site-packages/numpy/f2py/crackfortran.py", line 2557, in crack2fortrangen
    ret=ret+crack2fortrangen(g, tab, as_interface=as_interface)
  File "/Users/warren/local_scipy/lib/python2.7/site-packages/numpy/f2py/crackfortran.py", line 2600, in crack2fortrangen
    body=crack2fortrangen(block['body'], tab+tabchar)
  File "/Users/warren/local_scipy/lib/python2.7/site-packages/numpy/f2py/crackfortran.py", line 2557, in crack2fortrangen
    ret=ret+crack2fortrangen(g, tab, as_interface=as_interface)
  File "/Users/warren/local_scipy/lib/python2.7/site-packages/numpy/f2py/crackfortran.py", line 2601, in crack2fortrangen
    vars=vars2fortran(block, block['vars'], argsl, tab+tabchar, as_interface=as_interface)
  File "/Users/warren/local_scipy/lib/python2.7/site-packages/numpy/f2py/crackfortran.py", line 2751, in vars2fortran
    lst = true_intent_list(vars[a])
  File "/Users/warren/local_scipy/lib/python2.7/site-packages/numpy/f2py/crackfortran.py", line 2649, in true_intent_list
    c = eval('isintent_%s(var)' % intent)
  File "<string>", line 1
    isintent_in out(var)
                  ^
SyntaxError: invalid syntax
```

Arrggghhhh. Some day crackfortran needs to be rewritten using a standard parser ;)

The proximate cause is in the fortran file `intent(in out)`, the `in out` is not valid fortran. See http://h21007.www2.hp.com/portal/download/files/unprot/fortran/docs/lrm/lrm0103.htm#intent_attr. I suppose catching this and issuing a better error message would help.

I'm inclined to close this. The error is likely in the fortran source, and without the source it is not possible to make progress.

Although I note the original files use that construction. Hmm..., IIRC, Fortran should ignore spaces. So perhaps the correct fix here is to remove any spaces in that argument, basically the fix proposed but perhaps in a different place.

I think `intent(in out)` is valid Fortran; `foo.f90` compiles with gfortran, even with `-pedantic` (I'm not sure if there are other arguments to make gfortran stricter).  Fortran is notorious for ignoring spaces (e.g. `GO TO` and `GOTO` are both valid).

The proposed change works, but I don't know if that is the best place to handle this.

Little changes can lead to problems. I'm thinking the minimal fix as proposed is the safest thing. It's a hack, but... 

With the proposed fix, the signature file is not the same as when `inout` is used.

Fix only

```
python module foo ! in 
    interface  ! in :foo
        module foo_fixed ! in :foo:foo.f90
            subroutine foo(x) ! in :foo:foo.f90:foo_fixed
                real(kind=4) :: x
            end subroutine foo
        end module foo_fixed
    end interface 
end python module foo
```

Using `inout`

```
python module foo ! in 
    interface  ! in :foo
        module foo_fixed ! in :foo:foo.f90
            subroutine foo(x) ! in :foo:foo.f90:foo_fixed
                real(kind=4) intent(inout) :: x
            end subroutine foo
        end module foo_fixed
    end interface 
end python module foo
```

I don't know if that matters in practice, but there is a subtle difference in fortran between the two cases.


Resolving patch:
diff --git a/numpy/f2py/crackfortran.py b/numpy/f2py/crackfortran.py
--- a/numpy/f2py/crackfortran.py
+++ b/numpy/f2py/crackfortran.py
@@ -1274,7 +1274,7 @@ def markinnerspaces(line):
     cb=''
     for c in line:
         if cb=='\\' and c in ['\\', '\'', '"']:
-            l=l+c;
+            l=l+c
             cb=c
             continue
         if f==0 and c in ['\'', '"']: cc=c; cc1={'\'':'"','"':'\''}[c]
@@ -2198,8 +2198,10 @@ def analyzevars(block):
                     if 'intent' not in vars[n]:
                         vars[n]['intent']=[]
                     for c in [x.strip() for x in markoutercomma(intent).split('@,@')]:
-                        if not c in vars[n]['intent']:
-                            vars[n]['intent'].append(c)
+                        # Remove spaces so that 'in out' becomes 'inout'
+                        tmp = c.replace(' ', '')
+                        if tmp not in vars[n]['intent']:
+                            vars[n]['intent'].append(tmp)
                     intent=None
                 if note:
                     note=note.replace('\\n\\n', '\n\n')
@@ -2220,7 +2222,7 @@ def analyzevars(block):
                     if 'check' not in vars[n]:
                         vars[n]['check']=[]
                     for c in [x.strip() for x in markoutercomma(check).split('@,@')]:
-                        if not c in vars[n]['check']:
+                        if c not in vars[n]['check']:
                             vars[n]['check'].append(c)
                     check=None
             if dim and 'dimension' not in vars[n]:


Test patch:
diff --git a/numpy/f2py/tests/src/regression/inout.f90 b/numpy/f2py/tests/src/regression/inout.f90
new file mode 100644
--- /dev/null
+++ b/numpy/f2py/tests/src/regression/inout.f90
@@ -0,0 +1,9 @@
+! Check that intent(in out) translates as intent(inout).
+! The separation seems to be a common usage.
+      subroutine foo(x)
+          implicit none
+          real(4), intent(in out) :: x
+          dimension x(3)
+          x(1) = x(1) + x(2) + x(3)
+          return
+      end
diff --git a/numpy/f2py/tests/test_regression.py b/numpy/f2py/tests/test_regression.py
new file mode 100644
--- /dev/null
+++ b/numpy/f2py/tests/test_regression.py
@@ -0,0 +1,32 @@
+from __future__ import division, absolute_import, print_function
+
+import os
+import math
+
+import numpy as np
+from numpy.testing import dec, assert_raises, assert_equal
+
+import util
+
+def _path(*a):
+    return os.path.join(*((os.path.dirname(__file__),) + a))
+
+class TestIntentInOut(util.F2PyTest):
+    # Check that intent(in out) translates as intent(inout)
+    sources = [_path('src', 'regression', 'inout.f90')]
+
+    @dec.slow
+    def test_inout(self):
+        # non-contiguous should raise error
+        x = np.arange(6, dtype=np.float32)[::2]
+        assert_raises(ValueError, self.module.foo, x)
+
+        # check values with contiguous array
+        x = np.arange(3, dtype=np.float32)
+        self.module.foo(x)
+        assert_equal(x, [3, 1, 2])
+
+
+if __name__ == "__main__":
+    import nose
+    nose.runmodule()