Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
8c39ec0
Added Directive node separated from comments
LonelyCat124 Jun 26, 2025
80a4d18
Merge branch 'master' into 468_directives
LonelyCat124 Jun 26, 2025
6fb2a20
Formatting
LonelyCat124 Jun 26, 2025
4b6bf3d
Merge branch '468_directives' of github.com:stfc/fparser into 468_dir…
LonelyCat124 Jun 26, 2025
a953ed3
Fix coverage and update docs
LonelyCat124 Jun 26, 2025
22d27a2
Changes for review
LonelyCat124 Jun 30, 2025
1e8c84b
changes for review
LonelyCat124 Jun 30, 2025
826f493
Formatting
LonelyCat124 Jun 30, 2025
af58dde
Applied black to Fortran2003.py
LonelyCat124 Jun 30, 2025
b89e33b
Merge branch 'master' into 468_directives
arporter Jul 29, 2025
06280aa
Changes for review
LonelyCat124 Aug 6, 2025
0bd5e99
Merge branch '468_directives' of github.com:stfc/fparser into 468_dir…
LonelyCat124 Aug 6, 2025
cb37dec
Merge branch 'master' into 468_directives
LonelyCat124 Aug 6, 2025
c1ea3fb
formatting
LonelyCat124 Aug 6, 2025
a77f658
Merge branch '468_directives' of github.com:stfc/fparser into 468_dir…
LonelyCat124 Aug 6, 2025
ab39d9b
Changes for review
LonelyCat124 Aug 29, 2025
cd692dc
Formatting fixes
LonelyCat124 Aug 29, 2025
5b13ff4
Applied black to F2003
LonelyCat124 Aug 29, 2025
927693e
Merge branch 'master' into 468_directives
LonelyCat124 Aug 29, 2025
445ce3e
Changes for review
LonelyCat124 Sep 4, 2025
5cca730
Fix issues with always processing directives
LonelyCat124 Sep 4, 2025
39adf57
Example of usage
LonelyCat124 Sep 4, 2025
9822fd7
Changes for review
LonelyCat124 Sep 5, 2025
12195ad
Missing comment
LonelyCat124 Sep 5, 2025
0be460f
changes for review
LonelyCat124 Sep 8, 2025
ef9ae0f
#468 fix Black formatting
arporter Sep 8, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions doc/source/fparser2.rst
Original file line number Diff line number Diff line change
Expand Up @@ -500,14 +500,18 @@ Compiler/OpenMP Directive support
Most Fortran compilers support directives to enable compiler-specific
functionality. Fparser has an option to support converting these into
``Directive`` nodes where possible. This option is ``process_directives``,
and by default it is set to ``False``. For directives to be processed, the
``ignore_comments`` must be also be ``False``, otherwise Fparser will ignore
this option.
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).

For example::

reader = FortranFileReader("compute_mod.f90", process_directives=True)


Preprocessing Directives
------------------------

Expand Down
49 changes: 28 additions & 21 deletions src/fparser/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,29 +86,34 @@ def get_reader(
include_dirs=None,
source_only=None,
ignore_comments=True,
process_directives: bool = False,
):
"""
Returns Fortran reader instance.

If ``source`` is a C filename then the functions searches for comment
lines starting with ``/*f2py`` and reads following lines as PYF file
content until a line ``*/`` is found.

:param str source: Specify a string or filename containing Fortran code.
:param bool isfree: True if Fortran is free format
:param bool isstrict: True if we are to strictly enforce free/fixed format
:param list include_dirs: Specify a list of include directories. The
default list (when include_dirs=None) contains
the current working directory and the directory
of ``source``.
:param list source_only: Specify a list of Fortran file names that are
searched when the ``USE`` statement is
encountered.
:param bool ignore_comments: Whether or not to ignore (and discard)
comments when parsing the source.

:returns: a reader instance
:rtype: :py:class:`fparser.common.readfortran.FortranReader`
Returns Fortran reader instance.

If ``source`` is a C filename then the functions searches for comment
lines starting with ``/*f2py`` and reads following lines as PYF file
content until a line ``*/`` is found.

:param str source: Specify a string or filename containing Fortran code.
:param bool isfree: True if Fortran is free format
:param bool isstrict: True if we are to strictly enforce free/fixed format
:param list include_dirs: Specify a list of include directories. The
default list (when include_dirs=None) contains
the current working directory and the directory
of ``source``.
:param list source_only: Specify a list of Fortran file names that are
searched when the ``USE`` statement is
encountered.
:param bool ignore_comments: Whether or not to ignore (and discard)
comments when parsing the source.
:param process_directives: whether or not to process directives as
specialised Directive nodes. Default is False (in which case
directives are left as comments). This option overrides the
ignore_comments input.

:returns: a reader instance
:rtype: :py:class:`fparser.common.readfortran.FortranReader`
"""
import os
import re
Expand Down Expand Up @@ -138,13 +143,15 @@ def get_reader(
include_dirs=include_dirs,
source_only=source_only,
ignore_comments=ignore_comments,
process_directives=process_directives,
)
elif isinstance(source, str):
reader = FortranStringReader(
source,
include_dirs=include_dirs,
source_only=source_only,
ignore_comments=ignore_comments,
process_directives=process_directives,
)
else:
raise TypeError("Expected string or filename input but got %s" % (type(input)))
Expand Down
27 changes: 20 additions & 7 deletions src/fparser/common/readfortran.py
Original file line number Diff line number Diff line change
Expand Up @@ -548,10 +548,10 @@ class FortranReaderBase:
code, free format Fortran code, or PYF signatures (with extended
free format Fortran syntax).

:param source: a file-like object with .next() method used to \
:param source: a file-like object with .next() method used to
retrive a line.
:type source: :py:class:`StringIO` or a file handle
:param mode: a FortranFormat object as returned by \
:param mode: a FortranFormat object as returned by
`sourceinfo.get_source_info()`
:type mode: :py:class:`fparser.common.sourceinfo.Format`
:param isstrict: whether we are strictly enforcing fixed format.
Expand All @@ -564,7 +564,8 @@ class FortranReaderBase:
False (in which case it is treated as a Comment).
:param process_directives: whether or not to process directives as
specialised Directive nodes. Default is False (in which case
directives are left as comments).
directives are left as comments). This option overrides the
ignore_comments input.

The Fortran source is iterated by `get_single_line`,
`get_next_line`, `put_single_line` methods.
Expand All @@ -587,10 +588,10 @@ def __init__(
# This value for ignore_comments can be overridden by using the
# ignore_comments optional argument to e.g. get_single_line()
self._ignore_comments = ignore_comments
if self._ignore_comments:
self.process_directives = False
else:
self.process_directives = process_directives
# Enabling process directives forces comments to be processed.
if process_directives:
self._ignore_comments = False
self.process_directives = process_directives

self.filo_line = [] # used for un-consuming lines.
self.fifo_item = []
Expand Down Expand Up @@ -1692,6 +1693,10 @@ class FortranFileReader(FortranReaderBase):
:param Optional[bool] include_omp_conditional_lines: whether or not the
content of a line with an OMP sentinel is parsed or not. Default is
False (in which case it is treated as a Comment).
:param process_directives: whether or not to process directives as
specialised Directive nodes. Default is False (in which case
directives are left as comments). This option overrides the
ignore_comments input.

For example::

Expand All @@ -1709,6 +1714,7 @@ def __init__(
ignore_comments=True,
ignore_encoding=True,
include_omp_conditional_lines=False,
process_directives: bool = False,
):
# The filename is used as a unique ID. This is then used to cache the
# contents of the file. Obviously if the file changes content but not
Expand Down Expand Up @@ -1741,6 +1747,7 @@ def __init__(
mode,
ignore_comments,
include_omp_conditional_lines=include_omp_conditional_lines,
process_directives=process_directives,
)

if include_dirs is None:
Expand Down Expand Up @@ -1776,6 +1783,10 @@ class FortranStringReader(FortranReaderBase):
:param Optional[bool] include_omp_conditional_lines: whether or not
the content of a line with an OMP sentinel is parsed or not. Default
is False (in which case it is treated as a Comment).
:param process_directives: whether or not to process directives as
specialised Directive nodes. Default is False (in which case
directives are left as comments). This option overrides the
ignore_comments input.

For example:

Expand All @@ -1798,6 +1809,7 @@ def __init__(
ignore_comments=True,
ignore_encoding=True,
include_omp_conditional_lines=False,
process_directives: bool = False,
):
# The Python ID of the string was used to uniquely identify it for
# caching purposes. Unfortunately this ID is only unique for the
Expand All @@ -1818,6 +1830,7 @@ def __init__(
mode,
ignore_comments,
include_omp_conditional_lines=include_omp_conditional_lines,
process_directives=process_directives,
)
if include_dirs is not None:
self.include_dirs = include_dirs[:]
Expand Down
19 changes: 19 additions & 0 deletions src/fparser/common/tests/test_readfortran.py
Original file line number Diff line number Diff line change
Expand Up @@ -1730,3 +1730,22 @@ def test_conditional_include_omp_conditional_liness_free_format_multiple():
line = reader.next()
# 8 spaces in input_text, plus two for replacing the !$
assert line.line == "bla bla"


def test_process_directives_option_read_fortran():
"""Test handling of the process_directives option."""

input_text = "!$omp target\n! comment"
reader = FortranStringReader(input_text)
# Default is no comments no directives
# Make sure to enforce free format
reader.set_format(FortranFormat(True, True))
with pytest.raises(StopIteration):
comment = reader.next()

reader = FortranStringReader(input_text, process_directives=True)
# Default is no comments no directives
# Make sure to enforce free format
reader.set_format(FortranFormat(True, True))
direc = reader.next()
assert direc.line == "!$omp target"
1 change: 1 addition & 0 deletions src/fparser/two/Fortran2003.py
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,7 @@ def match_comment_or_include(reader):

"""
obj = None
# Whether or not to specialise Directives is a run-time option.
if reader.process_directives:
obj = Directive(reader)
obj = Comment(reader) if not obj else obj
Expand Down
9 changes: 7 additions & 2 deletions src/fparser/two/tests/test_comments_and_directives.py
Original file line number Diff line number Diff line change
Expand Up @@ -426,7 +426,9 @@ def test_directive_stmts():
!!$ Another comment
end do
End Program"""
reader = get_reader(source, isfree=True, ignore_comments=False)
reader = get_reader(
source, isfree=True, ignore_comments=False, process_directives=True
)
program = Program(reader)
out = walk(program, Directive)
assert len(out) == 4
Expand Down Expand Up @@ -468,6 +470,7 @@ def test_directive_stmts():
end program foo""",
isfree=False,
ignore_comments=False,
process_directives=True,
)
program = Program(reader)
out = walk(program, Directive)
Expand Down Expand Up @@ -514,7 +517,9 @@ def test_all_directive_formats(directive, expected, free):
source = source + directive + "\n"
source = source + " end program foo"

reader = get_reader(source, isfree=free, ignore_comments=False)
reader = get_reader(
source, isfree=free, ignore_comments=False, process_directives=True
)
program = Program(reader)
out = walk(program, Directive)
assert len(out) == 1
Expand Down
6 changes: 5 additions & 1 deletion src/fparser/two/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -706,7 +706,11 @@ def match(
start_name = obj.get_start_name()

# Directives, Comments and Include statements are always valid sub-classes
classes = subclasses + [di.Directive, di.Comment, di.Include_Stmt]
comments = [di.Comment, di.Include_Stmt]
# Only add directives if enabled.
if reader.process_directives:
comments.insert(0, di.Directive)
classes = subclasses + comments
# Preprocessor directives are always valid sub-classes
cpp_classes = [
getattr(di.C99Preprocessor, cls_name)
Expand Down