From a4b2b2a8cc76a3e3f07fc5a7e637889dfe65f872 Mon Sep 17 00:00:00 2001 From: LonelyCat124 <3043914+LonelyCat124@users.noreply.github.com.> Date: Mon, 13 Oct 2025 16:18:17 +0100 Subject: [PATCH 1/6] First try to recognize generic directives --- doc/source/fparser2.rst | 5 +++-- src/fparser/two/Fortran2003.py | 27 ++++++++++----------------- 2 files changed, 13 insertions(+), 19 deletions(-) diff --git a/doc/source/fparser2.rst b/doc/source/fparser2.rst index d3b45f7b..a7571db7 100644 --- a/doc/source/fparser2.rst +++ b/doc/source/fparser2.rst @@ -504,8 +504,9 @@ and by default it is set to ``False``. If its set to true, it forces ``ignore_comments`` to be ``False``. The supported directives are those recognized by flang, ifx, ifort (``!dir$``), -and gcc (``!gcc$``), as well as OpenMP directives (such as ``!$omp`` -or alternatives). +and gcc (``!gcc$``), as well as support for any generic directive. A generic +directive is any comment that begins ``!$``, ``c$`` or ``*$`` followed by an +alphabetical character. For example:: diff --git a/src/fparser/two/Fortran2003.py b/src/fparser/two/Fortran2003.py index 6497dac4..fa7f342a 100644 --- a/src/fparser/two/Fortran2003.py +++ b/src/fparser/two/Fortran2003.py @@ -129,27 +129,20 @@ class Directive(Base): Fparser supports the following directive formats: - 1. '!$dir' for generic directives. - 2. '!dir$' for the flang, ifx or ifort compilers. + 1. '!$', 'c$' or '*$' followed by any alphabetical character for + generic directives. + 2. '!dir$' or 'cdir$' for the flang, ifx or ifort compilers. 3. '!gcc$' for the gfortran compiler. - 4. '!$omp', '!$ompx', 'c$omp', '*$omp', '!$omx', 'c$omx', and '*$omx' for - OpenMP directives. """ subclass_names = [] - # TODO #483 - Add OpenACC directive support. _directive_formats = [ - "!$dir", # Generic directive - "!dir$", # flang, ifx, ifort directives. - "cdir$", # flang, ifx, ifort fixed format directive. - "!$omp", # OpenMP directive - "c$omp", # OpenMP fixed format directive - "*$omp", # OpenMP fixed format directive - "!$omx", # OpenMP fixed format directive - "c$omx", # OpenMP fixed format directive - "*$omx", # OpenMP fixed format directive - "!gcc$", # GCC compiler directive - "!$ompx", # OpenMP extension directive + r"\!\$[a-z]", # Generic directive + r"c\$[a-z]", # Generic directive + r"\*\$[a-z]", # Generic directive + r"\!dir\$", # flang, ifx, ifort directives. + r"cdir\$", # flang, ifx, ifort fixed format directive. + r"\!gcc\$", # GCC compiler directive ] @show_result @@ -172,7 +165,7 @@ def __new__(cls, string: Union[str, FortranReaderBase], parent_cls=None): if not ( any( [ - lower.startswith(prefix) + re.match(prefix, lower) is not None for prefix in Directive._directive_formats ] ) From 746b303971007c7427fbea9260bdb30f76e96a3f Mon Sep 17 00:00:00 2001 From: LonelyCat124 <3043914+LonelyCat124@users.noreply.github.com.> Date: Tue, 14 Oct 2025 15:23:54 +0100 Subject: [PATCH 2/6] Changes and fixes for review --- .../two/tests/test_comments_and_directives.py | 75 ++++++++++++------- 1 file changed, 50 insertions(+), 25 deletions(-) diff --git a/src/fparser/two/tests/test_comments_and_directives.py b/src/fparser/two/tests/test_comments_and_directives.py index 120a4444..e051e2ad 100644 --- a/src/fparser/two/tests/test_comments_and_directives.py +++ b/src/fparser/two/tests/test_comments_and_directives.py @@ -236,7 +236,7 @@ def test_prog_comments(): ) obj = cls(reader) - assert type(obj) == Program + assert type(obj) is Program # Check that the AST has the expected structure: # Program # |--> Comment @@ -254,21 +254,21 @@ def test_prog_comments(): from fparser.two.Fortran2003 import Main_Program, Write_Stmt, End_Program_Stmt walk(obj.children, Comment, debug=True) - assert type(obj.content[0]) == Comment + assert type(obj.content[0]) is Comment assert str(obj.content[0]) == "! A troublesome comment" - assert type(obj.content[1]) == Main_Program + assert type(obj.content[1]) is Main_Program main_prog = obj.content[1] - assert type(main_prog.content[1].content[0].content[0]) == Comment + assert type(main_prog.content[1].content[0].content[0]) is Comment assert str(main_prog.content[1].content[0].content[0]) == "! A full comment line" exec_part = main_prog.content[2] - assert type(exec_part.content[0]) == Write_Stmt + assert type(exec_part.content[0]) is Write_Stmt # Check that we have the in-line comment as a second statement assert len(exec_part.content) == 2 - assert type(exec_part.content[1]) == Comment - assert type(main_prog.content[3]) == End_Program_Stmt + assert type(exec_part.content[1]) is Comment + assert type(main_prog.content[3]) is End_Program_Stmt assert "! An in-line comment" in str(obj) # Check that we still have the ending comment - assert type(obj.content[-1]) == Comment + assert type(obj.content[-1]) is Comment assert str(obj).endswith("! A really problematic comment") @@ -283,7 +283,7 @@ def test_module_comments(): # Test when the reader is explicitly set to free-form mode reader = get_reader(source, isfree=True, ignore_comments=False) prog_unit = Program(reader) - assert type(prog_unit.content[0]) == Comment + assert type(prog_unit.content[0]) is Comment assert str(prog_unit.content[0]) == "! This is a module" @@ -441,7 +441,7 @@ def test_directive_stmts(): # Check the restore_reader works correctly for directive. old = reader.get_item() - assert old == None + assert old is None out[2].restore_reader(reader) old = reader.get_item() assert old is not None @@ -481,17 +481,23 @@ def test_directive_stmts(): @pytest.mark.parametrize( "directive,expected,free", [ - ("!$dir always", "!$dir always", True), - ("!dir$ always", "!dir$ always", True), - ("!gcc$ vector", "!gcc$ vector", True), - ("!$omp parallel", "!$omp parallel", True), - ("!$ompx parallel", "!$ompx parallel", True), - ("c$omp parallel", "c$omp parallel", False), - ("c$omx parallel", "c$omx parallel", False), - ("!$omx parallel", "!$omx parallel", False), - ("*$omp parallel", "*$omp parallel", False), - ("c$omx parallel", "c$omx parallel", False), - ("*$omx parallel", "*$omx parallel", False), + ("!$dir always", ("!$dir always",), True), + ("!dir$ always", ("!dir$ always",), True), + ("!gcc$ vector", ("!gcc$ vector",), True), + ("!$omp parallel", ("!$omp parallel",), True), + ("!$ompx parallel", ("!$ompx parallel",), True), + ("c$omp parallel", ("c$omp parallel",), False), + ("c$omx parallel", ("c$omx parallel",), False), + ("!$omx parallel", ("!$omx parallel",), False), + ("*$omp parallel", ("*$omp parallel",), False), + ("c$omx parallel", ("c$omx parallel",), False), + ("*$omx parallel", ("*$omx parallel",), False), + ("!$omp parallel&\n!$omp&do", ("!$omp parallel&", "!$omp&do"), True), + ( + "c$omp parallel do\nc$omp+shared(a,b,c)", + ("c$omp parallel do", "c$omp+shared(a,b,c)"), + False, + ), ], ) def test_all_directive_formats(directive, expected, free): @@ -522,12 +528,31 @@ def test_all_directive_formats(directive, expected, free): ) program = Program(reader) out = walk(program, Directive) - assert len(out) == 1 - assert out[0].items[0] == expected + assert len(out) == len(expected) + for i, direc in enumerate(out): + assert direc.items[0] == expected[i] # Test that we correctly get directives without ignore_comments=False. reader = get_reader(source, isfree=free, process_directives=True) program = Program(reader) out = walk(program, Directive) - assert len(out) == 1 - assert out[0].items[0] == expected + assert len(out) == len(expected) + for i, direc in enumerate(out): + assert direc.items[0] == expected[i] + + # Test that we get comments with ignore_comments only? + # For free form source we get an empty comment somewhere before the program. + reader = get_reader(source, isfree=free, ignore_comments=False, process_directives=False) + program = Program(reader) + out = walk(program, Comment) + if free: + n_expected = len(expected) + 1 + else: + n_expected = len(expected) + assert len(out) == n_expected + if free: + for i, direc in enumerate(out[1:]): + assert direc.items[0] == expected[i] + else: + for i, direc in enumerate(out): + assert direc.items[0] == expected[i] From 11a9075eb6c24a0ebe6af770acac0e762389c9bb Mon Sep 17 00:00:00 2001 From: LonelyCat124 <3043914+LonelyCat124@users.noreply.github.com.> Date: Tue, 14 Oct 2025 15:33:25 +0100 Subject: [PATCH 3/6] black issue --- src/fparser/two/tests/test_comments_and_directives.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/fparser/two/tests/test_comments_and_directives.py b/src/fparser/two/tests/test_comments_and_directives.py index e051e2ad..9e9e1537 100644 --- a/src/fparser/two/tests/test_comments_and_directives.py +++ b/src/fparser/two/tests/test_comments_and_directives.py @@ -542,7 +542,9 @@ def test_all_directive_formats(directive, expected, free): # Test that we get comments with ignore_comments only? # For free form source we get an empty comment somewhere before the program. - reader = get_reader(source, isfree=free, ignore_comments=False, process_directives=False) + reader = get_reader( + source, isfree=free, ignore_comments=False, process_directives=False + ) program = Program(reader) out = walk(program, Comment) if free: From 138f1ffa9af03c654a44bfad0311857b0a19774b Mon Sep 17 00:00:00 2001 From: LonelyCat124 <3043914+LonelyCat124@users.noreply.github.com.> Date: Fri, 31 Oct 2025 13:45:39 +0000 Subject: [PATCH 4/6] Fixes for latest review --- .../two/tests/test_comments_and_directives.py | 72 ++++++++++++++----- 1 file changed, 56 insertions(+), 16 deletions(-) diff --git a/src/fparser/two/tests/test_comments_and_directives.py b/src/fparser/two/tests/test_comments_and_directives.py index 9e9e1537..ce8ed453 100644 --- a/src/fparser/two/tests/test_comments_and_directives.py +++ b/src/fparser/two/tests/test_comments_and_directives.py @@ -492,6 +492,8 @@ def test_directive_stmts(): ("*$omp parallel", ("*$omp parallel",), False), ("c$omx parallel", ("c$omx parallel",), False), ("*$omx parallel", ("*$omx parallel",), False), + ("!$DIR ALWAYS", ("!$DIR ALWAYS",), True), + ("c$OMX PARALLEL", ("c$OMX PARALLEL",), False), ("!$omp parallel&\n!$omp&do", ("!$omp parallel&", "!$omp&do"), True), ( "c$omp parallel do\nc$omp+shared(a,b,c)", @@ -503,10 +505,9 @@ def test_directive_stmts(): def test_all_directive_formats(directive, expected, free): """Parameterized test to ensure that all directive formats are correctly recognized.""" - # Tests for free-form directives + # Generate the source code if free: - source = """ - Program my_prog + source = """Program my_prog integer :: x """ source = source + directive + "\n" @@ -540,21 +541,60 @@ def test_all_directive_formats(directive, expected, free): for i, direc in enumerate(out): assert direc.items[0] == expected[i] - # Test that we get comments with ignore_comments only? - # For free form source we get an empty comment somewhere before the program. + +@pytest.mark.parametrize( + "directive,expected,free", + [ + ("!$dir always", ("!$dir always",), True), + ("!dir$ always", ("!dir$ always",), True), + ("!gcc$ vector", ("!gcc$ vector",), True), + ("!$omp parallel", ("!$omp parallel",), True), + ("!$ompx parallel", ("!$ompx parallel",), True), + ("c$omp parallel", ("c$omp parallel",), False), + ("c$omx parallel", ("c$omx parallel",), False), + ("!$omx parallel", ("!$omx parallel",), False), + ("*$omp parallel", ("*$omp parallel",), False), + ("c$omx parallel", ("c$omx parallel",), False), + ("*$omx parallel", ("*$omx parallel",), False), + ("!$DIR ALWAYS", ("!$DIR ALWAYS",), True), + ("c$OMX PARALLEL", ("c$OMX PARALLEL",), False), + ("!$omp parallel&\n!$omp&do", ("!$omp parallel&", "!$omp&do"), True), + ( + "c$omp parallel do\nc$omp+shared(a,b,c)", + ("c$omp parallel do", "c$omp+shared(a,b,c)"), + False, + ), + ], +) +def test_directives_as_comments(directive, expected, free): + """Parameterized test to ensure all directives produce comments when + process_directives is disabled.""" + # Generate the source code + if free: + source = """Program my_prog + integer :: x + """ + source = source + directive + "\n" + source = ( + source + + """ do x= 1 , 100 + end do + End Program""" + ) + else: + source = """\ + program foo +""" + source = source + directive + "\n" + source = source + " end program foo" + # Test that we get the expected comments with comments only reader = get_reader( source, isfree=free, ignore_comments=False, process_directives=False ) program = Program(reader) out = walk(program, Comment) - if free: - n_expected = len(expected) + 1 - else: - n_expected = len(expected) - assert len(out) == n_expected - if free: - for i, direc in enumerate(out[1:]): - assert direc.items[0] == expected[i] - else: - for i, direc in enumerate(out): - assert direc.items[0] == expected[i] + # Check that we have the correct number of comments. + assert len(out) == len(expected) + # Check that the comments contain the correct strings. + for i, direc in enumerate(out): + assert direc.items[0] == expected[i] From ebd050be1e6ce95a53208784c732726d4ca6e59a Mon Sep 17 00:00:00 2001 From: LonelyCat124 <3043914+LonelyCat124@users.noreply.github.com.> Date: Fri, 31 Oct 2025 15:35:21 +0000 Subject: [PATCH 5/6] Added additional tests --- src/fparser/two/tests/test_comments_and_directives.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/fparser/two/tests/test_comments_and_directives.py b/src/fparser/two/tests/test_comments_and_directives.py index ce8ed453..1f93c220 100644 --- a/src/fparser/two/tests/test_comments_and_directives.py +++ b/src/fparser/two/tests/test_comments_and_directives.py @@ -484,6 +484,7 @@ def test_directive_stmts(): ("!$dir always", ("!$dir always",), True), ("!dir$ always", ("!dir$ always",), True), ("!gcc$ vector", ("!gcc$ vector",), True), + ("!$acc loop", ("!$acc loop",), True), ("!$omp parallel", ("!$omp parallel",), True), ("!$ompx parallel", ("!$ompx parallel",), True), ("c$omp parallel", ("c$omp parallel",), False), @@ -500,6 +501,7 @@ def test_directive_stmts(): ("c$omp parallel do", "c$omp+shared(a,b,c)"), False, ), + ("!!omp parallel", (), True), ], ) def test_all_directive_formats(directive, expected, free): @@ -564,6 +566,7 @@ def test_all_directive_formats(directive, expected, free): ("c$omp parallel do", "c$omp+shared(a,b,c)"), False, ), + ("!!omp parallel", ("!!omp parallel",), True), ], ) def test_directives_as_comments(directive, expected, free): From 37db93d8d168a35dddce73bbfa29c32e81812779 Mon Sep 17 00:00:00 2001 From: Sergi Siso Date: Fri, 31 Oct 2025 17:14:08 +0000 Subject: [PATCH 6/6] #483 Update changelog and small test change --- CHANGELOG.md | 5 +++++ src/fparser/two/tests/test_comments_and_directives.py | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 372a6d87..80fbcf8e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,11 @@ Modifications by (in alphabetical order): * P. Vitt, University of Siegen, Germany * A. Voysey, UK Met Office +31/10/2025 PR #486 for #483. Recognize any comment that begins ``!$``, ``c$`` or ``*$`` followed by + a character as a Directive node. + +## Release 0.2.1 (29/09/2025) ## + 08/09/2025 PR #469 for #468. Added (optional) Directive node separated from comments. 29/08/2025 PR #477 for #476. Add Python 3.9 testing back to support upstream requirements. diff --git a/src/fparser/two/tests/test_comments_and_directives.py b/src/fparser/two/tests/test_comments_and_directives.py index 1f93c220..59017f4e 100644 --- a/src/fparser/two/tests/test_comments_and_directives.py +++ b/src/fparser/two/tests/test_comments_and_directives.py @@ -501,7 +501,7 @@ def test_directive_stmts(): ("c$omp parallel do", "c$omp+shared(a,b,c)"), False, ), - ("!!omp parallel", (), True), + ("!!$omp parallel", (), True), ], ) def test_all_directive_formats(directive, expected, free): @@ -566,7 +566,7 @@ def test_all_directive_formats(directive, expected, free): ("c$omp parallel do", "c$omp+shared(a,b,c)"), False, ), - ("!!omp parallel", ("!!omp parallel",), True), + ("!!$omp parallel", ("!!$omp parallel",), True), ], ) def test_directives_as_comments(directive, expected, free):