Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
52 changes: 36 additions & 16 deletions src/psyclone/line_length.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@

import re

from fparser.common.readfortran import Comment, FortranStringReader
from fparser.common.sourceinfo import FortranFormat

from psyclone.errors import InternalError


Expand Down Expand Up @@ -99,15 +102,9 @@ class FortLineLength():
split within a string.

One known situation that could cause an instance of the
:class:`line_length.FortLineLength` class to fail is when an inline
comment is used at the end of a line to make it longer than the 132
character limit. Whilst PSyclone does not generate such code for the
PSy-layer, this might occur in Algorithm-layer code, even if the
Algorithm-layer code conforms to the 132 line length limit. The reason
for this is that PSyclone's internal parser concatenates lines
together, thus a long line correctly split with continuation characters
in the Algorithm-layer becomes a line that needs to be split by an
instance of the :class:`line_length.FortLineLength` class.
:class:`line_length.FortLineLength` class to fail is when an *inline*
comment at the end of a line containing a *directive* takes it over
the 132-character limit. (TODO fparser/#468)

'''
# pylint: disable=too-many-instance-attributes
Expand Down Expand Up @@ -143,18 +140,17 @@ def long_lines(self, fortran_in):
return False
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should long_lines also be updated? I'm not sure how hard it would be to update this to only return True for non comments/inline comments?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At the very least the docstring and type hints should be updated.


@property
def length(self):
''' returns the maximum allowed line length'''
def length(self) -> int:
''':returns: the maximum allowed line length.'''
return self._line_length

def process(self, fortran_in):
def process(self, fortran_in: str) -> str:
''' Processes unlimited line-length Fortran code into Fortran
code with long lines wrapped appropriately.

:param str fortran_in: Fortran code to be line wrapped.
:param fortran_in: Fortran code to be line wrapped.

:returns: line wrapped Fortran code.
:rtype: str
:returns: line-wrapped Fortran code.

'''
fortran_out = ""
Expand All @@ -179,6 +175,30 @@ def process(self, fortran_in):
break_point = find_break_point(
line, self._line_length-len(c_end), key_list)

if line_type != "comment":
# Check whether the proposed break point falls within an
# in-line comment.
line_no_indent = line.lstrip()
indent_size = len(line) - len(line_no_indent)
# FortranStringReader will return separate Line and Comment
# objects for a source line containing an in-line comment.
freader = FortranStringReader(line, ignore_comments=False,
process_directives=True)
# Use free format.
freader.set_format(FortranFormat(True, True))
fline = freader.next()
# This won't work for a directive with an in-line comment
# as FortranStringReader returns a single Comment object
# for the whole thing (TODO fparser/#468).
if ((break_point - indent_size) > len(fline.line) and
isinstance(freader.next(), Comment)):
# Breakpoint is inside a comment so change the chars
# used for the line-continuation end and start.
line_type = "comment"
c_start = self._cont_start[line_type]
c_end = self._cont_end[line_type]
key_list = self._key_lists[line_type]

fortran_out += line[:break_point] + c_end + "\n"
line = line[break_point:]
while len(line) + len(c_start) > self._line_length:
Expand All @@ -195,7 +215,7 @@ def process(self, fortran_in):
# We add an extra newline so remove it when we return
return fortran_out[:-1]

def _get_line_type(self, line):
def _get_line_type(self, line) -> str:
''' Classes lines into different types. This is required as
directives need different continuation characters to fortran
statements. It also enables us to know a little about the
Expand Down
27 changes: 23 additions & 4 deletions src/psyclone/tests/line_length_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,29 @@ def test_multiple_lines_comment():
assert output_file == expected_output


def test_inline_comment():
'''Test that an in-line comment that takes us over the length limit is
wrapped correctly.'''
input_code = (" wfx_err_sub(ji,jj) = wfx_err_sub(ji,jj) - "
"pevap_rema(ji,jj) * a_i(ji,jj,jl_cat) * r1_Dt_ice ! "
"<=0 (net evap for the ocean in kg.m-2.s-1)")
fll = FortLineLength(line_length=132)
output = fll.process(input_code)
assert "for the \n!& ocean in kg.m-2.s-1)" in output
input_code = (" wfx_err_sub(ji,jj) = wfx_err_sub(ji,jj) - "
"pevap_rema(ji,jj) * a_i(ji,jj,jl_cat) * r1_Dt_ice ! "
"<=0 (net evap for the ocean! in kg.m-2.s-1)")
output = fll.process(input_code)
assert "for the \n!& ocean! in kg.m-2.s-1)" in output
# Test when the comment is on a directive.
input_code = (f"!$acc kernels ! A very {' '.join(30*['long'])} comment")
output = fll.process(input_code)
if "long \n!$ long long" not in output:
pytest.xfail(
reason="TODO fparser/#468 - fparser.common.FortranReader "
"represents directives as Comments.")


def test_exception_line_too_long():
''' Test that output lines are not longer than the maximum
specified'''
Expand Down Expand Up @@ -245,8 +268,6 @@ def test_break_types_multi_line():

fll = FortLineLength(line_length=24)
output_file = fll.process(input_file)
print("("+output_file+")")
print(expected_output)
assert output_file == expected_output


Expand All @@ -266,8 +287,6 @@ def test_edge_conditions_statements():
"INTEGER &\n&INTEGER\n")
fll = FortLineLength(line_length=len("INTEGER INTEGE"))
output_string = fll.process(input_string)
print(output_string)
print(expected_output)
assert output_string == expected_output

input_string = (
Expand Down
Loading