diff --git a/CHANGELOG.md b/CHANGELOG.md index 37b8b359..4b7f83b3 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 d303d69e..3f93fd21 100644 --- a/src/fparser/two/Fortran2003.py +++ b/src/fparser/two/Fortran2003.py @@ -4651,15 +4651,12 @@ def tostr(self): class Namelist_Stmt(StmtBase): # R552 """ - :: - - = NAMELIST / / - [ [ , ] / / - ]... + 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 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 00000000..bdedf824 --- /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 fc75b86b..0ee35293 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)")