From c16e073453b033bf4ee30fd6b595a184ce9b7b63 Mon Sep 17 00:00:00 2001 From: Joerg Henrichs Date: Tue, 16 Jul 2024 10:23:18 +1000 Subject: [PATCH 01/12] #448 Added make_public script and functions. --- example/make_public.py | 102 +++++++++++++++++++++ example/test_files/make_public.f90 | 34 +++++++ example/test_files/make_public_correct.f90 | 15 +++ 3 files changed, 151 insertions(+) create mode 100755 example/make_public.py create mode 100644 example/test_files/make_public.f90 create mode 100644 example/test_files/make_public_correct.f90 diff --git a/example/make_public.py b/example/make_public.py new file mode 100755 index 00000000..82466b47 --- /dev/null +++ b/example/make_public.py @@ -0,0 +1,102 @@ +#!/usr/bin/env python + +# ----------------------------------------------------------------------------- +# BSD 3-Clause License +# +# Copyright (c) 2020, 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. +# ------------------------------------------------------------------------------ +# Author: Joerg Henrichs, Bureau of Meteorology + +'''This file contains an fparser script that parses Fortran files +and output the dependencies between these files suitable for a Makefile. + +It assumes that the module name in the use statement corresponds to the +name of the file (adding one of .F90/.f90/.x90). Only files in the current +directory will be tested, so external dependencies will not be listed. + +Usage: create_dependencies.py file1.f90 file2.F90 ... +''' + +import sys + +from fparser.common.readfortran import FortranFileReader +from fparser.two.Fortran2003 import (Access_Stmt, Access_Spec, Attr_Spec, + Binding_Private_Stmt, + Private_Components_Stmt, Protected_Stmt) +from fparser.two.parser import ParserFactory +from fparser.two.utils import walk + +# ----------------------------------------------------------------------------- +def remove_private(filename): + reader = FortranFileReader(filename) + parser = ParserFactory().create(std="f2008") + parse_tree = parser(reader) + # A useful print to see the actual rules + # print(repr(parse_tree)) + + # Loop over all access and protected statements. Note that a + # `protected_stmt` is not an access statement, so it needs to + # be listed additionally: + for node in walk(parse_tree, (Access_Stmt, Protected_Stmt, + Private_Components_Stmt, + Binding_Private_Stmt)): + # A Private_Components_Stms has no items: + if (isinstance(node, Private_Components_Stmt) or + node.items[0] in ["PRIVATE", "PROTECTED"]): + # Find the node in the parent, and remove it: + node.parent.children.remove(node) + + for node in walk(parse_tree, Access_Spec): + if str(node) == "PRIVATE": + node.string = "PUBLIC" + + all_nodes = [node for node in walk(parse_tree, Attr_Spec)] + for node in all_nodes: + if str(node) == "PROTECTED": + # This is a tuple, so we can't simple remove the attribute + node.parent.items = tuple(i for i in node.parent.items if i is not node) + # If all items in the Attr_Spec are removed, we need to replace the + # Attr_Spec in the parent-parent (Type_Declaration) with None, + # otherwise fparser will create e.g. `real, :: a` + if len(node.parent.items) == 0: + # Again all tuples, which we can't modify, so we need to + # recreate the tuple but replace the attr_spec with None + type_decl = node.parent.parent + type_decl.items = (type_decl.items[0], None, type_decl.items[2]) + + return parse_tree + +# ----------------------------------------------------------------------------- +if __name__ == "__main__": + + filename = sys.argv[1] + parse_tree = remove_private(filename) + print(parse_tree) diff --git a/example/test_files/make_public.f90 b/example/test_files/make_public.f90 new file mode 100644 index 00000000..62c1f22e --- /dev/null +++ b/example/test_files/make_public.f90 @@ -0,0 +1,34 @@ +module a_mod + + ! Access_Stmt + private + + ! Attr_Spec with 0 and 1 additional attribute + REAL, protected :: planet_radius = 123 + REAL, parameter, protected :: planet_radius_constant = 123 + + LOGICAL :: public_protected = .FALSE. + LOGICAL :: only_protected = .FALSE. + LOGICAL :: private_protected = .FALSE. + + ! Access_stmt + PUBLIC :: public_protected + ! Protected_Stmt + PROTECTED :: public_protected, only_protected + ! Access_stmt + private :: private_protected + + type :: my_type + ! Private_Components_Stmt + private + integer :: a, b + + end type my_type + + ! Access_Spec + type(my_type), private :: my_var + +contains + subroutine sub_a + end subroutine sub_a +end module a_mod \ No newline at end of file diff --git a/example/test_files/make_public_correct.f90 b/example/test_files/make_public_correct.f90 new file mode 100644 index 00000000..2cd905f0 --- /dev/null +++ b/example/test_files/make_public_correct.f90 @@ -0,0 +1,15 @@ +MODULE a_mod + REAL :: planet_radius = 123 + REAL, PARAMETER :: planet_radius_constant = 123 + LOGICAL :: public_protected = .FALSE. + LOGICAL :: only_protected = .FALSE. + LOGICAL :: private_protected = .FALSE. + PUBLIC :: public_protected + TYPE :: my_type + INTEGER :: a, b + END TYPE my_type + TYPE(my_type), PUBLIC :: my_var + CONTAINS + SUBROUTINE sub_a + END SUBROUTINE sub_a +END MODULE a_mod From a012aedc8c1763c1f33b731e33c192fa409a7e39 Mon Sep 17 00:00:00 2001 From: Joerg Henrichs Date: Tue, 16 Jul 2024 10:23:48 +1000 Subject: [PATCH 02/12] #448 Also cleanup created source files, and add new line at end of file. --- example/split_file.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/example/split_file.py b/example/split_file.py index 7bf075a1..5163feef 100755 --- a/example/split_file.py +++ b/example/split_file.py @@ -101,10 +101,10 @@ def create_makefile(main_name, all_objs, all_filenames): f"\t$(F90) $(F90FLAGS) $(OBJS) -o {main_name} " f"$(LDFLAGS)" ) - clean_actions = f"\trm -f {main_name} $(OBJS) *.mod" + clean_actions = f"\trm -f {main_name} $(OBJS) $(OBJS:%.o=%.f90) *.mod" else: default_target = "default: $(OBJS)" - clean_actions = "\trm -f $(OBJS) *.mod" + clean_actions = "\trm -f $(OBJS) $(OBJS:%.o=%.f90) *.mod" with open(makefile, mode="w", encoding="utf-8") as f_out: f_out.write( @@ -192,6 +192,7 @@ def main(): sys.exit(-1) with open(filename, mode="w", encoding="utf-8") as f_out: f_out.write(str(unit)) + f_out.write("\n") all_filenames.append(filename) all_objs.append(f"{unit_name}.o") From c42f398dd83bafc88415f93b9d3037bfefee2f96 Mon Sep 17 00:00:00 2001 From: Joerg Henrichs Date: Tue, 16 Jul 2024 10:26:05 +1000 Subject: [PATCH 03/12] #448 Add tests for most examples (all except fparser2_2008). --- example/Makefile | 19 ++++++---- .../create_dependencies/correct_dependencies | 2 ++ example/test_files/split_file/test.f90 | 35 +++++++++---------- 3 files changed, 31 insertions(+), 25 deletions(-) create mode 100644 example/test_files/create_dependencies/correct_dependencies diff --git a/example/Makefile b/example/Makefile index 924915e6..7e4c2a0f 100644 --- a/example/Makefile +++ b/example/Makefile @@ -33,17 +33,22 @@ # ------------------------------------------------------------------------------ # Author: J. Henrichs, Bureau of Meteorology -.PHONY: test create_dependency fparser2_f2008 +.PHONY: test create_dependency fparser2_f2008 make_public split_file -test: create_dependencies split_file fparser2_f2008 +test: create_dependencies fparser2_f2008 make_public split_file create_dependencies: - (cd test_files/create_dependencies; ../../create_dependencies.py *.f90) + (cd test_files/create_dependencies; \ + ../../create_dependencies.py *.f90 | diff correct_dependencies -) + +fparser2_f2008: + python ./fparser2_f2008.py + +make_public: + ./make_public.py test_files/make_public.f90 | diff test_files/make_public_correct.f90 - split_file: (cd test_files/split_file; \ rm -f Makefile func.f90 mod1.f90 sub.f90 test_prog.f90; \ - ../../split_file.py test.f90) - -fparser2_f2008: - python ./fparser2_f2008.py \ No newline at end of file + ../../split_file.py test.f90; \ + cat mod1.f90 sub.f90 func.f90 test_prog.f90 | diff -B test.f90 -) diff --git a/example/test_files/create_dependencies/correct_dependencies b/example/test_files/create_dependencies/correct_dependencies new file mode 100644 index 00000000..3b14731f --- /dev/null +++ b/example/test_files/create_dependencies/correct_dependencies @@ -0,0 +1,2 @@ +b.o: a.o +c.o: a.o b.o diff --git a/example/test_files/split_file/test.f90 b/example/test_files/split_file/test.f90 index 56ac3dbc..f550a2a0 100644 --- a/example/test_files/split_file/test.f90 +++ b/example/test_files/split_file/test.f90 @@ -1,22 +1,21 @@ -module mod1 - integer :: a -contains - subroutine p(args) - implicit none - real :: args - - end subroutine p -end module mod1 +MODULE mod1 + INTEGER :: a + CONTAINS + SUBROUTINE p(args) + IMPLICIT NONE + REAL :: args -subroutine sub() -end subroutine sub + END SUBROUTINE p +END MODULE mod1 -real function func(args) - use mod1 - func = 1 -end function func +SUBROUTINE sub +END SUBROUTINE sub -program test_prog - use mod1 -end program test_prog +REAL FUNCTION func(args) + USE mod1 + func = 1 +END FUNCTION func +PROGRAM test_prog + USE mod1 +END PROGRAM test_prog From 982ca17eb73e44be97fd3585ba8442a13556dc31 Mon Sep 17 00:00:00 2001 From: Joerg Henrichs Date: Thu, 18 Jul 2024 10:26:11 +1000 Subject: [PATCH 04/12] #448 Removed removal of Fortran files, it's too risky. --- example/split_file.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/example/split_file.py b/example/split_file.py index 5163feef..97babd22 100755 --- a/example/split_file.py +++ b/example/split_file.py @@ -101,10 +101,10 @@ def create_makefile(main_name, all_objs, all_filenames): f"\t$(F90) $(F90FLAGS) $(OBJS) -o {main_name} " f"$(LDFLAGS)" ) - clean_actions = f"\trm -f {main_name} $(OBJS) $(OBJS:%.o=%.f90) *.mod" + clean_actions = f"\trm -f {main_name} $(OBJS) *.mod" else: default_target = "default: $(OBJS)" - clean_actions = "\trm -f $(OBJS) $(OBJS:%.o=%.f90) *.mod" + clean_actions = "\trm -f $(OBJS) *.mod" with open(makefile, mode="w", encoding="utf-8") as f_out: f_out.write( From de7612f9a87233386052f2dd019494887284ebc9 Mon Sep 17 00:00:00 2001 From: Joerg Henrichs Date: Thu, 18 Jul 2024 10:48:45 +1000 Subject: [PATCH 05/12] #448 Added chapter for the examples, added author. --- doc/source/conf.py | 25 ++++++- doc/source/examples.rst | 149 ++++++++++++++++++++++++++++++++++++++++ doc/source/index.rst | 1 + doc/source/license.rst | 5 +- 4 files changed, 177 insertions(+), 3 deletions(-) create mode 100644 doc/source/examples.rst diff --git a/doc/source/conf.py b/doc/source/conf.py index 30e46ff3..04b36111 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -10,7 +10,7 @@ project = 'fparser' copyright = '2017-2024, Science and Technology Facilities Council' -author = 'Andrew Porter, Rupert Ford, Balthasar Reuter and Pearu Peterson' +author = 'Andrew Porter, Rupert Ford, Balthasar Reuter, Joerg Henrichs and Pearu Peterson' version = fparser._get_version() release = fparser._get_version() @@ -55,3 +55,26 @@ # Generate the Doxygen documentation subprocess.call('cd ..; doxygen doxygen.config', shell=True) + + +# -- Options for LaTeX output ------------------------------------------------ + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + # 'papersize': 'letterpaper', + + # The font size ('10pt', '11pt' or '12pt'). + # 'pointsize': '10pt', + + # Additional stuff for the LaTeX preamble. + # 'preamble': '', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, +# documentclass [howto/manual]). +latex_documents = [ + ('index', 'fparser.tex', 'fparser Documentation', + 'Andrew Porter, Rupert Ford, Balthasar Reuter, \\\\ ' + 'Joerg Henrichs and Pearu Peterson', 'manual'), +] diff --git a/doc/source/examples.rst b/doc/source/examples.rst new file mode 100644 index 00000000..d59456c2 --- /dev/null +++ b/doc/source/examples.rst @@ -0,0 +1,149 @@ +.. -*- rest -*- + +.. + Copyright (c) 2024 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. + + +.. _examples: + +Examples +======== + +The distribution comes with a set of examples showing how fparser can +be used. At the same time, some of these examples are actually useful tools +that are used in other projects. + + +fparser2_f2008.py +^^^^^^^^^^^^^^^^^ +This is a very small example code that shows how a Fortran code, given as +a string, is parsed, and then converted back into Fortran. It just prints +out the re-created Fortran source code. + + +create_dependencies.py +^^^^^^^^^^^^^^^^^^^^^^ +This file analysis the dependencies between a set of Fortran files, based on +the ``Use`` statements in each file. It assumes that the module name in the +``use`` statement corresponds to the name of the file (adding one of +.F90/.f90/.x90). Only files in the current directory will be tested, so +external dependencies will not be listed. Its output +is in a format that can be immediately used in a Makefile:: + + ../create_dependencies.py *.f90 + b.o: a.o + c.o: a.o b.o + + +Ignoring error handling, the simplified main part of this code +that is related to fparser is:: + + reader = FortranFileReader(filename) + parser = ParserFactory().create(std="f2003") + parse_tree = parser(reader) + + # Collect all used modules in a list + all_use = set() + for node in walk(parse_tree, Use_Stmt): + use_name = str(node.items[2]) + # A more sophisticated mapping could be used here. + # But for now just assume that the name in the use statement + # with an added ".o" is the required object file: + obj_dependency = use_name + ".o" + all_use.add(use_name) + # Now ``all_use`` contains all .o files that ``filename`` depends on + +The rest of the example is related to creating the proper format for +a Makefile. + +.. note:: It would be straight-forward to loop over all files twice, first + to collect the module names provided by each file, then use this + information + + +make_public.py +^^^^^^^^^^^^^^ +This example removes all ``private`` and ``protected`` attributes in any +declaration. In general these attributes are important and should +obviously not be removed in order to give the compiler more information +about intended use of the variables. But `PSyclone +`_ offers a feature called +Kernel Extraction, which automatically writes all variables read and written +in a code section to a file. It also then creates a stand-alone driver program +that will read this file, execute the kernel, and compare the results with +the original results. + +Since PSyclone will follow call tree, the code must be able to read even +variables declared as ``private`` (to write them into the output file), and +a driver program must be able to modify ``private`` and ``protected`` +variables in modules. If the driver creation is used, the +`Fab `_ based build system will +remove all ``private`` and ``protected`` attributes in a separate build phase, +so that the kernel extraction and driver creation works as expected. + +A short example, which shows how a ``Access_Stmt`` like ``private :: a`` is +removed:: + + for node in walk(parse_tree, Access_Stmt): + if node.items[0] == "PRIVATE": + # Find the node in the parent, and remove it: + node.parent.children.remove(node) + + +Modifying some of the fparser data structures can be more difficult, since +they are often based on Python tuples, which cannot be modified. The following +example from ``make_public.py`` shows how the middle element of a three-element +tuple is replaced with None:: + + type_decl.items = (type_decl.items[0], None, type_decl.items[2]) + + +split_file.py +~~~~~~~~~~~~~ +This script splits one Fortran source file into several files, each containing +one top level module, subroutine, function or program. Each file uses the name +of the program unit (module-, subroutine-, function-, program name). The +extension will be ``.F90`` if there are preprocessor directives in the file, +and ``.f90`` otherwise. + +Additionally, ``split_file.py`` will create a Makefile to build either the +binary (if a program is found in the file), or all object files. If any of +the environment variables ``F90``, ``F90FLAGS``, and ``LDFLAGS`` are set at +run time of the script, it will use these values as default values in the +makefile. But by setting these environment variables when running ``make``, +these defaults can always be overwritten. The Makefile also has a ``clean`` +target, which will remove all ``.mod``, object, and the program file (if +available). It uses the ``create_dependencies.py`` script to add the +required dependencies to the Makefile. diff --git a/doc/source/index.rst b/doc/source/index.rst index fc638c0e..231e57bc 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -79,5 +79,6 @@ Welcome to fparser's documentation! introduction fparser fparser2 + examples developers_guide reference_guide diff --git a/doc/source/license.rst b/doc/source/license.rst index 9789706f..db825cd5 100644 --- a/doc/source/license.rst +++ b/doc/source/license.rst @@ -5,8 +5,9 @@ License ======= | Modified work Copyright (c) 2017-2021 Science and Technology Facilities Council -| Authors: **Andrew Porter** and **Rupert Ford**, STFC Daresbury Laboratory, and -| **Balthasar Reuter**, ECMWF +| Authors: **Andrew Porter** and **Rupert Ford**, STFC Daresbury Laboratory, +| **Balthasar Reuter**, ECMWF, and +| **Joerg Henrichs**, Bureau of Meteorology | Original work Copyright (c) 1999-2008 **Pearu Peterson** | All rights reserved. From c0bb76b2532842873f5c5ce92127bc0415de4d31 Mon Sep 17 00:00:00 2001 From: Joerg Henrichs Date: Thu, 18 Jul 2024 10:49:07 +1000 Subject: [PATCH 06/12] #448 Ran black. --- example/make_public.py | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/example/make_public.py b/example/make_public.py index 82466b47..097d5246 100755 --- a/example/make_public.py +++ b/example/make_public.py @@ -35,7 +35,7 @@ # ------------------------------------------------------------------------------ # Author: Joerg Henrichs, Bureau of Meteorology -'''This file contains an fparser script that parses Fortran files +"""This file contains an fparser script that parses Fortran files and output the dependencies between these files suitable for a Makefile. It assumes that the module name in the use statement corresponds to the @@ -43,17 +43,23 @@ directory will be tested, so external dependencies will not be listed. Usage: create_dependencies.py file1.f90 file2.F90 ... -''' +""" import sys from fparser.common.readfortran import FortranFileReader -from fparser.two.Fortran2003 import (Access_Stmt, Access_Spec, Attr_Spec, - Binding_Private_Stmt, - Private_Components_Stmt, Protected_Stmt) +from fparser.two.Fortran2003 import ( + Access_Stmt, + Access_Spec, + Attr_Spec, + Binding_Private_Stmt, + Private_Components_Stmt, + Protected_Stmt, +) from fparser.two.parser import ParserFactory from fparser.two.utils import walk + # ----------------------------------------------------------------------------- def remove_private(filename): reader = FortranFileReader(filename) @@ -65,12 +71,15 @@ def remove_private(filename): # Loop over all access and protected statements. Note that a # `protected_stmt` is not an access statement, so it needs to # be listed additionally: - for node in walk(parse_tree, (Access_Stmt, Protected_Stmt, - Private_Components_Stmt, - Binding_Private_Stmt)): + for node in walk( + parse_tree, + (Access_Stmt, Protected_Stmt, Private_Components_Stmt, Binding_Private_Stmt), + ): # A Private_Components_Stms has no items: - if (isinstance(node, Private_Components_Stmt) or - node.items[0] in ["PRIVATE", "PROTECTED"]): + if isinstance(node, Private_Components_Stmt) or node.items[0] in [ + "PRIVATE", + "PROTECTED", + ]: # Find the node in the parent, and remove it: node.parent.children.remove(node) @@ -94,9 +103,9 @@ def remove_private(filename): return parse_tree + # ----------------------------------------------------------------------------- if __name__ == "__main__": - filename = sys.argv[1] parse_tree = remove_private(filename) print(parse_tree) From f43777eea219c362710a2a67edb24db5f8922964 Mon Sep 17 00:00:00 2001 From: Joerg Henrichs Date: Thu, 18 Jul 2024 10:51:11 +1000 Subject: [PATCH 07/12] #448 Test the examples. --- .github/workflows/unit-tests.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 898bff4d..8b99633f 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -84,6 +84,9 @@ jobs: # fparser should work even under limited terminal conditions so set # LC_ALL (this is only relevant for versions before Python 3.7). LC_ALL=POSIX pytest --cov=fparser --cov-report=xml src/fparser + - name: Test examples + run: | + make -C example test src/fparser - name: Upload coverage to Codecov with GitHub Action uses: codecov/codecov-action@v4 env: From a1189f7d1748b8696a3e370f5e43ded3c98957dc Mon Sep 17 00:00:00 2001 From: Joerg Henrichs Date: Thu, 18 Jul 2024 13:05:21 +1000 Subject: [PATCH 08/12] #448 Fixed typo in triggering the example tests. --- .github/workflows/unit-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 8b99633f..dea25569 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -86,7 +86,7 @@ jobs: LC_ALL=POSIX pytest --cov=fparser --cov-report=xml src/fparser - name: Test examples run: | - make -C example test src/fparser + make -C example test - name: Upload coverage to Codecov with GitHub Action uses: codecov/codecov-action@v4 env: From acd2da2a7016266efec487cb53e494662879d43f Mon Sep 17 00:00:00 2001 From: Joerg Henrichs Date: Thu, 1 Aug 2024 23:43:37 +1000 Subject: [PATCH 09/12] Support Binding_Private_Stmt. --- example/make_public.py | 12 +++++++----- example/test_files/make_public.f90 | 2 ++ example/test_files/make_public_correct.f90 | 1 + 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/example/make_public.py b/example/make_public.py index 097d5246..9fa5a392 100755 --- a/example/make_public.py +++ b/example/make_public.py @@ -62,6 +62,9 @@ # ----------------------------------------------------------------------------- def remove_private(filename): + """Simple function that removes all private and protected declarations. + :param str filename: the file in which to remove private and protected + """ reader = FortranFileReader(filename) parser = ParserFactory().create(std="f2008") parse_tree = parser(reader) @@ -76,10 +79,9 @@ def remove_private(filename): (Access_Stmt, Protected_Stmt, Private_Components_Stmt, Binding_Private_Stmt), ): # A Private_Components_Stms has no items: - if isinstance(node, Private_Components_Stmt) or node.items[0] in [ - "PRIVATE", - "PROTECTED", - ]: + if isinstance( + node, (Binding_Private_Stmt, Private_Components_Stmt) + ) or node.items[0] in ["PRIVATE", "PROTECTED"]: # Find the node in the parent, and remove it: node.parent.children.remove(node) @@ -87,7 +89,7 @@ def remove_private(filename): if str(node) == "PRIVATE": node.string = "PUBLIC" - all_nodes = [node for node in walk(parse_tree, Attr_Spec)] + all_nodes = list(walk(parse_tree, Attr_Spec)) for node in all_nodes: if str(node) == "PROTECTED": # This is a tuple, so we can't simple remove the attribute diff --git a/example/test_files/make_public.f90 b/example/test_files/make_public.f90 index 62c1f22e..85cac3ef 100644 --- a/example/test_files/make_public.f90 +++ b/example/test_files/make_public.f90 @@ -22,6 +22,8 @@ module a_mod ! Private_Components_Stmt private integer :: a, b + contains + private end type my_type diff --git a/example/test_files/make_public_correct.f90 b/example/test_files/make_public_correct.f90 index 2cd905f0..af58de56 100644 --- a/example/test_files/make_public_correct.f90 +++ b/example/test_files/make_public_correct.f90 @@ -7,6 +7,7 @@ MODULE a_mod PUBLIC :: public_protected TYPE :: my_type INTEGER :: a, b + CONTAINS END TYPE my_type TYPE(my_type), PUBLIC :: my_var CONTAINS From 7e0782126f019b204e92d4cd26ec4bf8c767029e Mon Sep 17 00:00:00 2001 From: Joerg Henrichs Date: Thu, 10 Oct 2024 09:58:15 +1100 Subject: [PATCH 10/12] #448 Addressed documentation issues raised in review. --- doc/source/examples.rst | 4 ++-- example/Makefile | 3 +++ example/make_public.py | 15 +++++++-------- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/doc/source/examples.rst b/doc/source/examples.rst index d59456c2..1c9e4519 100644 --- a/doc/source/examples.rst +++ b/doc/source/examples.rst @@ -55,7 +55,7 @@ out the re-created Fortran source code. create_dependencies.py ^^^^^^^^^^^^^^^^^^^^^^ -This file analysis the dependencies between a set of Fortran files, based on +This file analyses the dependencies between a set of Fortran files, based on the ``Use`` statements in each file. It assumes that the module name in the ``use`` statement corresponds to the name of the file (adding one of .F90/.f90/.x90). Only files in the current directory will be tested, so @@ -105,7 +105,7 @@ in a code section to a file. It also then creates a stand-alone driver program that will read this file, execute the kernel, and compare the results with the original results. -Since PSyclone will follow call tree, the code must be able to read even +Since PSyclone will follow the call tree, the code must be able to read even variables declared as ``private`` (to write them into the output file), and a driver program must be able to modify ``private`` and ``protected`` variables in modules. If the driver creation is used, the diff --git a/example/Makefile b/example/Makefile index 7e4c2a0f..b801388a 100644 --- a/example/Makefile +++ b/example/Makefile @@ -33,6 +33,9 @@ # ------------------------------------------------------------------------------ # Author: J. Henrichs, Bureau of Meteorology +# A simple Makefile driver to test the various examples. + + .PHONY: test create_dependency fparser2_f2008 make_public split_file test: create_dependencies fparser2_f2008 make_public split_file diff --git a/example/make_public.py b/example/make_public.py index 9fa5a392..64d1e88a 100755 --- a/example/make_public.py +++ b/example/make_public.py @@ -3,7 +3,7 @@ # ----------------------------------------------------------------------------- # BSD 3-Clause License # -# Copyright (c) 2020, Science and Technology Facilities Council. +# Copyright (c) 2024, Science and Technology Facilities Council. # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -35,14 +35,13 @@ # ------------------------------------------------------------------------------ # Author: Joerg Henrichs, Bureau of Meteorology -"""This file contains an fparser script that parses Fortran files -and output the dependencies between these files suitable for a Makefile. +"""This file contains a script that will remove any private or protected +declaration in a Fortran file. This is used by PSyclone's kernel extraction +and driver creation feature, see +https://psyclone.readthedocs.io/en/latest/psyke.html. +for details. -It assumes that the module name in the use statement corresponds to the -name of the file (adding one of .F90/.f90/.x90). Only files in the current -directory will be tested, so external dependencies will not be listed. - -Usage: create_dependencies.py file1.f90 file2.F90 ... +Usage: make_public.py file1.f90 """ import sys From 4e033b127005399d16fa5b6fdec11378a160dfa1 Mon Sep 17 00:00:00 2001 From: Joerg Henrichs Date: Thu, 10 Oct 2024 09:59:59 +1100 Subject: [PATCH 11/12] #448 Fixed typo and some pylint issues. --- example/make_public.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/example/make_public.py b/example/make_public.py index 64d1e88a..562c9857 100755 --- a/example/make_public.py +++ b/example/make_public.py @@ -91,7 +91,7 @@ def remove_private(filename): all_nodes = list(walk(parse_tree, Attr_Spec)) for node in all_nodes: if str(node) == "PROTECTED": - # This is a tuple, so we can't simple remove the attribute + # This is a tuple, so we can't simply remove the attribute node.parent.items = tuple(i for i in node.parent.items if i is not node) # If all items in the Attr_Spec are removed, we need to replace the # Attr_Spec in the parent-parent (Type_Declaration) with None, @@ -107,6 +107,6 @@ def remove_private(filename): # ----------------------------------------------------------------------------- if __name__ == "__main__": - filename = sys.argv[1] - parse_tree = remove_private(filename) - print(parse_tree) + user_filename = sys.argv[1] + modified_parse_tree = remove_private(user_filename) + print(modified_parse_tree) From 44c074eaa0c4636249db82c26d804ebbc4c69093 Mon Sep 17 00:00:00 2001 From: Andrew Porter Date: Fri, 11 Oct 2024 09:16:27 +0100 Subject: [PATCH 12/12] #448 update changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e0af643..981e5a55 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 +11/10/2024 PR #450 for #448. Adds an example script for removing all protected/private + attributes from a parse tree. + 15/07/2024 PR #438 for #437. Fix type guard statement bug. 24/04/2024 PR #444 for #443. Adds an option to the reader to handle code