Reference Guide  2.5.0
f2pygen.py
1 # -----------------------------------------------------------------------------
2 # BSD 3-Clause License
3 #
4 # Copyright (c) 2017-2024 and Technology Facilities Council.
5 # All rights reserved.
6 #
7 # Redistribution and use in source and binary forms, with or without
8 # modification, are permitted provided that the following conditions are met:
9 #
10 # * Redistributions of source code must retain the above copyright notice, this
11 # list of conditions and the following disclaimer.
12 #
13 # * Redistributions in binary form must reproduce the above copyright notice,
14 # this list of conditions and the following disclaimer in the documentation
15 # and/or other materials provided with the distribution.
16 #
17 # * Neither the name of the copyright holder nor the names of its
18 # contributors may be used to endorse or promote products derived from
19 # this software without specific prior written permission.
20 #
21 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24 # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
25 # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
27 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
29 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
31 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32 # POSSIBILITY OF SUCH DAMAGE.
33 # -----------------------------------------------------------------------------
34 # Authors R. W. Ford, A. R. Porter and S. Siso, STFC Daresbury Lab
35 # Modified: A. B. G. Chalk and N. Nobre, STFC Daresbury Lab
36 
37 ''' Fortran code-generation library. This wraps the f2py fortran parser to
38  provide routines which can be used to generate fortran code. '''
39 
40 import abc
41 from fparser.common.readfortran import FortranStringReader
42 from fparser.common.sourceinfo import FortranFormat
43 from fparser.one.statements import Comment, Case
44 from fparser.one.block_statements import SelectCase, SelectType, EndSelect
45 from fparser.one.parsefortran import FortranParser
46 # This alias is useful to refer to parts of fparser.one later but
47 # cannot be used for imports (as that involves looking for the
48 # specified name in sys.modules).
49 from fparser import one as fparser1
50 from psyclone.configuration import Config
51 from psyclone.errors import InternalError
52 
53 # Module-wide utility methods
54 
55 
56 def bubble_up_type(obj):
57  '''
58  Checks whether the supplied object must be bubbled-up (e.g. from
59  within DO loops).
60 
61  :returns: True if the supplied object is of a type which must be \
62  bubbled-up and False otherwise.
63  '''
64  return isinstance(obj, (UseGen, BaseDeclGen))
65 
66 
67 def index_of_object(alist, obj):
68  '''Effectively implements list.index(obj) but returns the index of
69  the first item in the list that *is* the supplied object (rather than
70  comparing values) '''
71  for idx, body in enumerate(alist):
72  if body is obj:
73  return idx
74  raise Exception(f"Object {obj} not found in list")
75 
76 
77 # This section subclasses the f2py comment class so that we can
78 # reason about directives
79 
80 
81 class Directive(Comment):
82  '''
83  Base class for directives so we can reason about them when walking
84  the tree. Sub-classes the fparser1 Comment class.
85 
86  :param root: the parent node in the AST to which we are adding the \
87  directive
88  :type root: subclass of :py:class:`fparser.common.base_classes.Statement`
89  :param line: the fparser object which we will manipulate to create \
90  the desired directive.
91  :type line: :py:class:`fparser.common.readfortran.Comment`
92  :param str position: e.g. 'begin' or 'end' (language specific)
93  :param str dir_type: the type of directive that this is (e.g. \
94  'parallel do')
95  '''
96  def __init__(self, root, line, position, dir_type):
97  if dir_type not in self._types:
98  raise RuntimeError(f"Error, unrecognised directive type "
99  f"'{dir_type}'. Should be one of {self._types}")
100  if position not in self._positions:
101  raise RuntimeError(f"Error, unrecognised position '{position}'. "
102  f"Should be one of {self._positions}")
103  self._my_type_my_type = dir_type
104  self._position_position = position
105  Comment.__init__(self, root, line)
106 
107  @property
108  def type(self):
109  '''
110  :returns: the type of this Directive.
111  :rtype: str
112  '''
113  return self._my_type_my_type
114 
115  @property
116  def position(self):
117  '''
118  :returns: the position of this Directive ('begin' or 'end').
119  :rtype: str
120  '''
121  return self._position_position
122 
123 
125  '''
126  Subclass Directive for OpenMP directives so we can reason about
127  them when walking the tree.
128 
129  :param root: the parent node in the AST to which we are adding the \
130  directive.
131  :type root: subclass of :py:class:`fparser.common.base_classes.Statement`
132  :param line: the fparser object which we will manipulate to create \
133  the desired directive.
134  :type line: :py:class:`fparser.common.readfortran.Comment`
135  :param str position: e.g. 'begin' or 'end' (language specific).
136  :param str dir_type: the type of directive that this is (e.g. \
137  'parallel do').
138  '''
139  def __init__(self, root, line, position, dir_type):
140  self._types_types = ["parallel do", "parallel", "do", "master", "single",
141  "taskloop", "taskwait", "declare"]
142  self._positions_positions = ["begin", "end"]
143 
144  super(OMPDirective, self).__init__(root, line, position, dir_type)
145 
146 
148  '''
149  Subclass Directive for OpenACC directives so we can reason about them
150  when walking the tree.
151 
152  :param root: the parent node in the AST to which we are adding the \
153  directive.
154  :type root: subclass of :py:class:`fparser.common.base_classes.Statement`
155  :param line: the fparser object which we will manipulate to create \
156  the desired directive.
157  :type line: :py:class:`fparser.common.readfortran.Comment`
158  :param str position: e.g. 'begin' or 'end' (language specific).
159  :param str dir_type: the type of directive that this is (e.g. \
160  'loop').
161  '''
162  def __init__(self, root, line, position, dir_type):
163  self._types_types = ["parallel", "kernels", "enter data", "loop", "routine"]
164  self._positions_positions = ["begin", "end"]
165 
166  super(ACCDirective, self).__init__(root, line, position, dir_type)
167 
168 
169 # This section provides new classes which provide a relatively high
170 # level interface to creating code and adding code to an existing ast
171 
172 
173 class BaseGen():
174  ''' The base class for all classes that are responsible for generating
175  distinct code elements (modules, subroutines, do loops etc.) '''
176  def __init__(self, parent, root):
177  self._parent_parent = parent
178  self._root_root = root
179  self._children_children = []
180 
181  @property
182  def parent(self):
183  ''' Returns the parent of this object '''
184  return self._parent_parent
185 
186  @property
187  def children(self):
188  ''' Returns the list of children of this object '''
189  return self._children_children
190 
191  @property
192  def root(self):
193  ''' Returns the root of the tree containing this object '''
194  return self._root_root
195 
196  def add(self, new_object, position=None):
197  '''Adds a new object to the tree. The actual position is determined by
198  the position argument. Note, there are two trees, the first is
199  the f2pygen object tree, the other is the f2py generated code
200  tree. These are similar but different. At the moment we
201  specify where to add things in terms of the f2pygen tree
202  (which is a higher level api) but we also insert into the f2py
203  tree at exactly the same location which needs to be sorted out
204  at some point.
205 
206  '''
207 
208  # By default the position is 'append'. We set it up this way for
209  # safety because in python, default arguments are instantiated
210  # as objects at the time of definition. If this object is
211  # subsequently modified then the value of the default argument
212  # is modified for subsequent calls of this routine.
213  if position is None:
214  position = ["append"]
215 
216  if position[0] == "auto":
217  raise Exception("Error: BaseGen:add: auto option must be "
218  "implemented by the sub class!")
219  options = ["append", "first", "after", "before", "insert",
220  "before_index", "after_index"]
221  if position[0] not in options:
222  raise Exception(f"Error: BaseGen:add: supported positions are "
223  f"{options} but found {position[0]}")
224  if position[0] == "append":
225  self.rootroot.content.append(new_object.root)
226  elif position[0] == "first":
227  self.rootroot.content.insert(0, new_object.root)
228  elif position[0] == "insert":
229  index = position[1]
230  self.rootroot.content.insert(index, new_object.root)
231  elif position[0] == "after":
232  idx = index_of_object(self.rootroot.content, position[1])
233  self.rootroot.content.insert(idx+1, new_object.root)
234  elif position[0] == "after_index":
235  self.rootroot.content.insert(position[1]+1, new_object.root)
236  elif position[0] == "before_index":
237  self.rootroot.content.insert(position[1], new_object.root)
238  elif position[0] == "before":
239  try:
240  idx = index_of_object(self.rootroot.content, position[1])
241  except Exception as err:
242  print(str(err))
243  raise RuntimeError(
244  "Failed to find supplied object in existing content - "
245  "is it a child of the parent?")
246  self.rootroot.content.insert(idx, new_object.root)
247  else:
248  raise Exception("Error: BaseGen:add: internal error, should "
249  "not get to here")
250  self.childrenchildren.append(new_object)
251 
252  def previous_loop(self):
253  ''' Returns the *last* occurrence of a loop in the list of
254  siblings of this node '''
255  from fparser.one.block_statements import Do
256  for sibling in reversed(self.rootroot.content):
257  if isinstance(sibling, Do):
258  return sibling
259  raise RuntimeError("Error, no loop found - there is no previous loop")
260 
261  def last_declaration(self):
262  '''Returns the *last* occurrence of a Declaration in the list of
263  siblings of this node
264 
265  '''
266  from fparser.one.typedecl_statements import TypeDeclarationStatement
267  for sibling in reversed(self.rootroot.content):
268  if isinstance(sibling, TypeDeclarationStatement):
269  return sibling
270 
271  raise RuntimeError("Error, no variable declarations found")
272 
273  def start_parent_loop(self, debug=False):
274  ''' Searches for the outer-most loop containing this object. Returns
275  the index of that line in the content of the parent. '''
276  from fparser.one.block_statements import Do
277  if debug:
278  print("Entered before_parent_loop")
279  print(f"The type of the current node is {type(self.root)}")
280  print(("If the current node is a Do loop then move up to the "
281  "top of the do loop nest"))
282 
283  # First off, check that we do actually have an enclosing Do loop
284  current = self.rootroot
285  while not isinstance(current, Do) and getattr(current, 'parent', None):
286  current = current.parent
287  if not isinstance(current, Do):
288  raise RuntimeError("This node has no enclosing Do loop")
289 
290  current = self.rootroot
291  local_current = self
292  while isinstance(current.parent, Do):
293  if debug:
294  print("Parent is a do loop so moving to the parent")
295  current = current.parent
296  local_current = local_current.parent
297  if debug:
298  print("The type of the current node is now " + str(type(current)))
299  print("The type of parent is " + str(type(current.parent)))
300  print("Finding the loops position in its parent ...")
301  index = current.parent.content.index(current)
302  if debug:
303  print("The loop's index is ", index)
304  parent = current.parent
305  local_current = local_current.parent
306  if debug:
307  print("The type of the object at the index is " +
308  str(type(parent.content[index])))
309  print("If preceding node is a directive then move back one")
310  if index == 0:
311  if debug:
312  print("current index is 0 so finish")
313  elif isinstance(parent.content[index-1], Directive):
314  if debug:
315  print(
316  f"preceding node is a directive so find out what type ..."
317  f"\n type is {parent.content[index-1].position}"
318  f"\n diretive is {parent.content[index-1]}")
319  if parent.content[index-1].position == "begin":
320  if debug:
321  print("type of directive is begin so move back one")
322  index -= 1
323  else:
324  if debug:
325  print("directive type is not begin so finish")
326  else:
327  if debug:
328  print("preceding node is not a directive so finish")
329  if debug:
330  print("type of final location ", type(parent.content[index]))
331  print("code for final location ", str(parent.content[index]))
332  return local_current, parent.content[index]
333 
334 
336  ''' Functionality relevant to program units (currently modules,
337  subroutines)'''
338  def __init__(self, parent, sub):
339  BaseGen.__init__(self, parent, sub)
340 
341  def add(self, content, position=None, bubble_up=False):
342  '''
343  Specialise the add method to provide module- and subroutine-
344  -specific intelligent adding of use statements, implicit
345  none statements and declarations if the position argument
346  is set to auto (which is the default).
347 
348  :param content: the Node (or sub-tree of Nodes) to add in to \
349  the AST.
350  :type content: :py:class:`psyclone.f2pygen.BaseGen`
351  :param list position: where to insert the node. One of "append", \
352  "first", "insert", "after", "after_index", \
353  "before_index", "before" or "auto". For the \
354  *_index options, the second element of the \
355  list holds the integer index.
356  :param bool bubble_up: whether or not object (content) is in the \
357  process of being bubbled-up.
358  '''
359  # By default the position is 'auto'. We set it up this way for
360  # safety because in python, default arguments are instantiated
361  # as objects at the time of definition. If this object is
362  # subsequently modified then the value of the default argument
363  # is modified for subsequent calls of this routine.
364  if position is None:
365  position = ["auto"]
366 
367  # For an object to be added to another we require that they
368  # share a common ancestor. This means that the added object must
369  # have the current object or one of its ancestors as an ancestor.
370  # Loop over the ancestors of this object (starting with itself)
371  self_ancestor = self.rootroot
372  while self_ancestor:
373  # Loop over the ancestors of the object being added
374  obj_parent = content.root.parent
375  while (obj_parent != self_ancestor and
376  getattr(obj_parent, 'parent', None)):
377  obj_parent = obj_parent.parent
378  if obj_parent == self_ancestor:
379  break
380  # Object being added is not an ancestor of the current
381  # self_ancestor so move one level back up the tree and
382  # try again
383  if getattr(self_ancestor, 'parent', None):
384  self_ancestor = self_ancestor.parent
385  else:
386  break
387 
388  if obj_parent != self_ancestor:
389  raise RuntimeError(
390  f"Cannot add '{content}' to '{self}' because it is not a "
391  f"descendant of it or of any of its ancestors.")
392 
393  if bubble_up:
394  # If content has been passed on (is being bubbled up) then change
395  # its parent to be this object
396  content.root.parent = self.rootroot
397 
398  if position[0] != "auto":
399  # position[0] is not 'auto' so the baseclass can deal with it
400  BaseGen.add(self, content, position)
401  else:
402  # position[0] == "auto" so insert in a context sensitive way
403  if isinstance(content, BaseDeclGen):
404 
405  if isinstance(content, (DeclGen, CharDeclGen)):
406  # have I already been declared?
407  for child in self._children_children:
408  if isinstance(child, (DeclGen, CharDeclGen)):
409  # is this declaration the same type as me?
410  if child.root.name == content.root.name:
411  # we are modifying the list so we need
412  # to iterate over a copy
413  for var_name in content.root.entity_decls[:]:
414  for child_name in child.root.entity_decls:
415  if var_name.lower() == \
416  child_name.lower():
417  content.root.entity_decls.\
418  remove(var_name)
419  if not content.root.entity_decls:
420  # return as all variables in
421  # this declaration already
422  # exist
423  return
424  if isinstance(content, TypeDeclGen):
425  # have I already been declared?
426  for child in self._children_children:
427  if isinstance(child, TypeDeclGen):
428  # is this declaration the same type as me?
429  if child.root.selector[1] == \
430  content.root.selector[1]:
431  # we are modifying the list so we need
432  # to iterate over a copy
433  for var_name in content.root.entity_decls[:]:
434  for child_name in child.root.entity_decls:
435  if var_name.lower() == \
436  child_name.lower():
437  content.root.entity_decls.\
438  remove(var_name)
439  if not content.root.entity_decls:
440  # return as all variables in
441  # this declaration already
442  # exist
443  return
444 
445  index = 0
446  # skip over any use statements
447  index = self._skip_use_and_comments_skip_use_and_comments(index)
448  # skip over implicit none if it exists
449  index = self._skip_imp_none_and_comments_skip_imp_none_and_comments(index)
450  # skip over any declarations which have an intent
451  try:
452  intent = True
453  while intent:
454  intent = False
455  for attr in self.rootroot.content[index].attrspec:
456  if attr.find("intent") == 0:
457  intent = True
458  index += 1
459  break
460  except AttributeError:
461  pass
462  elif isinstance(content.root, fparser1.statements.Use):
463  # have I already been declared?
464  for child in self._children_children:
465  if isinstance(child, UseGen):
466  if child.root.name == content.root.name:
467  # found an existing use with the same name
468  if not child.root.isonly and not \
469  content.root.isonly:
470  # both are generic use statements so
471  # skip this declaration
472  return
473  if child.root.isonly and not content.root.isonly:
474  # new use is generic and existing use
475  # is specific so we can safely add
476  pass
477  if not child.root.isonly and content.root.isonly:
478  # existing use is generic and new use
479  # is specific so we can skip this
480  # declaration
481  return
482  if child.root.isonly and content.root.isonly:
483  # we are modifying the list so we need
484  # to iterate over a copy
485  for new_name in content.root.items[:]:
486  for existing_name in child.root.items:
487  if existing_name.lower() == \
488  new_name.lower():
489  content.root.items.remove(new_name)
490  if not content.root.items:
491  return
492  index = 0
493  elif isinstance(content, ImplicitNoneGen):
494  # does implicit none already exist?
495  for child in self._children_children:
496  if isinstance(child, ImplicitNoneGen):
497  return
498  # skip over any use statements
499  index = 0
500  index = self._skip_use_and_comments_skip_use_and_comments(index)
501  else:
502  index = len(self.rootroot.content) - 1
503  self.rootroot.content.insert(index, content.root)
504  self._children_children.append(content)
505 
506  def _skip_use_and_comments(self, index):
507  ''' skip over any use statements and comments in the ast '''
508  while isinstance(self.rootroot.content[index],
509  fparser1.statements.Use) or\
510  isinstance(self.rootroot.content[index],
511  fparser1.statements.Comment):
512  index += 1
513  # now roll back to previous Use
514  while isinstance(self.rootroot.content[index-1],
515  fparser1.statements.Comment):
516  index -= 1
517  return index
518 
519  def _skip_imp_none_and_comments(self, index):
520  ''' skip over an implicit none statement if it exists and any
521  comments before it '''
522  end_index = index
523  while isinstance(self.rootroot.content[index],
524  fparser1.typedecl_statements.Implicit) or\
525  isinstance(self.rootroot.content[index],
526  fparser1.statements.Comment):
527  if isinstance(self.rootroot.content[index],
528  fparser1.typedecl_statements.Implicit):
529  end_index = index + 1
530  break
531  else:
532  index = index + 1
533  return end_index
534 
535 
537  ''' Create a Fortran block of code that comes from a given PSyIR tree.
538 
539  :param parent: node in AST to which we are adding the PSyIR block.
540  :type parent: :py:class:`psyclone.f2pygen.BaseGen`
541  :param content: the PSyIR tree we are adding.
542  :type content: :py:class:`psyclone.psyir.nodes.Node`
543 
544  '''
545 
546  def __init__(self, parent, content):
547  # Import FortranWriter here to avoid circular-dependency
548  # pylint: disable=import-outside-toplevel
549  from psyclone.psyir.backend.fortran import FortranWriter
550  # We need the Config object in order to see whether or not to disable
551  # the validation performed in the PSyIR backend.
552  config = Config.get()
553 
554  # Use the PSyIR Fortran backend to generate Fortran code of the
555  # supplied PSyIR tree and pass the resulting code to the fparser1
556  # Fortran parser.
557  fortran_writer = FortranWriter(
558  check_global_constraints=config.backend_checks_enabled)
559  reader = FortranStringReader(fortran_writer(content),
560  ignore_comments=False)
561  # Set reader as free form, strict
562  reader.set_format(FortranFormat(True, True))
563  fparser1_parser = FortranParser(reader, ignore_comments=False)
564  fparser1_parser.parse()
565 
566  # If the fparser content is larger than 1, add all the nodes but
567  # the last one as siblings of self. This is done because self
568  # can only represent one node.
569  for fparser_node in fparser1_parser.block.content[:-1]:
570  f2pygen_node = BaseGen(parent, fparser_node)
571  f2pygen_node.root.parent = parent.root
572  parent.add(f2pygen_node)
573 
574  # Update this f2pygen node to be equivalent to the last of the
575  # fparser nodes that represent the provided content.
576  BaseGen.__init__(self, parent, fparser1_parser.block.content[-1])
577  self.rootroot.parent = parent.root
578 
579 
581  ''' create a fortran module '''
582  def __init__(self, name="", contains=True, implicitnone=True):
583  from fparser import api
584 
585  code = '''\
586 module vanilla
587 '''
588  if contains:
589  code += '''\
590 contains
591 '''
592  code += '''\
593 end module vanilla
594 '''
595  tree = api.parse(code, ignore_comments=False)
596  module = tree.content[0]
597  module.name = name
598  endmod = module.content[len(module.content)-1]
599  endmod.name = name
600  ProgUnitGen.__init__(self, None, module)
601  if implicitnone:
602  self.addaddadd(ImplicitNoneGen(self))
603 
604  def add_raw_subroutine(self, content):
605  ''' adds a subroutine to the module that is a raw f2py parse object.
606  This is used for inlining kernel subroutines into a module.
607  '''
608  from psyclone.parse.kernel import KernelProcedure
609  if not isinstance(content, KernelProcedure):
610  raise Exception(
611  "Expecting a KernelProcedure type but received " +
612  str(type(content)))
613  content.ast.parent = self.rootroot
614  # add content after any existing subroutines
615  index = len(self.rootroot.content) - 1
616  self.rootroot.content.insert(index, content.ast)
617 
618 
620  ''' Create a Fortran Comment '''
621  def __init__(self, parent, content):
622  '''
623  :param parent: node in AST to which to add the Comment as a child
624  :type parent: :py:class:`psyclone.f2pygen.BaseGen`
625  :param str content: the content of the comment
626  '''
627  reader = FortranStringReader("! content\n", ignore_comments=False)
628  reader.set_format(FortranFormat(True, True)) # free form, strict
629  subline = reader.next()
630 
631  my_comment = Comment(parent.root, subline)
632  my_comment.content = content
633 
634  BaseGen.__init__(self, parent, my_comment)
635 
636 
638  '''
639  Class for creating a Fortran directive, e.g. OpenMP or OpenACC.
640 
641  :param parent: node in AST to which to add directive as a child.
642  :type parent: :py:class:`psyclone.f2pygen.BaseGen`
643  :param str language: the type of directive (e.g. OMP or ACC).
644  :param str position: "end" if this is the end of a directive block.
645  :param str directive_type: the directive itself (e.g. "PARALLEL DO").
646  :param str content: any additional arguments to add to the directive \
647  (e.g. "PRIVATE(ji)").
648 
649  :raises RuntimeError: if an unrecognised directive language is specified.
650  '''
651  def __init__(self, parent, language, position, directive_type, content=""):
652  self._supported_languages_supported_languages = ["omp", "acc"]
653  self._language_language = language
654  self._directive_type_directive_type = directive_type
655 
656  reader = FortranStringReader("! content\n", ignore_comments=False)
657  reader.set_format(FortranFormat(True, True)) # free form, strict
658  subline = reader.next()
659 
660  if language == "omp":
661  my_comment = OMPDirective(parent.root, subline, position,
662  directive_type)
663  my_comment.content = "$omp"
664  elif language == "acc":
665  my_comment = ACCDirective(parent.root, subline, position,
666  directive_type)
667  my_comment.content = "$acc"
668  else:
669  raise RuntimeError(
670  f"Error, unsupported directive language. Expecting one of "
671  f"{self._supported_languages} but found '{language}'")
672  if position == "end":
673  my_comment.content += " end"
674  my_comment.content += " " + directive_type
675  if content != "":
676  my_comment.content += " " + content
677 
678  BaseGen.__init__(self, parent, my_comment)
679 
680 
682  ''' Generate a Fortran 'implicit none' statement '''
683  def __init__(self, parent):
684  '''
685  :param parent: node in AST to which to add 'implicit none' as a child
686  :type parent: :py:class:`psyclone.f2pygen.ModuleGen` or
687  :py:class:`psyclone.f2pygen.SubroutineGen`
688 
689  :raises Exception: if `parent` is not a ModuleGen or SubroutineGen
690  '''
691  if not isinstance(parent, ModuleGen) and not isinstance(parent,
692  SubroutineGen):
693  raise Exception(
694  f"The parent of ImplicitNoneGen must be a module or a "
695  f"subroutine, but found {type(parent)}")
696  reader = FortranStringReader("IMPLICIT NONE\n")
697  reader.set_format(FortranFormat(True, True)) # free form, strict
698  subline = reader.next()
699 
700  from fparser.one.typedecl_statements import Implicit
701  my_imp_none = Implicit(parent.root, subline)
702 
703  BaseGen.__init__(self, parent, my_imp_none)
704 
705 
707  ''' Generate a Fortran subroutine '''
708  def __init__(self, parent, name="", args=None, implicitnone=False):
709  '''
710  :param parent: node in AST to which to add Subroutine as a child
711  :type parent: :py:class:`psyclone.f2pygen.BaseGen`
712  :param str name: name of the Fortran subroutine
713  :param list args: list of arguments accepted by the subroutine
714  :param bool implicitnone: whether or not we should specify
715  "implicit none" for the body of this
716  subroutine
717  '''
718  reader = FortranStringReader(
719  "subroutine vanilla(vanilla_arg)\nend subroutine")
720  reader.set_format(FortranFormat(True, True)) # free form, strict
721  subline = reader.next()
722  endsubline = reader.next()
723 
724  from fparser.one.block_statements import Subroutine, EndSubroutine
725  self._sub_sub = Subroutine(parent.root, subline)
726  self._sub_sub.name = name
727  if args is None:
728  args = []
729  self._sub_sub.args = args
730  endsub = EndSubroutine(self._sub_sub, endsubline)
731  self._sub_sub.content.append(endsub)
732  ProgUnitGen.__init__(self, parent, self._sub_sub)
733  if implicitnone:
734  self.addaddadd(ImplicitNoneGen(self))
735 
736  @property
737  def args(self):
738  ''' Returns the list of arguments of this subroutine '''
739  return self._sub_sub.args
740 
741  @args.setter
742  def args(self, namelist):
743  ''' sets the subroutine arguments to the values in the list provide.'''
744  self._sub_sub.args = namelist
745 
746 
748  ''' Generates a Fortran call of a subroutine '''
749  def __init__(self, parent, name="", args=None):
750  '''
751  :param parent: node in AST to which to add CallGen as a child
752  :type parent: :py:class:`psyclone.f2pygen.BaseGen`
753  :param str name: the name of the routine to call
754  :param list args: list of arguments to pass to the call
755  '''
756  reader = FortranStringReader("call vanilla(vanilla_arg)")
757  reader.set_format(FortranFormat(True, True)) # free form, strict
758  myline = reader.next()
759 
760  from fparser.one.block_statements import Call
761  self._call_call = Call(parent.root, myline)
762  self._call_call.designator = name
763  if args is None:
764  args = []
765  self._call_call.items = args
766 
767  BaseGen.__init__(self, parent, self._call_call)
768 
769 
771  ''' Generate a Fortran use statement '''
772  def __init__(self, parent, name="", only=False, funcnames=None):
773  '''
774  :param parent: node in AST to which to add UseGen as a child
775  :type parent: :py:class:`psyclone.f2pygen.BaseGen`
776  :param str name: name of the module to USE
777  :param bool only: whether this USE has an ONLY clause
778  :param list funcnames: list of names to follow ONLY clause
779  '''
780  reader = FortranStringReader("use kern,only : func1_kern=>func1")
781  reader.set_format(FortranFormat(True, True)) # free form, strict
782  myline = reader.next()
783  root = parent.root
784  from fparser.one.block_statements import Use
785  use = Use(root, myline)
786  use.name = name
787  use.isonly = only
788  if funcnames is None:
789  funcnames = []
790  use.isonly = False
791  local_funcnames = funcnames[:]
792  use.items = local_funcnames
793  BaseGen.__init__(self, parent, use)
794 
795 
796 def adduse(name, parent, only=False, funcnames=None):
797  '''
798  Adds a use statement with the specified name to the supplied object.
799  This routine is required when modifying an existing AST (e.g. when
800  modifying a kernel). The classes are used when creating an AST from
801  scratch (for the PSy layer).
802 
803  :param str name: name of module to USE
804  :param parent: node in fparser1 AST to which to add this USE as a child
805  :type parent: :py:class:`fparser.one.block_statements.*`
806  :param bool only: whether this USE has an "ONLY" clause
807  :param list funcnames: list of quantities to follow the "ONLY" clause
808 
809  :returns: an fparser1 Use object
810  :rtype: :py:class:`fparser.one.block_statements.Use`
811  '''
812  reader = FortranStringReader("use kern,only : func1_kern=>func1")
813  reader.set_format(FortranFormat(True, True)) # free form, strict
814  myline = reader.next()
815 
816  # find an appropriate place to add in our use statement
817  while not isinstance(parent, (fparser1.block_statements.Program,
818  fparser1.block_statements.Module,
819  fparser1.block_statements.Subroutine)):
820  parent = parent.parent
821  use = fparser1.block_statements.Use(parent, myline)
822  use.name = name
823  use.isonly = only
824  if funcnames is None:
825  funcnames = []
826  use.isonly = False
827  use.items = funcnames
828 
829  parent.content.insert(0, use)
830  return use
831 
832 
834  ''' Generates a Fortran allocate statement '''
835  def __init__(self, parent, content, mold=None):
836  '''
837  :param parent: node to which to add this ALLOCATE as a child
838  :type parent: :py:class:`psyclone.f2pygen.BaseGen`
839  :param content: string or list of variables to allocate
840  :type content: list of strings or a single string
841  :param mold: A string to be used as the 'mold' parameter of ALLOCATE.
842  :type mold: str or None.
843 
844  :raises RuntimeError: if `content` is not of correct type
845  '''
846  reader = FortranStringReader("allocate(dummy)")
847  reader.set_format(FortranFormat(True, False)) # free form, strict
848  myline = reader.next()
849  self._decl_decl = fparser1.statements.Allocate(parent.root, myline)
850  if isinstance(content, str):
851  self._decl_decl.items = [content]
852  elif isinstance(content, list):
853  self._decl_decl.items = content
854  else:
855  raise RuntimeError(
856  f"AllocateGen expected the content argument to be a str or"
857  f" a list, but found {type(content)}")
858  if mold:
859  self._decl_decl.items.append(f"mold={mold}")
860  BaseGen.__init__(self, parent, self._decl_decl)
861 
862 
864  ''' Generates a Fortran deallocate statement '''
865  def __init__(self, parent, content):
866  '''
867  :param parent: node to which to add this DEALLOCATE as a child
868  :type parent: :py:class:`psyclone.f2pygen.BaseGen`
869  :param content: string or list of variables to deallocate
870  :type content: list of strings or a single string
871 
872  :raises RuntimeError: if `content` is not of correct type
873  '''
874  reader = FortranStringReader("deallocate(dummy)")
875  reader.set_format(FortranFormat(True, False)) # free form, strict
876  myline = reader.next()
877  self._decl_decl = fparser1.statements.Deallocate(parent.root, myline)
878  if isinstance(content, str):
879  self._decl_decl.items = [content]
880  elif isinstance(content, list):
881  self._decl_decl.items = content
882  else:
883  raise RuntimeError(
884  f"DeallocateGen expected the content argument to be a str"
885  f" or a list, but found {type(content)}")
886  BaseGen.__init__(self, parent, self._decl_decl)
887 
888 
889 class BaseDeclGen(BaseGen, metaclass=abc.ABCMeta):
890  '''
891  Abstract base class for all types of Fortran declaration. Uses the
892  abc module so it cannot be instantiated.
893 
894  :param parent: node to which to add this declaration as a child.
895  :type parent: :py:class:`psyclone.f2pygen.BaseGen`
896  :param str datatype: the (intrinsic) type for this declaration.
897  :param list entity_decls: list of variable names to declare.
898  :param str intent: the INTENT attribute of this declaration.
899  :param bool pointer: whether or not this is a pointer declaration.
900  :param str dimension: the DIMENSION specifier (i.e. the xx in \
901  DIMENSION(xx)).
902  :param bool allocatable: whether this declaration is for an \
903  ALLOCATABLE quantity.
904  :param bool save: whether this declaration has the SAVE attribute.
905  :param bool target: whether this declaration has the TARGET attribute.
906  :param initial_values: initial value to give each variable.
907  :type initial_values: list of str with same no. of elements as entity_decls
908  :param bool private: whether this declaration has the PRIVATE attribute \
909  (default is False).
910 
911  :raises RuntimeError: if no variable names are specified.
912  :raises RuntimeError: if the wrong number or type of initial values are \
913  supplied.
914  :raises RuntimeError: if initial values are supplied for a quantity that \
915  is allocatable or has INTENT(in).
916  :raises NotImplementedError: if initial values are supplied for array \
917  variables (dimension != "").
918 
919  '''
920  _decl = None # Will hold the declaration object created by sub-class
921 
922  def __init__(self, parent, datatype="", entity_decls=None, intent="",
923  pointer=False, dimension="", allocatable=False,
924  save=False, target=False, initial_values=None, private=False):
925  if entity_decls is None:
926  raise RuntimeError(
927  "Cannot create a variable declaration without specifying the "
928  "name(s) of the variable(s)")
929 
930  # If initial values have been supplied then check that there
931  # are the right number of them and that they are consistent
932  # with the type of the variable(s) being declared.
933  if initial_values:
934  if len(initial_values) != len(entity_decls):
935  raise RuntimeError(
936  f"f2pygen.DeclGen.init: number of initial values supplied "
937  f"({len(initial_values)}) does not match the number of "
938  f"variables to be declared ({len(entity_decls)}: "
939  f"{entity_decls})")
940  if allocatable:
941  raise RuntimeError(
942  f"Cannot specify initial values for variable(s) "
943  f"{entity_decls} because they have the 'allocatable' "
944  f"attribute.")
945  if dimension:
946  raise NotImplementedError(
947  "Specifying initial values for array declarations is not "
948  "currently supported.")
949  if intent.lower() == "in":
950  raise RuntimeError(
951  f"Cannot assign (initial) values to variable(s) "
952  f"{entity_decls} as they have INTENT(in).")
953  # Call sub-class-provided implementation to check actual
954  # values provided.
955  self._check_initial_values_check_initial_values(datatype, initial_values)
956 
957  # Store the list of variable names
958  self._names_names = entity_decls[:]
959 
960  # Make a copy of entity_decls as we may modify it
961  local_entity_decls = entity_decls[:]
962  if initial_values:
963  # Create a list of 2-tuples
964  value_pairs = zip(local_entity_decls, initial_values)
965  # Construct an assignment from each tuple
966  self._decl_decl.entity_decls = ["=".join(_) for _ in value_pairs]
967  else:
968  self._decl_decl.entity_decls = local_entity_decls
969 
970  # Construct the list of attributes
971  my_attrspec = []
972  if intent != "":
973  my_attrspec.append(f"intent({intent})")
974  if pointer:
975  my_attrspec.append("pointer")
976  if target:
977  my_attrspec.append("target")
978  if allocatable:
979  my_attrspec.append("allocatable")
980  if save:
981  my_attrspec.append("save")
982  if private:
983  my_attrspec.append("private")
984  if dimension != "":
985  my_attrspec.append(f"dimension({dimension})")
986  self._decl_decl.attrspec = my_attrspec
987 
988  super(BaseDeclGen, self).__init__(parent, self._decl_decl)
989 
990  @property
991  def names(self):
992  '''
993  :returns: the names of the variables being declared.
994  :rtype: list of str.
995  '''
996  return self._names_names
997 
998  @property
999  def root(self):
1000  '''
1001  :returns: the associated Type object.
1002  :rtype: \
1003  :py:class:`fparser.one.typedecl_statements.TypeDeclarationStatement`.
1004  '''
1005  return self._decl_decl
1006 
1007  @abc.abstractmethod
1008  def _check_initial_values(self, dtype, values):
1009  '''
1010  Check that the supplied values are consistent with the requested
1011  data type. This method must be overridden in any sub-class of
1012  BaseDeclGen and is called by the BaseDeclGen constructor.
1013 
1014  :param str dtype: Fortran type.
1015  :param list values: list of values as strings.
1016  :raises RuntimeError: if the supplied values are not consistent \
1017  with the specified data type or are not \
1018  supported.
1019  '''
1020 
1021 
1022 class DeclGen(BaseDeclGen):
1023  '''Generates a Fortran declaration for variables of various intrinsic
1024  types (integer, real and logical). For character variables
1025  CharDeclGen should be used.
1026 
1027  :param parent: node to which to add this declaration as a child.
1028  :type parent: :py:class:`psyclone.f2pygen.BaseGen`
1029  :param str datatype: the (intrinsic) type for this declaration.
1030  :param list entity_decls: list of variable names to declare.
1031  :param str intent: the INTENT attribute of this declaration.
1032  :param bool pointer: whether or not this is a pointer declaration.
1033  :param str kind: the KIND attribute to use for this declaration.
1034  :param str dimension: the DIMENSION specifier (i.e. the xx in \
1035  DIMENSION(xx)).
1036  :param bool allocatable: whether this declaration is for an \
1037  ALLOCATABLE quantity.
1038  :param bool save: whether this declaration has the SAVE attribute.
1039  :param bool target: whether this declaration has the TARGET attribute.
1040  :param initial_values: initial value to give each variable.
1041  :type initial_values: list of str with same no. of elements as \
1042  entity_decls
1043  :param bool private: whether this declaration has the PRIVATE attribute \
1044  (default is False).
1045 
1046  :raises RuntimeError: if datatype is not one of DeclGen.SUPPORTED_TYPES.
1047 
1048  '''
1049  # The Fortran intrinsic types supported by this class
1050  SUPPORTED_TYPES = ["integer", "real", "logical"]
1051 
1052  def __init__(self, parent, datatype="", entity_decls=None, intent="",
1053  pointer=False, kind="", dimension="", allocatable=False,
1054  save=False, target=False, initial_values=None, private=False):
1055 
1056  dtype = datatype.lower()
1057  if dtype not in self.SUPPORTED_TYPESSUPPORTED_TYPES:
1058  raise RuntimeError(
1059  f"f2pygen.DeclGen.init: Only {self.SUPPORTED_TYPES} types are "
1060  f"currently supported and you specified '{datatype}'")
1061 
1062  fort_fmt = FortranFormat(True, False) # free form, strict
1063  if dtype == "integer":
1064  reader = FortranStringReader("integer :: vanilla")
1065  reader.set_format(fort_fmt)
1066  myline = reader.next()
1067  self._decl_decl_decl = fparser1.typedecl_statements.Integer(parent.root,
1068  myline)
1069  elif dtype == "real":
1070  reader = FortranStringReader("real :: vanilla")
1071  reader.set_format(fort_fmt)
1072  myline = reader.next()
1073  self._decl_decl_decl = fparser1.typedecl_statements.Real(parent.root, myline)
1074  elif dtype == "logical":
1075  reader = FortranStringReader("logical :: vanilla")
1076  reader.set_format(fort_fmt)
1077  myline = reader.next()
1078  self._decl_decl_decl = fparser1.typedecl_statements.Logical(parent.root,
1079  myline)
1080  else:
1081  # Defensive programming in case SUPPORTED_TYPES is added to
1082  # but not handled here
1083  raise InternalError(
1084  f"Type '{dtype}' is in DeclGen.SUPPORTED_TYPES "
1085  f"but not handled by constructor.")
1086 
1087  # Add any kind-selector
1088  if kind:
1089  self._decl_decl_decl.selector = ('', kind)
1090 
1091  super(DeclGen, self).__init__(parent=parent, datatype=datatype,
1092  entity_decls=entity_decls,
1093  intent=intent, pointer=pointer,
1094  dimension=dimension,
1095  allocatable=allocatable, save=save,
1096  target=target,
1097  initial_values=initial_values,
1098  private=private)
1099 
1100  def _check_initial_values(self, dtype, values):
1101  '''
1102  Check that the supplied values are consistent with the requested
1103  data type. Note that this checking is fairly basic and does not
1104  support a number of valid Fortran forms (e.g. arithmetic expressions
1105  involving constants or parameters).
1106 
1107  :param str dtype: Fortran intrinsic type.
1108  :param list values: list of values as strings.
1109  :raises RuntimeError: if the supplied values are not consistent \
1110  with the specified data type or are not \
1111  supported.
1112  '''
1113  from fparser.two.pattern_tools import abs_name, \
1114  abs_logical_literal_constant, abs_signed_int_literal_constant, \
1115  abs_signed_real_literal_constant
1116  if dtype == "logical":
1117  # Can be .true., .false. or a valid Fortran variable name
1118  for val in values:
1119  if not abs_logical_literal_constant.match(val) and \
1120  not abs_name.match(val):
1121  raise RuntimeError(
1122  f"Initial value of '{val}' for a logical variable is "
1123  f"invalid or unsupported")
1124  elif dtype == "integer":
1125  # Can be a an integer expression or a valid Fortran variable name
1126  for val in values:
1127  if not abs_signed_int_literal_constant.match(val) and \
1128  not abs_name.match(val):
1129  raise RuntimeError(
1130  f"Initial value of '{val}' for an integer variable is "
1131  f"invalid or unsupported")
1132  elif dtype == "real":
1133  # Can be a floating-point expression or a valid Fortran name
1134  for val in values:
1135  if not abs_signed_real_literal_constant.match(val) and \
1136  not abs_name.match(val):
1137  raise RuntimeError(
1138  f"Initial value of '{val}' for a real variable is "
1139  f"invalid or unsupported")
1140  else:
1141  # We should never get to here because we check that the type
1142  # is supported before calling this routine.
1143  raise InternalError(
1144  f"unsupported type '{dtype}' - should be "
1145  f"one of {DeclGen.SUPPORTED_TYPES}")
1146 
1147 
1149  '''
1150  Generates a Fortran declaration for character variables.
1151 
1152  :param parent: node to which to add this declaration as a child.
1153  :type parent: :py:class:`psyclone.f2pygen.BaseGen`.
1154  :param list entity_decls: list of variable names to declare.
1155  :param str intent: the INTENT attribute of this declaration.
1156  :param bool pointer: whether or not this is a pointer declaration.
1157  :param str kind: the KIND attribute to use for this declaration.
1158  :param str dimension: the DIMENSION specifier (i.e. the xx in \
1159  DIMENSION(xx)).
1160  :param bool allocatable: whether this declaration is for an \
1161  ALLOCATABLE quantity.
1162  :param bool save: whether this declaration has the SAVE attribute.
1163  :param bool target: whether this declaration has the TARGET attribute.
1164  :param str length: expression to use for the (len=xx) selector.
1165  :param initial_values: list of initial values, one for each variable. \
1166  Each of these can be either a variable name or a literal, quoted \
1167  string (e.g. "'hello'"). Default is None.
1168  :type initial_values: list of str with same no. of elements as entity_decls
1169  :param bool private: whether this declaration has the PRIVATE attribute.
1170 
1171  '''
1172  def __init__(self, parent, entity_decls=None, intent="",
1173  pointer=False, kind="", dimension="", allocatable=False,
1174  save=False, target=False, length="", initial_values=None,
1175  private=False):
1176 
1177  reader = FortranStringReader(
1178  "character(len=vanilla_len) :: vanilla")
1179  reader.set_format(FortranFormat(True, False))
1180  myline = reader.next()
1181  self._decl_decl_decl = fparser1.typedecl_statements.Character(parent.root,
1182  myline)
1183  # Add character- and kind-selectors
1184  self._decl_decl_decl.selector = (length, kind)
1185 
1186  super(CharDeclGen, self).__init__(parent=parent,
1187  datatype="character",
1188  entity_decls=entity_decls,
1189  intent=intent, pointer=pointer,
1190  dimension=dimension,
1191  allocatable=allocatable, save=save,
1192  target=target,
1193  initial_values=initial_values,
1194  private=private)
1195 
1196  def _check_initial_values(self, _, values):
1197  '''
1198  Check that initial values provided for a Character declaration are
1199  valid.
1200  :param _: for consistency with base-class interface.
1201  :param list values: list of strings containing initial values.
1202  :raises RuntimeError: if any of the supplied initial values is not \
1203  valid for a Character declaration.
1204  '''
1205  from fparser.two.pattern_tools import abs_name
1206  # Can be a quoted string or a valid Fortran name
1207  # TODO it would be nice if fparser.two.pattern_tools provided
1208  # e.g. abs_character_literal_constant
1209  for val in values:
1210  if not abs_name.match(val):
1211  if not ((val.startswith("'") and val.endswith("'")) or
1212  (val.startswith('"') and val.endswith('"'))):
1213  raise RuntimeError(
1214  f"Initial value of '{val}' for a character variable "
1215  f"is invalid or unsupported")
1216 
1217 
1219  '''
1220  Generates a Fortran declaration for variables of a derived type.
1221 
1222  :param parent: node to which to add this declaration as a child.
1223  :type parent: :py:class:`psyclone.f2pygen.BaseGen`
1224  :param str datatype: the type for this declaration.
1225  :param list entity_decls: list of variable names to declare.
1226  :param str intent: the INTENT attribute of this declaration.
1227  :param bool pointer: whether or not this is a pointer declaration.
1228  :param str dimension: the DIMENSION specifier (i.e. the xx in \
1229  DIMENSION(xx)).
1230  :param bool allocatable: whether this declaration is for an \
1231  ALLOCATABLE quantity.
1232  :param bool save: whether this declaration has the SAVE attribute.
1233  :param bool target: whether this declaration has the TARGET attribute.
1234  :param bool is_class: whether this is a class rather than type declaration.
1235  :param bool private: whether or not this declaration has the PRIVATE \
1236  attribute. (Defaults to False.)
1237  '''
1238  def __init__(self, parent, datatype="", entity_decls=None, intent="",
1239  pointer=False, dimension="", allocatable=False,
1240  save=False, target=False, is_class=False, private=False):
1241  if is_class:
1242  reader = FortranStringReader("class(vanillatype) :: vanilla")
1243  else:
1244  reader = FortranStringReader("type(vanillatype) :: vanilla")
1245  reader.set_format(FortranFormat(True, False)) # free form, strict
1246  myline = reader.next()
1247  if is_class:
1248  self._decl_decl_decl = fparser1.typedecl_statements.Class(parent.root,
1249  myline)
1250  else:
1251  self._decl_decl_decl = fparser1.typedecl_statements.Type(parent.root, myline)
1252  self._decl_decl_decl.selector = ('', datatype)
1253 
1254  super(TypeDeclGen, self).__init__(parent=parent, datatype=datatype,
1255  entity_decls=entity_decls,
1256  intent=intent, pointer=pointer,
1257  dimension=dimension,
1258  allocatable=allocatable, save=save,
1259  target=target, private=private)
1260 
1261  def _check_initial_values(self, _type, _values):
1262  '''
1263  Simply here to override abstract method in base class. It is an
1264  error if we ever call it because we don't support initial values for
1265  declarations of derived types.
1266 
1267  :param str _type: the type of the Fortran variable to be declared.
1268  :param list _values: list of str containing initialisation \
1269  values/expressions.
1270  :raises InternalError: because specifying initial values for \
1271  variables of derived type is not supported.
1272  '''
1273  raise InternalError(
1274  "This method should not have been called because initial values "
1275  "for derived-type declarations are not supported.")
1276 
1277 
1278 class TypeCase(Case):
1279  ''' Generate a Fortran SELECT CASE statement '''
1280  # TODO can this whole class be deleted?
1281  def tofortran(self, isfix=None):
1282  tab = self.get_indent_tab(isfix=isfix)
1283  type_str = 'TYPE IS'
1284  if self.items:
1285  item_list = []
1286  for item in self.items:
1287  item_list.append((' : '.join(item)).strip())
1288  type_str += f" ( {(', '.join(item_list))} )"
1289  else:
1290  type_str = 'CLASS DEFAULT'
1291  if self.name:
1292  type_str += ' ' + self.name
1293  return tab + type_str
1294 
1295 
1297  ''' Generate a Fortran SELECT block '''
1298  # TODO can this whole class be deleted?
1299 
1300  def __init__(self, parent, expr="UNSET", typeselect=False):
1301  '''
1302  Construct a SelectionGen for creating a SELECT block
1303 
1304  :param parent: node to which to add this select block as a child
1305  :type parent: :py:class:`psyclone.f2pygen.BaseGen`
1306  :param str expr: the CASE expression
1307  :param bool typeselect: whether or not this is a SELECT TYPE rather
1308  than a SELECT CASE
1309  '''
1310  self._typeselect_typeselect = typeselect
1311  reader = FortranStringReader(
1312  "SELECT CASE (x)\nCASE (1)\nCASE DEFAULT\nEND SELECT")
1313  reader.set_format(FortranFormat(True, True)) # free form, strict
1314  select_line = reader.next()
1315  self._case_line_case_line = reader.next()
1316  self._case_default_line_case_default_line = reader.next()
1317  end_select_line = reader.next()
1318  if self._typeselect_typeselect:
1319  select = SelectType(parent.root, select_line)
1320  else:
1321  select = SelectCase(parent.root, select_line)
1322  endselect = EndSelect(select, end_select_line)
1323  select.expr = expr
1324  select.content.append(endselect)
1325  BaseGen.__init__(self, parent, select)
1326 
1327  def addcase(self, casenames, content=None):
1328  ''' Add a case to this select block '''
1329  if content is None:
1330  content = []
1331  if self._typeselect_typeselect:
1332  case = TypeCase(self.rootroot, self._case_line_case_line)
1333  else:
1334  case = Case(self.rootroot, self._case_line_case_line)
1335  case.items = [casenames]
1336  self.rootroot.content.insert(0, case)
1337  idx = 0
1338  for stmt in content:
1339  idx += 1
1340  self.rootroot.content.insert(idx, stmt.root)
1341 
1342  def adddefault(self):
1343  ''' Add the default case to this select block '''
1344  if self._typeselect_typeselect:
1345  case_default = TypeCase(self.rootroot, self._case_default_line_case_default_line)
1346  else:
1347  case_default = Case(self.rootroot, self._case_default_line_case_default_line)
1348  self.rootroot.content.insert(len(self.rootroot.content)-1, case_default)
1349 
1350 
1352  ''' Create a Fortran Do loop '''
1353  def __init__(self, parent, variable_name, start, end, step=None):
1354  '''
1355  :param parent: the node to which to add this do loop as a child
1356  :type parent: :py:class:`psyclone.f2pygen.BaseGen`
1357  :param str variable_name: the name of the loop variable
1358  :param str start: start value for Do loop
1359  :param str end: upper-limit of Do loop
1360  :param str step: increment to use in Do loop
1361  '''
1362  reader = FortranStringReader("do i=1,n\nend do")
1363  reader.set_format(FortranFormat(True, True)) # free form, strict
1364  doline = reader.next()
1365  enddoline = reader.next()
1366  dogen = fparser1.block_statements.Do(parent.root, doline)
1367  dogen.loopcontrol = variable_name + "=" + start + "," + end
1368  if step is not None:
1369  dogen.loopcontrol = dogen.loopcontrol + "," + step
1370  enddo = fparser1.block_statements.EndDo(dogen, enddoline)
1371  dogen.content.append(enddo)
1372 
1373  BaseGen.__init__(self, parent, dogen)
1374 
1375  def add(self, content, position=None, bubble_up=False):
1376  if position is None:
1377  position = ["auto"]
1378 
1379  if position[0] == "auto" and bubble_up: # pragma: no cover
1380  # There's currently no case where a bubbled-up statement
1381  # will live within a do loop so bubble it up again.
1382  self.parentparent.add(content, bubble_up=True)
1383  return
1384 
1385  if position[0] == "auto" or position[0] == "append":
1386  if (position[0] == "auto" and
1387  bubble_up_type(content)): # pragma: no cover
1388  # use and declaration statements cannot appear in a do loop
1389  # so pass on to parent
1390  self.parentparent.add(content, bubble_up=True)
1391  return
1392  else:
1393  # append at the end of the loop. This is not a simple
1394  # append as the last element in the loop is the "end
1395  # do" so we insert at the penultimate location
1396  BaseGen.add(self, content,
1397  position=["insert", len(self.rootroot.content)-1])
1398  else:
1399  BaseGen.add(self, content, position=position)
1400 
1401 
1403  ''' Generate a fortran if, then, end if statement. '''
1404 
1405  def __init__(self, parent, clause):
1406  '''
1407  :param parent: Node to which to add this IfThen as a child
1408  :type parent: :py:class:`psyclone.f2pygen.BaseGen`
1409  :param str clause: the condition, xx, to evaluate in the if(xx)then
1410  '''
1411  reader = FortranStringReader("if (dummy) then\nend if")
1412  reader.set_format(FortranFormat(True, True)) # free form, strict
1413  ifthenline = reader.next()
1414  endifline = reader.next()
1415 
1416  my_if = fparser1.block_statements.IfThen(parent.root, ifthenline)
1417  my_if.expr = clause
1418  my_endif = fparser1.block_statements.EndIfThen(my_if, endifline)
1419  my_if.content.append(my_endif)
1420 
1421  BaseGen.__init__(self, parent, my_if)
1422 
1423  def add(self, content, position=None):
1424  if position is None:
1425  position = ["auto"]
1426  if position[0] == "auto" or position[0] == "append":
1427  if position[0] == "auto" and bubble_up_type(content):
1428  # use and declaration statements cannot appear in an if
1429  # block so pass on (bubble-up) to parent
1430  self.parentparent.add(content, bubble_up=True)
1431  else:
1432  # append at the end of the loop. This is not a simple
1433  # append as the last element in the if is the "end if"
1434  # so we insert at the penultimate location
1435  BaseGen.add(self, content,
1436  position=["insert", len(self.rootroot.content)-1])
1437  else:
1438  BaseGen.add(self, content, position=position)
1439 
1440 
1442  ''' Generates a Fortran statement where a value is assigned to a
1443  variable quantity '''
1444 
1445  def __init__(self, parent, lhs="", rhs="", pointer=False):
1446  '''
1447  :param parent: the node to which to add this assignment as a child
1448  :type parent: :py:class:`psyclone.f2pygen.BaseGen`
1449  :param str lhs: the LHS of the assignment expression
1450  :param str rhs: the RHS of the assignment expression
1451  :param bool pointer: whether or not this is a pointer assignment
1452  '''
1453  if pointer:
1454  reader = FortranStringReader("lhs=>rhs")
1455  else:
1456  reader = FortranStringReader("lhs=rhs")
1457  reader.set_format(FortranFormat(True, True)) # free form, strict
1458  myline = reader.next()
1459  if pointer:
1460  self._assign_assign = fparser1.statements.PointerAssignment(parent.root,
1461  myline)
1462  else:
1463  self._assign_assign = fparser1.statements.Assignment(parent.root, myline)
1464  self._assign_assign.expr = rhs
1465  self._assign_assign.variable = lhs
1466  BaseGen.__init__(self, parent, self._assign_assign)
def __init__(self, parent, content, mold=None)
Definition: f2pygen.py:835
def __init__(self, parent, lhs="", rhs="", pointer=False)
Definition: f2pygen.py:1445
def _check_initial_values(self, dtype, values)
Definition: f2pygen.py:1008
def previous_loop(self)
Definition: f2pygen.py:252
def last_declaration(self)
Definition: f2pygen.py:261
def start_parent_loop(self, debug=False)
Definition: f2pygen.py:273
def add(self, new_object, position=None)
Definition: f2pygen.py:196
def __init__(self, parent, name="", args=None)
Definition: f2pygen.py:749
def __init__(self, parent, content)
Definition: f2pygen.py:621
def __init__(self, parent, content)
Definition: f2pygen.py:865
def __init__(self, parent, variable_name, start, end, step=None)
Definition: f2pygen.py:1353
def __init__(self, parent, clause)
Definition: f2pygen.py:1405
def add(self, content, position=None)
Definition: f2pygen.py:1423
def __init__(self, parent)
Definition: f2pygen.py:683
def add_raw_subroutine(self, content)
Definition: f2pygen.py:604
def add(self, content, position=None, bubble_up=False)
Definition: f2pygen.py:341
def _skip_imp_none_and_comments(self, index)
Definition: f2pygen.py:519
def _skip_use_and_comments(self, index)
Definition: f2pygen.py:506
def addcase(self, casenames, content=None)
Definition: f2pygen.py:1327
def __init__(self, parent, expr="UNSET", typeselect=False)
Definition: f2pygen.py:1300
def __init__(self, parent, name="", args=None, implicitnone=False)
Definition: f2pygen.py:708
def __init__(self, parent, name="", only=False, funcnames=None)
Definition: f2pygen.py:772