Repository: stfc/fparser

Issue:
Space after 'end do'
Atm psyclone creates 'end do' statements as 'END DO ' - notice the space at the end. While mostly this does not matter, it triggers pylint warnings if a ''' multi-line string is used to compare the output with, e.g. in gocean1p0_config_test:
```
new_loop1 = '''      DO j=1,2
        DO i=3,4
          CALL compute_kern1_code(i, j, cu_fld%data, p_fld%data, u_fld%data)
        END DO 
      END DO '''   # nopep8
```
Since multi-line strings are very convenient for storing Fortran strings, I would prefer if the space at the end of 'end do' could be removed, so that we don't need to add pylint/pycodestyle directives.

The patch appears to be simple:
```
diff --git a/src/fparser/common/base_classes.py b/src/fparser/common/base_classes.py
index c27aca6..0d8a86c 100644
--- a/src/fparser/common/base_classes.py
+++ b/src/fparser/common/base_classes.py
@@ -1002,5 +1002,8 @@ class EndStatement(Statement):
         return Statement.get_indent_tab(self, deindent=True, isfix=isfix)
 
     def tofortran(self, isfix=None):
+        if self.name:
+            return self.get_indent_tab(isfix=isfix) + 'END %s %s'\
+                   % (self.blocktype.upper(), self.name or '')
         return self.get_indent_tab(isfix=isfix) + 'END %s %s'\
                % (self.blocktype.upper(), self.name or '')
```
But I am not sure if this might affect anything else. Let me know if you are interested in a pull request for this.

Note that I could not find a convenient way to fix this in psyclone itself (short of converting the EndDo instance to a new class that would return 'end do' without a string, which seems to be silly todo 😜 ).

Discussion:

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5d7d8a3..554ebeb 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,6 +13,9 @@ Modifications by (in alphabetical order):
 * J. Tiira, University of Helsinki, Finland
 * P. Vitt, University of Siegen, Germany
 
+08/01/2020 PR #234 for #228. Remove the blank space at the end of unnamed
+           "END DO" statements.
+
 07/01/2020 PR #233 for #161. Improve the reader handling of multi-statement
            source lines. Especially those containing labels or construct
            names.
diff --git a/src/fparser/common/base_classes.py b/src/fparser/common/base_classes.py
index c27aca6..1b19163 100644
--- a/src/fparser/common/base_classes.py
+++ b/src/fparser/common/base_classes.py
@@ -1002,5 +1002,21 @@ class EndStatement(Statement):
         return Statement.get_indent_tab(self, deindent=True, isfix=isfix)
 
     def tofortran(self, isfix=None):
-        return self.get_indent_tab(isfix=isfix) + 'END %s %s'\
-               % (self.blocktype.upper(), self.name or '')
+        '''Returns a valid Fortran string for this END statement. It
+        guarantees that there is no white space after the 'END' in case
+        of an unnamed statement.
+
+        :param bool isfix: True if the code is in fixed format.
+
+        :returns: the (named or unnamed) valid Fortran END statement \
+                  as a string.
+        :rtype: str
+
+        '''
+        if self.name:
+            return self.get_indent_tab(isfix=isfix) + 'END {0} {1}'\
+                .format(self.blocktype.upper(), self.name)
+
+        # Make sure there is no space after an unnamed END:
+        return self.get_indent_tab(isfix=isfix) + 'END {0}'\
+            .format(self.blocktype.upper())


Test patch:
diff --git a/src/fparser/common/tests/test_base_classes.py b/src/fparser/common/tests/test_base_classes.py
index b286f41..960632b 100644
--- a/src/fparser/common/tests/test_base_classes.py
+++ b/src/fparser/common/tests/test_base_classes.py
@@ -46,6 +46,7 @@ import fparser.common.base_classes
 import fparser.common.readfortran
 import fparser.common.sourceinfo
 import fparser.common.utils
+from fparser import api
 
 
 def test_statement_logging(log, monkeypatch):
@@ -165,3 +166,33 @@ def test_log_unexpected(log):
                "in 'BeginThing' block."
     result = log.messages['warning'][0].split('\n')[1]
     assert result == expected
+
+
+def test_space_after_enddo():
+    '''Make sure that there is no space after an 'END DO' without name,
+    but there is a space if there is a name after 'END DO'.
+    '''
+
+    # Unnamed loop:
+    source_str = '''\
+    subroutine foo
+    integer i, r
+    do i = 1,100
+      r = r + 1
+    end do
+    end subroutine foo
+    '''
+    tree = api.parse(source_str, isfree=True, isstrict=False)
+    assert "END DO " not in tree.tofortran()
+
+    # Named loop:
+    source_str = '''\
+    subroutine foo
+    integer i, r
+    loop1: do i = 1,100
+      r = r + 1
+    end do loop1
+    end subroutine foo
+    '''
+    tree = api.parse(source_str, isfree=True, isstrict=False)
+    assert "END DO loop1" in tree.tofortran()
diff --git a/src/fparser/one/tests/test_do_block_r814.py b/src/fparser/one/tests/test_do_block_r814.py
index 9b791ad..9406375 100644
--- a/src/fparser/one/tests/test_do_block_r814.py
+++ b/src/fparser/one/tests/test_do_block_r814.py
@@ -49,6 +49,26 @@ from fparser.one.parsefortran import FortranParser
 from fparser.common.readfortran import FortranStringReader
 
 
+def get_end_do(name):
+    '''A small helper function to return either "END DO" (without space) if
+    name is empty, or "END DO "+name. This simplifies the tests now that
+    tofortran does not return an "END DO" with one space in case of an
+    unnamed end statement.
+
+    :param str name: Either None if it is an unnamed statement, or \
+        the label to use in the end statement.
+
+    :returns: either "END DO" (without space) if name is empty, or \
+        "END DO "+name.
+    :rtype: str
+
+    '''
+
+    if name:
+        return "END DO {0}".format(name)
+    return "END DO"
+
+
 @pytest.mark.parametrize('name', [None, 'loop_name'])
 @pytest.mark.parametrize('label', [None, '123'])
 @pytest.mark.parametrize('control_comma', [False, True])
@@ -73,7 +93,7 @@ def test_do(name, label, control_comma, terminal_expression,
     # TODO: Although the Fortran standard allows for "continue" to be used in
     # place of "end do" fparser does not support it.
     end_snippet = 'continue' if end_name == 'continue' \
-                  else 'end do {endname}'.format(endname=end_name or '')
+                  else get_end_do(end_name)
     do_code = '''{name}do {label}{comma}variable = 1, {term}, 1
   write (6, '(I0)') variable
 {endlabel} {end}
@@ -85,12 +105,12 @@ def test_do(name, label, control_comma, terminal_expression,
            end=end_snippet)
     do_expected = '''  {name}DO {label}variable = 1, {term}, 1
     WRITE (6, '(I0)') variable
-{endlabel} END DO {endname}
+{endlabel} {endstmt}
 '''.format(name=name_snippet or '',
            label=label_snippet or '',
            term=terminal_expression,
            endlabel=end_label or ' ',
-           endname=end_name or '')
+           endstmt=get_end_do(end_name))
     do_reader = FortranStringReader(do_code)
     do_reader.set_format(FortranFormat(True, False))
     do_parser = FortranParser(do_reader)
@@ -122,21 +142,21 @@ def test_do_while(name, label, control_comma, terminal_expression,
     comma_snippet = ', ' if control_comma else None
     code = '''{name}do {label}{comma}while ({term})
   write (6, '(I0)') variable
-{endlabel} end do {endname}
+{endlabel} {endstmt}
 '''.format(name=name_snippet or '',
            label=label_snippet or '',
            comma=comma_snippet or '',
            term=terminal_expression,
            endlabel=end_label or '',
-           endname=end_name or '')
+           endstmt=get_end_do(end_name))
     expected = '''  {name}DO {label}while ({term})
     WRITE (6, '(I0)') variable
-{endlabel} END DO {endname}
+{endlabel} {endstmt}
 '''.format(name=name_snippet or '',
            label=label_snippet or '',
            term=terminal_expression,
            endlabel=end_label or ' ',
-           endname=end_name or '')
+           endstmt=get_end_do(end_name))
     print(code)
     reader = FortranStringReader(code)
     reader.set_format(FortranFormat(True, False))
diff --git a/src/fparser/one/tests/test_parsefortran.py b/src/fparser/one/tests/test_parsefortran.py
index e8abeb9..6c9948e 100644
--- a/src/fparser/one/tests/test_parsefortran.py
+++ b/src/fparser/one/tests/test_parsefortran.py
@@ -193,7 +193,7 @@ end module foo
                 ' trim(title), pcpsx_i_pel(), trim(c_flag), pc_get_lun()',
                 '      IF (.true.) THEN',
                 '        CALL smth',
-                '      END IF ',
+                '      END IF',
                 '      aaa: IF (.false.) THEN',
                 '      ELSE IF (a) THEN',
                 '      ELSE',
@@ -201,7 +201,7 @@ end module foo
                 '      hey = 1',
                 '    END SUBROUTINE bar',
                 '    ABSTRACT INTERFACE',
-                '    END INTERFACE ',
+                '    END INTERFACE',
                 '  END MODULE foo']
 
     reader = fparser.common.readfortran.FortranStringReader(string)