Reference Guide  2.5.0
line_length.py
1 # -----------------------------------------------------------------------------
2 # BSD 3-Clause License
3 #
4 # Copyright (c) 2017-2024, Science and Technology Facilities Council.
5 # All rights reserved.
6 #
7 # Redistribution and use in source and binary forms, with or without
8 # modification, are permitted provided that the following conditions are met:
9 #
10 # * Redistributions of source code must retain the above copyright notice, this
11 # list of conditions and the following disclaimer.
12 #
13 # * Redistributions in binary form must reproduce the above copyright notice,
14 # this list of conditions and the following disclaimer in the documentation
15 # and/or other materials provided with the distribution.
16 #
17 # * Neither the name of the copyright holder nor the names of its
18 # contributors may be used to endorse or promote products derived from
19 # this software without specific prior written permission.
20 #
21 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24 # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
25 # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
27 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
29 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
31 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32 # POSSIBILITY OF SUCH DAMAGE.
33 # -----------------------------------------------------------------------------
34 # Author R. Ford STFC Daresbury Lab
35 # Modified by A. B. G. Chalk, STFC Daresbury Lab
36 # -----------------------------------------------------------------------------
37 
38 ''' Provides support for breaking long fortran lines into smaller ones
39 to allow the code to conform to the maximum line length limits (132
40 for f90 free format is the default)'''
41 
42 import re
43 
44 from psyclone.errors import InternalError
45 
46 
47 def find_break_point(line, max_index, key_list):
48  ''' Finds the most appropriate line break point for the Fortran code in
49  line.
50 
51  :param str line: the Fortran code string to find the line break point for.
52  :param int max_index: the maximum index in line to search for the line
53  break point.
54  :param key_list: list of potential symbols to break the line at. The
55  members of the list early in the ordering have priority
56  for breaking the line, i.e. if the list contains multiple
57  elements, any possible position of the first element will
58  be found before trying any other element of the list.
59  :type key_list: List[str]
60 
61  :returns: index to break the line into multiple lines.
62  :rtype: int
63 
64  :raises InternalError: if no suitable break point is found in line.
65  '''
66  # We should never break the line before the first element on the
67  # line.
68  first_non_whitespace = len(line) - len(line.lstrip())
69  for key in key_list:
70  idx = line.rfind(key, first_non_whitespace+1, max_index)
71  if idx > 0:
72  return idx+len(key)
73  raise InternalError(
74  f"Error in find_break_point. No suitable break point found"
75  f" for line '{line[:max_index]}' and keys '{str(key_list)}'")
76 
77 
79 
80  ''' This class take a free format fortran code as a string and
81  line wraps any lines that are larger than the specified line
82  length'''
83 
84  # pylint: disable=too-many-instance-attributes
85  def __init__(self, line_length=132):
86  self._line_length_line_length = line_length
87  self._cont_start_cont_start = {"statement": "&",
88  "openmp_directive": "!$omp& ",
89  "openacc_directive": "!$acc& ",
90  "comment": "!& ",
91  "unknown": "&"}
92  self._cont_end_cont_end = {"statement": "&",
93  "openmp_directive": " &",
94  "openacc_directive": " &",
95  "comment": "",
96  "unknown": "&"}
97  self._key_lists_key_lists = {"statement": [", ", ",", " "],
98  "openmp_directive": [" ", ",", ")", "="],
99  "openacc_directive": [" ", ",", ")", "="],
100  "comment": [" ", ".", ","],
101  "unknown": [" ", ",", "=", "+", ")"]}
102  self._stat_stat = re.compile(r'^\s*(INTEGER|REAL|TYPE|CALL|SUBROUTINE|USE)',
103  flags=re.I)
104  self._omp_omp = re.compile(r'^\s*!\$OMP', flags=re.I)
105  self._acc_acc = re.compile(r'^\s*!\$ACC', flags=re.I)
106  self._comment_comment = re.compile(r'^\s*!')
107 
108  def long_lines(self, fortran_in):
109  '''returns true if at least one of the lines in the input code is
110  longer than the allowed length. Otherwise returns false '''
111  for line in fortran_in.split('\n'):
112  if len(line) > self._line_length_line_length:
113  return True
114  return False
115 
116  @property
117  def length(self):
118  ''' returns the maximum allowed line length'''
119  return self._line_length_line_length
120 
121  def process(self, fortran_in):
122  ''' Processes unlimited line-length Fortran code into Fortran
123  code with long lines wrapped appropriately.
124 
125  :param str fortran_in: Fortran code to be line wrapped.
126 
127  :returns: line wrapped Fortran code.
128  :rtype: str
129 
130  '''
131  fortran_out = ""
132  for line in fortran_in.split('\n'):
133  if len(line) > self._line_length_line_length:
134  line_type = self._get_line_type_get_line_type(line)
135 
136  c_start = self._cont_start_cont_start[line_type]
137  c_end = self._cont_end_cont_end[line_type]
138  key_list = self._key_lists_key_lists[line_type]
139 
140  try:
141  break_point = find_break_point(
142  line, self._line_length_line_length-len(c_end), key_list)
143  except InternalError:
144  # Couldn't find a valid point to break the line.
145  # Remove indentation and try again.
146  line = line.lstrip()
147  if len(line) < self._line_length_line_length:
148  fortran_out += line + "\n"
149  continue
150  break_point = find_break_point(
151  line, self._line_length_line_length-len(c_end), key_list)
152 
153  fortran_out += line[:break_point] + c_end + "\n"
154  line = line[break_point:]
155  while len(line) + len(c_start) > self._line_length_line_length:
156  break_point = find_break_point(
157  line, self._line_length_line_length-len(c_end)-len(c_start),
158  key_list)
159  fortran_out += c_start + line[:break_point] + c_end + "\n"
160  line = line[break_point:]
161  if line:
162  fortran_out += c_start + line + "\n"
163  else:
164  fortran_out += line + "\n"
165 
166  # We add an extra newline so remove it when we return
167  return fortran_out[:-1]
168 
169  def _get_line_type(self, line):
170  ''' Classes lines into diffrent types. This is required as
171  directives need different continuation characters to fortran
172  statements. It also enables us to know a little about the
173  structure of the line which could be useful at some point.'''
174 
175  if self._stat_stat.match(line):
176  return "statement"
177  if self._omp_omp.match(line):
178  return "openmp_directive"
179  if self._acc_acc.match(line):
180  return "openacc_directive"
181  if self._comment_comment.match(line):
182  return "comment"
183  return "unknown"
def process(self, fortran_in)
Definition: line_length.py:121
def long_lines(self, fortran_in)
Definition: line_length.py:108