diff --git a/CHANGELOG.md b/CHANGELOG.md index 80fbcf8e..1935d3f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,8 @@ Modifications by (in alphabetical order): * P. Vitt, University of Siegen, Germany * A. Voysey, UK Met Office +25/11/2025 PR #488 for #483. Do not recognize inline comments as Directives. + 31/10/2025 PR #486 for #483. Recognize any comment that begins ``!$``, ``c$`` or ``*$`` followed by a character as a Directive node. diff --git a/src/fparser/common/readfortran.py b/src/fparser/common/readfortran.py index 183248ca..c149d203 100644 --- a/src/fparser/common/readfortran.py +++ b/src/fparser/common/readfortran.py @@ -437,10 +437,10 @@ class Comment: :param reader: The reader object being used to read the input \ source. :type reader: :py:class:`fparser.common.readfortran.FortranReaderBase` - + :param inline: whether this was an inline comment. """ - def __init__(self, comment, linenospan, reader): + def __init__(self, comment, linenospan, reader, inline: bool = False): self.comment = comment self.span = linenospan self.reader = reader @@ -449,6 +449,7 @@ def __init__(self, comment, linenospan, reader): # tests as a reader can return an instance of either class and # we might want to check the contents in a consistent way. self.line = comment + self.inline = inline def __repr__(self): return self.__class__.__name__ + "(%r,%s)" % (self.comment, self.span) @@ -1010,9 +1011,15 @@ def multiline_item( prefix, lines, suffix, (startlineno, endlineno), self, errmessage ) - def comment_item(self, comment, startlineno, endlineno): - """Construct Comment item.""" - return Comment(comment, (startlineno, endlineno), self) + def comment_item( + self, comment, startlineno, endlineno, inline_comment: bool = False + ): + """Construct Comment item. + + :param inline_comment: whether the comment is an inline comment. + Defaults to False. + """ + return Comment(comment, (startlineno, endlineno), self, inline_comment) def cpp_directive_item(self, line, startlineno, endlineno): """ @@ -1245,7 +1252,14 @@ def handle_inline_comment( newline = line[:idx] if '"' not in newline and "'" not in newline: if self.format.is_f77 or not line[idx:].startswith("!f2py"): - put_item(self.comment_item(line[idx:], lineno, lineno)) + # Its an inline comment if there is a non whitespace + # character before the comment. + is_inline = not (line.lstrip() == line[idx:]) + put_item( + self.comment_item( + line[idx:], lineno, lineno, inline_comment=is_inline + ) + ) return newline, quotechar, True # We must allow for quotes... diff --git a/src/fparser/two/Fortran2003.py b/src/fparser/two/Fortran2003.py index fa7f342a..babf04cb 100644 --- a/src/fparser/two/Fortran2003.py +++ b/src/fparser/two/Fortran2003.py @@ -159,6 +159,9 @@ def __new__(cls, string: Union[str, FortranReaderBase], parent_cls=None): from fparser.common import readfortran if isinstance(string, readfortran.Comment): + # Inline comments cannot be directives. + if string.inline: + return # Directives must start with one of the specified directive # prefixes. lower = string.comment.lower() diff --git a/src/fparser/two/tests/fortran2003/test_usestmt_r1109.py b/src/fparser/two/tests/fortran2003/test_usestmt_r1109.py index 259972cb..aa9f9e67 100644 --- a/src/fparser/two/tests/fortran2003/test_usestmt_r1109.py +++ b/src/fparser/two/tests/fortran2003/test_usestmt_r1109.py @@ -45,8 +45,7 @@ @pytest.fixture(autouse=True) -@pytest.mark.usefixtures("f2003_create") -def use_f2003(): +def use_f2003(f2003_create): """ A pytest fixture that just sets things up so that the f2003_create fixture is used automatically for every test in this file. diff --git a/src/fparser/two/tests/test_comments_and_directives.py b/src/fparser/two/tests/test_comments_and_directives.py index 59017f4e..4293bf62 100644 --- a/src/fparser/two/tests/test_comments_and_directives.py +++ b/src/fparser/two/tests/test_comments_and_directives.py @@ -431,13 +431,12 @@ def test_directive_stmts(): ) program = Program(reader) out = walk(program, Directive) - assert len(out) == 4 - assert out[0].items[0] == "!$dir inline" - assert out[1].items[0] == "!dir$ compiler directive" - assert out[2].items[0] == "!$omp target" - assert out[3].items[0] == "!$omp loop" + assert len(out) == 3 + assert out[0].items[0] == "!dir$ compiler directive" + assert out[1].items[0] == "!$omp target" + assert out[2].items[0] == "!$omp loop" - assert out[3].tostr() == "!$omp loop" + assert out[2].tostr() == "!$omp loop" # Check the restore_reader works correctly for directive. old = reader.get_item() @@ -451,9 +450,10 @@ def test_directive_stmts(): for comment in out: if comment.items[0] != "": comments = comments + 1 - assert comments == 2 - assert str(out[2]) == "! A comment!" - assert str(out[3]) == "!!$ Another comment" + assert comments == 3 + assert str(out[1]) == "!$dir inline" + assert str(out[3]) == "! A comment!" + assert str(out[4]) == "!!$ Another comment" # Check that passing something that isn't a comment into a Directive # __new__ call doesn't create a Directive. @@ -601,3 +601,18 @@ def test_directives_as_comments(directive, expected, free): # Check that the comments contain the correct strings. for i, direc in enumerate(out): assert direc.items[0] == expected[i] + + +def test_inline_directive_is_comment(): + """Inline comments on statements should be comments even if containing + directive markers.""" + source = """Program my_prog + integer :: x !$dir directive + + x = 1 !$dir comment + end program + """ + reader = get_reader(source, ignore_comments=False, process_directives=True) + program = Program(reader) + out = walk(program, Directive) + assert len(out) == 0