Repository: stfc/fparser

Issue:
No Support for DO CONCURRENT
It seems like fparser does not support F08 `DO CONCURRENT`:

```
C:\PycharmProjects\fortran> python main.py
Traceback (most recent call last):
  File "C:\Users\micha\anaconda3\envs\fortran\lib\site-packages\fparser\two\Fortran2003.py", line 266, in __new__
    return Base.__new__(cls, string)
  File "C:\Users\micha\anaconda3\envs\fortran\lib\site-packages\fparser\two\utils.py", line 487, in __new__
    raise NoMatchError(errmsg)
fparser.two.utils.NoMatchError: at line 14
>>>        do concurrent (i=1:n) local_init(a)


During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:\Users\micha\PycharmProjects\fortran\main.py", line 7, in <module>
    parse_tree = f08_parser(reader)
  File "C:\Users\micha\anaconda3\envs\fortran\lib\site-packages\fparser\two\Fortran2003.py", line 270, in __new__
    raise FortranSyntaxError(string, "")
fparser.two.utils.FortranSyntaxError: at line 14
>>>        do concurrent (i=1:n) local_init(a)
```

Failing code is:

```Fortran
module saxpy_impl
    implicit none
contains
    subroutine saxpy(a, x, y, n)
        use iso_fortran_env
        implicit none

        integer :: n
        real(kind=real32) :: a
        real(kind=real32), dimension(n) :: x
        real(kind=real32), dimension(n) :: y
        integer :: i

        do concurrent (i=1:n) local_init(a)
            y(i) = a * x(i) + y(i)
        end do
    end subroutine saxpy
end module saxpy_impl
```


Comments:

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 202a531..37b8b35 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -18,6 +18,8 @@ Modifications by (in alphabetical order):
 * P. Vitt, University of Siegen, Germany
 * A. Voysey, UK Met Office
 
+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
            statement.
 
diff --git a/doc/source/fparser2.rst b/doc/source/fparser2.rst
index 66d55f7..c7a1722 100644
--- a/doc/source/fparser2.rst
+++ b/doc/source/fparser2.rst
@@ -45,7 +45,8 @@ the Fortran2008.py `file`__ which extends the Fortran2003 rules
 appropriately. At this time fparser2 supports the following
 Fortran2008 features: submodules, co-arrays, the 'mold' argument to
 allocate, the 'contiguous' keyword, the 'BLOCK' construct, the
-'CRITICAL' construct and the optional '::' for a procedure statement.
+'CRITICAL' construct, the optional '::' for a procedure statement and
+the 'do concurrent' construct.
 
 __ https://github.com/stfc/fparser/blob/master/src/fparser/two/Fortran2003.py
 __ https://github.com/stfc/fparser/blob/master/src/fparser/two/Fortran2008.py
diff --git a/src/fparser/two/Fortran2003.py b/src/fparser/two/Fortran2003.py
index d314d1e..d303d69 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
 
@@ -5249,7 +5249,7 @@ class Allocate_Stmt(StmtBase):  # R623
         allocate-stmt is ALLOCATE ( [ type-spec :: ] allocation-list
                                     [, alloc-opt-list ] )
 
-    Subject to the following constraints\:
+    Subject to the following constraints:
 
     C622 (R629) Each allocate-object shall be a nonprocedure pointer or an
                  allocatable variable.
@@ -7949,14 +7949,24 @@ class Nonlabel_Do_Stmt(StmtBase, WORDClsBase):  # pylint: disable=invalid-name
         return self.item.name
 
 
-class Loop_Control(Base):  # pylint: disable=invalid-name
+# pylint: disable=invalid-name
+class Loop_Control(Base):  # R830
     """
-    R830::
+    Fortran 2003 rule R830
 
-        <loop-control> = [ , ] <do-variable> = scalar-int-expr,
-                                               scalar-int-expr
-                                               [ , <scalar-int-expr> ]
-                         | [ , ] WHILE ( <scalar-logical-expr> )
+    loop-control is [ , ] do-variable = scalar-int-expr , scalar-int-expr
+                       [ , scalar-int-expr ]
+                    or [ , ] WHILE ( scalar-logical-expr )
+
+    This class would be better and more extensible if it called 2
+    classes, one for each of the above expressions. Something like the
+    suggestion below. However, this would result in a different
+    fparser tree, see issue #416.
+
+    F2003: While_Loop_Cntl: scalar-logical-expression, delim
+    F2003: Counter_Loop_Cntl: var, lower, upper, [step], delim
+    F2008: Concurrent_Loop_Cntl: conc_expr, delim
+    F2018: Concurrent_Loop_Cntl: conc_expr, local_x, delim
 
     """
 
@@ -7965,66 +7975,73 @@ class Loop_Control(Base):  # pylint: disable=invalid-name
 
     @staticmethod
     def match(string):
-        """
-        :param str string: Fortran code to check for a match
-        :return: 3-tuple containing strings and instances of the classes
-                 determining loop control (optional comma delimiter,
-                 optional scalar logical expression describing "WHILE"
-                 condition or optional counter expression containing loop
-                 counter and scalar integer expression)
-        :rtype: 3-tuple of objects or nothing for an "infinite loop"
-        """
-        # pylint: disable=unbalanced-tuple-unpacking
+        """Attempts to match the supplied text with this rule.
+
+        :param str string: Fortran code to check for a match.
+
+        :returns: None if there is no match, a 3-tuple with the first \
+            entry providing the result of matching the 'WHILE' part of \
+            the rule if there is a match, the second entry providing \
+            the result of matching the 'COUNTER' part of the rule if \
+            there is a match and the third entry indicating whether \
+            there is an optional preceding ','.
+        :rtype: Optional[Tuple[ \
+            Optional[ \
+                :py:class:`fparser.two.Fortran2003.Scalar_Logical_Expr`], \
+            Optional[Tuple[ \
+                :py:class:`fparser.two.Fortran2003.Do_Variable`, List[str]]], \
+            Optional[str]]]
+
+        """
+        line = string.lstrip().rstrip()
+        # Try to match optional delimiter
         optional_delim = None
-        # Match optional delimiter
-        if string.startswith(","):
-            line, repmap = string_replace_map(string[1:].lstrip())
-            optional_delim = ", "
-        else:
-            line, repmap = string_replace_map(string)
-        # Match "WHILE" scalar logical expression
+        if line.startswith(","):
+            line = line[1:].lstrip()
+            optional_delim = ","
+        line, repmap = string_replace_map(line)
+        # Try to match with WHILE
         if line[:5].upper() == "WHILE" and line[5:].lstrip().startswith("("):
-            lbrak = line[5:].lstrip()
-            i = lbrak.find(")")
-            if i != -1 and i == len(lbrak) - 1:
-                scalar_logical_expr = Scalar_Logical_Expr(repmap(lbrak[1:i].strip()))
-                return scalar_logical_expr, None, optional_delim
-        # Match counter expression
-        # More than one '=' in counter expression
+            brackets = line[5:].lstrip()
+            rbrack_index = brackets.find(")")
+            if rbrack_index != -1 and rbrack_index == len(brackets) - 1:
+                scalar_logical_expr = Scalar_Logical_Expr(
+                    repmap(brackets[1:rbrack_index].strip())
+                )
+                return (scalar_logical_expr, None, optional_delim)
+        # Try to match counter expression
+        # More than one '=' in counter expression is not valid
         if line.count("=") != 1:
-            return
+            return None
         var, rhs = line.split("=")
-        rhs = [s.strip() for s in rhs.lstrip().split(",")]
+        rhs = [entry.strip() for entry in rhs.lstrip().split(",")]
         # Incorrect number of elements in counter expression
         if not 2 <= len(rhs) <= 3:
-            return
+            return None
         counter_expr = (
-            Variable(repmap(var.rstrip())),
+            Do_Variable(repmap(var.rstrip())),
             list(map(Scalar_Int_Expr, list(map(repmap, rhs)))),
         )
-        return None, counter_expr, optional_delim
+        return (None, counter_expr, optional_delim)
 
     def tostr(self):
         """
-        :return: parsed representation of loop control construct
-        :rtype: string
+        :returns: the Fortran representation of this object.
+        :rtype: str
         """
-        # pylint: disable=unbalanced-tuple-unpacking
-        scalar_logical_expr, counter_expr, optional_delim = self.items
-        # Return loop control construct containing "WHILE" condition and
-        # its <scalar-logical-expr>
-        if scalar_logical_expr is not None:
-            loopctrl = "WHILE (%s)" % scalar_logical_expr
-        # Return loop control construct containing counter expression:
-        # <do-variable> as LHS and <scalar-int-expr> list as RHS
-        elif counter_expr[0] is not None and counter_expr[1] is not None:
-            loopctrl = "%s = %s" % (
-                counter_expr[0],
-                ", ".join(map(str, counter_expr[1])),
+        if self.items[0]:
+            # Return loop control construct containing "WHILE" condition and
+            # its <scalar-logical-expr>
+            loopctrl = f"WHILE ({self.items[0]})"
+        else:  # counter expression
+            # Return loop control construct containing counter expression:
+            # <do-variable> as LHS and <scalar-int-expr> list as RHS
+            loopctrl = (
+                f"{self.items[1][0]} = " f"{', '.join(map(str, self.items[1][1]))}"
             )
         # Add optional delimiter to loop control construct if present
-        if optional_delim is not None:
-            loopctrl = optional_delim + loopctrl
+        if self.items[2]:
+            loopctrl = f"{self.items[2]} {loopctrl}"
         return loopctrl
 
 
diff --git a/src/fparser/two/Fortran2008.py b/src/fparser/two/Fortran2008.py
index 9a46e26..134fcb4 100644
--- a/src/fparser/two/Fortran2008.py
+++ b/src/fparser/two/Fortran2008.py
@@ -93,6 +93,9 @@ from fparser.two.utils import (
     WORDClsBase,
 )
 
+# These pylint errors are due to the auto-generation of classes in the
+# Fortran2003 file.
+# pylint: disable=no-name-in-module
 from fparser.two.Fortran2003 import (
     Base,
     BlockBase,
@@ -105,6 +108,7 @@ from fparser.two.Fortran2003 import (
     Execution_Part_Construct,
     File_Name_Expr,
     File_Unit_Number,
+    Forall_Header,
     Implicit_Part,
     Implicit_Part_Stmt,
     Import_Stmt,
@@ -120,6 +124,8 @@ from fparser.two.Fortran2003 import (
     Use_Stmt,
 )
 
+# pylint: enable=no-name-in-module
+
 # Import of F2003 classes that are updated in this standard.
 from fparser.two.Fortran2003 import (
     Action_Stmt as Action_Stmt_2003,
@@ -136,6 +142,7 @@ from fparser.two.Fortran2003 import (
     Executable_Construct as Executable_Construct_2003,
     Executable_Construct_C201 as Executable_Construct_C201_2003,
     If_Stmt as If_Stmt_2003,
+    Loop_Control as Loop_Control_2003,
     Open_Stmt as Open_Stmt_2003,
     Procedure_Stmt as Procedure_Stmt_2003,
     Program_Unit as Program_Unit_2003,
@@ -817,6 +824,90 @@ class Allocate_Stmt(Allocate_Stmt_2003):  # R626
         return Alloc_Opt_List
 
 
+class Loop_Control(Loop_Control_2003):  # R818
+    """Fortran 2008 rule R818
+
+    loop-control is [ , ] do-variable = scalar-int-expr , scalar-int-expr
+                       [ , scalar-int-expr ]
+                    or [ , ] WHILE ( scalar-logical-expr )
+                    or [ , ] CONCURRENT forall-header
+
+    Extends the Fortran2003 rule R830 with the additional CONCURRENT clause.
+
+    The F2003 Loop_Control class would be better and more extensible
+    if it called 2 classes, one for each of the above
+    expressions. This would then affect the implementation of this
+    class. Something like the suggestion below. However, this would
+    result in a different fparser tree, see issue #416.
+
+    F2003: While_Loop_Cntl: scalar-logical-expression, delim
+    F2003: Counter_Loop_Cntl: var, lower, upper, [step], delim
+    F2008: Concurrent_Loop_Cntl: conc_expr, delim
+    F2018: Concurrent_Loop_Cntl: conc_expr, local_x, delim
+
+    """
+
+    subclass_names = []
+    # This class' match method makes use of the Fortran2003 match
+    # method so 'use_names' should include any classes used within
+    # there as well as any used here.
+    use_names = Loop_Control_2003.use_names[:]
+    use_names.append("Forall_Header")
+
+    @staticmethod
+    def match(string):
+        """Attempts to match the supplied text with this rule.
+
+        :param str string: Fortran code to check for a match.
+
+        :returns: None if there is no match, a tuple with the first \
+            entry providing the result of matching the 'WHILE' part of \
+            the rule if there is a match, the second entry providing \
+            the result of matching the 'COUNTER' part of the rule if \
+            there is a match, the third entry indicating whether \
+            there is an optional preceding ',' and the fourth entry \
+            providing the result of matching the 'CONCURRENT' part of \
+            the rule if there is a match.
+
+        :rtype: Optional[Tuple[ \
+            Optional[ \
+                :py:class:`fparser.two.Fortran2003.Scalar_Logical_Expr`], \
+            Optional[Tuple[ \
+                :py:class:`fparser.two.Fortran2003.Do_Variable`, List[str]]], \
+            Optional[str], \
+            Optional[:py:class:`fparser.two.Fortran2003.Forall_Header`]]]
+
+        """
+        # Fortran2003 matches all but CONCURRENT so try this first
+        result = Loop_Control_2003.match(string)
+        if result:
+            return result + (None,)
+        # Try to match with CONCURRENT
+        line = string.lstrip()
+        optional_delim = None
+        if line.startswith(","):
+            line = line[1:].lstrip()
+            optional_delim = ","
+        if line[:10].upper() != "CONCURRENT":
+            return None
+        return (None, None, optional_delim, Forall_Header(line[10:].lstrip().rstrip()))
+
+    def tostr(self):
+        """
+        :returns: the Fortran representation of this object.
+        :rtype: str
+        """
+        if self.items[0] or self.items[1]:
+            # Use the F2003 tostr() implementation
+            return Loop_Control_2003.tostr(self)
+        # Return loop control construct containing "CONCURRENT" clause
+        loopctrl = f"CONCURRENT {self.items[3]}"
+        # Add optional delimiter to loop control construct if present
+        if self.items[2]:
+            loopctrl = f"{self.items[2]} {loopctrl}"
+        return loopctrl
+
+
 class If_Stmt(If_Stmt_2003):  # R837
     """
     Fortran 2008 rule R837


Test patch:
diff --git a/src/fparser/two/tests/fortran2003/test_loop_control_r830.py b/src/fparser/two/tests/fortran2003/test_loop_control_r830.py
new file mode 100644
index 0000000..a758342
--- /dev/null
+++ b/src/fparser/two/tests/fortran2003/test_loop_control_r830.py
@@ -0,0 +1,120 @@
+# 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 R830 : This file tests the support for the
+loop-control rule.
+
+"""
+import pytest
+from fparser.two.Fortran2003 import Loop_Control
+from fparser.two.utils import NoMatchError
+
+
+def test_start_end_space_while():
+    """Test that there is a match if the string contains white space at
+    the start and end and that the tostr() output is as expected. Also
+    test matching to the while form of this rule.
+
+    """
+    result = Loop_Control(" while (.true.) ")
+    assert isinstance(result, Loop_Control)
+    assert str(result) == "WHILE (.TRUE.)"
+
+
+def test_delim():
+    """Test that there is a match if the string contains an optional
+    delimiter at the start and that the tostr() output is as expected.
+
+    """
+    result = Loop_Control(" , while (.true.) ")
+    assert isinstance(result, Loop_Control)
+    assert str(result) == ", WHILE (.TRUE.)"
+
+
+def test_repmap():
+    """Test matching when the while logical expresssion contains
+    brackets. This tests the use of the string_replace_map() function.
+
+    """
+    result = Loop_Control(" , while (((a .or. b) .and. (c .or. d))) ")
+    assert isinstance(result, Loop_Control)
+    assert str(result) == ", WHILE (((a .OR. b) .AND. (c .OR. d)))"
+
+
+def test_counter():
+    """Test matching to the counter form of this rule and that the tostr()
+    output is as expected.
+
+    """
+    # Lower-bound and upper-bound only
+    result = Loop_Control("idx = start,stop")
+    assert isinstance(result, Loop_Control)
+    assert str(result) == "idx = start, stop"
+    # Lower-bound, upper-bound and step
+    result = Loop_Control("idx = start,stop,step")
+    assert isinstance(result, Loop_Control)
+    assert str(result) == "idx = start, stop, step"
+    # Bounds are integer expressions
+    result = Loop_Control("idx = ((s+2)-q),(p*m)/4,a+b+c")
+    assert isinstance(result, Loop_Control)
+    assert str(result) == "idx = ((s + 2) - q), (p * m) / 4, a + b + c"
+
+
+@pytest.mark.parametrize(
+    "string",
+    [
+        "",
+        " ",
+        ": while(.true.)",
+        "whil (.true.)",
+        "while .true.",
+        "while ()",
+        "while( )",
+        "while (.true ",
+        "while ())",
+        "while('text')",
+        " == ",
+        " = ",
+        "idx=",
+        "=1,2",
+        "idx=1",
+        "idx=1,2,3,4",
+        "1=1,2",
+        "idx=1,.false.",
+    ],
+)
+def test_invalid(string):
+    """Test that there is no match for various invalid input strings."""
+    with pytest.raises(NoMatchError):
+        _ = Loop_Control(string)
diff --git a/src/fparser/two/tests/fortran2008/test_loop_control_r818.py b/src/fparser/two/tests/fortran2008/test_loop_control_r818.py
new file mode 100644
index 0000000..934800d
--- /dev/null
+++ b/src/fparser/two/tests/fortran2008/test_loop_control_r818.py
@@ -0,0 +1,93 @@
+# 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 2008 rule R818
+
+    loop-control is [ , ] do-variable = scalar-int-expr , scalar-int-expr
+                       [ , scalar-int-expr ]
+                    or [ , ] WHILE ( scalar-logical-expr )
+                    or [ , ] CONCURRENT forall-header
+
+    Extends the Fortran2003 rule R830 with the additional CONCURRENT clause.
+
+"""
+import pytest
+from fparser.two.Fortran2008 import Loop_Control
+from fparser.two.utils import NoMatchError
+
+
+def test_f2003_match():
+    """Test that there is a match if the string contains a F2003 form of
+    the rule and that the F2003 tostr() is as expected.
+
+    """
+    result = Loop_Control("while (.true.)")
+    assert isinstance(result, Loop_Control)
+    assert str(result) == "WHILE (.TRUE.)"
+
+
+def test_start_space():
+    """Test that there is a match if the string contains white space at
+    the start and end and that the tostr() output is as expected.
+
+    """
+    result = Loop_Control(" concurrent (i=1:10) ")
+    assert isinstance(result, Loop_Control)
+    assert str(result) == "CONCURRENT (i = 1 : 10)"
+
+
+def test_delim():
+    """Test that there is a match if the string contains an options
+    delimiter at the start and that the tostr() output is as expected.
+
+    """
+    result = Loop_Control(" , concurrent (i=1:10)")
+    assert isinstance(result, Loop_Control)
+    assert str(result) == ", CONCURRENT (i = 1 : 10)"
+
+
+@pytest.mark.parametrize(
+    "string",
+    [
+        "",
+        ": concurrent (i=1:10)",
+        "concurren (i=1:10)",
+        "concurrent",
+        "concurrent invalid",
+    ],
+)
+def test_invalid(string):
+    """Test that there is no match for various invalid input strings."""
+    with pytest.raises(NoMatchError):
+        _ = Loop_Control(string)
diff --git a/src/fparser/two/tests/test_fortran2003.py b/src/fparser/two/tests/test_fortran2003.py
index f49a2ad..fc75b86 100644
--- a/src/fparser/two/tests/test_fortran2003.py
+++ b/src/fparser/two/tests/test_fortran2003.py
@@ -2113,26 +2113,6 @@ def test_label_do_stmt():
     assert repr(obj) == "Label_Do_Stmt(None, Label('12'), None)"
 
 
-def test_loop_control():
-    """Tests incorrect loop control constructs (R829). Correct loop
-    control constructs are tested in test_block_label_do_construct()
-    and test_nonblock_label_do_construct()."""
-    tcls = Loop_Control
-
-    # More than one '=' in counter expression
-    with pytest.raises(NoMatchError) as excinfo:
-        _ = tcls("j = 1 = 10")
-    assert "Loop_Control: 'j = 1 = 10'" in str(excinfo.value)
-
-    # Incorrect number of elements in counter expression
-    with pytest.raises(NoMatchError) as excinfo:
-        _ = tcls("k = 10, -10, -2, -1")
-    assert "Loop_Control: 'k = 10, -10, -2, -1'" in str(excinfo.value)
-    with pytest.raises(NoMatchError) as excinfo:
-        _ = tcls("l = 5")
-    assert "Loop_Control: 'l = 5'" in str(excinfo.value)
-
-
 def test_continue_stmt():  # R848
     tcls = Continue_Stmt
     obj = tcls("continue")