fparser Reference Guide  0.0.14
utils.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 """Base classes and exception handling for Fortran parser.
67 
68 """
69 # Original author: Pearu Peterson <pearu@cens.ioc.ee>
70 # First version created: Oct 2006
71 
72 import re
73 from fparser.common import readfortran
74 from fparser.common.splitline import string_replace_map
75 from fparser.common.readfortran import FortranReaderBase
76 from fparser.two.symbol_table import SYMBOL_TABLES
77 
78 # A list of supported extensions to the standard(s)
79 
80 # An X edit descriptor in a format statement specifies the position
81 # (forward from the current position) at which the next character will
82 # be transmitted to or from a record. In standard Fortran2003 the X
83 # edit descriptor must be preceeded by an integer which specifies how
84 # far forward from the current position. The 'x-format' extension
85 # allows the X edit descriptor to be specified without a preceeding
86 # integer.
87 EXTENSIONS = ["x-format"]
88 
89 # Cray pointers are a well known extension to the Fortran
90 # standard. See http://pubs.cray.com/content/S-3901/8.6/
91 # cray-fortran-reference-manual-s-3901-86/types or
92 # https://gcc.gnu.org/onlinedocs/gfortran/Cray-pointers.html for
93 # example. If 'cray-pointer' is specified in EXTENSIONS then this
94 # extension is allowed in fparser.
95 EXTENSIONS += ["cray-pointer"]
96 
97 # A Hollerith constant is a way of specifying a string as a sequence
98 # of characters preceded by the string length and separated by an 'H'
99 # e.g. 5Hhello. See
100 # https://gcc.gnu.org/onlinedocs/gfortran/Hollerith-constants-support.html,
101 # for example for more details. fparser currently supports Hollerith
102 # constants specified in format statements when 'hollerith' is specified
103 # in the EXTENSIONS list.
104 EXTENSIONS += ["hollerith"]
105 
106 # Many compilers support the use of '$' in a fortran write statement
107 # to indicate that the carriage return should be suppressed. This is
108 # an extension to the Fortran standard and is supported by fparser if
109 # 'dollar-descriptor' is specified in the EXTENSIONS list.
110 EXTENSIONS += ["dollar-descriptor"]
111 
112 
113 class FparserException(Exception):
114  """Base class exception for fparser. This allows an external tool to
115  capture all exceptions if required.
116 
117  :param str info: a string giving contextual error information.
118 
119  """
120 
121  def __init__(self, info):
122  Exception.__init__(self, info)
123 
124 
126  """An exception indicating that a particular rule implemented by a
127  class does not match the provided string. It does not necessary
128  mean there is an error as another rule may match. This exception
129  is used internally so should never be visible externally.
130 
131  """
132 
133 
134 class FortranSyntaxError(FparserException):
135  """An exception indicating that fparser believes the provided code to
136  be invalid Fortran. Also returns information about the location of
137  the error if that information is available.
138 
139  :param reader: input string or reader where the error took \
140  place. This is used to provide line number and line content \
141  information.
142  :type reader: str or :py:class:`FortranReaderBase`
143  :param str info: a string giving contextual error information.
144 
145  """
146 
147  def __init__(self, reader, info):
148  output = "at unknown location "
149  if isinstance(reader, FortranReaderBase):
150  output = "at line {0}\n>>>{1}\n".format(
151  reader.linecount, reader.source_lines[reader.linecount - 1]
152  )
153  if info:
154  output += "{0}".format(info)
155  FparserException.__init__(self, output)
156 
157 
159  """An exception indicating that an unexpected error has occured in the
160  parser.
161 
162  :param str info: a string giving contextual error information.
163 
164  """
165 
166  def __init__(self, info):
167  new_info = "'{0}'. Please report this to the " "authors.".format(info)
168  FparserException.__init__(self, new_info)
169 
170 
172  """An exception indicating that a syntax error has been found by the
173  parser. This is used instead of `FortranSyntaxError` when the
174  reader object is not available.
175 
176  """
177 
178 
179 def show_result(func):
180  return func
181 
182  def new_func(cls, string, **kws):
183  r = func(cls, string, **kws)
184  if r is not None and isinstance(r, StmtBase):
185  print("%s(%r) -> %r" % (cls.__name__, string, str(r)))
186  return r
187 
188  return new_func
189 
190 
191 #
192 # BASE CLASSES
193 #
194 
195 
197  """Mixin class to provide rich comparison operators.
198 
199  This mixin provides a set of rich comparison operators. Each class using
200  this mixin has to provide a _cmpkey() method that returns a key of objects
201  that can be compared.
202 
203  See also http://python3porting.com/preparing.html#richcomparisons
204  """
205 
206  # pylint: disable=too-few-public-methods
207 
208  def _compare(self, other, method):
209  """Call the method, if other is able to be used within it.
210 
211  :param object other: The other object to compare with
212  :type other: object
213  :param method: The method to call to compare self and other.
214  :type method: LambdaType
215  :return: NotImplemented, when the comparison for the given type
216  combination can't be performed.
217  :rtype: :py:type:`NotImplementedType`
218  """
219  try:
220  # This routine's purpose is to access the protected method
221  # _cmpkey() from client classes, therefore: pylint:
222  # disable=protected-access
223  return method(self._cmpkey(), other._cmpkey())
224  except (AttributeError, TypeError):
225  # _cmpkey not implemented, or return different type,
226  # so I can't compare with "other".
227  # According to the Python Language Reference Manual
228  # (http://www.network-theory.co.uk/docs/pylang/Coercionrules.html)
229  # return NotImplemented
230  return NotImplemented
231 
232  def __lt__(self, other):
233  return self._compare(other, lambda s, o: s < o)
234 
235  def __le__(self, other):
236  return self._compare(other, lambda s, o: s <= o)
237 
238  def __eq__(self, other):
239  return self._compare(other, lambda s, o: s == o)
240 
241  def __ge__(self, other):
242  return self._compare(other, lambda s, o: s >= o)
243 
244  def __gt__(self, other):
245  return self._compare(other, lambda s, o: s > o)
246 
247  def __ne__(self, other):
248  return self._compare(other, lambda s, o: s != o)
249 
250 
252  """This class imports a set of fparser.two dependencies that can not
253  be imported during the Python Import time because they have a circular
254  dependency with this file.
255 
256  They are imported once when the Fortran2003 is already processed by
257  calling the import_now() method.
258 
259  The alternative is to have the equivalent top-level imports in the
260  Base.__new__ method, but this method is in the parser critical path and
261  is best to keep expensive operations out of it.
262  """
263 
264  @staticmethod
265  def import_now():
266  """Execute the Import of Fortran2003 dependencies."""
267  # pylint: disable=import-outside-toplevel
268  from fparser.two.Fortran2003 import (
269  Else_If_Stmt,
270  Else_Stmt,
271  End_If_Stmt,
272  Masked_Elsewhere_Stmt,
273  Elsewhere_Stmt,
274  End_Where_Stmt,
275  Type_Guard_Stmt,
276  End_Select_Type_Stmt,
277  Case_Stmt,
278  End_Select_Stmt,
279  Comment,
280  Include_Stmt,
281  add_comments_includes_directives,
282  )
283  from fparser.two import C99Preprocessor
284 
285  DynamicImport.Else_If_Stmt = Else_If_Stmt
286  DynamicImport.Else_Stmt = Else_Stmt
287  DynamicImport.End_If_Stmt = End_If_Stmt
288  DynamicImport.Masked_Elsewhere_Stmt = Masked_Elsewhere_Stmt
289  DynamicImport.Elsewhere_Stmt = Elsewhere_Stmt
290  DynamicImport.End_Where_Stmt = End_Where_Stmt
291  DynamicImport.Type_Guard_Stmt = Type_Guard_Stmt
292  DynamicImport.End_Select_Type_Stmt = End_Select_Type_Stmt
293  DynamicImport.Case_Stmt = Case_Stmt
294  DynamicImport.End_Select_Stmt = End_Select_Stmt
295  DynamicImport.Comment = Comment
296  DynamicImport.Include_Stmt = Include_Stmt
297  DynamicImport.C99Preprocessor = C99Preprocessor
298  DynamicImport.add_comments_includes_directives = (
299  add_comments_includes_directives
300  )
301 
302 
303 di = DynamicImport()
304 
305 
306 def _set_parent(parent_node, items):
307  """ Recursively set the parent of all of the elements
308  in the list that are a sub-class of Base. (Recursive because
309  sometimes the list of elements itself contains a list or tuple.)
310 
311  :param parent_node: the parent of the nodes listed in `items`.
312  :type parent_node: sub-class of :py:class:`fparser.two.utils.Base`
313  :param items: list or tuple of nodes for which to set the parent.
314  :type items: list or tuple of :py:class:`fparser.two.utils.Base` \
315  or `str` or `list` or `tuple` or NoneType.
316  """
317  for item in items:
318  if item:
319  if isinstance(item, Base):
320  # We can only set the parent of `Base` objects.
321  # Anything else (e.g. str) is passed over.
322  item.parent = parent_node
323  elif isinstance(item, (list, tuple)):
324  _set_parent(parent_node, item)
325 
326 
328  """ Base class for Fortran 2003 syntax rules.
329 
330  All Base classes have the following attributes:
331  self.string - original argument to construct a class instance, its type \
332  is either str or FortranReaderBase.
333  self.item - Line instance (holds label) or None.
334 
335  :param type cls: the class of object to create.
336  :param string: (source of) Fortran string to parse.
337  :type string: `str` or \
338  :py:class:`fparser.common.readfortran.FortranReaderBase`
339  :param parent_cls: the parent class of this object.
340  :type parent_cls: `type`
341 
342  """
343 
344  # This dict of subclasses is populated dynamically by code at the end
345  # of this module. That code uses the entries in the
346  # 'subclass_names' list belonging to each class defined in this module.
347  subclasses = {}
348 
349  def __init__(self, string, parent_cls=None):
350  # pylint:disable=unused-argument
351  self.parent = None
352 
353  @show_result
354  def __new__(cls, string, parent_cls=None):
355 
356  if parent_cls is None:
357  parent_cls = [cls]
358  elif cls not in parent_cls:
359  parent_cls.append(cls)
360 
361  # Get the class' match method if it has one
362  match = getattr(cls, "match") if hasattr(cls, "match") else None
363 
364  if (
365  isinstance(string, FortranReaderBase)
366  and match
367  and not issubclass(cls, BlockBase)
368  ):
369  reader = string
370  item = reader.get_item()
371  if item is None:
372  return None
373  if isinstance(item, readfortran.Comment):
374  # We got a comment but we weren't after a comment (we handle
375  # those in Comment.__new__)
376  obj = None
377  else:
378  try:
379  obj = item.parse_line(cls, parent_cls)
380  except NoMatchError:
381  obj = None
382  if obj is None:
383  # No match so give the item back to the reader
384  reader.put_item(item)
385  return None
386  obj.item = item
387  return obj
388 
389  result = None
390  if match:
391  # IMPORTANT: if string is FortranReaderBase then cls must
392  # restore readers content when no match is found.
393  try:
394  result = cls.match(string)
395  except NoMatchError as msg:
396  if str(msg) == "%s: %r" % (cls.__name__, string):
397  # avoid recursion 1.
398  raise
399 
400  if isinstance(result, tuple):
401  obj = object.__new__(cls)
402  obj.string = string
403  obj.item = None
404  # Set-up parent information for the results of the match
405  _set_parent(obj, result)
406  if hasattr(cls, "init"):
407  obj.init(*result)
408  return obj
409  if isinstance(result, Base):
410  return result
411  if result is None:
412  # Loop over the possible sub-classes of this class and
413  # check for matches
414  for subcls in Base.subclasses.get(cls.__name__, []):
415  if subcls in parent_cls: # avoid recursion 2.
416  continue
417  try:
418  obj = subcls(string, parent_cls=parent_cls)
419  except NoMatchError:
420  obj = None
421  if obj is not None:
422  return obj
423  else:
424  raise AssertionError(repr(result))
425  # If we get to here then we've failed to match the current line
426  if isinstance(string, FortranReaderBase):
427  content = False
428  for index in range(string.linecount):
429  # Check all lines up to this one for content. We
430  # should be able to only check the current line but
431  # but as the line number returned is not always
432  # correct (due to coding errors) we cannot assume the
433  # line pointed to is the line where the error actually
434  # happened.
435  if string.source_lines[index].strip():
436  content = True
437  break
438  if not content:
439  # There are no lines in the input or all lines up to
440  # this one are empty or contain only white space. This
441  # is typically accepted by fortran compilers so we
442  # follow their lead and do not raise an exception.
443  return
444  line = string.source_lines[string.linecount - 1]
445  errmsg = f"at line {string.linecount}\n>>>{line}\n"
446  else:
447  errmsg = f"{cls.__name__}: '{string}'"
448  raise NoMatchError(errmsg)
449 
450  def get_root(self):
451  """
452  Gets the node at the root of the parse tree to which this node belongs.
453 
454  :returns: the node at the root of the parse tree.
455  :rtype: :py:class:`fparser.two.utils.Base`
456 
457  """
458  current = self
459  while current.parent:
460  current = current.parent
461  return current
462 
463  @property
464  def children(self):
465  """Return an iterable containing the immediate children of this node in
466  the parse tree.
467 
468  If this node represents an expression then its children are
469  contained in a tuple which is immutable. Therefore, the
470  manipulation of the children of such a node must be done by
471  replacing the `items` property of the node directly rather than via the
472  objects returned by this method.
473 
474  :returns: the immediate children of this node.
475  :rtype: list or tuple containing zero or more of \
476  :py:class:`fparser.two.utils.Base` or NoneType or str
477 
478  """
479  child_list = getattr(self, "content", None)
480  if child_list is None:
481  child_list = getattr(self, "items", [])
482  return child_list
483 
484  def init(self, *items):
485  """
486  Store the supplied list of nodes in the `items` list of this node.
487 
488  :param items: the children of this node.
489  :type items: tuple of :py:class:`fparser.two.utils.Base`
490 
491  """
492  self.items = items
493 
494  def torepr(self):
495  return "%s(%s)" % (self.__class__.__name__, ", ".join(map(repr, self.items)))
496 
497  def __str__(self):
498  return self.tostr()
499 
500  def __repr__(self):
501  return self.torepr()
502 
503  def _cmpkey(self):
504  """Provides a key of objects to be used for comparing."""
505  return self.items
506 
507  def tofortran(self, tab="", isfix=None):
508  """
509  Produce the Fortran representation of this Comment.
510 
511  :param str tab: characters to pre-pend to output.
512  :param bool isfix: whether or not this is fixed-format code.
513 
514  :returns: Fortran representation of this comment.
515  :rtype: str
516  """
517  this_str = str(self)
518  if this_str.strip():
519  return tab + this_str
520  # If this_str is empty (i.e this Comment is a blank line) then
521  # don't prepend any spaces to it
522  return this_str
523 
524  def restore_reader(self, reader):
525  reader.put_item(self.item)
526 
527 
529  """
530  Base class for matching all block constructs.
531 
532  <block-base> = [ <startcls> ]
533  [ <subcls> ]...
534  ...
535  [ <subcls> ]...
536  [ <endcls> ]
537 
538  """
539 
540  @staticmethod
541  def match(
542  startcls,
543  subclasses,
544  endcls,
545  reader,
546  match_labels=False,
547  match_names=False,
548  match_name_classes=(),
549  enable_do_label_construct_hook=False,
550  enable_if_construct_hook=False,
551  enable_where_construct_hook=False,
552  strict_order=False,
553  strict_match_names=False,
554  ):
555  """
556  Checks whether the content in reader matches the given
557  type of block statement (e.g. DO..END DO, IF...END IF etc.)
558 
559  :param type startcls: the class marking the beginning of the block
560  :param list subclasses: list of classes that can be children of \
561  the block.
562  :param type endcls: the class marking the end of the block.
563  :param reader: content to check for match.
564  :type reader: str or instance of :py:class:`FortranReaderBase`
565  :param bool match_labels: whether or not the statement terminating \
566  the block must have a label that matches the opening statement. \
567  Default is False.
568  :param bool match_names: TBD
569  :param tuple match_name_classes: TBD
570  :param bool enable_do_label_construct_hook: TBD
571  :param bool enable_if_construct_hook: TBD
572  :param bool enable_where_construct_hook: TBD
573  :param bool strict_order: whether to enforce the order of the \
574  given subclasses.
575  :param bool strict_match_names: if start name present, end name \
576  must exist and match.
577 
578  :return: instance of startcls or None if no match is found
579  :rtype: startcls
580 
581  """
582  # This implementation uses the DynamicImport class and its instance di
583  # to access the Fortran2003 and C99Preprocessor classes, this is a
584  # performance optimization to avoid importing the classes inside this
585  # method since it is in the hotpath (and it can't be done in the
586  # top-level due to circular dependencies).
587  assert isinstance(reader, FortranReaderBase), repr(reader)
588  content = []
589 
590  if startcls is not None:
591  # Deal with any preceding comments, includes, and/or directives
592  DynamicImport.add_comments_includes_directives(content, reader)
593  # Now attempt to match the start of the block
594  try:
595  obj = startcls(reader)
596  except NoMatchError:
597  obj = None
598  if obj is None:
599  # Ultimately we failed to find a match for the
600  # start of the block so put back any comments that
601  # we processed along the way
602  for obj in reversed(content):
603  obj.restore_reader(reader)
604  return
605  if startcls in SYMBOL_TABLES.scoping_unit_classes:
606  # We are entering a new scoping unit so create a new
607  # symbol table.
608  # NOTE: if the match subsequently fails then we must
609  # delete this symbol table.
610  table_name = str(obj.children[1])
611  SYMBOL_TABLES.enter_scope(table_name)
612  # Store the index of the start of this block proper (i.e.
613  # excluding any comments)
614  start_idx = len(content)
615  content.append(obj)
616 
617  if hasattr(obj, "get_start_label") and enable_do_label_construct_hook:
618  start_label = obj.get_start_label()
619  if match_names:
620  start_name = obj.get_start_name()
621 
622  # Comments and Include statements are always valid sub-classes
623  classes = subclasses + [di.Comment, di.Include_Stmt]
624  # Preprocessor directives are always valid sub-classes
625  cpp_classes = [
626  getattr(di.C99Preprocessor, cls_name)
627  for cls_name in di.C99Preprocessor.CPP_CLASS_NAMES
628  ]
629  classes += cpp_classes
630  if endcls is not None:
631  classes += [endcls]
632  endcls_all = tuple([endcls] + endcls.subclasses[endcls.__name__])
633 
634  try:
635  # Start trying to match the various subclasses, starting from
636  # the beginning of the list (where else?)
637  i = 0
638  had_match = False
639  found_end = False
640  while i < len(classes):
641  if enable_do_label_construct_hook:
642  # Multiple, labelled DO statements can reference the
643  # same label.
644  obj = startcls(reader)
645  if obj is not None and hasattr(obj, "get_start_label"):
646  if start_label == obj.get_start_label():
647  content.append(obj)
648  continue
649  obj.restore_reader(reader)
650  # Attempt to match the i'th subclass
651  cls = classes[i]
652  try:
653  obj = cls(reader)
654  except NoMatchError:
655  obj = None
656  if obj is None:
657  # No match for this class, continue checking the list
658  # starting from the i+1'th...
659  i += 1
660  continue
661 
662  # We got a match for this class
663  had_match = True
664  content.append(obj)
665 
666  if match_names and isinstance(obj, match_name_classes):
667  end_name = obj.get_end_name()
668  if end_name and not start_name:
669  raise FortranSyntaxError(
670  reader,
671  f"Name '{end_name}' has no corresponding starting name",
672  )
673  if (
674  end_name
675  and start_name
676  and end_name.lower() != start_name.lower()
677  ):
678  raise FortranSyntaxError(
679  reader, f"Expecting name '{start_name}', got '{end_name}'"
680  )
681 
682  if endcls is not None and isinstance(obj, endcls_all):
683  if match_labels:
684  start_label, end_label = (
685  content[start_idx].get_start_label(),
686  content[-1].get_end_label(),
687  )
688  if start_label != end_label:
689  continue
690  if match_names:
691  start_name, end_name = (
692  content[start_idx].get_start_name(),
693  content[-1].get_end_name(),
694  )
695 
696  if end_name and not start_name:
697  raise FortranSyntaxError(
698  reader,
699  f"Name '{end_name}' has no corresponding starting name",
700  )
701  elif strict_match_names and start_name and not end_name:
702  raise FortranSyntaxError(
703  reader, f"Expecting name '{start_name}' but none given"
704  )
705  elif (
706  start_name
707  and end_name
708  and (start_name.lower() != end_name.lower())
709  ):
710  raise FortranSyntaxError(
711  reader,
712  f"Expecting name '{start_name}', got '{end_name}'",
713  )
714  # We've found the enclosing end statement so break out
715  found_end = True
716  break
717  if not strict_order:
718  # Return to start of classes list now that we've matched.
719  i = 0
720  if enable_if_construct_hook:
721  if isinstance(obj, di.Else_If_Stmt):
722  # Got an else-if so go back to start of possible
723  # classes to match
724  i = 0
725  if isinstance(obj, (di.Else_Stmt, di.End_If_Stmt)):
726  # Found end-if
727  enable_if_construct_hook = False
728  if enable_where_construct_hook:
729  if isinstance(obj, di.Masked_Elsewhere_Stmt):
730  i = 0
731  if isinstance(obj, (di.Elsewhere_Stmt, di.End_Where_Stmt)):
732  enable_where_construct_hook = False
733  continue
734 
735  except FortranSyntaxError as err:
736  # We hit trouble so clean up the symbol table
737  if startcls in SYMBOL_TABLES.scoping_unit_classes:
738  SYMBOL_TABLES.exit_scope()
739  # Remove any symbol table that we created
740  SYMBOL_TABLES.remove(table_name)
741  raise err
742 
743  if startcls in SYMBOL_TABLES.scoping_unit_classes:
744  SYMBOL_TABLES.exit_scope()
745 
746  if not had_match or endcls and not found_end:
747  # We did not get a match from any of the subclasses or
748  # failed to find the endcls
749  if endcls is not None:
750  if startcls in SYMBOL_TABLES.scoping_unit_classes:
751  # Remove any symbol table that we created
752  SYMBOL_TABLES.remove(table_name)
753  for obj in reversed(content):
754  obj.restore_reader(reader)
755  return None
756 
757  if not content:
758  # We can only get to here if startcls is None - if startcls is not
759  # None and fails to match then we will already have returned. If
760  # it is not None and matches then content will not be empty.
761  # Since startcls must be None, we won't have created a symbol
762  # table so we don't have to clean up.
763  return None
764 
765  if startcls is not None and endcls is not None:
766  # check names of start and end statements:
767  start_stmt = content[start_idx]
768  end_stmt = content[-1]
769  if (
770  isinstance(end_stmt, endcls_all)
771  and hasattr(end_stmt, "get_name")
772  and hasattr(start_stmt, "get_name")
773  ):
774  if end_stmt.get_name() is not None:
775  if (
776  start_stmt.get_name().string.lower()
777  != end_stmt.get_name().string.lower()
778  ):
779  end_stmt.item.reader.error(
780  "expected <%s-name> is %s but got %s. Ignoring."
781  % (
782  end_stmt.get_type().lower(),
783  start_stmt.get_name(),
784  end_stmt.get_name(),
785  )
786  )
787  return (content,)
788 
789  def init(self, content):
790  """
791  Initialise the `content` attribute with the list of child nodes.
792 
793  :param content: list of nodes that are children of this one.
794  :type content: list of :py:class:`fparser.two.utils.Base` or NoneType
795 
796  """
797  self.content = content
798 
799  def _cmpkey(self):
800  """Provides a key of objects to be used for comparing."""
801  return self.content
802 
803  def tostr(self):
804  return self.tofortran()
805 
806  def torepr(self):
807  return "%s(%s)" % (self.__class__.__name__, ", ".join(map(repr, self.content)))
808 
809  def tofortran(self, tab="", isfix=None):
810  """
811  Create a string containing the Fortran representation of this class
812 
813  :param str tab: indent to prefix to code.
814  :param bool isfix: whether or not to generate fixed-format code.
815 
816  :return: Fortran representation of this class.
817  :rtype: str
818  """
819  mylist = []
820  start = self.content[0]
821  end = self.content[-1]
822  extra_tab = ""
823  if isinstance(end, EndStmtBase):
824  extra_tab = " "
825  if start is not None:
826  mylist.append(start.tofortran(tab=tab, isfix=isfix))
827  for item in self.content[1:-1]:
828  mylist.append(item.tofortran(tab=tab + extra_tab, isfix=isfix))
829  if len(self.content) > 1:
830  mylist.append(end.tofortran(tab=tab, isfix=isfix))
831  return "\n".join(mylist)
832 
833  def restore_reader(self, reader):
834  for obj in reversed(self.content):
835  obj.restore_reader(reader)
836 
837 
839  """
840  Match one or more fparser2 rules separated by a defined separator.
841 
842  sequence-base is obj [sep obj ] ...
843 
844  """
845 
846  @staticmethod
847  def match(separator, subcls, string):
848  """Match one or more 'subcls' fparser2 rules in the string 'string'
849  separated by 'separator'.
850 
851  :param str separator: the separator used to split the supplied \
852  string.
853  :param subcls: an fparser2 object representing the rule that \
854  should be matched.
855  :type subcls: subclass of :py:class:`fparser.two.utils.Base`
856  :param str string: the input string to match.
857 
858  :returns: a tuple containing 1) the separator and 2) the \
859  matched objects in a tuple, or None if there is no match.
860  :rtype: (str, (Subclass of \
861  :py:class:`fparser.two.utils.Base`)) or NoneType
862 
863  :raises InternalError: if the separator or string arguments \
864  are not the expected type.
865  :raises InternalError: if the separator is white space.
866 
867  """
868  if not isinstance(separator, str):
869  raise InternalError(
870  f"SequenceBase class match method argument separator expected "
871  f"to be a string but found '{type(separator)}'."
872  )
873  if not isinstance(string, str):
874  raise InternalError(
875  f"SequenceBase class match method argument string expected to "
876  f"be a string but found '{type(string)}'."
877  )
878 
879  if separator == " ":
880  raise InternalError(
881  "SequenceBase class match method argument separator cannot "
882  "be white space."
883  )
884 
885  line, repmap = string_replace_map(string)
886  splitted = line.split(separator)
887  if not splitted:
888  # There should be at least one entry.
889  return None
890 
891  lst = [subcls(repmap(entry.strip())) for entry in splitted]
892 
893  return separator, tuple(lst)
894 
895  def init(self, separator, items):
896  """Store the result of the match method if the match is successful.
897 
898  :param str separator: the separator used to split the supplied string.
899  :param items: a tuple containing the matched objects.
900  :type items: tuple(Subclass of :py:class:`fparser.two.utils.Base`)
901 
902  """
903  self.separator = separator
904  self.items = items
905 
906  def tostr(self):
907  """
908  :returns: The Fortran representation of this object as a string.
909  :rtype: str
910 
911  """
912  sep = self.separator
913  if sep == ",":
914  sep = sep + " "
915  elif sep == " ":
916  pass
917  else:
918  sep = " " + sep + " "
919  return sep.join(map(str, self.items))
920 
921  def torepr(self):
922  """
923  :returns: The Python representation of this object as a string.
924  :rtype: str
925 
926  """
927  return "{0}('{1}', {2})".format(
928  self.__class__.__name__, self.separator, self.items
929  )
930 
931  # The mixin class is likely to be removed so _cmpkey would not be
932  # needed. It is not used at the moment. It is only commented out
933  # at this point, rather than removed, in case it turns out that
934  # the mixin class is useful.
935  # def _cmpkey(self):
936  # """ Provides a key of objects to be used for comparing.
937  # """
938  # return (self.separator, self.items)
939 
940 
942  """
943  ::
944  <unary-op-base> = <unary-op> <rhs>
945  """
946 
947  def tostr(self):
948  return "%s %s" % tuple(self.items)
949 
950  @staticmethod
951  def match(op_pattern, rhs_cls, string, exclude_op_pattern=None):
952  m = op_pattern.match(string)
953  if not m:
954  return
955  rhs = string[m.end() :].lstrip()
956  if not rhs:
957  return
958  op = string[: m.end()].rstrip().upper()
959  if exclude_op_pattern is not None:
960  if exclude_op_pattern.match(op):
961  return
962  return op, rhs_cls(rhs)
963 
964 
966  """binary-op-base is lhs op rhs
967 
968  Splits the input text into text to the left of the matched
969  operator and text to the right of the matched operator and tries
970  to match the lhs text with the supplied lhs class rule and the rhs
971  text with the supplied rhs class rule.
972 
973  """
974 
975  @staticmethod
976  def match(
977  lhs_cls, op_pattern, rhs_cls, string, right=True, exclude_op_pattern=None
978  ):
979  """Matches the binary-op-base rule.
980 
981  If the operator defined by argument 'op_pattern' is found in
982  the string provided in argument 'string' then the text to the
983  left-hand-side of the operator is matched with the class rule
984  provided in the 'lhs_cls' argument and the text to the
985  right-hand-side of the operator is matched with the class rule
986  provided in the 'rhs_cls' argument.
987 
988  If the optional 'right' argument is set to true (the default)
989  then, in the case where the pattern matches multiple times in
990  the input string, the right-most match will be chosen. If the
991  'right' argument is set to false then the left-most match will
992  be chosen.
993 
994  if a pattern is provided to the optional 'exclude_op_pattern'
995  argument then there will be no match if the pattern matched by
996  the 'op_pattern' argument also matches this pattern. The
997  default (None) does nothing.
998 
999  :param lhs_cls: an fparser2 object representing the rule that \
1000  should be matched to the lhs text.
1001  :type lhs_cls: subclass of :py:class:`fparser.two.utils.Base`
1002  :param op_pattern: the pattern to match.
1003  :type op_pattern: `str` or \
1004  :py:class:`fparser.two.pattern_tools.Pattern`
1005  :param rhs_cls: an fparser2 object representing the rule that \
1006  should be matched to the rhs text.
1007  :type rhs_cls: subclass of :py:class:`fparser.two.utils.Base`
1008  :param str string: the string to match with the pattern and \
1009  lhs and rhs rules.
1010  :param bool right: in the case where there are multiple \
1011  matches to the pattern in the string this optional \
1012  argument specifies whether the righmost pattern match \
1013  should be chosen (True, the default) or whether the \
1014  leftmost pattern should be chosen (False).
1015  :param exclude_op_pattern: optional argument which specifies a \
1016  particular subpattern to exclude from the match. Defaults \
1017  to None which means there is no subpattern.
1018  :type exclude_op_pattern: :py:class:`fparser.two.pattern_tools.Pattern`
1019 
1020  :returns: a tuple containing the matched lhs, the operator and \
1021  the matched rhs of the input string or None if there is \
1022  no match.
1023  :rtype: (:py:class:`fparser.two.utils.Base`, str, \
1024  :py:class:`fparser.two.utils.Base`) or NoneType
1025 
1026  """
1027  line, repmap = string_replace_map(string)
1028 
1029  if isinstance(op_pattern, str):
1030  if right:
1031  text_split = line.rsplit(op_pattern, 1)
1032  else:
1033  text_split = line.split(op_pattern, 1)
1034  if len(text_split) != 2:
1035  return None
1036  lhs, rhs = text_split[0].rstrip(), text_split[1].lstrip()
1037  oper = op_pattern
1038  else:
1039  if right:
1040  text_split = op_pattern.rsplit(line)
1041  else:
1042  text_split = op_pattern.lsplit(line)
1043  if not text_split or len(text_split) != 3:
1044  return None
1045  lhs, oper, rhs = text_split
1046  lhs = lhs.rstrip()
1047  rhs = rhs.lstrip()
1048  oper = oper.upper()
1049  if not lhs or not rhs:
1050  return None
1051  if exclude_op_pattern and exclude_op_pattern.match(oper):
1052  return None
1053 
1054  # Matching the shorter text first can be much more efficient
1055  # for complex expressions.
1056  if right:
1057  # The split is closest to the right so try to match the
1058  # RHS first.
1059  rhs_obj = rhs_cls(repmap(rhs))
1060  lhs_obj = lhs_cls(repmap(lhs))
1061  else:
1062  # The split is closest to the left so try to match the LHS
1063  # first.
1064  lhs_obj = lhs_cls(repmap(lhs))
1065  rhs_obj = rhs_cls(repmap(rhs))
1066 
1067  return (lhs_obj, oper.replace(" ", ""), rhs_obj)
1068 
1069  def tostr(self):
1070  """Return the string representation of this object. Uses join() which
1071  is efficient and can make a big performance difference for
1072  complex expressions.
1073 
1074  :returns: the string representation of this object.
1075  :rtype: str
1076 
1077  """
1078  return " ".join([str(self.items[0]), str(self.items[1]), str(self.items[2])])
1079 
1080 
1082  """
1083  ::
1084  <separator-base> = [ <lhs> ] : [ <rhs> ]
1085  """
1086 
1087  @staticmethod
1088  def match(lhs_cls, rhs_cls, string, require_lhs=False, require_rhs=False):
1089  line, repmap = string_replace_map(string)
1090  if ":" not in line:
1091  return
1092  lhs, rhs = line.split(":", 1)
1093  lhs = lhs.rstrip()
1094  rhs = rhs.lstrip()
1095  lhs_obj, rhs_obj = None, None
1096  if lhs:
1097  if lhs_cls is None:
1098  return
1099  lhs_obj = lhs_cls(repmap(lhs))
1100  elif require_lhs:
1101  return
1102  if rhs:
1103  if rhs_cls is None:
1104  return
1105  rhs_obj = rhs_cls(repmap(rhs))
1106  elif require_rhs:
1107  return
1108  return lhs_obj, rhs_obj
1109 
1110  def tostr(self):
1111  s = ""
1112  if self.items[0] is not None:
1113  s += "%s :" % (self.items[0])
1114  else:
1115  s += ":"
1116  if self.items[1] is not None:
1117  s += " %s" % (self.items[1])
1118  return s
1119 
1120 
1122  """
1123 
1124  keyword-value-base is [ lhs = ] rhs
1125 
1126  where:
1127 
1128  R215 keyword is name.
1129 
1130  """
1131 
1132  @staticmethod
1133  def match(lhs_cls, rhs_cls, string, require_lhs=True, upper_lhs=False):
1134  """
1135  Attempts to match the supplied `string` with `lhs_cls` = `rhs_cls`.
1136  If `lhs_cls` is a str then it is compared with the content to the
1137  left of the first '=' character in `string`. If that content is a
1138  valid Fortran name but does *not* match `lhs_cls` then the match
1139  fails, irrespective of the setting of `require_lhs`.
1140 
1141  :param lhs_cls: list, tuple or single value of classes to attempt to \
1142  match LHS against (in order), or string containing \
1143  keyword to match.
1144  :type lhs_cls: names of classes deriving from `:py:class:Base` or str
1145  :param rhs_cls: name of class to match RHS against.
1146  :type rhs_cls: name of a class deriving from `:py:class:Base`
1147  :param str string: text to be matched.
1148  :param bool require_lhs: whether the expression to be matched must \
1149  contain a LHS that is assigned to.
1150  :param bool upper_lhs: whether or not to convert the LHS of the \
1151  matched expression to upper case.
1152 
1153  :return: instances of the classes representing quantities on the LHS \
1154  and RHS (LHS is optional) or None if no match is found.
1155  :rtype: 2-tuple of objects or NoneType
1156 
1157  """
1158  if require_lhs and "=" not in string:
1159  return None
1160  if isinstance(lhs_cls, (list, tuple)):
1161  for cls in lhs_cls:
1162  obj = KeywordValueBase.match(
1163  cls, rhs_cls, string, require_lhs=require_lhs, upper_lhs=upper_lhs
1164  )
1165  if obj:
1166  return obj
1167  return obj
1168  # We can't just blindly check whether 'string' contains an '='
1169  # character as it could itself hold a string constant containing
1170  # an '=', e.g. FMT='("Hello = False")'.
1171  # Therefore we only split on the left-most '=' character
1172  pieces = string.split("=", 1)
1173  lhs = None
1174  if len(pieces) == 2:
1175  # It does contain at least one '='. Proceed to attempt to match
1176  # the content on the LHS of it.
1177  lhs = pieces[0].strip()
1178  if isinstance(lhs_cls, str):
1179  # lhs_cls is a keyword
1180  if upper_lhs:
1181  lhs = lhs.upper()
1182  if lhs != lhs_cls:
1183  # The content to the left of the '=' does not match the
1184  # supplied keyword
1185  lhs = None
1186  else:
1187  lhs = lhs_cls(lhs)
1188  if not lhs:
1189  # We haven't matched the LHS and therefore proceed to treat the
1190  # whole string as a RHS if the LHS is not strictly required.
1191  if require_lhs:
1192  return None
1193  rhs = string.strip()
1194  else:
1195  rhs = pieces[-1].strip()
1196  if rhs:
1197  rhs = rhs_cls(rhs)
1198  if not rhs:
1199  return None
1200  return lhs, rhs
1201 
1202  def tostr(self):
1203  if self.items[0] is None:
1204  return str(self.items[1])
1205  return "%s = %s" % tuple(self.items)
1206 
1207 
1209  """
1210  bracket-base is left-bracket something right-bracket.
1211 
1212  This class is able to cope with nested brackets as long as they
1213  are correctly nested. Brackets in strings are ignored.
1214 
1215  The 'something' can be specified as being optional.
1216 
1217  """
1218 
1219  @staticmethod
1220  def match(brackets, cls, string, require_cls=True):
1221  """A generic match method for all types of bracketed
1222  expressions.
1223 
1224  :param str brackets: the format of the left and right brackets \
1225  provided as a string, for example '()'
1226  :param cls: the class to match the content within the brackets \
1227  :type cls: subclass of :py:class:`fparser.two.utils.Base`
1228  :param str string: the content to match
1229  :param bool require_cls: whether the class and associated \
1230  content is mandatory (True) or optional (False). The default \
1231  is True.
1232  :return: None if there is no match, otherwise a tuple with the \
1233  first and third entries being strings containing the left and \
1234  right brackets respectively and the second entry being either \
1235  None or an instance of the class provided as the second \
1236  argument (cls).
1237  :rtype: 'NoneType', ( `str`, `NoneType`, `str`) or ( `str`, \
1238  `cls`, `str` )
1239 
1240  """
1241  if not cls and require_cls:
1242  return None
1243  if not string:
1244  return None
1245  string_strip = string.strip()
1246  if not brackets:
1247  return None
1248  brackets_nospc = brackets.replace(" ", "")
1249  if not brackets_nospc:
1250  return None
1251  if len(brackets_nospc) % 2 == 1:
1252  # LHS and RHS bracketing must be the same size
1253  return None
1254  bracket_len = len(brackets_nospc) // 2
1255  left = brackets_nospc[:bracket_len]
1256  right = brackets_nospc[-bracket_len:]
1257  if len(string_strip) < bracket_len * 2:
1258  return None
1259  if not (string_strip.startswith(left) and string_strip.endswith(right)):
1260  return None
1261  # Check whether or not there's anything between the open
1262  # and close brackets
1263  line = string_strip[bracket_len:-bracket_len].lstrip()
1264  if (not line and cls and require_cls) or (line and not cls):
1265  return None
1266  if not line and (not cls or not require_cls):
1267  return left, None, right
1268  return left, cls(line), right
1269 
1270  def tostr(self):
1271  """
1272  :raises InternalError: if the internal items list variable is \
1273  not the expected size.
1274  :raises InternalError: if the first element of the internal \
1275  items list is None or is an empty string.
1276  """
1277 
1278  if len(self.items) != 3:
1279  raise InternalError(
1280  "Class BracketBase method tostr() has '{0}' items, "
1281  "but expecting 3.".format(len(self.items))
1282  )
1283  if not self.items[0]:
1284  raise InternalError(
1285  "Class BracketBase method tostr(). 'Items' entry 0 "
1286  "should be a string containing the left hand bracket "
1287  "but it is empty or None"
1288  )
1289  if not self.items[2]:
1290  raise InternalError(
1291  "Class BracketBase method tostr(). 'Items' entry 2 "
1292  "should be a string containing the right hand bracket "
1293  "but it is empty or None"
1294  )
1295  if self.items[1] is None:
1296  return "{0}{1}".format(self.items[0], self.items[2])
1297  return "{0}{1}{2}".format(self.items[0], self.items[1], self.items[2])
1298 
1299 
1301  """
1302  ::
1303  <number-base> = <number> [ _ <kind-param> ]
1304  """
1305 
1306  @staticmethod
1307  def match(number_pattern, string):
1308  m = number_pattern.match(string.replace(" ", ""))
1309  if m is None:
1310  return
1311  d = m.groupdict()
1312  return d["value"].upper(), d.get("kind_param")
1313 
1314  def tostr(self):
1315  if self.items[1] is None:
1316  return str(self.items[0])
1317  return "%s_%s" % tuple(self.items)
1318 
1319  def _cmpkey(self):
1320  """Provides a key of objects to be used for comparing."""
1321  return self.items[0]
1322 
1323 
1325  """
1326  ::
1327  <call-base> = <lhs> ( [ <rhs> ] )
1328  """
1329 
1330  @staticmethod
1331  def match(lhs_cls, rhs_cls, string, upper_lhs=False, require_rhs=False):
1332  if not string.endswith(")"):
1333  return
1334  line, repmap = string_replace_map(string)
1335  i = line.rfind("(")
1336  if i == -1:
1337  return
1338  lhs = line[:i].rstrip()
1339  if not lhs:
1340  return
1341  j = line.rfind(")")
1342  rhs = line[i + 1 : j].strip()
1343  if line[j + 1 :].lstrip():
1344  return
1345  lhs = repmap(lhs)
1346  if upper_lhs:
1347  lhs = lhs.upper()
1348  rhs = repmap(rhs)
1349  if isinstance(lhs_cls, str):
1350  if lhs_cls != lhs:
1351  return
1352  else:
1353  lhs = lhs_cls(lhs)
1354  if rhs:
1355  if isinstance(rhs_cls, str):
1356  if rhs_cls != rhs:
1357  return
1358  else:
1359  rhs = rhs_cls(rhs)
1360  return lhs, rhs
1361  if require_rhs:
1362  return
1363  return lhs, None
1364 
1365  def tostr(self):
1366  if self.items[1] is None:
1367  return "%s()" % (self.items[0])
1368  return "%s(%s)" % (self.items[0], self.items[1])
1369 
1370 
1372  """
1373  ::
1374  <CALL-base> = <LHS> ( [ <rhs> ] )
1375  """
1376 
1377  @staticmethod
1378  def match(lhs_cls, rhs_cls, string, require_rhs=False):
1379  return CallBase.match(
1380  lhs_cls, rhs_cls, string, upper_lhs=True, require_rhs=require_rhs
1381  )
1382 
1383 
1385  """
1386  ::
1387  <string-base> = <xyz>
1388 
1389  Attributes
1390  ----------
1391  string
1392  """
1393 
1394  @staticmethod
1395  def match(pattern, string):
1396  if isinstance(pattern, (list, tuple)):
1397  for p in pattern:
1398  obj = StringBase.match(p, string)
1399  if obj is not None:
1400  return obj
1401  return
1402  if isinstance(pattern, str):
1403  if len(pattern) == len(string) and pattern == string:
1404  return (string,)
1405  return
1406  if pattern.match(string):
1407  return (string,)
1408  return None
1409 
1410  def init(self, string):
1411  self.string = string
1412 
1413  def tostr(self):
1414  return str(self.string)
1415 
1416  def torepr(self):
1417  return "%s(%r)" % (self.__class__.__name__, self.string)
1418 
1419  def _cmpkey(self):
1420  """Provides a key of objects to be used for comparing."""
1421  return self.string
1422 
1423 
1425  """STRINGBase matches an upper case version of the input string with
1426  another a pattern (typically taken from pattern_tools.py) and
1427  returns the string in upper case if there is a match.
1428 
1429  """
1430 
1431  @staticmethod
1432  def match(my_pattern, string):
1433  """Matches an input string with a specified pattern. Casts the string
1434  to upper case before performing a match and, if there is a
1435  match, returns the string in upper case.
1436 
1437  The pattern can be a regular expression, a string, a list or a
1438  tuple. If the input pattern is a regular expression or a
1439  string, a direct equivalence is performed. If the input pattern is a
1440  list or a tuple, then all of the contents of the list
1441  or tuple are searched for a match (by recursing). The list or tuple may
1442  contain regular expressions, strings, lists or tuples. This
1443  functionality can be used to recurse down a tree of lists and
1444  or tuples until regular expressions or strings are found (at
1445  the leaves of the tree) on which to match. The patterns used
1446  to match in fparser can be found in patterns_tools.py. These
1447  make use of the pattern class, whose match method behaves like
1448  a regular expression. For example:
1449 
1450  from fparser.two import pattern_tools
1451  pattern = pattern_tools.intrinsic_type_name
1452  result = STRINGBase.match(pattern, "logical")
1453 
1454  :param pattern: the pattern to match
1455  :type pattern: `list`, `tuple`, `str` or an `re` expression
1456  :param str string: the string to match with the pattern
1457  :return: None if there is no match, or a tuple containing the \
1458  matched string in upper case.
1459  :rtype: `NoneType` or ( `str` )
1460 
1461  """
1462  if string is None:
1463  return None
1464  if not isinstance(string, str):
1465  raise InternalError(
1466  f"Supplied string should be of type str, but found {type(string)}"
1467  )
1468  if isinstance(my_pattern, (list, tuple)):
1469  for child in my_pattern:
1470  result = STRINGBase.match(child, string)
1471  if result:
1472  return result
1473  return None
1474  string_upper = string.upper()
1475  if isinstance(my_pattern, str):
1476  if len(my_pattern) == len(string) and my_pattern == string_upper:
1477  return (string_upper,)
1478  return None
1479  try:
1480  if my_pattern.match(string_upper):
1481  return (string_upper,)
1482  except AttributeError:
1483  raise InternalError(
1484  f"Supplied pattern should be a list, tuple, str or regular "
1485  f"expression but found {type(my_pattern)}"
1486  )
1487  return None
1488 
1489 
1491  """
1492  ::
1493  [ [ <label> ] [ <construct-name> : ] ] <stmt>
1494 
1495  Attributes
1496  ----------
1497  item : readfortran.Line
1498  """
1499 
1500  def tofortran(self, tab="", isfix=None):
1501  label = None
1502  name = None
1503  if self.item is not None:
1504  label = self.item.label
1505  name = self.item.name
1506  if isfix:
1507  c = " "
1508  else:
1509  c = ""
1510  if label:
1511  t = c + str(label)
1512  if isfix:
1513  while len(t) < 6:
1514  t += " "
1515  else:
1516  tab = tab[len(t) :] or " "
1517  else:
1518  # BUG allow for fixed format here
1519  t = ""
1520  if name:
1521  return t + tab + name + ":" + str(self)
1522  return t + tab + str(self)
1523 
1524  def get_end_label(self):
1525  return self.item.label
1526 
1527 
1529  """
1530  ::
1531  <end-stmt-base> = END [ <stmt> [ <stmt-name>] ]
1532  """
1533 
1534  @staticmethod
1535  def match(stmt_type, stmt_name, string, require_stmt_type=False):
1536  start = string[:3].upper()
1537  if start != "END":
1538  return
1539  line = string[3:].lstrip()
1540  start = line[: len(stmt_type)].upper()
1541  if start:
1542  if start.replace(" ", "") != stmt_type.replace(" ", ""):
1543  return
1544  line = line[len(stmt_type) :].lstrip()
1545  else:
1546  if require_stmt_type:
1547  return
1548  return None, None
1549  if line:
1550  if stmt_name is None:
1551  return
1552  return stmt_type, stmt_name(line)
1553  return stmt_type, None
1554 
1555  def init(self, stmt_type, stmt_name):
1556  """
1557  Initialise this EndStmtBase object.
1558 
1559  :param str stmt_type: the type of statement, e.g. 'PROGRAM'.
1560  :param stmt_name: the name associated with the statement or None.
1561  :type stmt_name: :py:class:`fparser.two.Fortran2003.Name`
1562 
1563  """
1564  self.items = [stmt_type, stmt_name]
1565 
1566  def get_name(self):
1567  return self.items[1]
1568 
1569  def get_type(self):
1570  return self.items[0]
1571 
1572  def tostr(self):
1573  if self.items[1] is not None:
1574  return "END %s %s" % tuple(self.items)
1575  if self.items[0] is not None:
1576  return "END %s" % (self.items[0])
1577  return "END"
1578 
1579  def torepr(self):
1580  return "%s(%r, %r)" % (
1581  self.__class__.__name__,
1582  self.get_type(),
1583  self.get_name(),
1584  )
1585 
1586  def get_end_name(self):
1587  name = self.items[1]
1588  if name is not None:
1589  return name.string
1590 
1591 
1592 def isalnum(c):
1593  return c.isalnum() or c == "_"
1594 
1595 
1597  """Base class to support situations where there is a keyword which is
1598  optionally followed by further text, potentially separated by
1599  '::'.
1600 
1601  For example 'program fred', or 'import :: a,b'
1602 
1603  WORD-cls is WORD [ [ :: ] cls ]
1604 
1605  """
1606 
1607  @staticmethod
1608  def match(keyword, cls, string, colons=False, require_cls=False):
1609  """Checks whether the content in string matches the expected
1610  WORDClsBase format with 'keyword' providing the keyword, 'cls'
1611  providing the following text, 'colons' specifying whether an
1612  optional '::' is allowed as a separator between the keyword
1613  and cls and 'require_cls' specifying whether cls must have
1614  content or not.
1615 
1616  Note, if the optional '::' is allowed and exists in the string
1617  then 1) cls must also have content i.e. it implies
1618  `require_cls=True` and 2) white space is not required between
1619  the keyword and the '::' and the '::' and cls.
1620 
1621  The simplest form of keyword pattern is a string. However this
1622  method can also match more complex patterns as specified by
1623  the Pattern class in pattern_tools.py. As patterns can be
1624  built from combinations of other patterns (again see
1625  pattern_tool.py) this method also supports a hierarchy of
1626  lists and/or tuples of patterns.
1627 
1628  :param keyword: the pattern of the WORD to match. This can be \
1629  a Pattern, string, list or tuple, with a list or tuple \
1630  containing one or more Pattern, string, list or tuple.
1631  :type keyword: :py:class:`fparser.two.pattern_tools.Pattern`, \
1632  str, tuple of str/Pattern/tuple/list or list of \
1633  str/Pattern/tuple/list
1634  :param cls: the class to match.
1635  :type cls: a subclass of :py:class:`fparser.two.utils.Base`
1636  :param str string: Text that we are trying to match.
1637  :param bool colons: whether '::' is allowed as an optional \
1638  separator between between WORD and cls.
1639  :param bool require_cls: whether content for cls is required \
1640  or not.
1641 
1642  :returns: None if there is no match or, if there is a match, a \
1643  2-tuple containing a string matching the 'WORD' and an \
1644  instance of 'cls' (or None if an instance of cls is not \
1645  required and not provided).
1646  :rtype: (str, cls or NoneType) or NoneType
1647 
1648  """
1649  if isinstance(keyword, (tuple, list)):
1650  for child in keyword:
1651  try:
1652  obj = WORDClsBase.match(
1653  child, cls, string, colons=colons, require_cls=require_cls
1654  )
1655  except NoMatchError:
1656  obj = None
1657  if obj is not None:
1658  return obj
1659  return None
1660 
1661  if isinstance(keyword, str):
1662  line = string.lstrip()
1663  if line[: len(keyword)].upper() != keyword.upper():
1664  return None
1665  line = line[len(keyword) :]
1666  pattern_value = keyword
1667  else:
1668  my_match = keyword.match(string)
1669  if my_match is None:
1670  return None
1671  line = string[len(my_match.group()) :]
1672  pattern_value = keyword.value
1673 
1674  if not line:
1675  if require_cls:
1676  # no text found but it is required
1677  return None
1678  return pattern_value, None
1679  if isalnum(line[0]):
1680  return None
1681  line = line.lstrip()
1682  has_colons = False
1683  if colons and line.startswith("::"):
1684  has_colons = True
1685  line = line[2:].lstrip()
1686  if not line:
1687  if has_colons or require_cls:
1688  # colons without following content is not allowed.
1689  return None
1690  return pattern_value, None
1691  if cls is None:
1692  return None
1693  return pattern_value, cls(line)
1694 
1695  def tostr(self):
1696  """Convert the class into Fortran.
1697 
1698  :return: String representation of this class without any \
1699  optional '::'.
1700  :rtype: str
1701 
1702  """
1703  if self.items[1] is None:
1704  return str(self.items[0])
1705  s = str(self.items[1])
1706  if s and s[0] in "(*":
1707  return "%s%s" % (self.items[0], s)
1708  return "%s %s" % (self.items[0], s)
1709 
1710  def tostr_a(self):
1711  """Convert the class into Fortran, adding in "::".
1712 
1713  :return: String representation of this class including an \
1714  optional '::'.
1715  :rtype: str
1716 
1717  """
1718  if self.items[1] is None:
1719  return str(self.items[0])
1720  return "%s :: %s" % (self.items[0], self.items[1])
1721 
1722 
1724  """<type-declaration-stmt> = <declaration-type-spec> [ [ ,
1725  <attr-spec> ]... :: ] <entity-decl-list>
1726 
1727  """
1728 
1729  subclass_names = []
1730  use_names = None # derived class must define this list
1731 
1732  @staticmethod
1733  def match(decl_type_spec_cls, attr_spec_list_cls, entity_decl_list_cls, string):
1734  line, repmap = string_replace_map(string)
1735  i = line.find("::")
1736  if i != -1:
1737  j = line[:i].find(",")
1738  if j != -1:
1739  i = j
1740  else:
1741  if line[:6].upper() == "DOUBLE":
1742  m = re.search(r"\s[a-z_]", line[6:].lstrip(), re.I)
1743  if m is None:
1744  return
1745  i = m.start() + len(line) - len(line[6:].lstrip())
1746  else:
1747  m = re.search(r"\s[a-z_]", line, re.I)
1748  if m is None:
1749  return
1750  i = m.start()
1751  type_spec = decl_type_spec_cls(repmap(line[:i].rstrip()))
1752  if type_spec is None:
1753  return
1754  line = line[i:].lstrip()
1755  if line.startswith(","):
1756  i = line.find("::")
1757  if i == -1:
1758  return
1759  attr_specs = attr_spec_list_cls(repmap(line[1:i].strip()))
1760  if attr_specs is None:
1761  return
1762  line = line[i:]
1763  else:
1764  attr_specs = None
1765  if line.startswith("::"):
1766  line = line[2:].lstrip()
1767  entity_decls = entity_decl_list_cls(repmap(line))
1768  if entity_decls is None:
1769  return
1770  return type_spec, attr_specs, entity_decls
1771 
1772  def tostr(self):
1773  """
1774  :returns: the text representation of this node.
1775  :rtype: str
1776  """
1777  if self.items[1] is None:
1778  return f"{self.items[0]} :: {self.items[2]}"
1779  return f"{self.items[0]}, {self.items[1]} :: {self.items[2]}"
1780 
1781 
1782 def walk(node_list, types=None, indent=0, debug=False):
1783  """
1784  Walk down the parse tree produced by fparser2. Returns a list of all
1785  nodes with the specified type(s).
1786 
1787  :param node_list: node or list of nodes from which to walk.
1788  :type node_list: (list of) :py:class:fparser.two.utils.Base
1789  :param types: type or tuple of types of Node to return. (Default is to \
1790  return all nodes.)
1791  :type types: type or tuple of types
1792  :param int indent: extent to which to indent debug output.
1793  :param bool debug: whether or not to write textual representation of AST \
1794  to stdout.
1795  :returns: a list of nodes
1796  :rtype: `list` of :py:class:`fparser.two.utils.Base`
1797  """
1798  local_list = []
1799 
1800  if not isinstance(node_list, (list, tuple)):
1801  node_list = [node_list]
1802 
1803  for child in node_list:
1804  if debug:
1805  if isinstance(child, str):
1806  print(indent * " " + "child type = ", type(child), repr(child))
1807  else:
1808  print(indent * " " + "child type = ", type(child))
1809  if types is None or isinstance(child, types):
1810  local_list.append(child)
1811  # Recurse down
1812  if isinstance(child, Base):
1813  local_list += walk(child.children, types, indent + 1, debug)
1814  elif isinstance(child, tuple):
1815  for component in child:
1816  local_list += walk(component, types, indent + 1, debug)
1817 
1818  return local_list
1819 
1820 
1821 def get_child(node, node_type):
1822  """
1823  Searches for the first, immediate child of the supplied node that is of
1824  the specified type.
1825 
1826  :param node: the node whose children will be searched.
1827  :type node: :py:class:`fparser.two.utils.Base`
1828  :param type node_type: the class of child node to search for.
1829 
1830  :returns: the first child node of type node_type that is encountered \
1831  or None.
1832  :rtype: :py:class:`fparser.two.utils.Base`
1833 
1834  """
1835  for child in node.children:
1836  if isinstance(child, node_type):
1837  return child
1838  return None
def init(self, stmt_type, stmt_name)
Definition: utils.py:1555
def torepr(self)
Definition: utils.py:494
def tofortran(self, tab="", isfix=None)
Definition: utils.py:809
def match(separator, subcls, string)
Definition: utils.py:847
def init(self, items)
Definition: utils.py:484
def match(lhs_cls, op_pattern, rhs_cls, string, right=True, exclude_op_pattern=None)
Definition: utils.py:978
def children(self)
Definition: utils.py:464
def match(brackets, cls, string, require_cls=True)
Definition: utils.py:1220
def match(lhs_cls, rhs_cls, string, require_lhs=True, upper_lhs=False)
Definition: utils.py:1133
def match(keyword, cls, string, colons=False, require_cls=False)
Definition: utils.py:1608
def init(self, content)
Definition: utils.py:789
def match(my_pattern, string)
Definition: utils.py:1432
def match(startcls, subclasses, endcls, reader, match_labels=False, match_names=False, match_name_classes=(), enable_do_label_construct_hook=False, enable_if_construct_hook=False, enable_where_construct_hook=False, strict_order=False, strict_match_names=False)
Definition: utils.py:554
def get_root(self)
Definition: utils.py:450
def init(self, separator, items)
Definition: utils.py:895
def tofortran(self, tab="", isfix=None)
Definition: utils.py:507
def _compare(self, other, method)
Definition: utils.py:208