Repository: stfc/fparser

Issue:
Parsing of comment-only files
Hi,

fparser2 is currently unable to parse comment-only files. I appreciate that it's debatable whether it should, however, I have one admittedly very specific use-case where I may end up splitting source-files into pieces before parsing them individually (each of those pieces valid and complete fortran program units -- or only comments). It's easy enough to work around this, of course, but I was wondering if you consider this to be something that should be working in general?

```
from pathlib import Path
fcode = """
! Some file
! that has only comments
! for whatever reason
""".strip()
Path('/tmp/myfile.F90').write_text(fcode)
```

```
from fparser.two.parser import ParserFactory
from fparser.common.readfortran import FortranFileReader
f2008_parser = ParserFactory().create(std="f2008")

reader = FortranFileReader("/tmp/myfile.F90", ignore_comments=False)
parse_tree = f2008_parser(reader)
print(parse_tree)
```

```
---------------------------------------------------------------------------
NoMatchError                              Traceback (most recent call last)
File ~/loki/nabr-lightweight-sourcefiles/loki_env/lib/python3.8/site-packages/fparser/two/Fortran2003.py:237, in Program.__new__(cls, string)
    236 try:
--> 237     return Base.__new__(cls, string)
    238 except NoMatchError:
    239     # At the moment there is no useful information provided by
    240     # NoMatchError so we pass on an empty string.

File ~/loki/nabr-lightweight-sourcefiles/loki_env/lib/python3.8/site-packages/fparser/two/utils.py:428, in Base.__new__(cls, string, parent_cls)
    427     errmsg = f"{cls.__name__}: '{string}'"
--> 428 raise NoMatchError(errmsg)

NoMatchError: at line 3
>>>! for whatever reason


During handling of the above exception, another exception occurred:

FortranSyntaxError                        Traceback (most recent call last)
Untitled-1.ipynb Cell 3 in <cell line: 2>()
      <a href='vscode-notebook-cell:Untitled-1.ipynb?jupyter-notebook#W1sdW50aXRsZWQ%3D?line=0'>1</a> reader = FortranFileReader("/tmp/myfile.F90", ignore_comments=False)
----> <a href='vscode-notebook-cell:Untitled-1.ipynb?jupyter-notebook#W1sdW50aXRsZWQ%3D?line=1'>2</a> parse_tree = f2008_parser(reader)
      <a href='vscode-notebook-cell:Untitled-1.ipynb?jupyter-notebook#W1sdW50aXRsZWQ%3D?line=2'>3</a> print(parse_tree)

File ~/loki/nabr-lightweight-sourcefiles/loki_env/lib/python3.8/site-packages/fparser/two/Fortran2003.py:241, in Program.__new__(cls, string)
    237     return Base.__new__(cls, string)
    238 except NoMatchError:
    239     # At the moment there is no useful information provided by
    240     # NoMatchError so we pass on an empty string.
--> 241     raise FortranSyntaxError(string, "")
    242 except InternalSyntaxError as excinfo:
    243     # InternalSyntaxError is used when a syntax error has been
    244     # found in a rule that does not have access to the reader
    245     # object. This is then re-raised here as a
    246     # FortranSyntaxError, adding the reader object (which
    247     # provides line number information).
    248     raise FortranSyntaxError(string, excinfo)

FortranSyntaxError: at line 3
>>>! for whatever reason
```

Just to demonstrate that this parses fine once I add some Fortran afterwards:

```
fcode += """
subroutine main
end
""".rstrip()
Path('/tmp/myotherfile.F90').write_text(fcode)
```
```
reader = FortranFileReader("/tmp/myotherfile.F90", ignore_comments=False)
parse_tree = f2008_parser(reader)
print(parse_tree)

! Some file
! that has only comments
! for whatever reason
SUBROUTINE main
END
```

FWIW: Out of curiosity I had a look at what compilers do with this: gfortran, ifort, ifx parse this without problems, nvfortran is the only one at least printing a warning stating that this file is empty. All generate `.o` files, though.

Discussion:

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 558dbde..410780d 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
 
+15/09/2022 PR #378 for #375. Permit source files containing only comments
+           to be parsed.
+
 12/09/2022 PR #374 for #373. Removes @staticmethod decorator added to
            Stmt_Function_Stmt.tostr() in error.
 
diff --git a/src/fparser/two/Fortran2003.py b/src/fparser/two/Fortran2003.py
index 8b5b726..d63f7ce 100644
--- a/src/fparser/two/Fortran2003.py
+++ b/src/fparser/two/Fortran2003.py
@@ -290,6 +290,7 @@ class Program(BlockBase):  # R201
         """
         content = []
         add_comments_includes_directives(content, reader)
+        comments = content != []
         try:
             while True:
                 obj = Program_Unit(reader)
@@ -304,7 +305,12 @@ class Program(BlockBase):  # R201
             # (via Main_Program0) with a program containing no program
             # statement as this is optional in Fortran.
             #
-            return BlockBase.match(Main_Program0, [], None, reader)
+            result = BlockBase.match(Main_Program0, [], None, reader)
+            if not result and comments:
+                # This program only contains comments.
+                return (content,)
+            else:
+                return result
         except StopIteration:
             # Reader has no more lines.
             pass


Test patch:
diff --git a/src/fparser/two/tests/fortran2003/test_program_r201.py b/src/fparser/two/tests/fortran2003/test_program_r201.py
index cb33f6e..aa0f18b 100644
--- a/src/fparser/two/tests/fortran2003/test_program_r201.py
+++ b/src/fparser/two/tests/fortran2003/test_program_r201.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2018-2019 Science and Technology Facilities Council
+# Copyright (c) 2018-2022 Science and Technology Facilities Council
 
 # All rights reserved.
 
@@ -58,6 +58,17 @@ def test_empty_input(f2003_create):
         assert str(ast) == ""
 
 
+def test_only_comments(f2003_create):
+    """Test that a file containing only comments can be parsed
+    successfully
+
+    """
+    code = "! comment1\n! comment2"
+    reader = get_reader(code, ignore_comments=False)
+    ast = Program(reader)
+    assert code in str(ast)
+
+
 # Test single program units