Repository: stfc/fparser

Issue:
namelist false match error
The f2003 namelist match rule r552 produces an assertion error when a variable starting with the name `namelist' is initialised ...

```
program test
  NamelistFile = "dummy file"
end program
```

Discussion:

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 37b8b35..4b7f83b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -18,6 +18,9 @@ Modifications by (in alphabetical order):
 * P. Vitt, University of Siegen, Germany
 * A. Voysey, UK Met Office
 
+15/05/2023 PR #415 for #165. Bug fix for spurious matching of 'NAMELIST' in
+           certain contexts.
+
 15/05/2023 PR #408 for #403. Add support for the F2008 DO CONCURRENT.
 
 26/04/2023 PR #406 for #405. Add support for F2008 optional "::" in PROCEDURE
diff --git a/src/fparser/two/Fortran2003.py b/src/fparser/two/Fortran2003.py
index d303d69..3f93fd2 100644
--- a/src/fparser/two/Fortran2003.py
+++ b/src/fparser/two/Fortran2003.py
@@ -4651,15 +4651,12 @@ class Letter_Spec(Base):  # R551
 
 class Namelist_Stmt(StmtBase):  # R552
     """
-    ::
-
-        <namelist-stmt> = NAMELIST / <namelist-group-name> /
-            <namelist-group-object-list> [ [ , ] / <namelist-group-name> /
-            <namelist-group-object-list> ]...
+    Fortran 2003 rule R552::
 
-    Attributes::
-
-        items : (Namelist_Group_Name, Namelist_Group_Object_List)-tuple
+        namelist-stmt is NAMELIST
+                         / namelist-group-name / namelist-group-object-list
+                         [ [,] / namelist-group-name /
+                         namelist-group-object-list ] ...
 
     """
 
@@ -4668,28 +4665,46 @@ class Namelist_Stmt(StmtBase):  # R552
 
     @staticmethod
     def match(string):
-        if string[:8].upper() != "NAMELIST":
-            return
-        line = string[8:].lstrip()
+        """Implements the matching for a Namelist_Stmt.
+
+        :param str string: a string containing the code to match.
+
+        :returns: `None` if there is no match, otherwise a `tuple` \
+            containing 2-tuples with a namelist name and a namelist object \
+            list.
+        :rtype: Optional[Tuple[Tuple[ \
+            fparser.two.Fortran2003.Namelist_Group_Name, \
+            fparser.two.Fortran2003.Namelist_Group_Object_List]]]
+
+        """
+        line = string.lstrip()
+        if line[:8].upper() != "NAMELIST":
+            return None
+        line = line[8:].lstrip()
+        if not line:
+            return None
         parts = line.split("/")
+        text_before_slash = parts.pop(0)
+        if text_before_slash:
+            return None
         items = []
-        fst = parts.pop(0)
-        assert not fst, repr((fst, parts))
         while len(parts) >= 2:
-            name, lst = parts[:2]
-            del parts[:2]
-            name = name.strip()
-            lst = lst.strip()
+            name = parts.pop(0).strip()
+            lst = parts.pop(0).strip()
             if lst.endswith(","):
                 lst = lst[:-1].rstrip()
             items.append((Namelist_Group_Name(name), Namelist_Group_Object_List(lst)))
-        assert not parts, repr(parts)
+        if parts:
+            # There is a missing second '/'
+            return None
         return tuple(items)
 
     def tostr(self):
-        return "NAMELIST " + ", ".join(
-            "/%s/ %s" % (name_lst) for name_lst in self.items
-        )
+        """
+        :returns: this Namelist_Stmt as a string.
+        :rtype: str
+        """
+        return "NAMELIST " + ", ".join(f"/{name}/ {lst}" for name, lst in self.items)
 
 
 class Namelist_Group_Object(Base):  # R553


Test patch:
diff --git a/src/fparser/two/tests/fortran2003/test_namelist_stmt_r552.py b/src/fparser/two/tests/fortran2003/test_namelist_stmt_r552.py
new file mode 100644
index 0000000..bdedf82
--- /dev/null
+++ b/src/fparser/two/tests/fortran2003/test_namelist_stmt_r552.py
@@ -0,0 +1,88 @@
+# Copyright (c) 2023 Science and Technology Facilities Council.
+
+# All rights reserved.
+
+# Modifications made as part of the fparser project are distributed
+# under the following license:
+
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+
+# 3. Neither the name of the copyright holder nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""Test Fortran 2003 rule R552 : This file tests the support for the
+NAMELIST statement.
+
+"""
+import pytest
+
+from fparser.two.Fortran2003 import Namelist_Stmt
+from fparser.two.utils import NoMatchError
+
+
+@pytest.mark.parametrize(
+    "code",
+    [
+        "",
+        "namelost",
+        "namelist",
+        "namelist x",
+        "namelist x/",
+        "namelist /x",
+        "namelist /x/ y /",
+    ],
+)
+def test_match_errors(code):
+    """Test that the match method returns None when the supplied code is
+    invalid. Also check that this results in a NoMatchError for an
+    instance of the class.
+
+    """
+    assert not Namelist_Stmt.match(code)
+    with pytest.raises(NoMatchError):
+        _ = Namelist_Stmt(code)
+
+
+def test_simple():
+    """Test that a namelist with a single name and list is matched and
+    that the tostr() method outputs the resultant code as
+    expected. Also check that the namelist keyword matching is case
+    insensitive and that leading and trailing spaces are supported.
+
+    """
+    result = Namelist_Stmt(" NamelisT /x/ a ")
+    assert isinstance(result, Namelist_Stmt)
+    assert result.tostr() == "NAMELIST /x/ a"
+
+
+def test_multi():
+    """Test that multiple names and lists are matched, with and without a
+    comma separator and that the tostr() method outputs the resultant
+    code as expected.
+
+    """
+    result = Namelist_Stmt("namelist /x/ a, /y/ b,c /z/ d")
+    assert isinstance(result, Namelist_Stmt)
+    assert result.tostr() == "NAMELIST /x/ a, /y/ b, c, /z/ d"
diff --git a/src/fparser/two/tests/test_fortran2003.py b/src/fparser/two/tests/test_fortran2003.py
index fc75b86..0ee3529 100644
--- a/src/fparser/two/tests/test_fortran2003.py
+++ b/src/fparser/two/tests/test_fortran2003.py
@@ -1394,16 +1394,6 @@ def test_letter_spec():  # R551
     assert str(obj) == "D"
 
 
-def test_namelist_stmt():  # R552
-    tcls = Namelist_Stmt
-    obj = tcls("namelist / nlist / a")
-    assert isinstance(obj, tcls), repr(obj)
-    assert str(obj) == "NAMELIST /nlist/ a"
-
-    obj = tcls("namelist / nlist / a, /mlist/ b,c /klist/ d,e")
-    assert str(obj) == "NAMELIST /nlist/ a, /mlist/ b, c, /klist/ d, e"
-
-
 def test_equivalence_stmt():  # R554
     tcls = Equivalence_Stmt
     obj = tcls("equivalence (a, b ,z)")