diff --git a/doc/source/developers_guide.rst b/doc/source/developers_guide.rst index 7c35e6b3..1244b6ff 100644 --- a/doc/source/developers_guide.rst +++ b/doc/source/developers_guide.rst @@ -143,8 +143,8 @@ by the `Program_Unit` class. In general, the name of the class corresponding to a given rule can be obtained by replacing '-' with '_' and capitalising each word. -The Fortran2003 classes exist in the Fortran2003.py file and the -Fortran2008 classes exist in the Fortran2008.py file (see +The Fortran2003 classes exist in the ``Fortran2003.py`` file and the +Fortran2008 classes exist in the ``Fortran2008`` directory (see :ref:`Fortran2008` section for Fortran2008-specific implementation details). @@ -320,9 +320,9 @@ The reason for this is that such classes can be written in a generic, boiler-plate way so it is simpler if these are generated rather than them having to be hand written. -At the end of the Fortran2003.py and Fortran2008.py files there is -code that is executed when the file is imported. This code generates -the required classes described above in the local file. +At the end of the ``Fortran2003.py`` and ``Fortran2008/__init__.py`` +files there is code that is executed when the file is imported. This +code generates the required classes described above in the local file. .. note:: @@ -354,8 +354,8 @@ imported. .. note:: At the moment the same code-generation code is replicated in both - the Fortran2003.py and Fortran2008.py files. It would be better to - import this code from a separate file if it is possible to do so. + the ``Fortran2003.py`` and ``Fortran2008/__init__.py`` files. It would be + better to import this code from a separate file if it is possible to do so. .. _base-classes: diff --git a/src/fparser/two/Fortran2008/__init__.py b/src/fparser/two/Fortran2008/__init__.py index 7510cadc..e3707be3 100644 --- a/src/fparser/two/Fortran2008/__init__.py +++ b/src/fparser/two/Fortran2008/__init__.py @@ -67,6 +67,7 @@ from fparser.two.Fortran2008.loop_control_r818 import Loop_Control from fparser.two.Fortran2008.if_stmt_r837 import If_Stmt from fparser.two.Fortran2008.error_stop_stmt_r856 import Error_Stop_Stmt +from fparser.two.Fortran2008.format_item_r1003 import Format_Item from fparser.two.Fortran2008.stop_code_r857 import Stop_Code from fparser.two.Fortran2008.specification_part_c1112 import Specification_Part_C1112 from fparser.two.Fortran2008.implicit_part_c1112 import Implicit_Part_C1112 diff --git a/src/fparser/two/Fortran2008/format_item_r1003.py b/src/fparser/two/Fortran2008/format_item_r1003.py new file mode 100644 index 00000000..d1d73323 --- /dev/null +++ b/src/fparser/two/Fortran2008/format_item_r1003.py @@ -0,0 +1,112 @@ +# ----------------------------------------------------------------------------- +# BSD 3-Clause License +# +# Copyright (c) 2026, Science and Technology Facilities Council. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# * 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. +# +# * 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. +# ----------------------------------------------------------------------------- + +""" +Module containing Fortran 2008 Format_Item rule R1003 +""" + +from typing import Optional, Tuple, Union + +from fparser.two.Fortran2003 import ( + Digit_String, + Format_Item as Format_Item_2003, + Format_Item_List, +) +from fparser.two.utils import NoMatchError + + +class Format_Item(Format_Item_2003): # R1003 + """Fortran 2008 rule R1003 + + format-item is [ r ] data-edit-desc + or control-edit-desc + or char-string-edit-desc + or [ r ] ( format-item-list ) + or format-item-c1002 + or hollerith-item + or * ( format-item-list ) + + Extends the Fortran 2003 rule R1003 with the additional unlimited + format repeat specifier ``*`` before a parenthesised format-item-list. + + """ + + # Inherit the parent's subclass_names so that Control_Edit_Desc, + # Hollerith_Item, etc. remain registered when the F2008 parser + # replaces F2003 Format_Item in the class hierarchy. + # Include "Format_Item" itself so that this F2008 class is + # registered in Base.subclasses["Format_Item"] and can be + # discovered when F2003 code (e.g. Format_Item_List.match) + # directly references the F2003 Format_Item class. + subclass_names = Format_Item_2003.subclass_names[:] + ["Format_Item"] + use_names = Format_Item_2003.use_names[:] + + @staticmethod + def match( + string: str, + ) -> Optional[ + Tuple[Union[str, Digit_String], Union[Format_Item_2003, Format_Item_List]] + ]: + """Attempts to match the supplied text with this rule. + + Calls the Fortran 2003 match first. If that fails, checks + for the Fortran 2008 unlimited format repeat: ``*(format-item-list)``. + + :param string: Fortran code to check for a match. + + :returns: None if there is no match, a tuple of size 2 + containing a repeat specifier (``"*"`` or a Repeat instance) + and the matched descriptor or format-item-list. + + """ + if not string: + return None + # Fortran 2003 matches all but unlimited repeat, so try it first. + # The F2003 match raises NoMatchError rather than returning None + # when it cannot parse the string (e.g. Data_Edit_Desc fails). + try: + result = Format_Item_2003.match(string) + except NoMatchError: + result = None + if result: + return result + # Try to match unlimited format repeat: *(format-item-list) + strip_string = string.strip() + if not strip_string: + return None + if strip_string[0] == "*" and len(strip_string) > 1: + my_string = strip_string[1:].lstrip() + if my_string[0] == "(" and my_string[-1] == ")": + return ("*", Format_Item_List(my_string[1:-1].lstrip())) + return None diff --git a/src/fparser/two/tests/fortran2008/test_format_item_unlimited_repeat_r1003.py b/src/fparser/two/tests/fortran2008/test_format_item_unlimited_repeat_r1003.py new file mode 100644 index 00000000..eeba99c6 --- /dev/null +++ b/src/fparser/two/tests/fortran2008/test_format_item_unlimited_repeat_r1003.py @@ -0,0 +1,122 @@ +# Copyright (c) 2026 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 2008 rule R1003 + +format-item is [ r ] data-edit-desc + or control-edit-desc + or char-string-edit-desc + or [ r ] ( format-item-list ) + or format-item-c1002 + or hollerith-item + or * ( format-item-list ) + +Extends the Fortran 2003 rule R1003 with the additional unlimited +format repeat specifier ``*`` before a parenthesised format-item-list. + +""" + +import pytest + +from fparser.common.readfortran import FortranStringReader +from fparser.two.Fortran2008 import Format_Item +from fparser.two.utils import NoMatchError + + +def test_unlimited_repeat_basic(f2008_create): + """Test basic unlimited format repeat: *(I5).""" + obj = Format_Item("*(I5)") + assert isinstance(obj, Format_Item) + assert str(obj) == "*(I5)" + + +def test_unlimited_repeat_multiple_items(f2008_create): + """Test unlimited format repeat with multiple items: *(1X,A12).""" + obj = Format_Item("*(1X,A12)") + assert isinstance(obj, Format_Item) + assert str(obj) == "*(1X, A12)" + + +def test_unlimited_repeat_pe_descriptor(f2008_create): + """Test unlimited format repeat with PE descriptor: *(1X,1PE12.5).""" + obj = Format_Item("*(1X,1PE12.5)") + assert isinstance(obj, Format_Item) + assert str(obj) == "*(1X, 1P, E12.5)" + + +def test_f2003_match(f2008_create): + """Test that F2003 format items still match via the F2008 class.""" + obj = Format_Item("3(I5)") + assert isinstance(obj, Format_Item) + assert str(obj) == "3(I5)" + + +def test_format_stmt_no_match(): + """Test various cases that fail to match.""" + with pytest.raises(NoMatchError): + _ = Format_Item("") + with pytest.raises(NoMatchError): + _ = Format_Item(" ") + with pytest.raises(NoMatchError): + _ = Format_Item("*(") + with pytest.raises(NoMatchError): + _ = Format_Item("*(1X") + + +def test_format_stmt_unlimited_repeat(f2008_parser): + """Test unlimited format repeat in a full FORMAT statement.""" + + reader = FortranStringReader("subroutine t()\n 1 format(*(I5))\nend subroutine") + tree = f2008_parser(reader) + assert tree is not None + + +def test_format_stmt_mixed_items(f2008_parser): + """Test FORMAT with regular items followed by unlimited repeat.""" + + reader = FortranStringReader( + "subroutine t()\n 1 format('!',A12,*(1X,A12))\nend subroutine" + ) + tree = f2008_parser(reader) + assert tree is not None + + +def test_format_stmt_pe_descriptor(f2008_parser): + """Test FORMAT with unlimited repeat and PE edit descriptor.""" + + reader = FortranStringReader( + "subroutine t()\n 2 format(*(1X,1PE12.5))\nend subroutine" + ) + tree = f2008_parser(reader) + assert tree is not None