From 69183d943ea4a7e458ebda80359da408946bc478 Mon Sep 17 00:00:00 2001 From: rupertford Date: Fri, 12 May 2023 15:54:21 +0100 Subject: [PATCH 1/3] issue #165. Added tests. --- src/fparser/two/Fortran2003.py | 63 +++++++++------ .../fortran2003/test_namelist_stmt_r552.py | 79 +++++++++++++++++++ src/fparser/two/tests/test_fortran2003.py | 10 --- 3 files changed, 118 insertions(+), 34 deletions(-) create mode 100644 src/fparser/two/tests/fortran2003/test_namelist_stmt_r552.py diff --git a/src/fparser/two/Fortran2003.py b/src/fparser/two/Fortran2003.py index d314d1e4..94abe529 100644 --- a/src/fparser/two/Fortran2003.py +++ b/src/fparser/two/Fortran2003.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -# Modified work Copyright (c) 2017-2022 Science and Technology +# Modified work Copyright (c) 2017-2023 Science and Technology # Facilities Council. # Original work Copyright (c) 1999-2008 Pearu Peterson @@ -4651,45 +4651,61 @@ def tostr(self): class Namelist_Stmt(StmtBase): # R552 """ - :: - - = NAMELIST / / - [ [ , ] / / - ]... - - Attributes:: + Fortran 2003 rule R552:: - 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 ] ... """ - subclass_names = [] use_names = ["Namelist_Group_Name", "Namelist_Group_Object_List"] @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) + items.append( + (Namelist_Group_Name(name), Namelist_Group_Object_List(lst))) + if parts: + # There is a missing second '/' + return None return tuple(items) def tostr(self): + """ + :returns: this Namelist_Stmt as a string. + :rtype: str + """ return "NAMELIST " + ", ".join( - "/%s/ %s" % (name_lst) for name_lst in self.items - ) + f"/{name}/ {lst}" for name, lst in self.items) class Namelist_Group_Object(Base): # R553 @@ -7959,7 +7975,6 @@ class Loop_Control(Base): # pylint: disable=invalid-name | [ , ] WHILE ( ) """ - subclass_names = [] use_names = ["Do_Variable", "Scalar_Int_Expr", "Scalar_Logical_Expr"] 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..ad9279f0 --- /dev/null +++ b/src/fparser/two/tests/fortran2003/test_namelist_stmt_r552.py @@ -0,0 +1,79 @@ +# 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 f49a2ad1..474e2c46 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)") From 8997abc0a807880fc963cd0574046e0e6d474c4d Mon Sep 17 00:00:00 2001 From: rupertford Date: Fri, 12 May 2023 16:12:39 +0100 Subject: [PATCH 2/3] pr #415. Fixed black errors. --- src/fparser/two/Fortran2003.py | 8 +++--- .../fortran2003/test_namelist_stmt_r552.py | 27 ++++++++++++------- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/src/fparser/two/Fortran2003.py b/src/fparser/two/Fortran2003.py index 94abe529..9876de4b 100644 --- a/src/fparser/two/Fortran2003.py +++ b/src/fparser/two/Fortran2003.py @@ -4659,6 +4659,7 @@ class Namelist_Stmt(StmtBase): # R552 namelist-group-object-list ] ... """ + subclass_names = [] use_names = ["Namelist_Group_Name", "Namelist_Group_Object_List"] @@ -4692,8 +4693,7 @@ def match(string): lst = parts.pop(0).strip() if lst.endswith(","): lst = lst[:-1].rstrip() - items.append( - (Namelist_Group_Name(name), Namelist_Group_Object_List(lst))) + items.append((Namelist_Group_Name(name), Namelist_Group_Object_List(lst))) if parts: # There is a missing second '/' return None @@ -4704,8 +4704,7 @@ def tostr(self): :returns: this Namelist_Stmt as a string. :rtype: str """ - return "NAMELIST " + ", ".join( - f"/{name}/ {lst}" for name, lst in self.items) + return "NAMELIST " + ", ".join(f"/{name}/ {lst}" for name, lst in self.items) class Namelist_Group_Object(Base): # R553 @@ -7975,6 +7974,7 @@ class Loop_Control(Base): # pylint: disable=invalid-name | [ , ] WHILE ( ) """ + subclass_names = [] use_names = ["Do_Variable", "Scalar_Int_Expr", "Scalar_Logical_Expr"] diff --git a/src/fparser/two/tests/fortran2003/test_namelist_stmt_r552.py b/src/fparser/two/tests/fortran2003/test_namelist_stmt_r552.py index ad9279f0..bdedf824 100644 --- a/src/fparser/two/tests/fortran2003/test_namelist_stmt_r552.py +++ b/src/fparser/two/tests/fortran2003/test_namelist_stmt_r552.py @@ -42,38 +42,47 @@ from fparser.two.utils import NoMatchError -@pytest.mark.parametrize("code", [ - "", "namelost", "namelist", "namelist x", "namelist x/", - "namelist /x", "namelist /x/ y /"]) +@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 + """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 + """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 + """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" From a0d825339ce8f6aa90eb3782569117164a046b4b Mon Sep 17 00:00:00 2001 From: Andrew Porter Date: Mon, 15 May 2023 10:44:07 +0100 Subject: [PATCH 3/3] #415 update changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) 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