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. 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..20c3fc81 100644 --- a/src/fparser/two/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 ( @@ -394,6 +391,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 +462,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 +657,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 +830,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 +1160,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 +1235,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 +1280,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 +1325,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 +1423,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 +1594,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 +1704,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 +1734,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": @@ -1716,67 +1763,3 @@ def tostr(self): if self.items[2]: result = f"{result} ::" 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__, -) - -__all__ = [name[0] for name in classes] diff --git a/src/fparser/two/Fortran2008/__init__.py b/src/fparser/two/Fortran2008/__init__.py new file mode 100644 index 00000000..a68307d5 --- /dev/null +++ b/src/fparser/two/Fortran2008/__init__.py @@ -0,0 +1,154 @@ +# ----------------------------------------------------------------------------- +# 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. + +""" +import inspect +import sys + +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}\'] +""" + ) +# Make sure NEW_CLS does not reference a class so is not accidentally +# picked up in __all__. +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]] 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)