From ba3dea0bf95675f40b54cc56abd0a62f59d1505a Mon Sep 17 00:00:00 2001 From: rupertford Date: Fri, 19 May 2023 01:38:46 +0100 Subject: [PATCH 1/4] issue #411. First working version. --- .../two/{ => Fortran2008}/Fortran2008.py | 108 +++++++------- src/fparser/two/Fortran2008/__init__.py | 138 ++++++++++++++++++ src/fparser/two/parser.py | 4 +- .../fortran2008/test_submodule_stmt_r1117.py | 4 +- 4 files changed, 197 insertions(+), 57 deletions(-) rename src/fparser/two/{ => Fortran2008}/Fortran2008.py (96%) create mode 100644 src/fparser/two/Fortran2008/__init__.py diff --git a/src/fparser/two/Fortran2008.py b/src/fparser/two/Fortran2008/Fortran2008.py similarity index 96% rename from src/fparser/two/Fortran2008.py rename to src/fparser/two/Fortran2008/Fortran2008.py index 134fcb4b..efa18622 100644 --- a/src/fparser/two/Fortran2008.py +++ b/src/fparser/two/Fortran2008/Fortran2008.py @@ -394,6 +394,10 @@ def match(string): :py:class:`fparser.two.Fortran2003.Component_Decl_List`) """ + # Avoid circular dependencies by importing here. + # pylint: disable=import-outside-toplevel + from fparser.two.Fortran2008 import Component_Attr_Spec_List + return Type_Declaration_StmtBase.match( Declaration_Type_Spec, Component_Attr_Spec_List, Component_Decl_List, string ) @@ -461,6 +465,10 @@ def get_attr_spec_list_cls(): This overwrites the Fortran 2003 type with the Fortran 2008 variant. """ + # Avoid circular dependencies by importing here. + # pylint: disable=import-outside-toplevel + from fparser.two.Fortran2008 import Attr_Spec_List + return Attr_Spec_List @@ -652,6 +660,10 @@ def match(string): :py:class:`fparser.two:Fortran2008.Lower_Cobound` or `None`) """ + # Avoid circular dependencies by importing here. + # pylint: disable=import-outside-toplevel + from fparser.two.Fortran2008 import Coshape_Spec_List + if not string.endswith("*"): return None line = string[:-1].rstrip() @@ -821,6 +833,10 @@ def alloc_opt_list(cls): :returns: the Fortran2008 flavour of Alloc_Opt_List. :rtype: type """ + # Avoid circular dependencies by importing here. + # pylint: disable=import-outside-toplevel + from fparser.two.Fortran2008 import Alloc_Opt_List + return Alloc_Opt_List @@ -1147,6 +1163,10 @@ def match(fstring): :py:class:`fparser.two.Fortran2008.Submodule_Name`]] """ + # Avoid circular dependencies by importing here. + # pylint: disable=import-outside-toplevel + from fparser.two.Fortran2008 import Submodule_Name + # First look for "SUBMODULE" name = "SUBMODULE" if fstring[: len(name)].upper() != name: @@ -1218,6 +1238,10 @@ def match(fstring): is a match or `None` if there is no match """ + # Avoid circular dependencies by importing here. + # pylint: disable=import-outside-toplevel + from fparser.two.Fortran2008 import Submodule_Name + return EndStmtBase.match("SUBMODULE", Submodule_Name, fstring) def get_name(self): # C1114 @@ -1259,6 +1283,10 @@ def match(fstring): is a match or `None` if there is no match """ + # Avoid circular dependencies by importing here. + # pylint: disable=import-outside-toplevel + from fparser.two.Fortran2008 import Ancestor_Module_Name, Parent_SubModule_Name + split_string = fstring.split(":") len_split_string = len(split_string) lhs_name = split_string[0].lstrip().rstrip() @@ -1300,8 +1328,10 @@ def match(string): :rtype: Optional[:py:class:`fparser.two.Fortran2008.Open_Stmt] """ - # The Connect_Spec_List class is generated automatically - # by code at the end of this module + # Avoid circular dependencies by importing here. + # pylint: disable=import-outside-toplevel + from fparser.two.Fortran2008 import Connect_Spec_List + obj = CALLBase.match("OPEN", Connect_Spec_List, string, require_rhs=True) if not obj: return None @@ -1396,6 +1426,14 @@ def match(string): supplied string is not a match :rtype: Optional[Tuple[str, Any]] """ + # Avoid circular dependencies by importing here. + # pylint: disable=import-outside-toplevel + from fparser.two.Fortran2008 import ( + Scalar_Default_Char_Expr, + Scalar_Int_Variable, + Scalar_Int_Expr, + ) + if "=" not in string: # The only argument which need not be named is the unit number return "UNIT", File_Unit_Number(string) @@ -1559,6 +1597,10 @@ def match(string): Optional[:py:class:`fparser.two.Fortran2003.Block_Construct_Name`]]] """ + # Avoid circular dependencies by importing here. + # pylint: disable=import-outside-toplevel + from fparser.two.Fortran2008 import Block_Construct_Name + return EndStmtBase.match( "BLOCK", Block_Construct_Name, string, require_stmt_type=True ) @@ -1665,6 +1707,10 @@ def match(string): Optional[:py:class:`fparser.two.Fortran2003.Critical_Construct_Name`]]] """ + # Avoid circular dependencies by importing here. + # pylint: disable=import-outside-toplevel + from fparser.two.Fortran2008 import Critical_Construct_Name + return EndStmtBase.match( "CRITICAL", Critical_Construct_Name, string, require_stmt_type=True ) @@ -1691,6 +1737,10 @@ def match(string): :py:class:`fparser.two.Fortran2003.Procedure_Name_List`]]] """ + # Avoid circular dependencies by importing here. + # pylint: disable=import-outside-toplevel + from fparser.two.Fortran2008 import Procedure_Name_List + line = string.lstrip() optional_module = None if line[:6].upper() == "MODULE": @@ -1718,62 +1768,10 @@ def tostr(self): return f"{result} {self.items[0]}" -# -# GENERATE Scalar_, _List, _Name CLASSES -# - - -ClassType = type(Base) -_names = dir() -for clsname in _names: - new_cls = eval(clsname) - if not ( - isinstance(new_cls, ClassType) - and issubclass(new_cls, Base) - and not new_cls.__name__.endswith("Base") - ): - continue - - names = getattr(new_cls, "subclass_names", []) + getattr(new_cls, "use_names", []) - for n in names: - if n in _names: - continue - if n.endswith("_List"): - _names.append(n) - n = n[:-5] - # Generate 'list' class - exec( - f"""\ -class {n}_List(SequenceBase): - subclass_names = [\'{n}\'] - use_names = [] - @staticmethod - def match(string): return SequenceBase.match(r\',\', {n}, string) -""" - ) - elif n.endswith("_Name"): - _names.append(n) - n = n[:-5] - exec( - f"""\ -class {n}_Name(Base): - subclass_names = [\'Name\'] -""" - ) - elif n.startswith("Scalar_"): - _names.append(n) - n = n[7:] - exec( - f"""\ -class Scalar_{n}(Base): - subclass_names = [\'{n}\'] -""" - ) - - # Inspect the contents of this module and list all of the classes in __all__ # for automatic documentation generation with AutoDoc. + classes = inspect.getmembers( sys.modules[__name__], lambda member: inspect.isclass(member) and member.__module__ == __name__, diff --git a/src/fparser/two/Fortran2008/__init__.py b/src/fparser/two/Fortran2008/__init__.py new file mode 100644 index 00000000..1be23050 --- /dev/null +++ b/src/fparser/two/Fortran2008/__init__.py @@ -0,0 +1,138 @@ +# ----------------------------------------------------------------------------- +# BSD 3-Clause License +# +# Copyright (c) 2023, 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. +# ----------------------------------------------------------------------------- + +""" +Fortran 2008 module. Contains classes which extend the Fortran +2003 standard to implement the Fortran 2008 standard. + +""" +from fparser.two.Fortran2003 import Base, SequenceBase +from fparser.two.Fortran2008.Fortran2008 import ( + Program_Unit, + Executable_Construct, + Executable_Construct_C201, + Action_Stmt, + Action_Stmt_C201, + Action_Stmt_C816, + Action_Stmt_C828, + Data_Component_Def_Stmt, + Component_Attr_Spec, + Type_Declaration_Stmt, + Codimension_Attr_Spec, + Coarray_Bracket_Spec, + Attr_Spec, + Coarray_Spec, + Deferred_Coshape_Spec, + Explicit_Coshape_Spec, + Coshape_Spec, + Lower_Cobound, + Upper_Cobound, + Do_Term_Action_Stmt, + Alloc_Opt, + Allocate_Stmt, + Loop_Control, + If_Stmt, + Error_Stop_Stmt, + Specification_Part_C1112, + Implicit_Part_C1112, + Implicit_Part_Stmt_C1112, + Declaration_Construct_C1112, + Submodule, + Submodule_Stmt, + End_Submodule_Stmt, + Parent_Identifier, + Open_Stmt, + Connect_Spec, + Block_Construct, + Block_Stmt, + End_Block_Stmt, + Critical_Construct, + Critical_Stmt, + End_Critical_Stmt, + Procedure_Stmt, +) + + +# pylint: disable=eval-used +# pylint: disable=exec-used + +# +# GENERATE Scalar_, _List, _Name CLASSES +# +ClassType = type(Base) +_names = dir() +for clsname in _names: + new_cls = eval(clsname) + if not ( + isinstance(new_cls, ClassType) + and issubclass(new_cls, Base) + and not new_cls.__name__.endswith("Base") + ): + continue + + names = getattr(new_cls, "subclass_names", []) + getattr(new_cls, "use_names", []) + for n in names: + if n in _names: + continue + if n.endswith("_List"): + _names.append(n) + n = n[:-5] + # Generate 'list' class + exec( + f"""\ +class {n}_List(SequenceBase): + subclass_names = [\'{n}\'] + use_names = [] + @staticmethod + def match(string): return SequenceBase.match(r\',\', {n}, string) +""" + ) + elif n.endswith("_Name"): + _names.append(n) + n = n[:-5] + exec( + f"""\ +class {n}_Name(Base): + subclass_names = [\'Name\'] +""" + ) + elif n.startswith("Scalar_"): + _names.append(n) + n = n[7:] + exec( + f"""\ +class Scalar_{n}(Base): + subclass_names = [\'{n}\'] +""" + ) diff --git a/src/fparser/two/parser.py b/src/fparser/two/parser.py index 6cf599be..2e18be25 100644 --- a/src/fparser/two/parser.py +++ b/src/fparser/two/parser.py @@ -150,7 +150,9 @@ def create(self, std=None): # First find all Fortran2008 classes. from fparser.two import Fortran2008 - f2008_cls_members = get_module_classes(Fortran2008) + f2008_cls_members = inspect.getmembers( + sys.modules[Fortran2008.__name__], inspect.isclass + ) # next add in Fortran2003 classes if they do not already # exist as a Fortran2008 class. diff --git a/src/fparser/two/tests/fortran2008/test_submodule_stmt_r1117.py b/src/fparser/two/tests/fortran2008/test_submodule_stmt_r1117.py index c7ffc554..22dc4f1b 100644 --- a/src/fparser/two/tests/fortran2008/test_submodule_stmt_r1117.py +++ b/src/fparser/two/tests/fortran2008/test_submodule_stmt_r1117.py @@ -127,7 +127,9 @@ def test_splitparen_error(monkeypatch): """ # We must monkeypatch the splitparen function that has already been # imported into the F2008 module. - monkeypatch.setattr("fparser.two.Fortran2008.splitparen", lambda x: ["XXX", "", ""]) + monkeypatch.setattr( + "fparser.two.Fortran2008.Fortran2008.splitparen", lambda x: ["XXX", "", ""] + ) with pytest.raises(NoMatchError) as excinfo: dummy_ = Submodule_Stmt("submodule (id) name") assert "Submodule_Stmt: 'submodule (id) name'" in str(excinfo.value) From 84cb35454fba68a312b2e18708a3a35777d37ace Mon Sep 17 00:00:00 2001 From: rupertford Date: Thu, 8 Jun 2023 17:41:41 +0100 Subject: [PATCH 2/4] pr #417. Addressed reviewer's comments. --- src/fparser/two/Fortran2008/Fortran2008.py | 15 ------------- src/fparser/two/Fortran2008/__init__.py | 26 +++++++++++++++++----- 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/src/fparser/two/Fortran2008/Fortran2008.py b/src/fparser/two/Fortran2008/Fortran2008.py index efa18622..20c3fc81 100644 --- a/src/fparser/two/Fortran2008/Fortran2008.py +++ b/src/fparser/two/Fortran2008/Fortran2008.py @@ -75,9 +75,6 @@ # module is first imported. # pylint: disable=exec-used # pylint: disable=unused-import -import inspect -import sys - from fparser.common.splitline import string_replace_map, splitparen from fparser.two import pattern_tools as pattern from fparser.two.utils import ( @@ -1766,15 +1763,3 @@ def tostr(self): if self.items[2]: result = f"{result} ::" return f"{result} {self.items[0]}" - - -# Inspect the contents of this module and list all of the classes in __all__ -# for automatic documentation generation with AutoDoc. - - -classes = inspect.getmembers( - sys.modules[__name__], - lambda member: inspect.isclass(member) and member.__module__ == __name__, -) - -__all__ = [name[0] for name in classes] diff --git a/src/fparser/two/Fortran2008/__init__.py b/src/fparser/two/Fortran2008/__init__.py index 1be23050..c8f8ccd9 100644 --- a/src/fparser/two/Fortran2008/__init__.py +++ b/src/fparser/two/Fortran2008/__init__.py @@ -37,6 +37,9 @@ 2003 standard to implement the Fortran 2008 standard. """ +import inspect +import sys + from fparser.two.Fortran2003 import Base, SequenceBase from fparser.two.Fortran2008.Fortran2008 import ( Program_Unit, @@ -93,15 +96,15 @@ ClassType = type(Base) _names = dir() for clsname in _names: - new_cls = eval(clsname) + NEW_CLS = eval(clsname) if not ( - isinstance(new_cls, ClassType) - and issubclass(new_cls, Base) - and not new_cls.__name__.endswith("Base") + isinstance(NEW_CLS, ClassType) + and issubclass(NEW_CLS, Base) + and not NEW_CLS.__name__.endswith("Base") ): continue - names = getattr(new_cls, "subclass_names", []) + getattr(new_cls, "use_names", []) + names = getattr(NEW_CLS, "subclass_names", []) + getattr(NEW_CLS, "use_names", []) for n in names: if n in _names: continue @@ -136,3 +139,16 @@ class Scalar_{n}(Base): subclass_names = [\'{n}\'] """ ) +# Make sure NEW_CLS does not reference a class so is not accidentally +# picked up in __all__ after all classnames have been processed. +NEW_CLS = None + + +# Determine the generated classes in this module and list these in +# __all__ to support automatic documentation generation with AutoDoc. + +classes = inspect.getmembers( + sys.modules[__name__], + lambda member: inspect.isclass(member) and member.__module__ == __name__, +) +__all__ = [name[0] for name in classes if name[0]] From 0d7fb1e0bcdd0ab43236127b20f21b8a824fc491 Mon Sep 17 00:00:00 2001 From: rupertford Date: Thu, 8 Jun 2023 17:55:32 +0100 Subject: [PATCH 3/4] pr #417. Minor comment change. --- src/fparser/two/Fortran2008/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fparser/two/Fortran2008/__init__.py b/src/fparser/two/Fortran2008/__init__.py index c8f8ccd9..a68307d5 100644 --- a/src/fparser/two/Fortran2008/__init__.py +++ b/src/fparser/two/Fortran2008/__init__.py @@ -140,7 +140,7 @@ class Scalar_{n}(Base): """ ) # Make sure NEW_CLS does not reference a class so is not accidentally -# picked up in __all__ after all classnames have been processed. +# picked up in __all__. NEW_CLS = None From 937050448daaf437bce9c13cf44027455574fe57 Mon Sep 17 00:00:00 2001 From: Andrew Porter Date: Mon, 12 Jun 2023 14:49:16 +0100 Subject: [PATCH 4/4] #417 update changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 151f365a..c90af255 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,10 @@ Modifications by (in alphabetical order): * P. Vitt, University of Siegen, Germany * A. Voysey, UK Met Office +12/06/2023 PR #417 towards #411. Moves Fortran2008.py into a 'Fortran2008' + directory and moves the associated class generation into an '__init__.py' + in that directory. + 16/05/2023 PR #414 for #412. Bug fix for disappearing line when parsing include files.