fparser Reference Guide  0.0.14
base_classes.py
1 # -*- coding: utf-8 -*-
2 # Modified work Copyright (c) 2017-2019 Science and Technology
3 # Facilities Council.
4 # Original work Copyright (c) 1999-2008 Pearu Peterson
5 
6 # All rights reserved.
7 
8 # Modifications made as part of the fparser project are distributed
9 # under the following license:
10 
11 # Redistribution and use in source and binary forms, with or without
12 # modification, are permitted provided that the following conditions are
13 # met:
14 
15 # 1. Redistributions of source code must retain the above copyright
16 # notice, this list of conditions and the following disclaimer.
17 
18 # 2. Redistributions in binary form must reproduce the above copyright
19 # notice, this list of conditions and the following disclaimer in the
20 # documentation and/or other materials provided with the distribution.
21 
22 # 3. Neither the name of the copyright holder nor the names of its
23 # contributors may be used to endorse or promote products derived from
24 # this software without specific prior written permission.
25 
26 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
27 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
28 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
29 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
30 # HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
31 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
32 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
33 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
34 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
35 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
36 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
37 
38 # --------------------------------------------------------------------
39 
40 # The original software (in the f2py project) was distributed under
41 # the following license:
42 
43 # Redistribution and use in source and binary forms, with or without
44 # modification, are permitted provided that the following conditions are met:
45 
46 # a. Redistributions of source code must retain the above copyright notice,
47 # this list of conditions and the following disclaimer.
48 # b. Redistributions in binary form must reproduce the above copyright
49 # notice, this list of conditions and the following disclaimer in the
50 # documentation and/or other materials provided with the distribution.
51 # c. Neither the name of the F2PY project nor the names of its
52 # contributors may be used to endorse or promote products derived from
53 # this software without specific prior written permission.
54 
55 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
56 # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
57 # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
58 # ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
59 # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
60 # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
61 # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
62 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
63 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
64 # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
65 # DAMAGE.
66 
67 """
68 Base classes for all Fortran statement types
69 """
70 
71 __all__ = [
72  "Statement",
73  "BeginStatement",
74  "EndStatement",
75  "Variable",
76  "AttributeHolder",
77  "ProgramBlock",
78 ]
79 
80 import copy
81 import logging
82 
83 from fparser.common.readfortran import Line, Comment
84 from fparser.common.utils import split_comma, specs_split_comma, is_int_literal_constant
85 from fparser.common.utils import classes, AnalyzeError
86 
87 
89  # copied from symbolic.base module
90  """
91  Defines a object with predefined attributes. Only those attributes
92  are allowed that are specified as keyword arguments of a constructor.
93  When an argument is callable then the corresponding attribute will
94  be read-only and set by the value the callable object returns.
95  """
96 
97  def __init__(self, **kws):
98  self._attributes = {}
99  self._readonly = []
100  for k, v in list(kws.items()):
101  self._attributes[k] = v
102  if callable(v):
103  self._readonly.append(k)
104 
105  def __getattr__(self, name):
106  if name not in self._attributes:
107  message = "%s instance has no attribute %r, " + "expected attributes: %s"
108  attributes = ", ".join(list(self._attributes.keys()))
109  raise AttributeError(message % (self.__class__.__name__, name, attributes))
110  value = self._attributes[name]
111  if callable(value):
112  value = value()
113  self._attributes[name] = value
114  return value
115 
116  def __setattr__(self, name, value):
117  if name in ["_attributes", "_readonly"]:
118  self.__dict__[name] = value
119  return
120  if name in self._readonly:
121  message = "%s instance attribute %r is readonly"
122  raise AttributeError(message % (self.__class__.__name__, name))
123  if name not in self._attributes:
124  message = "%s instance has no attribute %r, " + "expected attributes: %s"
125  attributes = ",".join(list(self._attributes.keys()))
126  raise AttributeError(message % (self.__class__.__name__, name, attributes))
127  self._attributes[name] = value
128 
129  def isempty(self):
130  for k in list(self._attributes.keys()):
131  v = getattr(self, k)
132  if v:
133  return False
134  return True
135 
136  def __repr__(self):
137  return self.torepr()
138 
139  def torepr(self, depth=-1, tab=""):
140  if depth == 0:
141  return tab + self.__class__.__name__
142  lines = [self.__class__.__name__ + ":"]
143  ttab = tab + " "
144  for k in list(self._attributes.keys()):
145  v = getattr(self, k)
146  if v:
147  if isinstance(v, list):
148  lines.append(ttab + "%s=<%s-list>" % (k, len(v)))
149  elif isinstance(v, dict):
150  lines.append(ttab + "%s=<dict with keys %s>" % (k, list(v.keys())))
151  else:
152  lines.append(ttab + "%s=<%s>" % (k, type(v)))
153  return "\n".join(lines)
154 
155  def todict(self):
156  d = {}
157  for k in list(self._attributes.keys()):
158  v = getattr(self, k)
159  d[k] = v
160  return d
161 
162 
163 def get_base_classes(cls):
164  bases = ()
165  for c in cls.__bases__:
166  bases += get_base_classes(c)
167  return bases + cls.__bases__ + (cls,)
168 
169 
170 class Variable(metaclass=classes):
171  """
172  Variable instance has attributes:
173  name
174  typedecl
175  dimension
176  attributes
177  intent
178  parent - Statement instances defining the variable
179  """
180 
181  def __init__(self, parent, name):
182  self.parent = parent
183  self.parents = [parent]
184  self.name = name
185  self.typedecl = None
186  self.dimension = None
187  self.bounds = None
188  self.length = None
189  self.attributes = []
190  self.intent = None
191  self.bind = []
192  self.check = []
193  self.init = None
194 
195  # after calling analyze the following additional attributes are set:
196  # .is_array:
197  # rank
198  # shape
199 
200  def __repr__(self):
201  line = []
202  for a in [
203  "name",
204  "typedecl",
205  "dimension",
206  "bounds",
207  "length",
208  "attributes",
209  "intent",
210  "bind",
211  "check",
212  "init",
213  ]:
214  v = getattr(self, a)
215  if v:
216  line.append("%s=%r" % (a, v))
217  return "Variable: " + ", ".join(line)
218 
219  def get_typedecl(self):
220  if self.typedecl is None:
221  self.set_type(self.parent.get_type(self.name))
222  return self.typedecl
223 
224  def add_parent(self, parent):
225  if id(parent) not in list(map(id, self.parents)):
226  self.parents.append(parent)
227  self.parent = parent
228 
229  def set_type(self, typedecl):
230  if self.typedecl is not None:
231  if not self.typedecl == typedecl:
232  self.parent.warning(
233  "variable %r already has type %s, "
234  "resetting to %s"
235  % (self.name, self.typedecl.tostr(), typedecl.tostr())
236  )
237  assert typedecl is not None
238  self.typedecl = typedecl
239 
240  def set_init(self, expr):
241  if self.init is not None:
242  if not self.init == expr:
243  self.parent.warning(
244  "variable %r already has initialization %r, "
245  "resetting to %r" % (self.name, self.expr, expr)
246  )
247  self.init = expr
248 
249  def set_dimension(self, dims):
250  dims = [tuple(dim.split(":")) for dim in dims]
251  dims = [tuple(map(str.strip, dim)) for dim in dims]
252  if self.dimension is not None:
253  if not self.dimension == dims:
254  self.parent.warning(
255  "variable %r already has dimension %r, "
256  "resetting to %r" % (self.name, self.dimension, dims)
257  )
258  self.dimension = dims
259 
260  def set_bounds(self, bounds):
261  if self.bounds is not None:
262  if not self.bounds == bounds:
263  self.parent.warning(
264  "variable %r already has bounds %r, "
265  "resetting to %r" % (self.name, self.bounds, bounds)
266  )
267  self.bounds = bounds
268 
269  def set_length(self, length):
270  if self.length is not None:
271  if not self.length == length:
272  self.parent.warning(
273  "variable %r already has length %r, "
274  "resetting to %r" % (self.name, self.length, length)
275  )
276  self.length = length
277 
278  known_intent_specs = [
279  "IN",
280  "OUT",
281  "INOUT",
282  "CACHE",
283  "HIDE",
284  "COPY",
285  "OVERWRITE",
286  "CALLBACK",
287  "AUX",
288  "C",
289  "INPLACE",
290  "OUT=",
291  ]
292 
293  def set_intent(self, intent):
294  if self.intent is None:
295  self.intent = []
296  for i in intent:
297  if i not in self.intent:
298  if i not in self.known_intent_specs:
299  self.parent.warning(
300  "unknown intent-spec %r for %r" % (i, self.name)
301  )
302  self.intent.append(i)
303 
304  known_attributes = [
305  "PUBLIC",
306  "PRIVATE",
307  "ALLOCATABLE",
308  "ASYNCHRONOUS",
309  "EXTERNAL",
310  "INTRINSIC",
311  "OPTIONAL",
312  "PARAMETER",
313  "POINTER",
314  "PROTECTED",
315  "SAVE",
316  "TARGET",
317  "VALUE",
318  "VOLATILE",
319  "REQUIRED",
320  ]
321 
322  def is_intent_in(self):
323  # TODO Something hinky is going on here. self.intent is a list which
324  # doesn't make a lot of sense. How can a variable have more
325  # than one intent?
326  # TODO Furthermore if no intent is specified the assumed intent is
327  # "inout". Below an explicit "inout" returns False but a None
328  # returns True.
329  if not self.intent:
330  return True
331  if "HIDE" in self.intent:
332  return False
333  if "INPLACE" in self.intent:
334  return False
335  if "IN" in self.intent:
336  return True
337  if "OUT" in self.intent:
338  return False
339  if "INOUT" in self.intent:
340  return False
341  if "OUTIN" in self.intent:
342  return False
343  return True
344 
345  def is_intent_inout(self):
346  if not self.intent:
347  return False
348  if "INOUT" in self.intent:
349  if "IN" in self.intent or "HIDE" in self.intent or "INPLACE" in self.intent:
350  message = "INOUT ignored in INPUT(%s)"
351  self.warning(message % (", ".join(self.intent)))
352  return False
353  return True
354  return False
355 
356  def is_intent_hide(self):
357  if not self.intent:
358  return False
359  if "HIDE" in self.intent:
360  return True
361  if "OUT" in self.intent:
362  return (
363  "IN" not in self.intent
364  and "INPLACE" not in self.intent
365  and "INOUT" not in self.intent
366  )
367  return False
368 
369  def is_intent_inplace(self):
370  return self.intent and "INPLACE" in self.intent
371 
372  def is_intent_out(self):
373  return self.intent and "OUT" in self.intent
374 
375  def is_intent_c(self):
376  return self.intent and "C" in self.intent
377 
378  def is_intent_cache(self):
379  return self.intent and "CACHE" in self.intent
380 
381  def is_intent_copy(self):
382  return self.intent and "COPY" in self.intent
383 
384  def is_intent_overwrite(self):
385  return self.intent and "OVERWRITE" in self.intent
386 
387  def is_intent_callback(self):
388  return self.intent and "CALLBACK" in self.intent
389 
390  def is_intent_aux(self):
391  return self.intent and "AUX" in self.intent
392 
393  def is_private(self):
394  if "PUBLIC" in self.attributes:
395  return False
396  if "PRIVATE" in self.attributes:
397  return True
398  return self.parent.parent.check_private(self.name)
399 
400  def is_public(self):
401  return not self.is_private()
402 
403  def is_allocatable(self):
404  return "ALLOCATABLE" in self.attributes
405 
406  def is_external(self):
407  return "EXTERNAL" in self.attributes
408 
409  def is_intrinsic(self):
410  return "INTRINSIC" in self.attributes
411 
412  def is_parameter(self):
413  return "PARAMETER" in self.attributes
414 
415  def is_optional(self):
416  return (
417  "OPTIONAL" in self.attributes
418  and "REQUIRED" not in self.attributes
419  and not self.is_intent_hide()
420  )
421 
422  def is_required(self):
423  return self.is_optional() and not self.is_intent_hide()
424 
425  def is_pointer(self):
426  return "POINTER" in self.attributes
427 
428  def is_array(self):
429  return not not (self.bounds or self.dimension)
430 
431  def is_scalar(self):
432  return not self.is_array()
433 
434  def update(self, *attrs):
435  attributes = self.attributes
436  if len(attrs) == 1 and isinstance(attrs[0], (tuple, list)):
437  attrs = attrs[0]
438  for attr in attrs:
439  lattr = attr.lower()
440  uattr = attr.upper()
441  if lattr.startswith("dimension"):
442  assert self.dimension is None, repr((self.dimension, attr))
443  line = attr[9:].lstrip()
444  assert line[0] + line[-1] == "()", repr(line)
445  self.set_dimension(split_comma(line[1:-1].strip(), self.parent.item))
446  continue
447  if lattr.startswith("intent"):
448  line = attr[6:].lstrip()
449  assert line[0] + line[-1] == "()", repr(line)
450  self.set_intent(
451  specs_split_comma(line[1:-1].strip(), self.parent.item, upper=True)
452  )
453  continue
454  if lattr.startswith("bind"):
455  line = attr[4:].lstrip()
456  assert line[0] + line[-1] == "()", repr(line)
457  self.bind = specs_split_comma(
458  line[1:-1].strip(), self.parent.item, upper=True
459  )
460  continue
461  if lattr.startswith("check"):
462  line = attr[5:].lstrip()
463  assert line[0] + line[-1] == "()", repr(line)
464  self.check.extend(split_comma(line[1:-1].strip(), self.parent.item))
465  continue
466  if uattr not in attributes:
467  if uattr not in self.known_attributes:
468  self.parent.warning("unknown attribute %r" % (attr))
469  attributes.append(uattr)
470 
471  def __str__(self):
472  s = ""
473  typedecl = self.get_typedecl()
474  if typedecl is not None:
475  s += typedecl.tostr() + " "
476  a = self.attributes[:]
477  if self.dimension is not None:
478  dimensions = [":".join(spec) for spec in self.dimension]
479  a.append("DIMENSION(%s)" % (", ".join(dimensions)))
480  if self.intent is not None:
481  a.append("INTENT(%s)" % (", ".join(self.intent)))
482  if self.bind:
483  a.append("BIND(%s)" % (", ".join(self.bind)))
484  if self.check:
485  a.append("CHECK(%s)" % (", ".join(self.check)))
486  if a:
487  s += ", " + ", ".join(a) + " :: "
488  s += self.name
489  if self.bounds:
490  s += "(%s)" % (", ".join([":".join(spec) for spec in self.bounds]))
491  if self.length:
492  if is_int_literal_constant(self.length):
493  s += "*%s" % (self.length)
494  else:
495  s += "*(%s)" % (self.length)
496  if self.init:
497  s += " = " + self.init
498  return s
499 
500  def get_array_spec(self):
501  assert self.is_array(), "array_spec is available only for arrays"
502  if self.bounds:
503  if self.dimension:
504  message = (
505  "both bounds=%r and dimension=%r are defined, "
506  + "ignoring dimension."
507  )
508  self.parent.warning(message % (self.bounds, self.dimension))
509  array_spec = self.bounds
510  else:
511  array_spec = self.dimension
512  return array_spec
513 
514  def is_deferred_shape_array(self):
515  if not self.is_array():
516  return False
517  return self.is_allocatable() or self.is_pointer()
518 
519  def is_assumed_size_array(self):
520  if not self.is_array():
521  return False
522  return self.get_array_spec()[-1][-1] == "*"
523 
524  def is_assumed_shape_array(self):
525  if not self.is_array():
526  return False
527  if self.is_deferred_shape_array():
528  return False
529  for spec in self.get_array_spec():
530  if not spec[-1]:
531  return True
532  return False
533 
534  def is_explicit_shape_array(self):
535  if not self.is_array():
536  return False
537  if self.is_deferred_shape_array():
538  return False
539  for spec in self.get_array_spec():
540  if not spec[-1] or spec[-1] == "*":
541  return False
542  return True
543 
544  def is_allocatable_array(self):
545  return self.is_array() and self.is_allocatable()
546 
547  def is_array_pointer(self):
548  return self.is_array() and self.is_pointer()
549 
550  def analyze(self):
551  typedecl = self.get_typedecl()
552  if self.is_array():
553  array_spec = self.get_array_spec()
554  self.rank = len(array_spec)
555  if self.is_deferred_shape_array(): # a(:, :)
556  pass
557  elif self.is_explicit_shape_array():
558  shape = []
559  for spec in array_spec:
560  if len(spec) == 1:
561  shape.append(spec[0].replace(" ", ""))
562  else:
563  try:
564  # lower subscript
565  lss = int(spec[0].replace(" ", ""))
566  # upper subscript
567  uss = int(spec[1].replace(" ", ""))
568  n = uss - (lss - 1)
569  except ValueError:
570  n = "(%s)-(%s)" % (spec[1], spec[0])
571  shape.append(str(n))
572  self.shape = shape
573 
574  def error(self, message):
575  return self.parent.error(message)
576 
577  def warning(self, message):
578  return self.parent.warning(message)
579 
580  def info(self, message):
581  return self.parent.info(message)
582 
583 
584 class ProgramBlock(metaclass=classes):
585  pass
586 
587 
588 class Statement(metaclass=classes):
589  """
590  Statement instance has attributes:
591  parent - Parent BeginStatement or FortranParser instance
592  item - Line instance containing the statement line
593  isvalid - boolean, when False, the Statement instance will be ignored
594  """
595 
596  modes = ["free", "fix", "f77", "pyf"]
597  _repr_attr_names = []
598 
599  def __init__(self, parent, item):
600  self.parent = parent
601  if item is not None:
602  self.reader = item.reader
603  else:
604  self.reader = parent.reader
605  self.top = getattr(parent, "top", None) # the top of statement tree
606  self.item = item
607 
608  if isinstance(parent, ProgramBlock):
609  self.programblock = parent
610  elif isinstance(self, ProgramBlock):
611  self.programblock = self
612  elif hasattr(parent, "programblock"):
613  self.programblock = parent.programblock
614  else:
615  pass
616 
617  # When a statement instance is constructed by error, set isvalid to
618  # False
619  self.isvalid = True
620  # when a statement should be ignored, set ignore to True
621  self.ignore = False
622 
623  # attribute a will hold analyze information.
624  a_dict = {}
625  for cls in get_base_classes(self.__class__):
626  if hasattr(cls, "a"):
627  a_dict.update(copy.deepcopy(cls.a.todict()))
628  self.a = AttributeHolder(**a_dict)
629  if hasattr(self.__class__, "a"):
630  assert self.a is not self.__class__.a
631 
632  self.process_item()
633 
634  def __repr__(self):
635  return self.torepr()
636 
637  def torepr(self, depth=-1, incrtab=""):
638  tab = incrtab + self.get_indent_tab()
639  clsname = self.__class__.__name__
640  lines = [tab + clsname]
641  if depth == 0:
642  return "\n".join(lines)
643  ttab = tab + " "
644  for n in self._repr_attr_names:
645  attr = getattr(self, n, None)
646  if not attr:
647  continue
648  if hasattr(attr, "torepr"):
649  r = attr.torepr(depth - 1, incrtab)
650  else:
651  r = repr(attr)
652  lines.append(ttab + "%s=%s" % (n, r))
653  if self.item is not None:
654  lines.append(ttab + "item=%r" % (self.item))
655  if not self.isvalid:
656  lines.append(ttab + "isvalid=%r" % (self.isvalid))
657  if self.ignore:
658  lines.append(ttab + "ignore=%r" % (self.ignore))
659  if not self.a.isempty():
660  lines.append(
661  ttab + "a=" + self.a.torepr(depth - 1, incrtab + " ").lstrip()
662  )
663  return "\n".join(lines)
664 
665  def get_indent_tab(self, deindent=False, isfix=None):
666  if isfix is None:
667  isfix = self.reader.format.is_fixed
668  if isfix:
669  tab = " " * 6
670  else:
671  tab = ""
672  p = self.parent
673  while isinstance(p, Statement):
674  tab += " "
675  p = p.parent
676  if deindent:
677  tab = tab[:-2]
678  label = getattr(self.item, "label", None)
679  if label is None:
680  return tab
681  s = str(label)
682  if isfix:
683  s = " " + s
684  tab = tab[len(s) :]
685  if not tab:
686  tab = " "
687  tab = s + tab
688  return tab
689 
690  def __str__(self):
691  return self.tofortran()
692 
693  def asfix(self):
694  lines = []
695  for line in self.tofortran(isfix=True).split("\n"):
696  if len(line) > 72 and line[0] == " ":
697  lines.append(line[:72] + "&\n &")
698  line = line[72:]
699  while len(line) > 66:
700  lines.append(line[:66] + "&\n &")
701  line = line[66:]
702  lines.append(line + "\n")
703  else:
704  lines.append(line + "\n")
705  return "".join(lines).replace("\n &\n", "\n")
706 
707  def format_message(self, kind, message):
708  if self.item is not None:
709  message = self.reader.format_message(
710  kind, message, self.item.span[0], self.item.span[1]
711  )
712  else:
713  return message
714  return message
715 
716  # def show_message(self, message, stream=sys.stderr):
717  # print >> stream, message
718  # stream.flush()
719  # return
720 
721  def error(self, message):
722  message = self.format_message("ERROR", message)
723  logging.getLogger(__name__).error(message)
724 
725  def warning(self, message):
726  message = self.format_message("WARNING", message)
727  logging.getLogger(__name__).warning(message)
728 
729  def info(self, message):
730  message = self.format_message("INFO", message)
731  logging.getLogger(__name__).info(message)
732 
733  def analyze(self):
734  self.warning("nothing analyzed")
735 
736  def get_variable(self, name):
737  """Return Variable instance of variable name."""
738  mth = getattr(self, "get_variable_by_name", self.parent.get_variable)
739  return mth(name)
740 
741  def get_type(self, name):
742  """Return type declaration using implicit rules
743  for name.
744  """
745  mth = getattr(self, "get_type_by_name", self.parent.get_type)
746  return mth(name)
747 
748  def get_type_decl(self, kind):
749  mth = getattr(self, "get_type_decl_by_kind", self.parent.get_type_decl)
750  return mth(kind)
751 
752  def get_provides(self):
753  """
754  Returns dictonary containing statements that block provides or None
755  when N/A.
756  """
757  return None
758 
759 
761  """[ construct_name : ] <blocktype> [ <name> ]
762 
763  BeginStatement instances have additional attributes:
764  name
765  blocktype
766 
767  Block instance has attributes:
768  content - list of Line or Statement instances
769  name - name of the block, unnamed blocks are named
770  with the line label
771  construct_name - name of a construct
772  parent - Block or FortranParser instance
773  item - Line instance containing the block start statement
774  get_item, put_item - methods to retrive/submit Line instances
775  from/to Fortran reader.
776  isvalid - boolean, when False, the Block instance will be ignored.
777 
778  stmt_cls, end_stmt_cls
779 
780  """
781 
782  _repr_attr_names = [
783  "blocktype",
784  "name",
785  "construct_name",
786  ] + Statement._repr_attr_names
787 
788  def __init__(self, parent, item=None):
789 
790  self.content = []
791  self.get_item = parent.get_item # get line function
792  self.put_item = parent.put_item # put line function
793  if not hasattr(self, "blocktype"):
794  self.blocktype = self.__class__.__name__.lower()
795  if not hasattr(self, "name"):
796  # process_item may change this
797  self.name = "__" + self.blocktype.upper() + "__"
798  self.construct_name = getattr(item, "name", None)
799  Statement.__init__(self, parent, item)
800 
801  def tostr(self):
802  return self.blocktype.upper() + " " + self.name
803 
804  def tofortran(self, isfix=None):
805  construct_name = self.construct_name
806  construct_name = construct_name + ": " if construct_name else ""
807  lines = [self.get_indent_tab(isfix=isfix) + construct_name + self.tostr()]
808  for c in self.content:
809  lines.append(c.tofortran(isfix=isfix))
810  return "\n".join(lines)
811 
812  def torepr(self, depth=-1, incrtab=""):
813  tab = incrtab + self.get_indent_tab()
814  ttab = tab + " "
815  lines = [Statement.torepr(self, depth=depth, incrtab=incrtab)]
816  if depth == 0 or not self.content:
817  return "\n".join(lines)
818  lines.append(ttab + "content:")
819  for c in self.content:
820  if isinstance(c, EndStatement):
821  lines.append(c.torepr(depth - 1, incrtab))
822  else:
823  lines.append(c.torepr(depth - 1, incrtab + " "))
824  return "\n".join(lines)
825 
826  def process_item(self):
827  """Process the line"""
828  item = self.item
829  if item is None:
830  return
831  self.fill()
832 
833  def fill(self, end_flag=False):
834  """
835  Fills blocks content until the end of block statement.
836  """
837 
838  mode = self.reader.format.mode
839  class_list = self.get_classes()
840  self.classes = [cls for cls in class_list if mode in cls.modes]
841  self.pyf_classes = [cls for cls in class_list if "pyf" in cls.modes]
842 
843  item = self.get_item()
844  while item is not None:
845  if isinstance(item, Line):
846  if self.process_subitem(item):
847  end_flag = True
848  break
849  elif isinstance(item, Comment):
850  # TODO: FIX ME, Comment content is a string
851  self.content.append(classes.Comment(self, item))
852  else:
853  raise NotImplementedError(repr(item))
854  item = self.get_item()
855 
856  if not end_flag:
857  self.warning("failed to find the end of block")
858 
859  def process_subitem(self, item):
860  """
861  Check if item is blocks start statement, if it is, read the block.
862 
863  Return True to stop adding items to given block.
864  """
865  line = item.get_line()
866 
867  # First check for the end of block
868  cls = self.end_stmt_cls
869  if cls.match(line):
870  stmt = cls(self, item)
871  if stmt.isvalid:
872  self.content.append(stmt)
873  return True
874 
875  if item.is_f2py_directive:
876  classes = self.pyf_classes
877  else:
878  classes = self.classes
879 
880  # Look for statement match
881  for cls in classes:
882  if cls.match(line):
883  stmt = cls(self, item)
884  if stmt.isvalid:
885  if not stmt.ignore:
886  self.content.append(stmt)
887  return False
888  # item may be cloned that changes the items line:
889  line = item.get_line()
890 
891  # Check if f77 code contains inline comments or other f90
892  # constructs that got undetected by get_source_info.
893  if item.reader.format.is_f77:
894  i = line.find("!")
895  if i != -1:
896  message = item.reader.format_message(
897  "WARNING",
898  'no parse pattern found for "%s" in %r block, '
899  "trying to remove inline comment (not in Fortran 77)."
900  % (item.get_line(), self.__class__.__name__),
901  item.span[0],
902  item.span[1],
903  )
904  # .. but at the expense of loosing the comment.
905  logging.getLogger(__name__).warning(message)
906  if line[:i]:
907  newitem = item.copy(line[:i].rstrip())
908  return self.process_subitem(newitem)
909  else:
910  return True
911 
912  # try fix statement classes
913  f77_classes = self.classes
914  classes = []
915  for cls in self.get_classes():
916  if "f77" in cls.modes and cls not in f77_classes:
917  classes.append(cls)
918  if classes:
919  message = item.reader.format_message(
920  "WARNING",
921  'no parse pattern found for "%s" in %r block'
922  " maybe due to strict f77 mode."
923  " Trying f90 fix mode patterns.."
924  % (item.get_line(), self.__class__.__name__),
925  item.span[0],
926  item.span[1],
927  )
928  logging.getLogger(__name__).warning(message)
929 
930  item.reader.set_mode(False, False)
931  self.classes = classes
932 
933  r = BeginStatement.process_subitem(self, item)
934  if r is None:
935  # restore f77 fix mode
936  self.classes = f77_classes
937  item.reader.set_mode(False, True)
938  else:
939  message = item.reader.format_message(
940  "INFORMATION",
941  "The f90 fix mode resolved the parse pattern issue."
942  " Setting reader to f90 fix mode.",
943  item.span[0],
944  item.span[1],
945  )
946  logging.getLogger(__name__).info(message)
947  # set f90 fix mode
948  self.classes = f77_classes + classes
949  self.reader.set_mode(False, False)
950  return r
951 
953 
955  """Called when process_subitem does not find a start or end of block.
956  It adds the item (which is an instance of Line) to the content, but
957  then raises an AnalyzeError. An instance of Line in content typically
958  results in other errors later (e.g. because Line has no analyze
959  method).
960  """
961  message = item.reader.format_message(
962  "WARNING",
963  'no parse pattern found for "%s" in %r block.'
964  % (item.get_line(), self.__class__.__name__),
965  item.span[0],
966  item.span[1],
967  )
968  logging.getLogger(__name__).warning(message)
969  self.content.append(item)
970  raise AnalyzeError(message)
971 
972  def analyze(self):
973  for stmt in self.content:
974  stmt.analyze()
975 
976 
978  """
979  END [<blocktype> [<name>]]
980 
981  EndStatement instances have additional attributes:
982  name
983  blocktype
984  """
985 
986  _repr_attr_names = ["blocktype", "name"] + Statement._repr_attr_names
987 
988  def __init__(self, parent, item):
989  if not hasattr(self, "blocktype"):
990  self.blocktype = self.__class__.__name__.lower()[3:]
991  Statement.__init__(self, parent, item)
992 
993  def process_item(self):
994  item = self.item
995  line = item.get_line().replace(" ", "")[3:]
996  line = item.apply_map(line)
997  blocktype = self.blocktype
998 
999  if line.lower().startswith(blocktype):
1000  line = line[len(blocktype) :].strip()
1001  else:
1002  if line:
1003  # not the end of expected block
1004  line = ""
1005  self.isvalid = False
1006  if self.parent.construct_name:
1007  name = self.parent.construct_name
1008  else:
1009  name = self.parent.name
1010  if line:
1011  # line variable is already cast to lower case so would fail if any
1012  # upper case letters exist in the label. Also, fortran is case
1013  # insensitive anyway so we should assume labels may have a
1014  # different case and therefore cast both to the same case in our
1015  # equivalence test.
1016  if line.lower() != name.lower():
1017  message = (
1018  "expected the end of %r block " + "but got the end of %r, skipping."
1019  )
1020  self.warning(message % (name, line))
1021  self.isvalid = False
1022  self.name = name
1023 
1024  def analyze(self):
1025  pass
1026 
1027  def get_indent_tab(self, deindent=False, isfix=None):
1028  return Statement.get_indent_tab(self, deindent=True, isfix=isfix)
1029 
1030  def tofortran(self, isfix=None):
1031  """Returns a valid Fortran string for this END statement. It
1032  guarantees that there is no white space after the 'END' in case
1033  of an unnamed statement.
1034 
1035  :param bool isfix: True if the code is in fixed format.
1036 
1037  :returns: the (named or unnamed) valid Fortran END statement \
1038  as a string.
1039  :rtype: str
1040 
1041  """
1042  if self.name:
1043  return self.get_indent_tab(isfix=isfix) + "END {0} {1}".format(
1044  self.blocktype.upper(), self.name
1045  )
1046 
1047  # Make sure there is no space after an unnamed END:
1048  return self.get_indent_tab(isfix=isfix) + "END {0}".format(
1049  self.blocktype.upper()
1050  )
def torepr(self, depth=-1, incrtab="")
def get_indent_tab(self, deindent=False, isfix=None)
def torepr(self, depth=-1, tab="")
def format_message(self, kind, message)