fparser Reference Guide  0.0.14
sourceinfo.py
1 # Modified work Copyright (c) 2017-2022 Science and Technology
2 # Facilities Council
3 # Original work Copyright (c) 1999-2008 Pearu Peterson
4 
5 # All rights reserved.
6 
7 # Modifications made as part of the fparser project are distributed
8 # under the following license:
9 
10 # Redistribution and use in source and binary forms, with or without
11 # modification, are permitted provided that the following conditions are
12 # met:
13 
14 # 1. Redistributions of source code must retain the above copyright
15 # notice, this list of conditions and the following disclaimer.
16 
17 # 2. Redistributions in binary form must reproduce the above copyright
18 # notice, this list of conditions and the following disclaimer in the
19 # documentation and/or other materials provided with the distribution.
20 
21 # 3. Neither the name of the copyright holder nor the names of its
22 # contributors may be used to endorse or promote products derived from
23 # this software without specific prior written permission.
24 
25 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
26 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
27 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
28 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
29 # HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
30 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
31 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
32 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
33 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
34 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
35 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
36 
37 # --------------------------------------------------------------------
38 
39 # The original software (in the f2py project) was distributed under
40 # the following license:
41 
42 # Redistribution and use in source and binary forms, with or without
43 # modification, are permitted provided that the following conditions are met:
44 
45 # a. Redistributions of source code must retain the above copyright notice,
46 # this list of conditions and the following disclaimer.
47 # b. Redistributions in binary form must reproduce the above copyright
48 # notice, this list of conditions and the following disclaimer in the
49 # documentation and/or other materials provided with the distribution.
50 # c. Neither the name of the F2PY project nor the names of its
51 # contributors may be used to endorse or promote products derived from
52 # this software without specific prior written permission.
53 
54 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
55 # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
56 # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
57 # ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
58 # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
59 # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
60 # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
61 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
62 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
63 # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
64 # DAMAGE.
65 
66 """
67 Provides functions to determine whether a piece of Fortran source is free or
68 fixed format. It also tries to differentiate between strict and "pyf" although
69 I'm not sure what that is.
70 
71 """
72 import os
73 import re
74 
75 
76 
77 
78 
80  """
81  Describes the nature of a piece of Fortran source.
82 
83  Source can be fixed or free format. It can also be "strict" or
84  "not strict" although it's not entirely clear what that means. It may
85  refer to the strictness of adherance to fixed format although what that
86  means in the context of free format I don't know.
87 
88  :param bool is_free: True for free format, False for fixed.
89  :param bool is_strict: some amount of strictness.
90  :param bool enable_f2py: whether f2py directives are enabled or treated \
91  as comments (the default).
92  """
93 
94  def __init__(self, is_free, is_strict, enable_f2py=False):
95  if is_free is None:
96  raise Exception("FortranFormat does not accept a None is_free")
97  if is_strict is None:
98  raise Exception("FortranFormat does not accept a None is_strict")
99 
100  self._is_free = is_free
101  self._is_strict = is_strict
102  self._f2py_enabled = enable_f2py
103 
104  @classmethod
105  def from_mode(cls, mode):
106  """
107  Constructs a FortranFormat object from a mode string.
108 
109  Arguments:
110  mode - (String) One of 'free', 'fix', 'f77' or 'pyf'
111  """
112  if mode == "free":
113  is_free, is_strict = True, False
114  elif mode == "fix":
115  is_free, is_strict = False, False
116  elif mode == "f77":
117  is_free, is_strict = False, True
118  elif mode == "pyf":
119  is_free, is_strict = True, True
120  else:
121  raise NotImplementedError(repr(mode))
122  return cls(is_free, is_strict)
123 
124  def __eq__(self, other):
125  if isinstance(other, FortranFormat):
126  return (
127  self.is_free == other.is_free
128  and self.is_strict == other.is_strict
129  and self.f2py_enabled == other.f2py_enabled
130  )
131  raise NotImplementedError
132 
133  def __str__(self):
134  if self.is_strict:
135  string = "Strict"
136  else:
137  string = "Non-strict"
138 
139  if self.is_free:
140  string += " free"
141  else:
142  string += " fixed"
143 
144  return string + " format"
145 
146  @property
147  def is_free(self):
148  """
149  Returns true for free format.
150  """
151  return self._is_free
152 
153  @property
154  def is_fixed(self):
155  """
156  Returns true for fixed format.
157  """
158  return not self._is_free
159 
160  @property
161  def is_strict(self):
162  """
163  Returns true for strict format.
164  """
165  return self._is_strict
166 
167  @property
168  def is_f77(self):
169  """
170  Returns true for strict fixed format.
171  """
172  return not self._is_free and self._is_strict
173 
174  @property
175  def is_fix(self):
176  """
177  Returns true for slack fixed format.
178  """
179  return not self._is_free and not self._is_strict
180 
181  @property
182  def is_pyf(self):
183  """
184  Returns true for strict free format.
185  """
186  return self._is_free and self._is_strict
187 
188  @property
189  def f2py_enabled(self):
190  """
191  :returns: whether or not f2py directives are enabled.
192  :rtype: bool
193  """
194  return self._f2py_enabled
195 
196  @property
197  def mode(self):
198  """
199  Returns a string representing this format.
200  """
201  if self._is_free and self._is_strict:
202  mode = "pyf"
203  elif self._is_free:
204  mode = "free"
205  elif self.is_fix:
206  mode = "fix"
207  elif self.is_f77:
208  mode = "f77"
209  # While mode is determined by is_free and is_strict all permutations
210  # are covered. There is no need for a final "else" clause as the
211  # object cannot get wedged in an invalid mode.
212  return mode
213 
214 
215 
216 
217 _HAS_F_EXTENSION = re.compile(r".*[.](for|ftn|f77|f)\Z", re.I).match
218 
219 _HAS_F_HEADER = re.compile(r"-[*]-\s*(fortran|f77)\s*-[*]-", re.I).search
220 _HAS_F90_HEADER = re.compile(r"-[*]-\s*f90\s*-[*]-", re.I).search
221 _HAS_F03_HEADER = re.compile(r"-[*]-\s*f03\s*-[*]-", re.I).search
222 _HAS_F08_HEADER = re.compile(r"-[*]-\s*f08\s*-[*]-", re.I).search
223 _HAS_FREE_HEADER = re.compile(r"-[*]-\s*(f90|f95|f03|f08)\s*-[*]-", re.I).search
224 _HAS_FIX_HEADER = re.compile(r"-[*]-\s*fix\s*-[*]-", re.I).search
225 _HAS_PYF_HEADER = re.compile(r"-[*]-\s*pyf\s*-[*]-", re.I).search
226 
227 _FREE_FORMAT_START = re.compile(r"[^c*!]\s*[^\s\d\t]", re.I).match
228 
229 
230 def get_source_info_str(source):
231  """
232  Determines the format of Fortran source held in a string.
233 
234  Returns a FortranFormat object.
235  """
236  lines = source.splitlines()
237  if not lines:
238  return FortranFormat(False, False)
239 
240  firstline = lines[0].lstrip()
241  if _HAS_F_HEADER(firstline):
242  return FortranFormat(False, True)
243  if _HAS_FIX_HEADER(firstline):
244  return FortranFormat(False, False)
245  if _HAS_FREE_HEADER(firstline):
246  return FortranFormat(True, False)
247  if _HAS_PYF_HEADER(firstline):
248  return FortranFormat(True, True)
249 
250  line_tally = 10000 # Check up to this number of non-comment lines
251  is_free = False
252  while line_tally > 0 and lines:
253  line = lines.pop(0).rstrip()
254  if line and line[0] != "!":
255  line_tally -= 1
256  if line[0] != "\t" and _FREE_FORMAT_START(line[:5]) or line[-1:] == "&":
257  is_free = True
258  break
259 
260  return FortranFormat(is_free, False)
261 
262 
263 
264 
265 
266 def get_source_info(file_candidate):
267  """
268  Determines the format of Fortran source held in a file.
269 
270  :param file_candidate: a filename or a file object
271  :type file_candidate: str or _io.TextIOWrapper (py3)
272 
273  :returns: the Fortran format encoded as a string.
274  :rtype: str
275 
276  """
277  if hasattr(file_candidate, "name") and hasattr(file_candidate, "read"):
278  filename = file_candidate.name
279 
280  # Under Python 3 file.name holds an integer file handle when
281  # associated with a file without a name.
282  if isinstance(filename, int):
283  filename = None
284 
285  elif isinstance(file_candidate, str):
286  filename = file_candidate
287  else:
288  message = "Argument must be a filename or file-like object."
289  raise ValueError(message)
290 
291  if filename:
292  _, ext = os.path.splitext(filename)
293  if ext == ".pyf":
294  return FortranFormat(True, True)
295 
296  if hasattr(file_candidate, "read"):
297  # If the candidate object has a "read" method we assume it's a file
298  # object.
299  #
300  # If it is a file object then it may be in the process of being read.
301  # As such we need to take a note of the current state of the file
302  # pointer so we can restore it when we've finished what we're doing.
303  #
304  pointer = file_candidate.tell()
305  file_candidate.seek(0)
306  source_info = get_source_info_str(file_candidate.read())
307  file_candidate.seek(pointer)
308  return source_info
309 
310  # It isn't a file and it passed the type check above so it must be
311  # a string.
312  #
313  # If it's a string we assume it is a filename. In which case we need
314  # to open the named file so we can read it.
315  #
316  # It is closed on completion so as to return it to the state it was
317  # found in.
318  #
319  # The 'fparser-logging' handler is setup in fparser/__init__.py and
320  # ensures any occurrences of invalid characters are skipped and
321  # logged.
322  with open(
323  file_candidate, "r", encoding="utf-8", errors="fparser-logging" ) as file_object:
324  string = get_source_info_str(file_object.read())
325  return string
326 
327 
328