fparser Reference Guide  0.0.14
readfortran.py
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 # Modified work Copyright (c) 2017-2022 Science and Technology
4 # Facilities Council.
5 # Original work Copyright (c) 1999-2008 Pearu Peterson
6 
7 # All rights reserved.
8 
9 # Modifications made as part of the fparser project are distributed
10 # under the following license:
11 
12 # Redistribution and use in source and binary forms, with or without
13 # modification, are permitted provided that the following conditions are
14 # met:
15 
16 # 1. Redistributions of source code must retain the above copyright
17 # notice, this list of conditions and the following disclaimer.
18 
19 # 2. Redistributions in binary form must reproduce the above copyright
20 # notice, this list of conditions and the following disclaimer in the
21 # documentation and/or other materials provided with the distribution.
22 
23 # 3. Neither the name of the copyright holder nor the names of its
24 # contributors may be used to endorse or promote products derived from
25 # this software without specific prior written permission.
26 
27 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
28 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
29 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
30 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
31 # HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
32 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
33 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
34 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
35 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
36 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
37 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
38 
39 # --------------------------------------------------------------------
40 
41 # The original software (in the f2py project) was distributed under
42 # the following license:
43 
44 # Redistribution and use in source and binary forms, with or without
45 # modification, are permitted provided that the following conditions are met:
46 
47 # a. Redistributions of source code must retain the above copyright notice,
48 # this list of conditions and the following disclaimer.
49 # b. Redistributions in binary form must reproduce the above copyright
50 # notice, this list of conditions and the following disclaimer in the
51 # documentation and/or other materials provided with the distribution.
52 # c. Neither the name of the F2PY project nor the names of its
53 # contributors may be used to endorse or promote products derived from
54 # this software without specific prior written permission.
55 
56 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
57 # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
58 # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
59 # ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
60 # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
61 # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
62 # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
63 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
64 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
65 # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
66 # DAMAGE.
67 #
68 # Author: Pearu Peterson <pearu@cens.ioc.ee>
69 # Created: May 2006
70 # Modified by R. W. Ford and A. R. Porter, STFC Daresbury Lab
71 # Modified by P. Elson, Met Office
72 
73 """Provides Fortran reader classes.
74 
75 Overview
76 
77 Provides FortranReader classes for reading Fortran codes from files and
78 strings. FortranReader handles comments and line continuations of both
79 fix and free format Fortran codes.
80 
81 Examples
82 
83 ::
84 
85  >> from fparser.common.readfortran import FortranFileReader
86  >>> import os
87  >>> reader = FortranFileReader(os.path.expanduser('~/src/blas/daxpy.f'))
88  >>> print reader.next()
89  line #1 'subroutine daxpy(n,da,dx,incx,dy,incy)'
90  >>> print `reader.next()`
91  Comment('c constant times a vector plus a vector.\\n
92  c uses unrolled loops for increments equal to one.\\n
93  c jack dongarra, linpack, 3/11/78.\\n
94  c modified 12/3/93, array(1) declarations changed to array(*)',(3, 6))
95  >>> print `reader.next()`
96  Line('double precision dx(*),dy(*),da',(8, 8),'')
97  >>> print `reader.next()`
98  Line('integer i,incx,incy,ix,iy,m,mp1,n',(9, 9),'')
99 
100 Note that the ``.next()`` method may return `Line`, `SyntaxErrorLine`,
101 `Comment`, `MultiLine`, or `SyntaxErrorMultiLine` instance.
102 Let us continue with the above example session to illustrate the `Line`
103 methods and attributes::
104 
105  >>> item = reader.next()
106  >>> item
107  Line('if (da .eq. 0.0d0) return',(12, 12),'')
108  >>> item.line
109  'if (da .eq. 0.0d0) return'
110  >>> item.strline
111  'if (F2PY_EXPR_TUPLE_5) return'
112  >>> item.strlinemap
113  {'F2PY_EXPR_TUPLE_5': 'da .eq. 0.0d0'}
114  >>> item.span
115  (12, 12)
116  >>> item.get_line()
117  'if (F2PY_EXPR_TUPLE_5) return'
118 
119 To read a Fortran code from a string, use `FortranStringReader` class::
120 
121  >>> from fparser.common.sourceinfo import FortranFormat
122  >>> from fparser.common.readfortran import FortranStringReader
123  >>> code = '''
124  ... subroutine foo(a)
125  ... integer a
126  ... print*,\"a=\",a
127  ... end
128  ... '''
129  >>> reader = FortranStringReader(code)
130  >>> reader.set_format(FortranFormat(False, True))
131  >>> reader.next()
132  Line('subroutine foo(a)',(2, 2),'')
133  >>> reader.next()
134  Line('integer a',(3, 3),'')
135  >>> reader.next()
136  Line('print*,\"a=\",a',(4, 4),'')
137 
138 """
139 
140 import logging
141 import os
142 import re
143 import sys
144 import traceback
145 from io import StringIO
147 from fparser.common.splitline import String, string_replace_map, splitquote
148 
149 
150 __all__ = [
151  "FortranFileReader",
152  "FortranStringReader",
153  "FortranReaderError",
154  "Line",
155  "SyntaxErrorLine",
156  "Comment",
157  "MultiLine",
158  "SyntaxErrorMultiLine",
159 ]
160 
161 _SPACEDIGITS = " 0123456789"
162 _CF2PY_RE = re.compile(r"(?P<indent>\s*)!f2py(?P<rest>.*)", re.I)
163 _LABEL_RE = re.compile(r"\s*(?P<label>\d+)\s*(\b|(?=&)|\Z)", re.I)
164 _CONSTRUCT_NAME_RE = re.compile(r"\s*(?P<name>\w+)\s*:\s*(\b|(?=&)|\Z)", re.I)
165 _IS_INCLUDE_LINE = re.compile(
166  r'\s*include\s*("[^"]+"' + r"|\'[^\']+\')\s*\Z", re.I
167 ).match
168 
169 
170 def _is_fix_cont(line):
171  return line and len(line) > 5 and line[5] != " " and line[:5] == 5 * " "
172 
173 
174 def _is_fix_comment(line, isstrict, f2py_enabled):
175  """
176  Check whether line is a comment line in fixed format Fortran source.
177 
178  References
179  ----------
180  :f2008:`3.3.3`
181 
182  :param str line: line of code to check.
183  :param bool isstrict: whether we are strictly enforcing fixed/free fmt.
184  :param bool f2py_enabled: whether support for f2py directives is enabled.
185 
186  :returns: whether or not the supplied line is a fixed-format comment.
187  :rtype: bool
188 
189  """
190  if line:
191  if line[0] in "*cC!":
192  if f2py_enabled and line[1:5].lower() == "f2py":
193  return False
194  return True
195  if not isstrict:
196  i = line.find("!")
197  if i != -1:
198  start = line[:i].lstrip()
199  if not start:
200  if i == 5:
201  # line continuation
202  return False
203  return True
204  else:
205  # inline comment or ! is used in character context
206  # inline comments are handled elsewhere
207  pass
208  elif line == "":
209  return True
210  return False
211 
212 
213 _HOLLERITH_START_SEARCH = re.compile(
214  r"(?P<pre>\A|,\s*)" + r"(?P<num>\d+)h", re.I
215 ).search
216 _IS_CALL_STMT = re.compile(r"call\b", re.I).match
217 
218 
219 def extract_label(line):
220  """Look for an integer label at the start of 'line' and if there is
221  one then remove it from 'line' and store it as an integer in
222  'label', returning both in a tuple.
223 
224  :param str line: a string that potentially contains a label at the \
225  start.
226 
227  :returns: a 2-tuple containing the label and updated line if a \
228  label is found or None and the unchanged line if a label is \
229  not found.
230  :rtype: (int or NoneType, str)
231 
232  """
233  label = None
234  match = _LABEL_RE.match(line)
235  if match:
236  label = int(match.group("label"))
237  line = line[match.end() :].lstrip()
238  return label, line
239 
240 
241 def extract_construct_name(line):
242  """Look for a construct name at the start of 'line' and if there is
243  one then remove it from 'line' and return it as a string in
244  'name', returning both in a tuple.
245 
246  :param str line: a string that potentially contains a construct \
247  name at the start.
248 
249  :returns: a 2-tuple containing the construct name and updated line \
250  if a construct name is found or None and the unchanged line if \
251  a construct name is not found.
252  :rtype: (str or NoneType, str)
253 
254  """
255  construct_name = None
256  match = _CONSTRUCT_NAME_RE.match(line)
257  if match:
258  construct_name = match.group("name")
259  line = line[match.end() :].lstrip()
260  return construct_name, line
261 
262 
263 class FortranReaderError(Exception):
264  """
265  Thrown when there is an error reading the Fortran source file.
266  """
267 
268  pass
269 
270 
271 class Line:
272  """Holds a Fortran source line.
273 
274  Attributes
275 
276  line : str
277  code line
278  span : 2-tuple
279  starting and ending line numbers
280  label : {int, None}
281  Specify statement label
282  name : {str, None}
283  Specify construct name.
284  reader : FortranReaderBase
285  strline : {None, str}
286  is_f2py_directive : bool
287  the line contains f2py directive
288 
289  """
290 
291  def __init__(self, line, linenospan, label, name, reader):
292  self.line = line.strip()
293  if not self.line:
294  raise FortranReaderError(
295  "Got empty line: '{0}'. linenospan={1}, "
296  "label='{2}'".format(line, linenospan, label)
297  )
298  self.span = linenospan
299  assert label is None or isinstance(label, int), repr(label)
300  assert name is None or isinstance(name, str) and name != "", repr(name)
301  self.label = label
302  self.name = name
303  self.reader = reader
304  self.strline = None
305  self.is_f2py_directive = linenospan[0] in reader.f2py_comment_lines
306  self.parse_cache = {}
307 
308  def has_map(self):
309  """
310  Returns true when a substitution map has been registered.
311  """
312  return hasattr(self, "strlinemap") and self.strlinemap
313 
314  def apply_map(self, line):
315  """
316  Substitutes magic strings in a line with values specified in a map.
317  """
318  if not hasattr(self, "strlinemap") or not self.strlinemap:
319  return line
320  return self.strlinemap(line)
321 
322  def copy(self, line=None, apply_map=False):
323  """
324  Creates a Line object from a string.
325 
326  If no line argument is specified a copy is made of this Line.
327 
328  If a substitution map is provided it is used while making the copy.
329  """
330  if line is None:
331  line = self.line
332  if apply_map:
333  line = self.apply_map(line)
334  return Line(line, self.span, self.label, self.name, self.reader)
335 
336  def clone(self, line):
337  """
338  This Line has its contents overwitten by the passed string. The
339  incoming string has substitution applied.
340  """
341  self.line = self.apply_map(line)
342  self.strline = None
343 
344  def __repr__(self):
345  return self.__class__.__name__ + "(%r,%s,%r,%r,<reader>)" % (
346  self.line,
347  self.span,
348  self.label,
349  self.name,
350  )
351 
352  def __str__(self):
353  s = "line #%s" % (self.span[0])
354  if self.label is not None:
355  s += " %s " % (self.label)
356  if self.name is not None:
357  s += "%s: " % (self.name)
358  return s + repr(self.line)
359 
360  def isempty(self, ignore_comments=False):
361  return not (self.line or self.label is not None or self.name is not None)
362 
363  def get_line(self, apply_map=False):
364  if apply_map:
365  return self.apply_map(self.get_line(apply_map=False))
366  if self.strline is not None:
367  return self.strline
368  line = self.line
369 
370  if self.reader.format.is_f77:
371  # Handle Hollerith constants by replacing them
372  # with char-literal-constants.
373  # H constants may appear only in DATA statements and
374  # in the argument list of CALL statement.
375  # Hollerith constants were removed from the Fortran 77 standard.
376  # The following handling is not perfect but works for simple
377  # usage cases.
378  # todo: Handle hollerith constants in DATA statement
379  if _IS_CALL_STMT(line):
380  l2 = self.line[4:].lstrip()
381  i = l2.find("(")
382  if i != -1 and l2[-1] == ")":
383  substrings = ["call " + l2[: i + 1]]
384  start_search = _HOLLERITH_START_SEARCH
385  l2 = l2[i + 1 : -1].strip()
386  m = start_search(l2)
387  while m:
388  substrings.append(l2[: m.start()])
389  substrings.append(m.group("pre"))
390  num = int(m.group("num"))
391  substrings.append("'" + l2[m.end() : m.end() + num] + "'")
392  l2 = l2[m.end() + num :]
393  m = start_search(l2)
394  substrings.append(l2)
395  substrings.append(")")
396  line = "".join(substrings)
397 
398  line, str_map = string_replace_map(line, lower=not self.reader.format.is_pyf)
399  self.strline = line
400  self.strlinemap = str_map
401  return line
402 
403  def parse_line(self, cls, parent_cls):
404  if cls not in self.parse_cache:
405  self.parse_cache[cls] = None
406  obj = cls(self.line, parent_cls=parent_cls)
407  self.parse_cache[cls] = obj
408  else:
409  obj = self.parse_cache[cls]
410  return obj
411 
412  def parse_block(self, reader, cls, parent_cls):
413  key = cls, tuple(parent_cls)
414  if key not in self.parse_cache:
415  obj = cls(reader, parent_cls=parent_cls)
416  self.parse_cache[key] = obj
417  else:
418  obj = self.parse_cache[key]
419  return obj
420 
421 
423  """
424  Indicates a syntax error while processing a line.
425  """
426 
427  def __init__(self, line, linenospan, label, name, reader, message):
428  Line.__init__(self, line, linenospan, label, name, reader)
429  FortranReaderError.__init__(self, message)
430 
431 
432 class Comment:
433  """Holds a Fortran comment.
434 
435  :param str comment: String containing the text of a single or \
436  multi-line comment
437  :param linenospan: A 2-tuple containing the start and end line \
438  numbers of the comment from the input source.
439  :type linenospan: (int, int)
440  :param reader: The reader object being used to read the input \
441  source.
442  :type reader: :py:class:`fparser.common.readfortran.FortranReaderBase`
443 
444  """
445 
446  def __init__(self, comment, linenospan, reader):
447 
448  self.comment = comment
449  self.span = linenospan
450  self.reader = reader
451  # self.line provides a common way to retrieve the content from
452  # either a 'Line' or a 'Comment' class. This is useful for
453  # tests as a reader can return an instance of either class and
454  # we might want to check the contents in a consistent way.
455  self.line = comment
456 
457  def __repr__(self):
458  return self.__class__.__name__ + "(%r,%s)" % (self.comment, self.span)
459 
460  def isempty(self, ignore_comments=False):
461  """
462  Whether or not this comment is in fact empty (or we are ignoring
463  it). Provided for compatibility with Line.isempty()
464 
465  :param bool ignore_comments: whether we ignore comments
466  :return: True if we are ignoring comments, False otherwise
467  :rtype: bool
468  """
469  return ignore_comments
470 
471 
472 class MultiLine:
473  """Holds PYF file multiline.
474 
475  PYF file multiline is represented as follows::
476  prefix+'''+lines+'''+suffix.
477 
478  :param str prefix: the prefix of the line(s)
479  :param block: list of lines
480  :type block: List[:py:class:`fparser.common.readfortran.Line`]
481  :param str suffix: the suffix of the block of lines
482  :param linenospan: starting and ending line numbers
483  :type linenospan: Tuple[int, int]
484  :param reader: the current reader instance.
485  :type reader: :py:class:`fparser.common.readfortran.FortranReaderBase`
486 
487  """
488 
489  def __init__(self, prefix, block, suffix, linenospan, reader):
490  self.prefix = prefix
491  self.block = block
492  self.suffix = suffix
493  self.span = linenospan
494  self.reader = reader
495 
496  def __repr__(self):
497  string = "{cls}({prefix!r},{block},{suffix!r},{span})"
498  return string.format(
499  cls=self.__class__.__name__,
500  prefix=self.prefix,
501  block=self.block,
502  suffix=self.suffix,
503  span=self.span,
504  )
505 
506  def isempty(self, ignore_comments=False):
507  """
508  Returns true if there is no significant text in this multi-line
509  string.
510  """
511  return not (self.prefix or self.block or self.suffix)
512 
513 
515  """
516  Indicates a syntax error while processing Python multi-line strings.
517  """
518 
519  def __init__(self, prefix, block, suffix, linenospan, reader, message):
520  MultiLine.__init__(self, prefix, block, suffix, linenospan, reader)
521  FortranReaderError.__init__(self, message)
522 
523 
525  """Holds a preprocessor directive source line.
526 
527  :param str line: string containing the text of a single or \
528  multi-line preprocessor directive.
529  :param linenospan: a 2-tuple containing the start and end line \
530  numbers of the directive from the input source.
531  :type linenospan: (int, int)
532  :param reader: The reader object being used to read the input \
533  source.
534  :type reader: :py:class:`fparser.common.readfortran.FortranReaderBase`
535 
536  """
537 
538  def __init__(self, line, linenospan, reader):
539  super(CppDirective, self).__init__(line, linenospan, None, None, reader)
540 
541 
542 
543 
544 
546  """
547  Base class for reading Fortran sources.
548 
549  A Fortran source must be a file-like object (have a ``.next()``
550  method) and it may hold Fortran 77 code, fixed format Fortran
551  code, free format Fortran code, or PYF signatures (with extended
552  free format Fortran syntax).
553 
554  :param source: a file-like object with .next() method used to \
555  retrive a line.
556  :type source: :py:class:`StringIO` or a file handle
557  :param mode: a FortranFormat object as returned by \
558  `sourceinfo.get_source_info()`
559  :type mode: :py:class:`fparser.common.sourceinfo.Format`
560  :param bool isstrict: whether we are strictly enforcing fixed format.
561  :param bool ignore_comments: whether or not to discard comments.
562 
563  The Fortran source is iterated by `get_single_line`,
564  `get_next_line`, `put_single_line` methods.
565 
566  """
567 
568  def __init__(self, source, mode, ignore_comments):
569  self.source = source
570  self._format = mode
571 
572  self.linecount = 0 # the current number of consumed lines
573  self.isclosed = False
574  # This value for ignore_comments can be overridden by using the
575  # ignore_comments optional argument to e.g. get_single_line()
576  self._ignore_comments = ignore_comments
577 
578  self.filo_line = [] # used for un-consuming lines.
579  self.fifo_item = []
580  self.source_lines = [] # source lines cache
581 
582  self.f2py_comment_lines = [] # line numbers of f2py directives
583 
584  self.reader = None
585  self.include_dirs = ["."]
586 
587  self.source_only = None
588 
589  self.exit_on_error = True
590  self.restore_cache = []
591 
592 
593 
594  def __repr__(self):
595  return "%s(%r, %r, %r)" % (
596  self.__class__.__name__,
597  self.source,
598  self._format.is_free,
599  self._format.is_strict,
600  )
601 
602  def find_module_source_file(self, mod_name):
603  """
604  Scans registered dependees for a named module.
605  """
606  from .utils import get_module_file, module_in_file
607 
608  if self.source_only:
609  for sf in self.source_only:
610  if module_in_file(mod_name, sf):
611  return sf
612  else:
613  fn = None
614  for d in self.include_dirs:
615  fn = get_module_file(mod_name, d)
616  if fn is not None:
617  return fn
618 
619  def set_format(self, mode):
620  """
621  Set Fortran code mode (fixed/free format etc).
622 
623  :param mode: Object describing the desired mode for the reader
624  :type mode: :py:class:`fparser.common.sourceinfo.FortranFormat`
625  """
626  self._format = mode
627 
628  @property
629  def format(self):
630  """
631  :returns: the currently applicable format.
632  :rtype: :py:class:`fparser.sourceinfo.FortranFormat`
633  """
634  return self._format
635 
636  @property
637  def name(self):
638  """
639  :returns: the name of this reader.
640  :rtype: str
641  """
642  return "{source} mode={mode}".format(source=self.source, mode=self._format.mode)
643 
644  def close_source(self):
645  """Called when self.source.next() raises StopIteration."""
646  pass
647 
648  # For handling raw source lines:
649 
650  def put_single_line(self, line):
651  """Put single line to FILO line buffer.
652 
653  ``linecount`` will be decremented, that is, the line was
654  returned by ``get_single_line`` call then it will be
655  un-consumed.
656 
657  See also
658  --------
659  get_single_line, get_next_line
660  """
661  self.filo_line.append(line)
662  self.linecount -= 1
663 
664  def get_single_line(self, ignore_empty=False, ignore_comments=None):
665  """ Return line from FILO line buffer or from source.
666 
667  First try getting the line from FILO line buffer.
668 
669  If FILO line buffer is empty then get the next line from
670  source. Tabs in source line are expanded, ending blank and new
671  line characters will be removed. The source line will be
672  added to ``source_lines`` list. If source line is empty then
673  recursively get next non-empty line.
674 
675  In both situations ``linecount`` will be incremented, that is,
676  the line will be consumed.
677 
678  :param bool ignore_empty: if True then ignore empty lines.
679  :param bool ignore_comments: if True then ignore comments (overrides \
680  self._ignore_comments)
681 
682  See also
683  --------
684  put_single_line, get_next_line
685  """
686  if ignore_comments is None:
687  ignore_comments = self._ignore_comments
688 
689  try:
690  line = self.filo_line.pop()
691  self.linecount += 1
692  return line
693  except IndexError:
694  pass
695  if self.isclosed:
696  return None
697  try:
698  line = next(self.source)
699  except StopIteration:
700  self.isclosed = True
701  self.close_source()
702  return None
703  self.linecount += 1
704 
705  # expand tabs, replace special symbols, get rid of nl characters
706  line = line.expandtabs().replace("\xa0", " ").rstrip()
707 
708  self.source_lines.append(line)
709 
710  if ignore_comments and (self._format.is_fixed or self._format.is_f77):
711  # Check for a fixed-format comment. If the current line *is*
712  # a comment and we are ignoring them, then recursively call this
713  # routine again to get the next source line.
714  if _is_fix_comment(
715  line,
716  isstrict=self._format.is_strict,
717  f2py_enabled=self._format.f2py_enabled,
718  ):
719  return self.get_single_line(ignore_empty, ignore_comments)
720 
721  if ignore_empty and not line:
722  return self.get_single_line(ignore_empty, ignore_comments)
723 
724  return line
725 
726  def get_next_line(self, ignore_empty=False, ignore_comments=None):
727  """Return next non-empty line from FILO line buffer or from source.
728 
729  The line will be put to FILO line buffer. So, this method can
730  be used for looking forward lines without consuming them.
731 
732  See also
733  --------
734  get_single_line, put_single_line
735  """
736  if ignore_comments is None:
737  ignore_comments = self._ignore_comments
738 
739  line = self.get_single_line(ignore_empty, ignore_comments)
740  if line is None:
741  return
742  self.put_single_line(line)
743  return line
744 
745  # Parser methods:
746  def get_item(self, ignore_comments=None):
747  """Return next item."""
748  if ignore_comments is None:
749  ignore_comments = self._ignore_comments
750 
751  try:
752  item = self.next(ignore_comments=ignore_comments)
753  except StopIteration:
754  return
755  return item
756 
757  def put_item(self, item):
758  """Insert item to FIFO item buffer."""
759  self.fifo_item.insert(0, item)
760 
761  # Iterator methods:
762 
763  def __iter__(self):
764  """Make FortranReader an iterator."""
765  return self
766 
767  def __next__(self):
768  return self.next()
769 
770  def next(self, ignore_comments=None):
771  """Return the next Fortran code item. Include statements are dealt
772  with here.
773 
774  :param bool ignore_comments: When True then act as if Fortran \
775  code does not contain any comments or blank lines. if this \
776  optional arguement is not provided then use the default \
777  value.
778 
779  :returns: the next line item. This can be from a local fifo \
780  buffer, from an include reader or from this reader.
781  :rtype: py:class:`fparser.common.readfortran.Line`
782 
783  :raises StopIteration: if no more lines are found.
784  :raises StopIteration: if a general error has occured.
785 
786  """
787  if ignore_comments is None:
788  ignore_comments = self._ignore_comments
789  try:
790  if self.reader is not None:
791  # inside INCLUDE statement
792  try:
793  # Manually check to see if something has not
794  # matched and has been placed in the fifo. We
795  # can't use _next() as this method is associated
796  # with the include reader (self.reader._next()),
797  # not this reader (self._next()).
798  return self.fifo_item.pop(0)
799  except IndexError:
800  # There is nothing in the fifo buffer.
801  try:
802  # Return a line from the include.
803  return self.reader.next(ignore_comments)
804  except StopIteration:
805  # There is nothing left in the include
806  # file. Setting reader to None indicates that
807  # we should now read from the main reader.
808  self.reader = None
809  item = self._next(ignore_comments)
810  if isinstance(item, Line) and _IS_INCLUDE_LINE(item.line):
811  # catch INCLUDE statement and create a new FortranReader
812  # to enter to included file.
813  reader = item.reader
814  filename = item.line.strip()[7:].lstrip()[1:-1]
815  include_dirs = self.include_dirs[:]
816  path = filename
817  for incl_dir in include_dirs:
818  path = os.path.join(incl_dir, filename)
819  if os.path.exists(path):
820  break
821  if not os.path.isfile(path):
822  # The include file does not exist in the specified
823  # locations.
824  #
825  # The Fortran standard states that an INCLUDE line
826  # is not a Fortran statement. However, fparser is
827  # a parser not a compiler and some subsequent tool
828  # might need to make use of this include so we
829  # return it and let the parser deal with it.
830  #
831  return item
832  reader.info("including file %r" % (path), item)
833  self.reader = FortranFileReader(
834  path, include_dirs=include_dirs, ignore_comments=ignore_comments
835  )
836  result = self.reader.next(ignore_comments=ignore_comments)
837  return result
838  return item
839  except StopIteration:
840  raise
841  # TODO can we specify one or more specific exception types
842  # rather than catching *every* exception.
843  except Exception as err:
844  message = self.format_message(
845  "FATAL ERROR", "while processing line", self.linecount, self.linecount
846  )
847  logging.getLogger(__name__).critical(message)
848  message = "Traceback\n" + "".join(traceback.format_stack())
849  logging.getLogger(__name__).debug(message)
850  logging.getLogger(__name__).debug(str(err))
851  logging.getLogger(__name__).critical("STOPPED READING")
852  raise StopIteration
853 
854  def _next(self, ignore_comments=None):
855  """
856  Return the next item from FIFO item buffer or construct
857  one from source line.
858 
859  Resolves ``;`` statement terminations.
860 
861  See also
862  --------
863  next, get_source_item
864 
865  :param bool ignore_comments: Whether or not to ignore comments \
866  (overrides self._ignore_comments)
867 
868  :returns: the next line of Fortran.
869  :rtype: :py:class:`fparser.common.readfortran.Line`
870 
871  :raises StopIteration: if no new items are found.
872 
873  """
874  if ignore_comments is None:
875  ignore_comments = self._ignore_comments
876  fifo_item_pop = self.fifo_item.pop
877  while 1:
878  try:
879  # first empty the FIFO item buffer:
880  item = fifo_item_pop(0)
881  except IndexError:
882  # construct a new item from source
883  item = self.get_source_item()
884  if item is None:
885  raise StopIteration
886  if not item.isempty(ignore_comments):
887  break
888  # else ignore empty lines and comments by getting next line
889 
890  if not isinstance(item, Comment):
891  # resolve `;` statement terminations
892  if (
893  not self._format.is_pyf
894  and isinstance(item, Line)
895  and not item.is_f2py_directive
896  and ";" in item.get_line()
897  ):
898  # ;-separator not recognized in pyf-mode
899  items = []
900  # Deal with each Fortran statement separately.
901  split_line_iter = iter(item.get_line().split(";"))
902  first = next(split_line_iter)
903  # The full line has already been processed as a Line
904  # object in 'item' (and may therefore have label
905  # and/or construct name properties extracted from the
906  # start of the line). The simplest way to avoid losing
907  # any label or construct name properties for the first
908  # statement is to copy the 'item' object and update it
909  # so that it only includes text for the first
910  # statement (rather than the full line). Subsequent
911  # statements need to be processed into Line
912  # objects.
913  items.append(item.copy(first.strip(), apply_map=True))
914  for line in split_line_iter:
915  # Any subsequent statements have not been processed
916  # before, so new Line objects need to be created.
917  line = line.strip()
918  if line:
919  # The statement might have a label and/or construct
920  # name.
921  label, line = extract_label(line)
922  name, line = extract_construct_name(line)
923  # Create a new Line object and append to items
924  # using the existing span (line numbers) and
925  # reader.
926  new_line = Line(
927  item.apply_map(line), item.span, label, name, item.reader
928  )
929  items.append(new_line)
930  items.reverse()
931  for newitem in items:
932  self.fifo_item.insert(0, newitem)
933  return fifo_item_pop(0)
934  return item
935 
936  # Interface to returned items:
937 
938  def line_item(self, line, startlineno, endlineno, label, name, errmessage=None):
939  """Construct Line item."""
940  if errmessage is None:
941  return Line(line, (startlineno, endlineno), label, name, self)
942  return SyntaxErrorLine(
943  line, (startlineno, endlineno), label, name, self, errmessage
944  )
945 
946  def multiline_item(
947  self, prefix, lines, suffix, startlineno, endlineno, errmessage=None
948  ):
949  """Construct MultiLine item."""
950  if errmessage is None:
951  return MultiLine(prefix, lines, suffix, (startlineno, endlineno), self)
952  return SyntaxErrorMultiLine(
953  prefix, lines, suffix, (startlineno, endlineno), self, errmessage
954  )
955 
956  def comment_item(self, comment, startlineno, endlineno):
957  """Construct Comment item."""
958  return Comment(comment, (startlineno, endlineno), self)
959 
960  def cpp_directive_item(self, line, startlineno, endlineno):
961  """
962  Construct :py:class:`fparser.common.readfortran.CppDirective` item.
963 
964  :param str line: string containing the text of a single or \
965  multi-line preprocessor directive.
966  :param int startlineno: start line number of the directive from \
967  the input source.
968  :param int endlineno: end line number of the directive from \
969  the input source.
970 
971  """
972  return CppDirective(line, (startlineno, endlineno), self)
973 
974  # For handling messages:
975 
976  def format_message(
977  self, kind, message, startlineno, endlineno, startcolno=0, endcolno=-1
978  ):
979  """
980  Prepares a string for logging.
981  """
982  back_index = {"warning": 2, "error": 3, "info": 0}.get(kind.lower(), 3)
983  r = ["While processing %r (mode=%r).." % (self.id, self._format.mode)]
984  for i in range(max(1, startlineno - back_index), startlineno):
985  r.append("%5d:%s" % (i, self.source_lines[i - 1]))
986  for i in range(
987  startlineno, min(endlineno + back_index, len(self.source_lines)) + 1
988  ):
989  if i == 0 and not self.source_lines:
990  break
991  linenostr = "%5d:" % (i)
992  if i == endlineno:
993  sourceline = self.source_lines[i - 1]
994  l0 = linenostr + sourceline[:startcolno]
995  if endcolno == -1:
996  l1 = sourceline[startcolno:]
997  l2 = ""
998  else:
999  l1 = sourceline[startcolno:endcolno]
1000  l2 = sourceline[endcolno:]
1001  r.append("%s%s%s <== %s" % (l0, l1, l2, message))
1002  else:
1003  r.append(linenostr + self.source_lines[i - 1])
1004  return "\n".join(r)
1005 
1007  self, message, startlineno, endlineno, startcolno=0, endcolno=-1
1008  ):
1009  """Create a string with an error message."""
1010  return self.format_message(
1011  "ERROR", message, startlineno, endlineno, startcolno, endcolno
1012  )
1013 
1015  self, message, startlineno, endlineno, startcolno=0, endcolno=-1
1016  ):
1017  """Create a string with a warning message."""
1018  return self.format_message(
1019  "WARNING", message, startlineno, endlineno, startcolno, endcolno
1020  )
1021 
1022  def info(self, message, item=None):
1023  """
1024  Logs an information message.
1025  """
1026  if item is None:
1027  m = self.format_message(
1028  "INFORMATION",
1029  message,
1030  len(self.source_lines) - 2,
1031  len(self.source_lines),
1032  )
1033  else:
1034  m = self.format_message("INFORMATION", message, item.span[0], item.span[1])
1035  logging.getLogger(__name__).info(m)
1036 
1037  def error(self, message, item=None):
1038  """
1039  Logs an error message.
1040  """
1041  if item is None:
1042  m = self.format_error_message(
1043  message, len(self.source_lines) - 2, len(self.source_lines)
1044  )
1045  else:
1046  m = self.format_error_message(message, item.span[0], item.span[1])
1047  logging.getLogger(__name__).error(m)
1048  if self.exit_on_error:
1049  sys.exit(1)
1050 
1051  def warning(self, message, item=None):
1052  """
1053  Logs a warning message.
1054  """
1055  if item is None:
1056  m = self.format_warning_message(
1057  message, len(self.source_lines) - 2, len(self.source_lines)
1058  )
1059  else:
1060  m = self.format_warning_message(message, item.span[0], item.span[1])
1061  logging.getLogger(__name__).warning(m)
1062 
1063  # Auxiliary methods for processing raw source lines:
1064 
1065  def handle_cpp_directive(self, line):
1066  """
1067  Determine whether the current line is likely to hold
1068  C preprocessor directive.
1069 
1070  If the first non whitespace character of a line is the symbol ``#``
1071  it is assumed this is a preprocessor directive.
1072 
1073  Preprocessor directives can be used only in Fortran codes. They are
1074  ignored when used inside PYF files.
1075 
1076  The actual line content is not altered.
1077 
1078  :param str line: the line to be tested for directives.
1079 
1080  :return: a tuple containing the line and True/False depending on \
1081  whether it holds a C preprocessor directive.
1082  :rtype: (str, bool)
1083 
1084  """
1085  if not line or self._format.is_pyf:
1086  return (line, False)
1087  return (line, line.lstrip().startswith("#"))
1088 
1089  def handle_cf2py_start(self, line):
1090  """
1091  Process any f2py directives contained in the supplied line. If
1092  support for such directives has been disabled then the line is
1093  returned unchanged.
1094 
1095  F2py directives are specified in the beginning of the line.
1096 
1097  f2py directives can be used only in Fortran codes. They are
1098  ignored when used inside PYF files.
1099 
1100  :param str line: the line to check for f2py directives.
1101 
1102  :returns: the line with any f2py directives applied (if they are \
1103  enabled in the reader).
1104  :rtype: str
1105 
1106  """
1107  if not line or self._format.is_pyf or not self._format.f2py_enabled:
1108  return line
1109  if self._format.is_fixed:
1110  if line[0] in "*cC!#":
1111  if line[1:5].lower() == "f2py":
1112  line = 5 * " " + line[5:]
1113  self.f2py_comment_lines.append(self.linecount)
1114  if self._format.is_f77:
1115  return line
1116  m = _CF2PY_RE.match(line)
1117  if m:
1118  newline = m.group("indent") + 5 * " " + m.group("rest")
1119  self.f2py_comment_lines.append(self.linecount)
1120  assert len(newline) == len(line), repr((newline, line))
1121  return newline
1122  return line
1123 
1124  def handle_inline_comment(self, line, lineno, quotechar=None):
1125  """
1126  Any in-line comment is extracted from the line. If
1127  keep_inline_comments==True then the extracted comments are put back
1128  into the fifo sequence where they will subsequently be processed as
1129  a comment line.
1130 
1131  :param str line: line of code from which to remove in-line comment
1132  :param int lineno: line-no. in orig. file
1133  :param quotechar: String to use as character-string delimiter
1134  :type quotechar: {None, str}
1135 
1136  :return: line_with_no_comments, quotechar, had_comment
1137  :rtype: 3-tuple of str, str, bool
1138  """
1139  had_comment = False
1140  if (
1141  quotechar is None
1142  and "!" not in line
1143  and '"' not in line
1144  and "'" not in line
1145  ):
1146  # There's no comment on this line
1147  return line, quotechar, had_comment
1148 
1149  idx = line.find("!")
1150  put_item = self.fifo_item.append
1151  if quotechar is None and idx != -1:
1152  # first try a quick method:
1153  newline = line[:idx]
1154  if '"' not in newline and "'" not in newline:
1155  if self.format.is_f77 or not line[idx:].startswith("!f2py"):
1156  put_item(self.comment_item(line[idx:], lineno, lineno))
1157  return newline, quotechar, True
1158 
1159  # We must allow for quotes...
1160  items, newquotechar = splitquote(line, quotechar)
1161  noncomment_items = []
1162  noncomment_items_append = noncomment_items.append
1163  n = len(items)
1164  commentline = None
1165  for k in range(n):
1166  item = items[k]
1167  if isinstance(item, String) or "!" not in item:
1168  noncomment_items_append(item)
1169  continue
1170  j = item.find("!")
1171  noncomment_items_append(item[:j])
1172  items[k] = item[j:]
1173  commentline = "".join(items[k:])
1174  break
1175  if commentline is not None:
1176  if self._format.f2py_enabled and commentline.startswith("!f2py"):
1177  # go to next iteration:
1178  newline = "".join(noncomment_items) + commentline[5:]
1179  self.f2py_comment_lines.append(lineno)
1180  return self.handle_inline_comment(newline, lineno, quotechar)
1181  put_item(self.comment_item(commentline, lineno, lineno))
1182  had_comment = True
1183  return "".join(noncomment_items), newquotechar, had_comment
1184 
1185  def handle_multilines(self, line, startlineno, mlstr):
1186  '''
1187  Examines line for Python triple quote strings (f2py feature).
1188 
1189  :param str line: line of Fortran source text
1190  :param int startlineno: the number of the line on which this
1191  multi-line string began.
1192  :param list mlstr: list of delimiters for a multi-line string
1193  (e.g. '"""')
1194  '''
1195  i = line.find(mlstr)
1196  if i != -1:
1197  prefix = line[:i]
1198  # skip fake multiline starts
1199  p, k = prefix, 0
1200  while p.endswith("\\"):
1201  p, k = p[:-1], k + 1
1202  if k % 2:
1203  return
1204  if i != -1 and "!" not in prefix:
1205  # Note character constants like 'abc"""123',
1206  # so multiline prefix should better not contain `'' or `"' not `!'.
1207  for quote in "\"'":
1208  if prefix.count(quote) % 2:
1209  message = (
1210  "multiline prefix contains odd number of"
1211  + " {!r} characters".format(quote)
1212  )
1213  message = self.format_warning_message(
1214  message, startlineno, startlineno, 0, len(prefix)
1215  )
1216  logging.getLogger(__name__).warning(message)
1217 
1218  suffix = None
1219  multilines = []
1220  line = line[i + 3 :]
1221  while line is not None:
1222  j = line.find(mlstr)
1223  if j != -1 and "!" not in line[:j]:
1224  multilines.append(line[:j])
1225  suffix = line[j + 3 :]
1226  break
1227  multilines.append(line)
1228  line = self.get_single_line()
1229  if line is None:
1230  message = "multiline block never ends"
1231  message = self.format_error_message(
1232  message, startlineno, startlineno, i
1233  )
1234  return self.multiline_item(
1235  prefix, multilines, suffix, startlineno, self.linecount, message
1236  )
1237  suffix, qc, had_comment = self.handle_inline_comment(suffix, self.linecount)
1238  # no line continuation allowed in multiline suffix
1239  if qc is not None:
1240  message = "following character continuation: {!r}," + " expected None."
1241  message = self.format_message(
1242  "ASSERTION FAILURE(pyf)",
1243  message.format(qc),
1244  startlineno,
1245  self.linecount,
1246  )
1247  logging.getLogger(__name__).warning(message)
1248  # XXX: should we do line.replace('\\'+mlstr[0],mlstr[0])
1249  # for line in multilines?
1250  return self.multiline_item(
1251  prefix, multilines, suffix, startlineno, self.linecount
1252  )
1253 
1254  # The main method of interpreting raw source lines within
1255  # the following contexts: f77, fixed, free, pyf.
1256 
1257  def get_source_item(self):
1258  """
1259  Return the next source item.
1260 
1261  A source item is ..
1262  - a fortran line
1263  - a list of continued fortran lines
1264  - a multiline - lines inside triple-quotes, only when in ispyf mode
1265  - a comment line
1266  - a preprocessor directive line
1267 
1268  :returns: the next source item.
1269  :rtype: :py:class:`fparser.common.readfortran.Line` or \
1270  :py:class:`fparser.common.readfortran.MultiLine` or \
1271  :py:class:`fparser.common.readfortran.Comment` or \
1272  :py:class:`fparser.common.readfortran.CppDirective` or \
1273  :py:class:`fparser.common.readfortran.SyntaxErrorLine` or \
1274  :py:class:`fparser.common.readfortran.SyntaxErrorMultiLine`
1275 
1276  """
1277  get_single_line = self.get_single_line
1278  line = get_single_line()
1279  if line is None:
1280  return None
1281  startlineno = self.linecount
1282  line, is_cpp_directive = self.handle_cpp_directive(line)
1283  if is_cpp_directive:
1284  # CPP directive line
1285  lines = []
1286  while line.rstrip().endswith("\\"):
1287  # Line continuation
1288  lines.append(line.rstrip()[:-1])
1289  line = get_single_line()
1290  lines.append(line)
1291  endlineno = self.linecount
1292  return self.cpp_directive_item("".join(lines), startlineno, endlineno)
1293 
1294  line = self.handle_cf2py_start(line)
1295  is_f2py_directive = (
1296  self._format.f2py_enabled and startlineno in self.f2py_comment_lines
1297  )
1298  isstrict = self._format.is_strict
1299  have_comment = False
1300  label = None
1301  name = None
1302 
1303  if self._format.is_pyf:
1304  # handle multilines
1305  for mlstr in ['"""', "'''"]:
1306  multiline = self.handle_multilines(line, startlineno, mlstr)
1307  if multiline:
1308  return multiline
1309  if self._format.is_fixed:
1310  if _is_fix_comment(line, isstrict, self._format.f2py_enabled):
1311  # comment line:
1312  return self.comment_item(line, startlineno, startlineno)
1313 
1314  for i in range(min(5, len(line))):
1315  # check that fixed format line starts according to Fortran
1316  # standard
1317  if line[i] not in _SPACEDIGITS:
1318  message = (
1319  "non-space/digit char %r found in column %i"
1320  " of fixed Fortran code" % (line[i], i + 1)
1321  )
1322  if i == 0:
1323  message += ", interpreting line as comment line"
1324  if self._format.is_fix:
1325  if i != 0:
1326  message += ", switching to free format mode"
1327  message = self.format_warning_message(
1328  message, startlineno, self.linecount
1329  )
1330  logging.getLogger(__name__).warning(message)
1331  if i == 0:
1332  # non standard comment line:
1333  return self.comment_item(line, startlineno, startlineno)
1334  mode = fparser.common.sourceinfo.FortranFormat(True, False)
1335  self.set_format(mode)
1336  else:
1337  message = self.format_warning_message(
1338  message, startlineno, self.linecount
1339  )
1340  logging.getLogger(__name__).warning(message)
1341  if i == 0:
1342  # non standard comment line:
1343  return self.comment_item(line, startlineno, startlineno)
1344  # return line item with error message
1345  # TODO: handle cases with line[6:]==''
1346  message = self.format_error_message(
1347  message, startlineno, self.linecount
1348  )
1349  return self.line_item(
1350  line[6:], startlineno, self.linecount, label, name, message
1351  )
1352  if self._format.is_fixed: # Check for switched to free format
1353  # check for label
1354  s = line[:5].strip().lower()
1355  if s:
1356  label = int(s)
1357  if not self._format.is_f77:
1358  m = _CONSTRUCT_NAME_RE.match(line[6:])
1359  if m:
1360  name = m.group("name")
1361  line = line[:6] + line[6:][m.end() :].lstrip()
1362  if not line[6:].strip():
1363  # check for a blank line
1364  if name is not None:
1365  self.error("No construct following construct-name.")
1366  elif label is not None:
1367  self.warning(
1368  "Label must follow nonblank character" + " (F2008:3.2.5_2)"
1369  )
1370  return self.comment_item("", startlineno, self.linecount)
1371  # line is not a comment and the start of the line is valid
1372 
1373  if self._format.is_f77 and not is_f2py_directive:
1374  # Fortran 77 is easy..
1375  lines = [line[6:72]]
1376  # get_next_line does not actually consume lines - they are put
1377  # into the FILO buffer as well as being returned. This means
1378  # that we can ignore comments for the purposes of dealing
1379  # with the continued line and then handle them as though they
1380  # follow on after the single line constructed from the multiple
1381  # continued lines.
1382  while _is_fix_cont(
1383  self.get_next_line(ignore_empty=True, ignore_comments=True)
1384  ):
1385  # handle fix format line continuations for F77 code
1386  line = get_single_line()
1387  lines.append(line[6:72])
1388  return self.line_item(
1389  "".join(lines), startlineno, self.linecount, label, name
1390  )
1391 
1392  handle_inline_comment = self.handle_inline_comment
1393 
1394  endlineno = self.linecount
1395  if self._format.is_fix and not is_f2py_directive:
1396  # handle inline comment
1397  newline, qc, had_comment = handle_inline_comment(line[6:], startlineno)
1398  have_comment |= had_comment
1399  lines = [newline]
1400  next_line = self.get_next_line()
1401 
1402  while _is_fix_cont(next_line) or _is_fix_comment(
1403  next_line, isstrict, self._format.f2py_enabled
1404  ):
1405  # handle fix format line continuations for F90 or
1406  # newer code. Mixing fix format and free format line
1407  # continuations is not allowed nor detected, just
1408  # eject warnings.
1409  line2 = get_single_line() # consume next_line as line2
1410  if _is_fix_comment(line2, isstrict, self._format.f2py_enabled):
1411  # handle fix format comments inside line continuations
1412  # after the line construction
1413  citem = self.comment_item(line2, self.linecount, self.linecount)
1414  self.fifo_item.append(citem)
1415  else:
1416  # line continuation
1417  newline, qc, had_comment = self.handle_inline_comment(
1418  line2[6:], self.linecount, qc
1419  )
1420  have_comment |= had_comment
1421  lines.append(newline)
1422  endlineno = self.linecount
1423  next_line = self.get_next_line()
1424  # no character continuation should follows now
1425  if qc is not None:
1426  message = "following character continuation: " + "{!r}, expected None."
1427  message = self.format_message(
1428  "ASSERTION FAILURE(fix)",
1429  message.format(qc),
1430  startlineno,
1431  self.linecount,
1432  )
1433  logging.getLogger(__name__).warning(message)
1434  if len(lines) > 1:
1435  for i in range(len(lines)):
1436  line = lines[i]
1437  if line.rstrip().endswith("&"):
1438  message = (
1439  "free format line continuation character "
1440  + "`&' detected in fix format code"
1441  )
1442  location = line.rfind("&") + 5
1443  message = self.format_warning_message(
1444  message, startlineno + i, startlineno + i, location
1445  )
1446  logging.getLogger(__name__).warning(message)
1447  return self.line_item("".join(lines), startlineno, endlineno, label, name)
1448 
1449  # line is free format or fixed format with f2py directive (that
1450  # will be interpretted as free format line).
1451 
1452  start_index = 0
1453  if self._format.is_fix:
1454  start_index = 6
1455  lines = []
1456  lines_append = lines.append
1457  put_item = self.fifo_item.append
1458  qc = None
1459  while line is not None:
1460  if start_index: # fix format code
1461  line, qc, had_comment = handle_inline_comment(
1462  line[start_index:], self.linecount, qc
1463  )
1464  have_comment |= had_comment
1465  is_f2py_directive = self.linecount in self.f2py_comment_lines
1466  else:
1467  # free format
1468  line_lstrip = line.lstrip()
1469  if lines:
1470  if line_lstrip.startswith("!"):
1471  # check for comment line within line continuation
1472  put_item(
1473  self.comment_item(
1474  line_lstrip, self.linecount, self.linecount
1475  )
1476  )
1477  have_comment = True
1478  line = get_single_line()
1479  continue
1480  elif line_lstrip == "":
1481  # skip blank lines within a line continuation
1482  line = get_single_line()
1483  continue
1484  else:
1485  # Extract label and/or construct name from line if
1486  # there is one.
1487  label, line = extract_label(line)
1488  name, line = extract_construct_name(line)
1489 
1490  line, qc, had_comment = handle_inline_comment(line, self.linecount, qc)
1491  have_comment |= had_comment
1492  is_f2py_directive = self.linecount in self.f2py_comment_lines
1493 
1494  i = line.rfind("&")
1495  if i != -1:
1496  line_i1_rstrip = line[i + 1 :].rstrip()
1497  if not lines:
1498  # first line
1499  if i == -1 or line_i1_rstrip:
1500  lines_append(line)
1501  break
1502  endlineno = self.linecount
1503  lines_append(line[:i])
1504  line = get_single_line()
1505  continue
1506  if i == -1 or line_i1_rstrip:
1507  # no line continuation follows
1508  i = len(line)
1509  k = -1
1510  if i != -1:
1511  # handle the beggining of continued line
1512  k = line[:i].find("&")
1513  if k != 1 and line[:k].lstrip():
1514  k = -1
1515  endlineno = self.linecount
1516  lines_append(line[k + 1 : i])
1517  if i == len(line):
1518  break
1519  line = get_single_line()
1520 
1521  if qc is not None:
1522  message = "following character continuation: {!r}, " + "expected None."
1523  message = self.format_message(
1524  "ASSERTION FAILURE(free)", message.format(qc), startlineno, endlineno
1525  )
1526  logging.getLogger(__name__).error(message)
1527  line_content = "".join(lines).strip()
1528  if line_content:
1529  return self.line_item(line_content, startlineno, endlineno, label, name)
1530  if label is not None:
1531  message = "Label must follow nonblank character (F2008:3.2.5_2)"
1532  self.warning(message)
1533  if name is not None:
1534  self.error("No construct following construct-name.")
1535 
1536  # If this point is reached, the line is a comment or is
1537  # blank. If it is a comment, it has been pushed onto the
1538  # fifo_item list.
1539  try:
1540  return self.fifo_item.pop(0)
1541  except IndexError:
1542  # A blank line is represented as an empty comment
1543  return Comment("", (startlineno, endlineno), self)
1544 
1545 
1547  """
1548  Constructs a FortranFileReader object from a file.
1549 
1550  :param file_candidate: A filename or file-like object.
1551  :param list include_dirs: Directories in which to look for inclusions.
1552  :param list source_only: Fortran source files to search for modules
1553  required by "use" statements.
1554  :param bool ignore_comments: Whether or not to ignore comments
1555 
1556  For example:
1557 
1558  >>> from fparser.common.readfortran import FortranFileReader
1559  >>> import os
1560  >>> reader = FortranFileReader(\'myfile.f90\')
1561 
1562  """
1563 
1564  def __init__(
1565  self, file_candidate, include_dirs=None, source_only=None, ignore_comments=True
1566  ):
1567  # The filename is used as a unique ID. This is then used to cache the
1568  # contents of the file. Obviously if the file changes content but not
1569  # filename, problems will ensue.
1570  #
1571  self._close_on_destruction = False
1572  if isinstance(file_candidate, str):
1573  self.id = file_candidate
1574  # The 'fparser-logging' handler for errors ensures that any invalid
1575  # characters in the input are skipped but logged.
1576  self.file = open(
1577  file_candidate, "r", encoding="UTF-8", errors="fparser-logging"
1578  )
1579  self._close_on_destruction = True
1580  elif hasattr(file_candidate, "read") and hasattr(
1581  file_candidate, "name"
1582  ): # Is likely a file
1583  self.id = file_candidate.name
1584  self.file = file_candidate
1585  else: # Probably not something we can deal with
1586  message = "FortranFileReader is used with a filename"
1587  message += " or file-like object."
1588  raise ValueError(message)
1589  mode = fparser.common.sourceinfo.get_source_info(file_candidate)
1590 
1591  FortranReaderBase.__init__(self, self.file, mode, ignore_comments)
1592 
1593  if include_dirs is None:
1594  self.include_dirs.insert(0, os.path.dirname(self.id))
1595  else:
1596  self.include_dirs = include_dirs[:]
1597  if source_only is not None:
1598  self.source_only = source_only[:]
1599 
1600  def __del__(self):
1601  if self._close_on_destruction:
1602  self.file.close()
1603 
1604  def close_source(self):
1605  self.file.close()
1606 
1607 
1609  """
1610  Reads Fortran source code as a string.
1611 
1612  :param str string: string to read
1613  :param list include_dirs: List of dirs to search for include files
1614  :param list source_only: Fortran source files to search for modules
1615  required by "use" statements.
1616  :param bool ignore_comments: Whether or not to ignore comments
1617 
1618  For example:
1619 
1620  >>> from fparser.common.readfortran import FortranStringReader
1621  >>> code = \'\'\'
1622  subroutine foo(a)
1623  integer a
1624  print*,\"a=\",a
1625  end
1626  \'\'\'
1627  >>> reader = FortranStringReader(code)
1628 
1629  """
1630 
1631  def __init__(
1632  self, string, include_dirs=None, source_only=None, ignore_comments=True
1633  ):
1634  # The Python ID of the string was used to uniquely identify it for
1635  # caching purposes. Unfortunately this ID is only unique for the
1636  # lifetime of the string. In CPython it is the address of the string
1637  # and the chance of a new string being allocated to the same address
1638  # is actually quite high. Particularly in a unit-testing scenario.
1639  #
1640  # For this reason the hash is used instead. A much better solution
1641  # anyway.
1642  #
1643  self.id = "string-" + str(hash(string))
1644  source = StringIO(string)
1646  FortranReaderBase.__init__(self, source, mode, ignore_comments)
1647  if include_dirs is not None:
1648  self.include_dirs = include_dirs[:]
1649  if source_only is not None:
1650  self.source_only = source_only[:]
def format_warning_message(self, message, startlineno, endlineno, startcolno=0, endcolno=-1)
def isempty(self, ignore_comments=False)
Definition: readfortran.py:460
def get_next_line(self, ignore_empty=False, ignore_comments=None)
Definition: readfortran.py:726
def error(self, message, item=None)
def handle_multilines(self, line, startlineno, mlstr)
def warning(self, message, item=None)
def comment_item(self, comment, startlineno, endlineno)
Definition: readfortran.py:956
def copy(self, line=None, apply_map=False)
Definition: readfortran.py:322
def cpp_directive_item(self, line, startlineno, endlineno)
Definition: readfortran.py:960
def format_message(self, kind, message, startlineno, endlineno, startcolno=0, endcolno=-1)
Definition: readfortran.py:978
def handle_inline_comment(self, line, lineno, quotechar=None)
def _next(self, ignore_comments=None)
Definition: readfortran.py:854
def get_line(self, apply_map=False)
Definition: readfortran.py:363
def get_source_info_str(source)
Definition: sourceinfo.py:230
def get_source_info(file_candidate)
Definition: sourceinfo.py:266
def info(self, message, item=None)
def next(self, ignore_comments=None)
Definition: readfortran.py:770
def format_error_message(self, message, startlineno, endlineno, startcolno=0, endcolno=-1)
def get_single_line(self, ignore_empty=False, ignore_comments=None)
Definition: readfortran.py:664
def multiline_item(self, prefix, lines, suffix, startlineno, endlineno, errmessage=None)
Definition: readfortran.py:948
def isempty(self, ignore_comments=False)
Definition: readfortran.py:506
def get_item(self, ignore_comments=None)
Definition: readfortran.py:746
def line_item(self, line, startlineno, endlineno, label, name, errmessage=None)
Definition: readfortran.py:938