diff --git a/src/psyclone/line_length.py b/src/psyclone/line_length.py index 521a8a2804..d5d627795e 100644 --- a/src/psyclone/line_length.py +++ b/src/psyclone/line_length.py @@ -41,6 +41,9 @@ import re +from fparser.common.readfortran import Comment, FortranStringReader +from fparser.common.sourceinfo import FortranFormat + from psyclone.errors import InternalError @@ -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 @@ -143,18 +140,17 @@ def long_lines(self, fortran_in): return False @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 = "" @@ -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: @@ -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 diff --git a/src/psyclone/tests/line_length_test.py b/src/psyclone/tests/line_length_test.py index 2cb7965800..4847f8e019 100644 --- a/src/psyclone/tests/line_length_test.py +++ b/src/psyclone/tests/line_length_test.py @@ -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''' @@ -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 @@ -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 = (