Reference Guide  2.5.0
fparser2.py
1 # BSD 3-Clause License
2 #
3 # Copyright (c) 2017-2024, Science and Technology Facilities Council.
4 # All rights reserved.
5 #
6 # Redistribution and use in source and binary forms, with or without
7 # modification, are permitted provided that the following conditions are met:
8 #
9 # * Redistributions of source code must retain the above copyright notice, this
10 # list of conditions and the following disclaimer.
11 #
12 # * Redistributions in binary form must reproduce the above copyright notice,
13 # this list of conditions and the following disclaimer in the documentation
14 # and/or other materials provided with the distribution.
15 #
16 # * Neither the name of the copyright holder nor the names of its
17 # contributors may be used to endorse or promote products derived from
18 # this software without specific prior written permission.
19 #
20 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
23 # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
24 # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
25 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
26 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
27 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
30 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31 # POSSIBILITY OF SUCH DAMAGE.
32 # -----------------------------------------------------------------------------
33 # Authors: R. W. Ford, A. R. Porter, S. Siso and N. Nobre, STFC Daresbury Lab
34 # J. Henrichs, Bureau of Meteorology
35 # I. Kavcic, Met Office
36 # Modified: A. B. G. Chalk, STFC Daresbury Lab
37 # -----------------------------------------------------------------------------
38 
39 ''' This module provides the fparser2 to PSyIR front-end, it follows a
40  Visitor Pattern to traverse relevant fparser2 nodes and contains the logic
41  to transform each node into the equivalent PSyIR representation.'''
42 
43 from collections import OrderedDict
44 from dataclasses import dataclass, field
45 import os
46 from typing import Optional, List
47 
48 from fparser.common.readfortran import FortranStringReader
49 from fparser.two import C99Preprocessor, Fortran2003, utils
50 from fparser.two.parser import ParserFactory
51 from fparser.two.utils import walk, BlockBase, StmtBase
52 
53 from psyclone.configuration import Config
54 from psyclone.errors import InternalError, GenerationError
55 from psyclone.psyir.nodes import (
56  ArrayMember, ArrayOfStructuresReference, ArrayReference, Assignment,
57  BinaryOperation, Call, CodeBlock, Container, Directive, FileContainer,
58  IfBlock, IntrinsicCall, Literal, Loop, Member, Node, Range,
59  Reference, Return, Routine, Schedule, StructureReference, UnaryOperation,
60  WhileLoop)
61 from psyclone.psyir.nodes.array_mixin import ArrayMixin
63  ArrayOfStructuresMixin)
64 from psyclone.psyir.symbols import (
65  ArgumentInterface, ArrayType, AutomaticInterface, CHARACTER_TYPE,
66  CommonBlockInterface, ContainerSymbol, DataSymbol, DataTypeSymbol,
67  DefaultModuleInterface, GenericInterfaceSymbol, ImportInterface,
68  INTEGER_TYPE, NoType, RoutineSymbol, ScalarType, StaticInterface,
69  StructureType, Symbol, SymbolError, SymbolTable, UnknownInterface,
70  UnresolvedInterface, UnresolvedType, UnsupportedFortranType,
71  UnsupportedType)
72 
73 # fparser dynamically generates classes which confuses pylint membership checks
74 # pylint: disable=maybe-no-member
75 # pylint: disable=too-many-branches
76 # pylint: disable=too-many-locals
77 # pylint: disable=too-many-statements
78 # pylint: disable=too-many-lines
79 
80 #: The list of Fortran intrinsic functions that we know about (and can
81 #: therefore distinguish from array accesses). These are taken from
82 #: fparser.
83 FORTRAN_INTRINSICS = Fortran2003.Intrinsic_Name.function_names
84 
85 #: Mapping from Fortran data types to PSyIR types
86 TYPE_MAP_FROM_FORTRAN = {"integer": ScalarType.Intrinsic.INTEGER,
87  "character": ScalarType.Intrinsic.CHARACTER,
88  "logical": ScalarType.Intrinsic.BOOLEAN,
89  "real": ScalarType.Intrinsic.REAL,
90  "double precision": ScalarType.Intrinsic.REAL}
91 
92 #: Mapping from Fortran access specifiers to PSyIR visibilities
93 VISIBILITY_MAP_FROM_FORTRAN = {"public": Symbol.Visibility.PUBLIC,
94  "private": Symbol.Visibility.PRIVATE}
95 
96 #: Mapping from fparser2 Fortran Literal types to PSyIR types
97 CONSTANT_TYPE_MAP = {
98  Fortran2003.Real_Literal_Constant: ScalarType.Intrinsic.REAL,
99  Fortran2003.Logical_Literal_Constant: ScalarType.Intrinsic.BOOLEAN,
100  Fortran2003.Char_Literal_Constant: ScalarType.Intrinsic.CHARACTER,
101  Fortran2003.Int_Literal_Constant: ScalarType.Intrinsic.INTEGER}
102 
103 #: Mapping from Fortran intent to PSyIR access type
104 INTENT_MAPPING = {"in": ArgumentInterface.Access.READ,
105  "out": ArgumentInterface.Access.WRITE,
106  "inout": ArgumentInterface.Access.READWRITE}
107 
108 #: Those routine prefix specifications that we support.
109 SUPPORTED_ROUTINE_PREFIXES = ["ELEMENTAL", "PURE", "IMPURE"]
110 
111 
112 # TODO #2302: It may be that this method could be made more general so
113 # that it works for more intrinsics, to help minimise the number of
114 # canonicalise_* functions.
115 def _canonicalise_minmaxsum(arg_nodes, arg_names, node):
116  '''Canonicalise the arguments to any of the minval, maxval or sum
117  intrinsics. These three intrinsics can use the same function as
118  they have the same argument rules:
119 
120  RESULT = [MINVAL, MAXVAL, SUM](ARRAY[, MASK])
121  RESULT = [MINVAL, MAXVAL, SUM](ARRAY, DIM[, MASK])
122 
123  This function re-orderes and modifies the supplied arguments a
124  canonical form so that the PSyIR does not need to support the
125  different forms that are allowed in Fortran.
126 
127  In general Fortran supports all arguments being named, all
128  arguments being positional and everything in-between, as long as
129  all named arguments follow all positional arguments.
130 
131  For example, both SUM(A, DIM, MASK) and SUM(DIM=DIM, MASK=MASK,
132  ARRAY=A) are equivalent in Fortran.
133 
134  The PSyIR canonical form has all required arguments as positional
135  arguments and all optional arguments as named arguments, which
136  would result in SUM(A, DIM=DIM, MASK=MASK) in this case. Note that
137  the canonical form does not constrain the order of named
138  arguments.
139 
140  In the case where the argument type needs to be determined in
141  order to create the PSyIR canonical form a CodeBlock is used (by
142  raising NotImplementedError).
143 
144  :param arg_nodes: a list of fparser2 arguments.
145  :type arg_nodes: List[:py:class:`fparser.two.utils.Base`]
146  :param arg_names: a list of named-argument names.
147  :type arg_names: List[Union[str, None]]
148  :param node: the PSyIR Call or IntrinsicCall node.
149  :type node: :py:class:`psyclone.psyir.nodes.Call` or \
150  :py:class:`psyclone.psyir.nodes.IntrinsicCall`
151 
152  :raises InternalError: if the array argument is not found in the \
153  argument list.
154  :raises NotImplementedError: if there are two arguments and both \
155  of them are not named as the second argument could be a \
156  dimension or a mask and it is not currently possible to \
157  determine which.
158 
159  '''
160  # if the array argument is named then make it the first positional
161  # argument. Simply checking arg_names[0] is OK as, if the first
162  # argument is named, then all arguments must be named (to be valid
163  # Fortran).
164  if arg_names[0]:
165  arg_name_index = 0
166  for name in arg_names:
167  if name.lower() == "array":
168  break
169  arg_name_index += 1
170  else:
171  raise InternalError(
172  f"Invalid intrinsic arguments found. Expecting one "
173  f"of the named arguments to be 'array', but found "
174  f"'{node}'.")
175  # Remove the argument name and add an empty argument name to
176  # the start of the list.
177  _ = arg_names.pop(arg_name_index)
178  arg_names.insert(0, None)
179  # Move the array argument to the start of the list.
180  node = arg_nodes.pop(arg_name_index)
181  arg_nodes.insert(0, node)
182  return
183 
184  num_arg_names = len([arg_name for arg_name in arg_names
185  if arg_name])
186 
187  # If there are two arguments and they are both not
188  # named then the second argument could be a dim
189  # (integer) or mask (logical) argument. We could
190  # attempt to determine the datatype of the argument
191  # but for the moment give up and return a CodeBlock.
192  if len(arg_nodes) == 2 and num_arg_names == 0:
193  raise NotImplementedError(
194  f"In '{node}' there are two arguments that are not named. "
195  f"The second could be a dim or a mask so we need datatype "
196  f"information to determine which and we do not determine "
197  f"this information at the moment.")
198 
199  # If there are three arguments, and fewer than two are
200  # named, then the argument order is known, so we can just
201  # add any missing named arguments.
202  if len(arg_nodes) == 3 and num_arg_names < 2:
203  # Update the existing list otherwise changes are
204  # local to this function.
205  arg_names[0] = None
206  arg_names[1] = "dim"
207  arg_names[2] = "mask"
208 
209 
210 def _first_type_match(nodelist, typekind):
211  '''Returns the first instance of the specified type in the given
212  node list.
213 
214  :param list nodelist: list of fparser2 nodes.
215  :param type typekind: the fparser2 Type we are searching for.
216 
217  :returns: the first instance of the specified type.
218  :rtype: instance of typekind
219 
220  :raises ValueError: if the list does not contain an object of type \
221  typekind.
222 
223  '''
224  for node in nodelist:
225  if isinstance(node, typekind):
226  return node
227  raise ValueError # Type not found
228 
229 
230 def _find_or_create_unresolved_symbol(location, name, scope_limit=None,
231  **kargs):
232  '''Returns the symbol with the given 'name' from a symbol table
233  associated with the 'location' node or one of its ancestors. If a
234  symbol is found then the type of the existing symbol is compared
235  with the specified 'symbol_type' parameter (passed as part of
236  '**kargs'). If it is not already an instance of this type, then
237  the symbol is specialised (in place).
238 
239  If the symbol is not found then a new Symbol with the specified
240  visibility but of unresolved interface is created and inserted in the
241  most local SymbolTable that has a Routine or Container node as
242  parent.
243 
244  The scope_limit variable further limits the symbol table search so
245  that the search through ancestor nodes stops when the scope_limit
246  node is reached i.e. ancestors of the scope_limit node are not
247  searched.
248 
249  :param location: PSyIR node from which to operate.
250  :type location: :py:class:`psyclone.psyir.nodes.Node`
251  :param str name: the name of the symbol.
252  :param scope_limit: optional Node which limits the symbol
253  search space to the symbol tables of the nodes within the
254  given scope. If it is None (the default), the whole
255  scope (all symbol tables in ancestor nodes) is searched
256  otherwise ancestors of the scope_limit node are not
257  searched.
258  :type scope_limit: :py:class:`psyclone.psyir.nodes.Node` or
259  `NoneType`
260 
261  :returns: the matching symbol.
262  :rtype: :py:class:`psyclone.psyir.symbols.Symbol`
263 
264  :raises TypeError: if the supplied scope_limit is not a Node.
265  :raises ValueError: if the supplied scope_limit node is not an
266  ancestor of the supplied node.
267 
268  '''
269  if not isinstance(location, Node):
270  raise TypeError(
271  f"The location argument '{location}' provided to "
272  f"_find_or_create_unresolved_symbol() is not of type `Node`.")
273 
274  if scope_limit is not None:
275  # Validate the supplied scope_limit
276  if not isinstance(scope_limit, Node):
277  raise TypeError(
278  f"The scope_limit argument '{scope_limit}' provided to "
279  f"_find_or_create_unresolved_symbol() is not of type `Node`.")
280 
281  # Check that the scope_limit Node is an ancestor of this
282  # Reference Node and raise an exception if not.
283  mynode = location.parent
284  while mynode is not None:
285  if mynode is scope_limit:
286  # The scope_limit node is an ancestor of the
287  # supplied node.
288  break
289  mynode = mynode.parent
290  else:
291  # The scope_limit node is not an ancestor of the
292  # supplied node so raise an exception.
293  raise ValueError(
294  f"The scope_limit node '{scope_limit}' provided to "
295  f"_find_or_create_unresolved_symbol() is not an ancestor of "
296  f"this node '{location}'.")
297 
298  try:
299  sym = location.scope.symbol_table.lookup(name, scope_limit=scope_limit)
300  if "symbol_type" in kargs:
301  expected_type = kargs.pop("symbol_type")
302  if not isinstance(sym, expected_type):
303  # The caller specified a sub-class so we need to
304  # specialise the existing symbol.
305  sym.specialise(expected_type, **kargs)
306  return sym
307  except KeyError:
308  pass
309 
310  # find the closest ancestor symbol table attached to a Routine or
311  # Container node. We don't want to add to a Schedule node as in
312  # some situations PSyclone assumes symbols are declared within
313  # Routine or Container symbol tables due to its Fortran provenance
314  # (but should probably not!). We also have cases when the whole
315  # tree has not been built so the symbol table is not connected to
316  # a node.
317  symbol_table = location.scope.symbol_table
318  while symbol_table.node and not isinstance(
319  symbol_table.node, (Routine, Container)):
320  symbol_table = symbol_table.parent_symbol_table()
321 
322  # All requested Nodes have been checked but there has been no
323  # match. Add it to the symbol table as an unresolved symbol in any
324  # case as, for example, it might be declared later, or the
325  # declaration may be hidden (perhaps in a codeblock), or it may be
326  # imported with a wildcard import.
327  return symbol_table.new_symbol(
328  name, interface=UnresolvedInterface(), **kargs)
329 
330 
331 def _find_or_create_psyclone_internal_cmp(node):
332  '''
333  Utility routine to return a symbol of the generic psyclone comparison
334  interface. If the interface does not exist in the scope it first adds
335  the necessary code to the parent module.
336 
337  :param node: location where the comparison interface is needed.
338  :type node: :py:class:`psyclone.psyir.nodes.Node`
339  :returns: the comparison interface symbol.
340  :rtype: :py:class:`psyclone.psyir.symbols.Symbol`
341 
342  :raises NotImplementedError: if there is no ancestor module container
343  on which to add the interface code into.
344  '''
345  try:
346  return node.scope.symbol_table.lookup_with_tag("psyclone_internal_cmp")
347  except KeyError:
348  container = node.ancestor(Container)
349  if container and not isinstance(container, FileContainer):
350  # pylint: disable=import-outside-toplevel
351  from psyclone.psyir.frontend.fortran import FortranReader
352  name_interface = node.scope.symbol_table.next_available_name(
353  "psyclone_internal_cmp")
354  name_f_int = node.scope.symbol_table.next_available_name(
355  "psyclone_cmp_int")
356  name_f_logical = node.scope.symbol_table.next_available_name(
357  "psyclone_cmp_logical")
358  name_f_char = node.scope.symbol_table.next_available_name(
359  "psyclone_cmp_char")
360  fortran_reader = FortranReader()
361  dummymod = fortran_reader.psyir_from_source(f'''
362  module dummy
363  implicit none
364  interface {name_interface}
365  procedure {name_f_int}
366  procedure {name_f_logical}
367  procedure {name_f_char}
368  end interface {name_interface}
369  private {name_interface}
370  private {name_f_int}, {name_f_logical}, {name_f_char}
371  contains
372  logical pure function {name_f_int}(op1, op2)
373  integer, intent(in) :: op1, op2
374  {name_f_int} = op1.eq.op2
375  end function
376  logical pure function {name_f_logical}(op1, op2)
377  logical, intent(in) :: op1, op2
378  {name_f_logical} = op1.eqv.op2
379  end function
380  logical pure function {name_f_char}(op1, op2)
381  character(*), intent(in) :: op1, op2
382  {name_f_char} = op1.eq.op2
383  end function
384  end module dummy
385  ''').children[0] # We skip the top FileContainer
386 
387  # Add the new functions and interface to the ancestor container
388  container.children.extend(dummymod.pop_all_children())
389  container.symbol_table.merge(dummymod.symbol_table)
390  symbol = container.symbol_table.lookup(name_interface)
391  # Add the appropriate tag to find it regardless of the name
392  container.symbol_table.tags_dict['psyclone_internal_cmp'] = symbol
393  return symbol
394 
395  raise NotImplementedError(
396  "Could not find the generic comparison interface and the scope does "
397  "not have an ancestor container in which to add it.")
398 
399 
400 def _check_args(array, dim):
401  '''Utility routine used by the _check_bound_is_full_extent and
402  _check_array_range_literal functions to check common arguments.
403 
404  This routine is only in fparser2.py until #717 is complete as it
405  is used to check that array syntax in a where statement is for the
406  full extent of the dimension. Once #717 is complete this routine
407  can be removed.
408 
409  :param array: the node to check.
410  :type array: :py:class:`pysclone.psyir.node.array`
411  :param int dim: the dimension index to use.
412 
413  :raises TypeError: if the supplied arguments are of the wrong type.
414  :raises ValueError: if the value of the supplied dim argument is \
415  less than 1 or greater than the number of dimensions in the \
416  supplied array argument.
417 
418  '''
419  if not isinstance(array, ArrayMixin):
420  raise TypeError(
421  f"method _check_args 'array' argument should be some sort of "
422  f"array access (i.e. a sub-class of ArrayMixin) but found "
423  f"'{type(array).__name__}'.")
424 
425  if not isinstance(dim, int):
426  raise TypeError(
427  f"method _check_args 'dim' argument should be an "
428  f"int type but found '{type(dim).__name__}'.")
429  if dim < 1:
430  raise ValueError(
431  f"method _check_args 'dim' argument should be at "
432  f"least 1 but found {dim}.")
433  if dim > len(array.children):
434  raise ValueError(
435  f"method _check_args 'dim' argument should be at most the number "
436  f"of dimensions of the array ({len(array.children)}) but found "
437  f"{dim}.")
438 
439  # The first element of the array (index 0) relates to the first
440  # dimension (dim 1), so we need to reduce dim by 1.
441  if not isinstance(array.indices[dim-1], Range):
442  raise TypeError(
443  f"method _check_args 'array' argument index '{dim-1}' should be a "
444  f"Range type but found '{type(array.indices[dim-1]).__name__}'.")
445 
446 
447 def _is_bound_full_extent(array, dim, intrinsic):
448  '''A Fortran array section with a missing lower bound implies the
449  access starts at the first element and a missing upper bound
450  implies the access ends at the last element e.g. a(:,:)
451  accesses all elements of array a and is equivalent to
452  a(lbound(a,1):ubound(a,1),lbound(a,2):ubound(a,2)). The PSyIR
453  does not support the shorthand notation, therefore the lbound
454  and ubound operators are used in the PSyIR.
455 
456  This utility function checks that shorthand lower or upper
457  bound Fortran code is captured as longhand lbound and/or
458  ubound functions as expected in the PSyIR.
459 
460  This routine is only in fparser2.py until #717 is complete as it
461  is used to check that array syntax in a where statement is for the
462  full extent of the dimension. Once #717 is complete this routine
463  can be moved into fparser2_test.py as it is used there in a
464  different context.
465 
466  :param array: the node to check.
467  :type array: :py:class:`pysclone.psyir.nodes.ArrayMixin`
468  :param int dim: the dimension index to use.
469  :param intrinsic: the intrinsic to check.
470  :type intrinsic:
471  :py:class:`psyclone.psyir.nodes.IntrinsicCall.Intrinsic.LBOUND` |
472  :py:class:`psyclone.psyir.nodes.IntrinsicCall.Intrinsic.UBOUND`
473 
474  :returns: True if the supplied array has the expected properties,
475  otherwise returns False.
476  :rtype: bool
477 
478  :raises TypeError: if the supplied arguments are of the wrong type.
479 
480  '''
481  _check_args(array, dim)
482 
483  if intrinsic == IntrinsicCall.Intrinsic.LBOUND:
484  index = 0
485  elif intrinsic == IntrinsicCall.Intrinsic.UBOUND:
486  index = 1
487  else:
488  raise TypeError(
489  f"'intrinsic' argument expected to be LBOUND or UBOUND but "
490  f"found '{type(intrinsic).__name__}'.")
491 
492  # The first element of the array (index 0) relates to the first
493  # dimension (dim 1), so we need to reduce dim by 1.
494  bound = array.indices[dim-1].children[index]
495 
496  if not isinstance(bound, IntrinsicCall):
497  return False
498 
499  reference = bound.arguments[0]
500  literal = bound.arguments[1]
501 
502  if bound.intrinsic != intrinsic:
503  return False
504 
505  if (not isinstance(literal, Literal) or
506  literal.datatype.intrinsic != ScalarType.Intrinsic.INTEGER or
507  literal.value != str(dim)):
508  return False
509 
510  return isinstance(reference, Reference) and array.is_same_array(reference)
511 
512 
513 def _is_array_range_literal(array, dim, index, value):
514  '''Utility function to check that the supplied array has an integer
515  literal at dimension index "dim" and range index "index" with
516  value "value".
517 
518  The step part of the range node has an integer literal with
519  value 1 by default.
520 
521  This routine is only in fparser2.py until #717 is complete as it
522  is used to check that array syntax in a where statement is for the
523  full extent of the dimension. Once #717 is complete this routine
524  can be moved into fparser2_test.py as it is used there in a
525  different context.
526 
527  :param array: the node to check.
528  :type array: :py:class:`pysclone.psyir.node.ArrayReference`
529  :param int dim: the dimension index to check.
530  :param int index: the index of the range to check (0 is the \
531  lower bound, 1 is the upper bound and 2 is the step).
532  :param int value: the expected value of the literal.
533 
534  :raises NotImplementedError: if the supplied argument does not \
535  have the required properties.
536 
537  :returns: True if the supplied array has the expected properties, \
538  otherwise returns False.
539  :rtype: bool
540 
541  :raises TypeError: if the supplied arguments are of the wrong type.
542  :raises ValueError: if the index argument has an incorrect value.
543 
544  '''
545  _check_args(array, dim)
546 
547  if not isinstance(index, int):
548  raise TypeError(
549  f"method _check_array_range_literal 'index' argument should be an "
550  f"int type but found '{type(index).__name__}'.")
551 
552  if index < 0 or index > 2:
553  raise ValueError(
554  f"method _check_array_range_literal 'index' argument should be "
555  f"0, 1 or 2 but found {index}.")
556 
557  if not isinstance(value, int):
558  raise TypeError(
559  f"method _check_array_range_literal 'value' argument should be an "
560  f"int type but found '{type(value).__name__}'.")
561 
562  # The first child of the array (index 0) relates to the first
563  # dimension (dim 1), so we need to reduce dim by 1.
564  literal = array.children[dim-1].children[index]
565 
566  if (isinstance(literal, Literal) and
567  literal.datatype.intrinsic == ScalarType.Intrinsic.INTEGER and
568  literal.value == str(value)):
569  return True
570  return False
571 
572 
573 def _is_range_full_extent(my_range):
574  '''Utility function to check whether a Range object is equivalent to a
575  ":" in Fortran array notation. The PSyIR representation of "a(:)"
576  is "a(lbound(a,1):ubound(a,1):1). Therefore, for array a index 1,
577  the lower bound is compared with "lbound(a,1)", the upper bound is
578  compared with "ubound(a,1)" and the step is compared with 1.
579 
580  If everything is OK then this routine silently returns, otherwise
581  an exception is raised by one of the functions
582  (_check_bound_is_full_extent or _check_array_range_literal) called by this
583  function.
584 
585  This routine is only in fparser2.py until #717 is complete as it
586  is used to check that array syntax in a where statement is for the
587  full extent of the dimension. Once #717 is complete this routine
588  can be removed.
589 
590  :param my_range: the Range node to check.
591  :type my_range: :py:class:`psyclone.psyir.node.Range`
592 
593  '''
594 
595  array = my_range.parent
596  # The array index of this range is determined by its position in
597  # the array list (+1 as the index starts from 0 but Fortran
598  # dimensions start from 1).
599  dim = array.children.index(my_range) + 1
600  # Check lower bound
601  is_lower = _is_bound_full_extent(
602  array, dim, IntrinsicCall.Intrinsic.LBOUND)
603  # Check upper bound
604  is_upper = _is_bound_full_extent(
605  array, dim, IntrinsicCall.Intrinsic.UBOUND)
606  # Check step (index 2 is the step index for the range function)
607  is_step = _is_array_range_literal(array, dim, 2, 1)
608  return is_lower and is_upper and is_step
609 
610 
611 def _copy_full_base_reference(node):
612  '''
613  Given the supplied node, creates a new node with the same access
614  apart from the final array access. Such a node is then suitable for use
615  as an argument to either e.g. LBOUND or UBOUND.
616 
617  e.g. if `node` is an ArrayMember representing the inner access in
618  'grid%data(:)' then this routine will return a PSyIR node for
619  'grid%data'.
620 
621  :param node: the array access. In the case of a structure, this \
622  must be the inner-most part of the access.
623  :type node: :py:class:`psyclone.psyir.nodes.Reference` or \
624  :py:class:`psyclone.psyir.nodes.Member`
625 
626  :returns: the PSyIR for a suitable argument to either LBOUND or \
627  UBOUND applied to the supplied `node`.
628  :rtype: :py:class:`psyclone.psyir.nodes.Node`
629 
630  :raises InternalError: if the supplied node is not an instance of \
631  either Reference or Member.
632  '''
633  if isinstance(node, Reference):
634  return Reference(node.symbol)
635 
636  if isinstance(node, Member):
637  # We have to take care with derived types:
638  # grid(1)%data(:...) becomes
639  # grid(1)%data(lbound(grid(1)%data,1):...)
640  # N.B. the argument to lbound becomes a Member access rather
641  # than an ArrayMember access.
642  parent_ref = node.ancestor(Reference, include_self=True)
643  # We have to find the location of the supplied node in the
644  # StructureReference.
645  inner = parent_ref
646  depth = 0
647  while hasattr(inner, "member") and inner is not node:
648  depth += 1
649  inner = inner.member
650  # Now we take a copy of the full reference and then modify it so
651  # that the copy of 'node' is replaced by a Member().
652  arg = parent_ref.copy()
653  # We use the depth computed for the original reference in order
654  # to find the copy of 'node'.
655  inner = arg
656  for _ in range(depth-1):
657  inner = inner.member
658  # Change the innermost access to be a Member.
659  inner.children[0] = Member(node.name, inner)
660  return arg
661 
662  raise InternalError(
663  f"The supplied node must be an instance of either Reference "
664  f"or Member but got '{type(node).__name__}'.")
665 
666 
667 def _kind_find_or_create(name, symbol_table):
668  '''
669  Utility method that returns a Symbol representing the named KIND
670  parameter. If the supplied Symbol Table (or one of its ancestors)
671  does not contain an appropriate entry then one is created. If it does
672  contain a matching entry then it must be either a Symbol or a
673  DataSymbol.
674 
675  If it is a DataSymbol then it must have a datatype of 'Integer',
676  'Unresolved' or 'Unsupported'. If it is Unresolved then the fact
677  that we now know that this Symbol represents a KIND parameter means we
678  can change the datatype to be 'integer' and mark it as constant.
679 
680  If the existing symbol is a generic Symbol then it is replaced with
681  a new DataSymbol of type 'integer'.
682 
683  :param str name: the name of the variable holding the KIND value.
684  :param symbol_table: the Symbol Table associated with the code being \
685  processed.
686  :type symbol_table: :py:class:`psyclone.psyir.symbols.SymbolTable`
687 
688  :returns: the Symbol representing the KIND parameter.
689  :rtype: :py:class:`psyclone.psyir.symbols.DataSymbol`
690 
691  :raises TypeError: if the symbol table already contains an entry for \
692  `name` but it is not an instance of Symbol or DataSymbol.
693  :raises TypeError: if the symbol table already contains a DataSymbol \
694  for `name` and its datatype is not 'Integer' or 'Unresolved'.
695 
696  '''
697  lower_name = name.lower()
698 
699  try:
700  kind_symbol = symbol_table.lookup(lower_name)
701  # pylint: disable=unidiomatic-typecheck
702  if type(kind_symbol) is Symbol:
703  # There is an existing entry but it's only a generic Symbol
704  # so we need to specialise it to a DataSymbol of integer type.
705  kind_symbol.specialise(DataSymbol, datatype=default_integer_type(),
706  is_constant=True)
707  elif isinstance(kind_symbol, DataSymbol):
708 
709  if not (isinstance(kind_symbol.datatype,
710  (UnsupportedType, UnresolvedType)) or
711  (isinstance(kind_symbol.datatype, ScalarType) and
712  kind_symbol.datatype.intrinsic ==
713  ScalarType.Intrinsic.INTEGER)):
714  raise TypeError(
715  f"SymbolTable already contains a DataSymbol for variable "
716  f"'{lower_name}' used as a kind parameter but it is not a "
717  f"'Unresolved', 'Unsupported' or 'scalar Integer' type.")
718  # A KIND parameter must be of type integer so set it here if it
719  # was previously 'Unresolved'. We don't know what precision this is
720  # so set it to the default.
721  if isinstance(kind_symbol.datatype, UnresolvedType):
722  kind_symbol.datatype = default_integer_type()
723  kind_symbol.is_constant = True
724  else:
725  raise TypeError(
726  f"A symbol representing a kind parameter must be an instance "
727  f"of either a Symbol or a DataSymbol. However, found an entry "
728  f"of type '{type(kind_symbol).__name__}' for variable "
729  f"'{lower_name}'.")
730  except KeyError:
731  # The SymbolTable does not contain an entry for this kind parameter
732  # so look to see if it is imported and if not create one.
733  kind_symbol = _find_or_create_unresolved_symbol(
734  symbol_table.node, lower_name,
735  symbol_type=DataSymbol,
736  datatype=default_integer_type(),
737  visibility=symbol_table.default_visibility,
738  is_constant=True)
739  return kind_symbol
740 
741 
742 def default_precision(_):
743  '''Returns the default precision specified by the front end. This is
744  currently always set to undefined irrespective of the datatype but
745  could be read from a config file in the future. The unused
746  argument provides the name of the datatype. This name will allow a
747  future implementation of this method to choose different default
748  precisions for different datatypes if required.
749 
750  There are alternative options for setting a default precision,
751  such as:
752 
753  1) The back-end sets the default precision in a similar manner
754  to this routine.
755  2) A PSyIR transformation is used to set default precision.
756 
757  This routine is primarily here as a placeholder and could be
758  replaced by an alternative solution, see issue #748.
759 
760  :returns: the default precision for the supplied datatype name.
761  :rtype: :py:class:`psyclone.psyir.symbols.scalartype.Precision`
762 
763  '''
764  return ScalarType.Precision.UNDEFINED
765 
766 
767 def default_integer_type():
768  '''Returns the default integer datatype specified by the front end.
769 
770  :returns: the default integer datatype.
771  :rtype: :py:class:`psyclone.psyir.symbols.ScalarType`
772 
773  '''
774  return ScalarType(ScalarType.Intrinsic.INTEGER,
775  default_precision(ScalarType.Intrinsic.INTEGER))
776 
777 
778 def default_real_type():
779  '''Returns the default real datatype specified by the front end.
780 
781  :returns: the default real datatype.
782  :rtype: :py:class:`psyclone.psyir.symbols.ScalarType`
783 
784  '''
785  return ScalarType(ScalarType.Intrinsic.REAL,
786  default_precision(ScalarType.Intrinsic.REAL))
787 
788 
789 def get_literal_precision(fparser2_node, psyir_literal_parent):
790  '''Takes a Fortran2003 literal node as input and returns the appropriat
791  PSyIR precision type for that node. Adds a UnresolvedType DataSymbol in
792  the SymbolTable if the precision is given by an undefined symbol.
793 
794  :param fparser2_node: the fparser2 literal node.
795  :type fparser2_node: :py:class:`Fortran2003.Real_Literal_Constant` or \
796  :py:class:`Fortran2003.Logical_Literal_Constant` or \
797  :py:class:`Fortran2003.Char_Literal_Constant` or \
798  :py:class:`Fortran2003.Int_Literal_Constant`
799  :param psyir_literal_parent: the PSyIR node that will be the \
800  parent of the PSyIR literal node that will be created from the \
801  fparser2 node information.
802  :type psyir_literal_parent: :py:class:`psyclone.psyir.nodes.Node`
803 
804  :returns: the PSyIR Precision of this literal value.
805  :rtype: :py:class:`psyclone.psyir.symbols.DataSymbol`, int or \
806  :py:class:`psyclone.psyir.symbols.ScalarType.Precision`
807 
808  :raises InternalError: if the arguments are of the wrong type.
809  :raises InternalError: if there's no symbol table associated with \
810  `psyir_literal_parent` or one of its ancestors.
811 
812  '''
813  if not isinstance(fparser2_node,
814  (Fortran2003.Real_Literal_Constant,
815  Fortran2003.Logical_Literal_Constant,
816  Fortran2003.Char_Literal_Constant,
817  Fortran2003.Int_Literal_Constant)):
818  raise InternalError(
819  f"Unsupported literal type '{type(fparser2_node).__name__}' found "
820  f"in get_literal_precision.")
821  if not isinstance(psyir_literal_parent, Node):
822  raise InternalError(
823  f"Expecting argument psyir_literal_parent to be a PSyIR Node but "
824  f"found '{type(psyir_literal_parent).__name__}' in "
825  f"get_literal_precision.")
826  precision_name = fparser2_node.items[1]
827  if not precision_name:
828  # Precision may still be specified by the exponent in a real literal
829  if isinstance(fparser2_node, Fortran2003.Real_Literal_Constant):
830  precision_value = fparser2_node.items[0]
831  if "d" in precision_value.lower():
832  return ScalarType.Precision.DOUBLE
833  if "e" in precision_value.lower():
834  return ScalarType.Precision.SINGLE
835  # Return the default precision
836  try:
837  data_name = CONSTANT_TYPE_MAP[type(fparser2_node)]
838  except KeyError as err:
839  raise NotImplementedError(
840  f"Could not process {type(fparser2_node).__name__}. Only "
841  f"'real', 'integer', 'logical' and 'character' intrinsic "
842  f"types are supported.") from err
843  return default_precision(data_name)
844  try:
845  # Precision is specified as an integer
846  return int(precision_name)
847  except ValueError:
848  # Precision is not an integer so should be a kind symbol
849  # PSyIR stores names as lower case.
850  precision_name = precision_name.lower()
851  # Find the closest symbol table
852  try:
853  symbol_table = psyir_literal_parent.scope.symbol_table
854  except SymbolError as err:
855  # No symbol table found. This should never happen in
856  # normal usage but could occur if a test constructs a
857  # PSyIR without a Schedule.
858  raise InternalError(
859  f"Failed to find a symbol table to which to add the kind "
860  f"symbol '{precision_name}'.") from err
861  return _kind_find_or_create(precision_name, symbol_table)
862 
863 
864 def _process_routine_symbols(module_ast, symbol_table, visibility_map):
865  '''
866  Examines the supplied fparser2 parse tree for a module and creates
867  RoutineSymbols for every routine (function or subroutine) that it
868  contains.
869 
870  :param module_ast: fparser2 parse tree for module.
871  :type module_ast: :py:class:`fparser.two.Fortran2003.Program`
872  :param symbol_table: the SymbolTable to which to add the symbols.
873  :type symbol_table: :py:class:`psyclone.psyir.symbols.SymbolTable`
874  :param visibility_map: dict of symbol names with explicit visibilities.
875  :type visibility_map: Dict[str, \
876  :py:class:`psyclone.psyir.symbols.Symbol.Visibility`]
877 
878  '''
879  routines = walk(module_ast, (Fortran2003.Subroutine_Subprogram,
880  Fortran2003.Function_Subprogram))
881  # A subroutine has no type but a function does. However, we don't know what
882  # it is at this stage so we give all functions a UnresolvedType.
883  # TODO #1314 extend the frontend to ensure that the type of a Routine's
884  # return_symbol matches the type of the associated RoutineSymbol.
885  type_map = {Fortran2003.Subroutine_Subprogram: NoType,
886  Fortran2003.Function_Subprogram: UnresolvedType}
887 
888  for routine in routines:
889 
890  # Fortran routines are impure by default.
891  is_pure = False
892  # By default, Fortran routines are not elemental.
893  is_elemental = False
894  # Name of the routine.
895  name = str(routine.children[0].children[1]).lower()
896  # Type to give the RoutineSymbol.
897  sym_type = type_map[type(routine)]()
898  # Visibility of the symbol.
899  vis = visibility_map.get(name, symbol_table.default_visibility)
900  # Check any prefixes on the routine declaration.
901  prefix = routine.children[0].children[0]
902  if prefix:
903  for child in prefix.children:
904  if isinstance(child, Fortran2003.Prefix_Spec):
905  if child.string == "PURE":
906  is_pure = True
907  elif child.string == "IMPURE":
908  is_pure = False
909  elif child.string == "ELEMENTAL":
910  is_elemental = True
911 
912  rsymbol = RoutineSymbol(name, sym_type, visibility=vis,
913  is_pure=is_pure, is_elemental=is_elemental,
914  interface=DefaultModuleInterface())
915  symbol_table.add(rsymbol)
916 
917 
918 def _process_access_spec(attr):
919  '''
920  Converts from an fparser2 Access_Spec node to a PSyIR visibility.
921 
922  :param attr: the fparser2 AST node to process.
923  :type attr: :py:class:`fparser.two.Fortran2003.Access_Spec`
924 
925  :return: the PSyIR visibility corresponding to the access spec.
926  :rtype: :py:class:`psyclone.psyir.Symbol.Visibility`
927 
928  :raises InternalError: if an invalid access specification is found.
929 
930  '''
931  try:
932  return VISIBILITY_MAP_FROM_FORTRAN[attr.string.lower()]
933  except KeyError as err:
934  raise InternalError(f"Unexpected Access Spec attribute "
935  f"'{attr}'.") from err
936 
937 
938 def _create_struct_reference(parent, base_ref, base_symbol, members,
939  indices):
940  '''
941  Utility to create a StructureReference or ArrayOfStructuresReference. Any
942  PSyIR nodes in the supplied lists of members and indices are copied
943  when making the new node.
944 
945  :param parent: Parent node of the PSyIR node we are constructing.
946  :type parent: :py:class:`psyclone.psyir.nodes.Node`
947  :param type base_ref: the type of Reference to create.
948  :param base_symbol: the Symbol that the reference is to.
949  :type base_symbol: :py:class:`psyclone.psyir.symbols.Symbol`
950  :param members: the component(s) of the structure that are being accessed.\
951  Any components that are array references must provide the name of the \
952  array and a list of DataNodes describing which part of it is accessed.
953  :type members: list of str or 2-tuples containing (str, \
954  list of nodes describing array access)
955  :param indices: a list of Nodes describing the array indices for \
956  the base reference (if any).
957  :type indices: list of :py:class:`psyclone.psyir.nodes.Node`
958 
959  :raises InternalError: if any element in the `members` list is not a \
960  str or tuple or if `indices` are supplied for a StructureReference \
961  or *not* supplied for an ArrayOfStructuresReference.
962  :raises NotImplementedError: if `base_ref` is not a StructureReference or \
963  an ArrayOfStructuresReference.
964 
965  '''
966  # Ensure we create a copy of any References within the list of
967  # members making up this structure access.
968  new_members = []
969  for member in members:
970  if isinstance(member, str):
971  new_members.append(member)
972  elif isinstance(member, tuple):
973  # Second member of the tuple is a list of index expressions
974  new_members.append((member[0], [kid.copy() for kid in member[1]]))
975  else:
976  raise InternalError(
977  f"List of members must contain only strings or tuples "
978  f"but found entry of type '{type(member).__name__}'")
979  if base_ref is StructureReference:
980  if indices:
981  raise InternalError(
982  f"Creating a StructureReference but array indices have been "
983  f"supplied ({indices}) which makes no sense.")
984  return base_ref.create(base_symbol, new_members, parent=parent)
985  if base_ref is ArrayOfStructuresReference:
986  if not indices:
987  raise InternalError(
988  "Cannot create an ArrayOfStructuresReference without one or "
989  "more index expressions but the 'indices' argument is empty.")
990  return base_ref.create(base_symbol, [idx.copy() for idx in indices],
991  new_members, parent=parent)
992 
993  raise NotImplementedError(
994  f"Cannot create structure reference for type '{base_ref}' - expected "
995  f"either StructureReference or ArrayOfStructuresReference.")
996 
997 
998 def _get_arg_names(node_list):
999  '''Utility function that given an fparser2 argument list returns two
1000  separate lists, one with the arguments themselves and another with
1001  the argument names.
1002 
1003  :param node_list: a list of fparser2 argument nodes which could \
1004  be positional or named.
1005  :type node_list: List[:py:class:`fparser.two.utils.Base`]
1006 
1007  :returns: a list of fparser2 arguments with any name \
1008  information and a separate list of named argument names.
1009  :rtype: Tuple[List[:py:class:`fparser.two.utils.Base`], \
1010  List[Union[str, None]]
1011 
1012  '''
1013  arg_names = []
1014  arg_nodes = []
1015  for node in node_list:
1016  if isinstance(node, Fortran2003.Actual_Arg_Spec):
1017  arg_names.append(node.children[0].string)
1018  arg_nodes.append(node.children[1])
1019  else:
1020  arg_names.append(None)
1021  arg_nodes.append(node)
1022  return arg_nodes, arg_names
1023 
1024 
1026  '''
1027  Class to encapsulate the functionality for processing the fparser2 AST and
1028  convert the nodes to PSyIR.
1029  '''
1030 
1031  unary_operators = OrderedDict([
1032  ('+', UnaryOperation.Operator.PLUS),
1033  ('-', UnaryOperation.Operator.MINUS),
1034  ('.not.', UnaryOperation.Operator.NOT)])
1035 
1036  binary_operators = OrderedDict([
1037  ('+', BinaryOperation.Operator.ADD),
1038  ('-', BinaryOperation.Operator.SUB),
1039  ('*', BinaryOperation.Operator.MUL),
1040  ('/', BinaryOperation.Operator.DIV),
1041  ('**', BinaryOperation.Operator.POW),
1042  ('==', BinaryOperation.Operator.EQ),
1043  ('.eq.', BinaryOperation.Operator.EQ),
1044  ('.eqv.', BinaryOperation.Operator.EQV),
1045  ('/=', BinaryOperation.Operator.NE),
1046  ('.ne.', BinaryOperation.Operator.NE),
1047  ('.neqv.', BinaryOperation.Operator.NEQV),
1048  ('<=', BinaryOperation.Operator.LE),
1049  ('.le.', BinaryOperation.Operator.LE),
1050  ('<', BinaryOperation.Operator.LT),
1051  ('.lt.', BinaryOperation.Operator.LT),
1052  ('>=', BinaryOperation.Operator.GE),
1053  ('.ge.', BinaryOperation.Operator.GE),
1054  ('>', BinaryOperation.Operator.GT),
1055  ('.gt.', BinaryOperation.Operator.GT),
1056  ('.and.', BinaryOperation.Operator.AND),
1057  ('.or.', BinaryOperation.Operator.OR)])
1058 
1059  @dataclass
1061  """Class for storing required information from an fparser2
1062  Select_Type_Construct.
1063 
1064  :param guard_type: the guard types used by 'type is' and 'class is'
1065  select-type clauses e.g. 'REAL', 'REAL(KIND = 4), or 'mytype'
1066  in 'type_is(REAL)' 'type_is(REAL(KIND = 4)' and 'class
1067  is(mytype)' respectively. These are stored as a list of
1068  str, ordered as found within the select-type
1069  construct's 'type is', 'class is' and 'class default'
1070  clauses with None indicating the 'class default' clause.
1071  :param guard_type_name: a string representation of the guard types used
1072  by 'type is' and 'class is' select-type clauses e.g. 'REAL',
1073  'REAL(KIND = 4)', or 'mytype' are stored as
1074  'REAL', 'REAL_4' and 'mytype' respectively. These are
1075  designed to be used as base variable names in
1076  the code. These are ordered as they are found in the
1077  the select type construct 'type is, 'class is'
1078  and 'class default' clauses with None representing the
1079  'class default'.
1080  :param intrinsic_type_name: the base intrinsic string name for the
1081  particular clause or None if there is no intrinsic type. e.g.
1082  'type is(REAL*4)' becomes 'REAL' and 'type is(mytype)' becomes
1083  None. These are ordered as they occur in the select-type
1084  construct's clauses.
1085  :param clause_type: the name of the clause in the select-type construct
1086  i.e. one of 'type is', 'class is' and 'class default'. These are
1087  ordered as they occur within the select-type construct.
1088  :param stmts: a list of fparser2 statements holding the content of each
1089  of the select-type construct 'type is, 'class is' and
1090  'class default' clauses. These are ordered as they occur within the
1091  select-type construct.
1092  :param selector: the name of the select-type construct selector e.g.
1093  'selector' in 'select type(selector)'.
1094  :param num_clauses: the number of 'type is', 'class is' and
1095  'class default' clauses in the select type construct.
1096  :param default_idx: index of the 'default' clause as it appears within
1097  the select-type construct's 'type is, 'class is' and
1098  'class default' clauses, or -1 if no default clause is found.
1099 
1100  """
1101  # 'str | None' syntax is only supported in Python >=3.10 so use
1102  # 'typing.Optional[]'. Similarly, 'list[str]' is only valid in
1103  # Python >=3.9 so use 'typing.List[str]'.
1104  guard_type: List[Optional[str]] = field(default_factory=list)
1105  guard_type_name: List[Optional[str]] = field(default_factory=list)
1106  intrinsic_type_name: List[Optional[str]] = field(default_factory=list)
1107  clause_type: List[str] = field(default_factory=list)
1108  stmts: List[List[StmtBase]] = field(default_factory=list)
1109  selector: str = ""
1110  num_clauses: int = -1
1111  default_idx: int = -1
1112 
1113  def __init__(self):
1114  # Map of fparser2 node types to handlers (which are class methods)
1115  self.handlers = {
1116  Fortran2003.Allocate_Stmt: self._allocate_handler,
1117  Fortran2003.Allocate_Shape_Spec: self._allocate_shape_spec_handler,
1118  Fortran2003.Assignment_Stmt: self._assignment_handler,
1119  Fortran2003.Data_Ref: self._structure_accessor_handler,
1120  Fortran2003.Procedure_Designator: self._structure_accessor_handler,
1121  Fortran2003.Deallocate_Stmt: self._deallocate_handler,
1122  Fortran2003.Function_Subprogram: self._subroutine_handler,
1123  Fortran2003.Name: self._name_handler,
1124  Fortran2003.Parenthesis: self._parenthesis_handler,
1125  Fortran2003.Part_Ref: self._part_ref_handler,
1126  Fortran2003.Subscript_Triplet: self._subscript_triplet_handler,
1127  Fortran2003.If_Stmt: self._if_stmt_handler,
1128  utils.NumberBase: self._number_handler,
1129  Fortran2003.Include_Stmt: self._include_handler,
1130  C99Preprocessor.Cpp_Include_Stmt: self._include_handler,
1131  Fortran2003.Int_Literal_Constant: self._number_handler,
1132  Fortran2003.Char_Literal_Constant: self._char_literal_handler,
1133  Fortran2003.Logical_Literal_Constant: self._bool_literal_handler,
1134  utils.BinaryOpBase: self._binary_op_handler,
1135  Fortran2003.End_Do_Stmt: self._ignore_handler,
1136  Fortran2003.End_Subroutine_Stmt: self._ignore_handler,
1137  Fortran2003.If_Construct: self._if_construct_handler,
1138  Fortran2003.Case_Construct: self._case_construct_handler,
1139  Fortran2003.Select_Type_Construct:
1140  self._select_type_construct_handler,
1141  Fortran2003.Return_Stmt: self._return_handler,
1142  Fortran2003.UnaryOpBase: self._unary_op_handler,
1143  Fortran2003.Block_Nonlabel_Do_Construct:
1144  self._do_construct_handler,
1145  Fortran2003.Intrinsic_Function_Reference: self._intrinsic_handler,
1146  Fortran2003.Where_Construct: self._where_construct_handler,
1147  Fortran2003.Where_Stmt: self._where_construct_handler,
1148  Fortran2003.Call_Stmt: self._call_handler,
1149  Fortran2003.Subroutine_Subprogram: self._subroutine_handler,
1150  Fortran2003.Module: self._module_handler,
1151  Fortran2003.Main_Program: self._main_program_handler,
1152  Fortran2003.Program: self._program_handler,
1153  }
1154 
1155  @staticmethod
1156  def nodes_to_code_block(parent, fp2_nodes, message=None):
1157  '''Create a CodeBlock for the supplied list of fparser2 nodes and then
1158  wipe the list. A CodeBlock is a node in the PSyIR (Schedule)
1159  that represents a sequence of one or more Fortran statements
1160  and/or expressions which PSyclone does not attempt to handle.
1161 
1162  :param parent: Node in the PSyclone AST to which to add this CodeBlock.
1163  :type parent: :py:class:`psyclone.psyir.nodes.Node`
1164  :param fp2_nodes: list of fparser2 AST nodes constituting the
1165  CodeBlock.
1166  :type fp2_nodes: list of :py:class:`fparser.two.utils.Base`
1167  :param message: Include a preceeding comment attached to the CodeBlock.
1168  :type message: Optional[str]
1169 
1170  :returns: a CodeBlock instance.
1171  :rtype: :py:class:`psyclone.CodeBlock`
1172 
1173  '''
1174  if not fp2_nodes:
1175  return None
1176 
1177  # Determine whether this code block is a statement or an
1178  # expression. Statements always have a `Schedule` as parent
1179  # and expressions do not. The only unknown at this point are
1180  # directives whose structure are in discussion. Therefore, for
1181  # the moment, an exception is raised if a directive is found
1182  # as a parent.
1183  if isinstance(parent, (Schedule, Container)):
1184  structure = CodeBlock.Structure.STATEMENT
1185  elif isinstance(parent, Directive):
1186  raise InternalError(
1187  "Fparser2Reader:nodes_to_code_block: A CodeBlock with "
1188  "a Directive as parent is not yet supported.")
1189  else:
1190  structure = CodeBlock.Structure.EXPRESSION
1191 
1192  code_block = CodeBlock(fp2_nodes, structure, parent=parent)
1193  if message:
1194  code_block.preceding_comment = message
1195  parent.addchild(code_block)
1196  del fp2_nodes[:]
1197  return code_block
1198 
1199  def generate_psyir(self, parse_tree):
1200  '''Translate the supplied fparser2 parse_tree into PSyIR.
1201 
1202  :param parse_tree: the supplied fparser2 parse tree.
1203  :type parse_tree: :py:class:`fparser.two.Fortran2003.Program`
1204 
1205  :returns: PSyIR representation of the supplied fparser2 parse_tree.
1206  :rtype: :py:class:`psyclone.psyir.nodes.Container` or \
1207  :py:class:`psyclone.psyir.nodes.Routine`
1208 
1209  :raises GenerationError: if the root of the supplied fparser2 \
1210  parse tree is not a Program.
1211 
1212  '''
1213  if not isinstance(parse_tree, Fortran2003.Program):
1214  raise GenerationError(
1215  f"The Fparser2Reader generate_psyir method expects the root "
1216  f"of the supplied fparser2 tree to be a Program, but found "
1217  f"'{type(parse_tree).__name__}'")
1218 
1219  node = Container("dummy")
1220  self.process_nodesprocess_nodes(node, [parse_tree])
1221  result = node.children[0]
1222  return result.detach()
1223 
1224  def generate_container(self, module_ast):
1225  '''
1226  Create a Container from the supplied fparser2 module AST.
1227 
1228  :param module_ast: fparser2 AST of the full module.
1229  :type module_ast: :py:class:`fparser.two.Fortran2003.Program`
1230 
1231  :returns: PSyIR container representing the given module_ast or None \
1232  if there's no module in the parse tree.
1233  :rtype: :py:class:`psyclone.psyir.nodes.Container`
1234 
1235  :raises GenerationError: unable to generate a Container from the \
1236  provided fpaser2 parse tree.
1237  '''
1238  # Assume just 1 or 0 Fortran module definitions in the file
1239  modules = walk(module_ast, Fortran2003.Module_Stmt)
1240  if len(modules) > 1:
1241  raise GenerationError(
1242  f"Could not process {module_ast}. Just one module definition "
1243  f"per file supported.")
1244  if not modules:
1245  return None
1246 
1247  module = modules[0].parent
1248  mod_name = str(modules[0].children[1])
1249 
1250  # Create a container to capture the module information
1251  new_container = Container(mod_name)
1252 
1253  # Search for any accessibility statements (e.g. "PUBLIC :: my_var") to
1254  # determine the default accessibility of symbols as well as identifying
1255  # those that are explicitly declared as public or private.
1256  (default_visibility, visibility_map) = self.process_access_statementsprocess_access_statements(
1257  module)
1258  new_container.symbol_table.default_visibility = default_visibility
1259 
1260  # Create symbols for all routines defined within this module
1261  _process_routine_symbols(module_ast, new_container.symbol_table,
1262  visibility_map)
1263 
1264  # Parse the declarations if it has any
1265  for child in module.children:
1266  if isinstance(child, Fortran2003.Specification_Part):
1267  self.process_declarationsprocess_declarations(new_container, child.children,
1268  [], visibility_map)
1269  break
1270 
1271  return new_container
1272 
1273  def get_routine_schedules(self, name, module_ast):
1274  '''Create one or more schedules for routines corresponding to the
1275  supplied name in the supplied fparser2 AST. (There can be more than
1276  one routine if the supplied name corresponds to an interface block
1277  in the AST.)
1278 
1279  :param str name: name of the subroutine represented by the kernel.
1280  :param module_ast: fparser2 AST of the full module where the kernel \
1281  code is located.
1282  :type module_ast: :py:class:`fparser.two.Fortran2003.Program`
1283 
1284  :returns: PSyIR schedules representing the matching subroutine(s).
1285  :rtype: List[:py:class:`psyclone.psyir.nodes.KernelSchedule`]
1286 
1287  :raises GenerationError: if supplied parse tree contains more than \
1288  one module.
1289  :raises GenerationError: unable to generate a kernel schedule from \
1290  the provided fpaser2 parse tree.
1291 
1292  '''
1293  psyir = self.generate_psyirgenerate_psyir(module_ast)
1294  lname = name.lower()
1295 
1296  containers = [ctr for ctr in psyir.walk(Container) if
1297  not isinstance(ctr, FileContainer)]
1298  if not containers:
1299  raise GenerationError(
1300  f"The parse tree supplied to get_routine_schedules() must "
1301  f"contain a single module but found none when searching for "
1302  f"kernel '{name}'.")
1303  if len(containers) > 1:
1304  raise GenerationError(
1305  f"The parse tree supplied to get_routine_schedules() must "
1306  f"contain a single module but found more than one "
1307  f"({[ctr.name for ctr in containers]}) when searching for "
1308  f"kernel '{name}'.")
1309  container = containers[0]
1310 
1311  # Check for an interface block
1312  actual_names = []
1313  interfaces = walk(module_ast, Fortran2003.Interface_Block)
1314 
1315  for interface in interfaces:
1316  if interface.children[0].children[0].string.lower() == lname:
1317  # We have an interface block with the name of the routine
1318  # we are searching for.
1319  procs = walk(interface, Fortran2003.Procedure_Stmt)
1320  for proc in procs:
1321  for child in proc.children[0].children:
1322  actual_names.append(child.string.lower())
1323  break
1324  if not actual_names:
1325  # No interface block was found so we proceed to search for a
1326  # routine with the original name that we were passed.
1327  actual_names = [lname]
1328 
1329  routines = container.walk(Routine)
1330  selected_routines = [routine for routine in routines
1331  if routine.name.lower() in actual_names]
1332 
1333  if not selected_routines:
1334  raise GenerationError(
1335  f"Could not find subroutine or interface '{name}' in the "
1336  f"module '{container.name}'.")
1337 
1338  return selected_routines
1339 
1340  @staticmethod
1341  def _parse_dimensions(dimensions, symbol_table):
1342  '''
1343  Parse the fparser dimension attribute into a shape list. Each entry of
1344  this list is either None (if the extent is unknown) or a 2-tuple
1345  containing the lower and upper bound of that dimension. If any of the
1346  symbols encountered are instances of the generic Symbol class, they are
1347  specialised (in place) and become instances of DataSymbol with
1348  UnresolvedType.
1349 
1350  :param dimensions: fparser dimension attribute.
1351  :type dimensions: \
1352  :py:class:`fparser.two.Fortran2003.Dimension_Attr_Spec`
1353  :param symbol_table: symbol table of the declaration context.
1354  :type symbol_table: :py:class:`psyclone.psyir.symbols.SymbolTable`
1355 
1356  :returns: shape of the attribute in column-major order (leftmost \
1357  index is contiguous in memory). Each entry represents an array \
1358  dimension. If it is 'None' the extent of that dimension is \
1359  unknown, otherwise it holds a 2-tuple with the upper and lower \
1360  bounds of the dimension. If it is an empty list then the symbol \
1361  represents a scalar.
1362  :rtype: list of NoneType or 2-tuples of \
1363  :py:class:`psyclone.psyir.nodes.DataNode`
1364 
1365  :raises NotImplementedError: if anything other than scalar, integer \
1366  literals or symbols are encounted in the dimensions list.
1367 
1368  '''
1369  def _process_bound(bound_expr):
1370  '''Process the supplied fparser2 parse tree for the upper/lower
1371  bound of a dimension in an array declaration.
1372 
1373  :param bound_expr: fparser2 parse tree for lower/upper bound.
1374  :type bound_expr: :py:class:`fparser.two.utils.Base`
1375 
1376  :returns: PSyIR for the bound.
1377  :rtype: :py:class:`psyclone.psyir.nodes.DataNode`
1378 
1379  :raises NotImplementedError: if an unsupported form of array \
1380  bound is found.
1381  :raises GenerationError: invalid Fortran declaration of an \
1382  upper bound without an associated lower bound.
1383 
1384  '''
1385  if isinstance(bound_expr, Fortran2003.Int_Literal_Constant):
1386  return Literal(bound_expr.items[0], INTEGER_TYPE)
1387 
1388  if isinstance(bound_expr, Fortran2003.Name):
1389  # Fortran does not regulate the order in which variables
1390  # may be declared so it's possible for the shape
1391  # specification of an array to reference variables that
1392  # come later in the list of declarations. The reference
1393  # may also be to a symbol present in a parent symbol table
1394  # (e.g. if the variable is declared in an outer, module
1395  # scope).
1396  dim_name = bound_expr.string.lower()
1397  try:
1398  sym = symbol_table.lookup(dim_name)
1399  # pylint: disable=unidiomatic-typecheck
1400  if type(sym) is Symbol:
1401  # An entry for this symbol exists but it's only a
1402  # generic Symbol and we now know it must be a
1403  # DataSymbol.
1404  sym.specialise(DataSymbol, datatype=UnresolvedType())
1405  elif isinstance(sym.datatype, (UnsupportedType,
1406  UnresolvedType)):
1407  # Allow symbols of Unsupported/UnresolvedType.
1408  pass
1409  elif not (isinstance(sym.datatype, ScalarType) and
1410  sym.datatype.intrinsic ==
1411  ScalarType.Intrinsic.INTEGER):
1412  # It's not of Unsupported/UnresolvedType and it's not
1413  # an integer scalar.
1414  raise NotImplementedError(
1415  "Unsupported shape dimension")
1416  except KeyError:
1417  # We haven't seen this symbol before so create a new
1418  # one with a unresolved interface (since we don't
1419  # currently know where it is declared).
1420  sym = DataSymbol(dim_name, default_integer_type(),
1421  interface=UnresolvedInterface())
1422  symbol_table.add(sym)
1423  return Reference(sym)
1424 
1425  raise NotImplementedError("Unsupported shape dimension")
1426 
1427  one = Literal("1", INTEGER_TYPE)
1428  shape = []
1429  # Traverse shape specs in Depth-first-search order
1430  for dim in walk(dimensions, (Fortran2003.Assumed_Shape_Spec,
1431  Fortran2003.Explicit_Shape_Spec,
1432  Fortran2003.Assumed_Size_Spec)):
1433 
1434  if isinstance(dim, Fortran2003.Assumed_Shape_Spec):
1435  # Assumed_Shape_Spec has two children holding the lower and
1436  # upper bounds. It is valid Fortran (R514) to specify only the
1437  # lower bound:
1438  # ":" -> Assumed_Shape_Spec(None, None)
1439  # "4:" -> Assumed_Shape_Spec(Int_Literal_Constant('4', None),
1440  # None)
1441  lower = (_process_bound(dim.children[0]) if dim.children[0]
1442  else None)
1443  if dim.children[1]:
1444  upper = _process_bound(dim.children[1])
1445  else:
1446  upper = ArrayType.Extent.ATTRIBUTE if lower else None
1447 
1448  if upper and not lower:
1449  raise GenerationError(
1450  f"Found an assumed-shape array declaration with only "
1451  f"an upper bound ({dimensions}). This is not valid "
1452  f"Fortran.")
1453  if upper:
1454  shape.append((lower, upper))
1455  else:
1456  shape.append(None)
1457 
1458  elif isinstance(dim, Fortran2003.Explicit_Shape_Spec):
1459  try:
1460  upper = _process_bound(dim.items[1])
1461  if dim.items[0]:
1462  lower = _process_bound(dim.items[0])
1463  shape.append((lower, upper))
1464  else:
1465  # Lower bound defaults to 1 in Fortran
1466  shape.append((one.copy(), upper))
1467  except NotImplementedError as err:
1468  raise NotImplementedError(
1469  f"Could not process {dimensions}. Only scalar integer "
1470  f"literals or symbols are supported for explicit-shape"
1471  f" array declarations.") from err
1472 
1473  elif isinstance(dim, Fortran2003.Assumed_Size_Spec):
1474  raise NotImplementedError(
1475  f"Could not process {dimensions}. Assumed-size arrays"
1476  f" are not supported.")
1477 
1478  else:
1479  raise InternalError(
1480  f"Reached end of loop body and array-shape specification "
1481  f"{type(dim)} has not been handled.")
1482 
1483  return shape
1484 
1485  @staticmethod
1487  '''
1488  Search the supplied list of fparser2 nodes (which must represent a
1489  complete Specification Part) for any accessibility
1490  statements (e.g. "PUBLIC :: my_var") to determine the default
1491  visibility of symbols as well as identifying those that are
1492  explicitly declared as public or private.
1493 
1494  :param nodes: nodes in the fparser2 parse tree describing a \
1495  Specification Part that will be searched.
1496  :type nodes: list of :py:class:`fparser.two.utils.Base`
1497 
1498  :returns: default visibility of symbols within the current scoping \
1499  unit and dict of symbol names with explicit visibilities.
1500  :rtype: 2-tuple of (:py:class:`psyclone.symbols.Symbol.Visibility`, \
1501  dict)
1502 
1503  :raises InternalError: if an accessibility attribute which is not \
1504  'public' or 'private' is encountered.
1505  :raises GenerationError: if the parse tree is found to contain more \
1506  than one bare accessibility statement (i.e. 'PUBLIC' or 'PRIVATE')
1507  :raises GenerationError: if a symbol is explicitly declared as being \
1508  both public and private.
1509 
1510  '''
1511  default_visibility = None
1512  # Sets holding the names of those symbols whose access is specified
1513  # explicitly via an access-stmt (e.g. "PUBLIC :: my_var")
1514  explicit_public = set()
1515  explicit_private = set()
1516  # R518 an access-stmt shall appear only in the specification-part
1517  # of a *module*.
1518  access_stmts = walk(nodes, Fortran2003.Access_Stmt)
1519 
1520  for stmt in access_stmts:
1521 
1522  if stmt.children[0].lower() == "public":
1523  public_stmt = True
1524  elif stmt.children[0].lower() == "private":
1525  public_stmt = False
1526  else:
1527  raise InternalError(
1528  f"Failed to process '{stmt}'. Found an accessibility "
1529  f"attribute of '{stmt.children[0]}' but expected either "
1530  f"'public' or 'private'.")
1531  if not stmt.children[1]:
1532  if default_visibility:
1533  # We've already seen an access statement without an
1534  # access-id-list. This is therefore invalid Fortran (which
1535  # fparser does not catch).
1536  current_node = stmt.parent
1537  while current_node:
1538  if isinstance(current_node, Fortran2003.Module):
1539  mod_name = str(
1540  current_node.children[0].children[1])
1541  raise GenerationError(
1542  f"Module '{mod_name}' contains more than one "
1543  f"access statement with an omitted "
1544  f"access-id-list. This is invalid Fortran.")
1545  current_node = current_node.parent
1546  # Failed to find an enclosing Module. This is also invalid
1547  # Fortran since an access statement is only permitted
1548  # within a module.
1549  raise GenerationError(
1550  "Found multiple access statements with omitted access-"
1551  "id-lists and no enclosing Module. Both of these "
1552  "things are invalid Fortran.")
1553  if public_stmt:
1554  default_visibility = Symbol.Visibility.PUBLIC
1555  else:
1556  default_visibility = Symbol.Visibility.PRIVATE
1557  else:
1558  symbol_names = [child.string.lower() for child in
1559  stmt.children[1].children]
1560  if public_stmt:
1561  explicit_public.update(symbol_names)
1562  else:
1563  explicit_private.update(symbol_names)
1564  # Sanity check the lists of symbols (because fparser2 does not
1565  # currently do much validation)
1566  invalid_symbols = explicit_public.intersection(explicit_private)
1567  if invalid_symbols:
1568  raise GenerationError(
1569  f"Symbols {list(invalid_symbols)} appear in access statements "
1570  f"with both PUBLIC and PRIVATE access-ids. This is invalid "
1571  f"Fortran.")
1572 
1573  # Symbols are public by default in Fortran
1574  if default_visibility is None:
1575  default_visibility = Symbol.Visibility.PUBLIC
1576 
1577  visibility_map = {}
1578  for name in explicit_public:
1579  visibility_map[name] = Symbol.Visibility.PUBLIC
1580  for name in explicit_private:
1581  visibility_map[name] = Symbol.Visibility.PRIVATE
1582 
1583  return (default_visibility, visibility_map)
1584 
1585  @staticmethod
1586  def _process_save_statements(nodes, parent):
1587  '''
1588  Search the supplied list of fparser2 nodes (which must represent a
1589  complete Specification Part) for any SAVE statements (e.g.
1590  "SAVE :: my_var") to determine which Symbols are static.
1591 
1592  Any common blocks referred to in a SAVE will result in Symbols of
1593  UnsupportedFortranType being added to the symbol table associated with
1594  `parent`.
1595 
1596  :param nodes: nodes in the fparser2 parse tree describing a
1597  Specification Part that will be searched.
1598  :type nodes: List[:py:class:`fparser.two.utils.Base`]
1599  :param : the parent node in the PSyIR under construction.
1600  :type : :py:class:`psyclone.psyir.nodes.Node`
1601 
1602  :returns: names of symbols that are static or just "*" if they all are.
1603  :rtype: List[str]
1604 
1605  :raises GenerationError: if the parse tree is found to contain a SAVE
1606  without a saved-entity list *and* one or more SAVE attributes or
1607  SAVE statements (C580).
1608 
1609  '''
1610  symbol_table = parent.scope.symbol_table
1611  default_save = False
1612  # Set holding the names of those symbols which are marked as static
1613  # via an explicit SAVE stmt (e.g. "SAVE :: my_var")
1614  explicit_save = set()
1615 
1616  save_stmts = walk(nodes, Fortran2003.Save_Stmt)
1617 
1618  for stmt in save_stmts:
1619 
1620  if not stmt.children[1]:
1621  # No saved-entity list means that all entities are static.
1622  default_save = True
1623  else:
1624  symbol_names = [child.string.lower() for child in
1625  stmt.children[1].children]
1626  explicit_save.update(symbol_names)
1627 
1628  if default_save:
1629  if explicit_save:
1630  # This should really be caught by the Fortran parser but
1631  # fparser2 is lax.
1632  names = sorted(list(explicit_save))
1633  raise GenerationError(
1634  f"Supplied nodes contain a SAVE without a saved-entity "
1635  f"list plus one or more SAVES *with* saved-entity lists "
1636  f"(naming {names}). This is not valid Fortran.")
1637  explicit_save.add("*")
1638 
1639  # If there are any named Common blocks listed in a SAVE statement then
1640  # we create Symbols of UnsupportedFortranType for them (so that the
1641  # backend can recreate the necessary SAVE statement) and remove them
1642  # from the list returned by this method.
1643  for name in explicit_save.copy():
1644  if name.startswith("/"):
1645  uftype = UnsupportedFortranType(f"SAVE :: {name}")
1646  symbol_table.new_symbol(root_name="_PSYCLONE_INTERNAL_SAVE",
1647  symbol_type=DataSymbol,
1648  datatype=uftype)
1649  explicit_save.remove(name)
1650  return list(explicit_save)
1651 
1652  @staticmethod
1653  def _process_use_stmts(parent, nodes, visibility_map=None):
1654  '''
1655  Process all of the USE statements in the fparser2 parse tree
1656  supplied as a list of nodes. Imported symbols are added to
1657  the symbol table associated with the supplied parent node with
1658  Import interfaces.
1659 
1660  :param parent: PSyIR node in which to insert the symbols found.
1661  :type parent: :py:class:`psyclone.psyir.nodes.KernelSchedule`
1662  :param nodes: fparser2 AST nodes to search for use statements.
1663  :type nodes: list of :py:class:`fparser.two.utils.Base`
1664  :param visibility_map: mapping of symbol name to visibility (for \
1665  those symbols listed in an accessibility statement).
1666  :type visibility_map: dict with str keys and \
1667  :py:class:`psyclone.psyir.symbols.Symbol.Visibility` values
1668 
1669  :raises GenerationError: if the parse tree for a use statement has an \
1670  unrecognised structure.
1671  :raises SymbolError: if a symbol imported via a use statement is \
1672  already present in the symbol table.
1673  :raises NotImplementedError: if the form of use statement is not \
1674  supported.
1675 
1676  '''
1677  if visibility_map is None:
1678  visibility_map = {}
1679 
1680  for decl in walk(nodes, Fortran2003.Use_Stmt):
1681 
1682  # Check that the parse tree is what we expect
1683  if len(decl.items) != 5:
1684  # We can't just do str(decl) as that also checks that items
1685  # is of length 5
1686  text = ""
1687  for item in decl.items:
1688  if item:
1689  text += str(item)
1690  raise GenerationError(
1691  f"Expected the parse tree for a USE statement to contain "
1692  f"5 items but found {len(decl.items)} for '{text}'")
1693 
1694  mod_name = str(decl.items[2])
1695  mod_visibility = visibility_map.get(
1696  mod_name, parent.symbol_table.default_visibility)
1697 
1698  # Add the module symbol to the symbol table. Keep a record of
1699  # whether or not we've seen this module before for reporting
1700  # purposes in the code below.
1701  if mod_name not in parent.symbol_table:
1702  new_container = True
1703  container = ContainerSymbol(mod_name,
1704  visibility=mod_visibility)
1705  parent.symbol_table.add(container)
1706  else:
1707  new_container = False
1708  container = parent.symbol_table.lookup(mod_name)
1709  if not isinstance(container, ContainerSymbol):
1710  raise SymbolError(
1711  f"Found a USE of module '{mod_name}' but the symbol "
1712  f"table already has a non-container entry with that "
1713  f"name ({container}). This is invalid Fortran.")
1714 
1715  # Create a generic Symbol for each element in the ONLY clause.
1716  if isinstance(decl.items[4], Fortran2003.Only_List):
1717  if not new_container and not container.wildcard_import and \
1718  not parent.symbol_table.symbols_imported_from(container):
1719  # TODO #11 Log the fact that this explicit symbol import
1720  # will replace a previous import with an empty only-list.
1721  pass
1722  for name in decl.items[4].items:
1723  if isinstance(name, Fortran2003.Rename):
1724  # This variable is renamed using Fortran's
1725  # 'new_name=>orig_name' syntax, so capture the
1726  # original name ('orig_name') as well as the new
1727  # name ('sym_name').
1728  sym_name = str(name.children[1]).lower()
1729  orig_name = str(name.children[2]).lower()
1730  else:
1731  # This variable is not renamed.
1732  sym_name = str(name).lower()
1733  orig_name = None
1734  sym_visibility = visibility_map.get(
1735  sym_name, parent.symbol_table.default_visibility)
1736  if sym_name not in parent.symbol_table:
1737  # We're dealing with a symbol named in a use statement
1738  # in the *current* scope therefore we do not check
1739  # any ancestor symbol tables; we just create a
1740  # new symbol. Since we don't yet know anything about
1741  # the type of this symbol we create a generic Symbol.
1742  parent.symbol_table.add(
1743  Symbol(sym_name, visibility=sym_visibility,
1744  interface=ImportInterface(
1745  container, orig_name=orig_name)))
1746  else:
1747  # There's already a symbol with this name
1748  existing_symbol = parent.symbol_table.lookup(
1749  sym_name)
1750  if isinstance(existing_symbol, RoutineSymbol):
1751  # We already knew it was a RoutineSymbol (probably
1752  # because it is referenced by a Generic Interface)
1753  # but not where it came from so add an interface.
1754  existing_symbol.interface = ImportInterface(
1755  container, orig_name=orig_name)
1756  elif not existing_symbol.is_import:
1757  raise SymbolError(
1758  f"Symbol '{sym_name}' is imported from module "
1759  f"'{mod_name}' but is already present in the "
1760  f"symbol table as either an argument or a "
1761  f"local ({existing_symbol}).")
1762  # TODO #11 Log the fact that we've already got an
1763  # import of this symbol and that will take precedence.
1764  elif not decl.items[3]:
1765  # We have a USE statement without an ONLY clause.
1766  if not new_container and not container.wildcard_import and \
1767  not parent.symbol_table.symbols_imported_from(container):
1768  # TODO #11 Log the fact that this explicit symbol import
1769  # will replace a previous import that had an empty
1770  # only-list.
1771  pass
1772  container.wildcard_import = True
1773  elif decl.items[3].lower().replace(" ", "") == ",only:":
1774  # This use has an 'only: ' but no associated list of
1775  # imported symbols. (It serves to keep a module in scope while
1776  # not actually importing anything from it.) We do not need to
1777  # set anything as the defaults (empty 'only' list and no
1778  # wildcard import) imply 'only:'.
1779  if not new_container and \
1780  (container.wildcard_import or
1781  parent.symbol_table.symbols_imported_from(container)):
1782  # TODO #11 Log the fact that this import with an empty
1783  # only-list is ignored because of existing 'use's of
1784  # the module.
1785  pass
1786  else:
1787  raise NotImplementedError(f"Found unsupported USE statement: "
1788  f"'{decl}'")
1789 
1790  def _process_type_spec(self, parent, type_spec):
1791  '''
1792  Processes the fparser2 parse tree of a type specification in order to
1793  extract the type and precision that are specified.
1794 
1795  :param parent: the parent of the current PSyIR node under construction.
1796  :type parent: :py:class:`psyclone.psyir.nodes.Node`
1797  :param type_spec: the fparser2 parse tree of the type specification.
1798  :type type_spec: \
1799  :py:class:`fparser.two.Fortran2003.Intrinsic_Type_Spec` or \
1800  :py:class:`fparser.two.Fortran2003.Declaration_Type_Spec`
1801 
1802  :returns: the type and precision specified by the type-spec.
1803  :rtype: 2-tuple of :py:class:`psyclone.psyir.symbols.ScalarType` or \
1804  :py:class:`psyclone.psyir.symbols.DataTypeSymbol` and \
1805  :py:class:`psyclone.psyir.symbols.DataSymbol.Precision` or \
1806  :py:class:`psyclone.psyir.symbols.DataSymbol` or int or NoneType
1807 
1808  :raises NotImplementedError: if an unsupported intrinsic type is found.
1809  :raises SymbolError: if a symbol already exists for the name of a \
1810  derived type but is not a DataTypeSymbol.
1811  :raises NotImplementedError: if the supplied type specification is \
1812  not for an intrinsic type or a derived type.
1813 
1814  '''
1815  base_type = None
1816  precision = None
1817 
1818  if isinstance(type_spec, Fortran2003.Intrinsic_Type_Spec):
1819  fort_type = str(type_spec.items[0]).lower()
1820  try:
1821  data_name = TYPE_MAP_FROM_FORTRAN[fort_type]
1822  except KeyError as err:
1823  raise NotImplementedError(
1824  f"Could not process {type_spec}. Only 'real', 'double "
1825  f"precision', 'integer', 'logical' and 'character' "
1826  f"intrinsic types are supported.") from err
1827  if fort_type == "double precision":
1828  # Fortran double precision is equivalent to a REAL
1829  # intrinsic with precision DOUBLE in the PSyIR.
1830  precision = ScalarType.Precision.DOUBLE
1831  else:
1832  # Check for precision being specified.
1833  precision = self._process_precision_process_precision(type_spec, parent)
1834  if not precision:
1835  precision = default_precision(data_name)
1836  # We don't support len or kind specifiers for character variables
1837  if fort_type == "character" and type_spec.children[1]:
1838  raise NotImplementedError(
1839  f"Length or kind attributes not supported on a character "
1840  f"variable: '{type_spec}'")
1841  base_type = ScalarType(data_name, precision)
1842 
1843  elif isinstance(type_spec, Fortran2003.Declaration_Type_Spec):
1844  # This is a variable of derived type
1845  if type_spec.children[0].lower() != "type":
1846  # We don't yet support declarations that use 'class'
1847  # TODO #1504 extend the PSyIR for this variable type.
1848  raise NotImplementedError(
1849  f"Could not process {type_spec} - declarations "
1850  f"other than 'type' are not yet supported.")
1851  type_name = str(walk(type_spec, Fortran2003.Type_Name)[0])
1852  # Do we already have a Symbol for this derived type?
1853  type_symbol = _find_or_create_unresolved_symbol(parent, type_name)
1854  # pylint: disable=unidiomatic-typecheck
1855  if type(type_symbol) is Symbol:
1856  # We do but we didn't know what kind of symbol it was. Create
1857  # a DataTypeSymbol to replace it.
1858  new_symbol = DataTypeSymbol(type_name, UnresolvedType(),
1859  interface=type_symbol.interface,
1860  visibility=type_symbol.visibility)
1861  table = type_symbol.find_symbol_table(parent)
1862  table.swap(type_symbol, new_symbol)
1863  type_symbol = new_symbol
1864  elif not isinstance(type_symbol, DataTypeSymbol):
1865  raise SymbolError(
1866  f"Search for a DataTypeSymbol named '{type_name}' "
1867  f"(required by specification '{type_spec}') found a "
1868  f"'{type(type_symbol).__name__}' instead.")
1869  base_type = type_symbol
1870 
1871  else:
1872  # Not a supported type specification. This will result in a
1873  # CodeBlock or UnsupportedFortranType, depending on the context.
1874  raise NotImplementedError("Unsupported type specification")
1875 
1876  return base_type, precision
1877 
1878  def _process_decln(self, scope, symbol_table, decl, visibility_map=None,
1879  statics_list=()):
1880  '''
1881  Process the supplied fparser2 parse tree for a declaration. For each
1882  entity that is declared, a symbol is added to the supplied symbol
1883  table.
1884 
1885  :param scope: PSyIR node in which to insert the symbols found.
1886  :type scope: :py:class:`psyclone.psyir.nodes.ScopingNode`
1887  :param symbol_table: the symbol table to which to add new symbols.
1888  :type symbol_table: py:class:`psyclone.psyir.symbols.SymbolTable`
1889  :param decl: fparser2 parse tree of declaration to process.
1890  :type decl: :py:class:`fparser.two.Fortran2003.Type_Declaration_Stmt`
1891  :param visibility_map: mapping of symbol name to visibility (for
1892  those symbols listed in an accessibility statement).
1893  :type visibility_map: dict with str keys and
1894  :py:class:`psyclone.psyir.symbols.Symbol.Visibility` values
1895  :param statics_list: the names of symbols which are static (due to
1896  appearing in a SAVE statement). If all symbols are static then
1897  this contains the single entry "*".
1898  :type statics_list: Iterable[str]
1899 
1900  :raises NotImplementedError: if an unsupported attribute is found.
1901  :raises NotImplementedError: if an unsupported intent attribute is
1902  found.
1903  :raises NotImplementedError: if an unsupported access-spec attribute
1904  is found.
1905  :raises NotImplementedError: if the allocatable attribute is found on
1906  a non-array declaration.
1907  :raises InternalError: if an array with defined extent has the
1908  allocatable attribute.
1909  :raises NotImplementedError: if an unsupported initialisation
1910  expression is found for a parameter declaration.
1911  :raises NotImplementedError: if a character-length specification is
1912  found.
1913  :raises SymbolError: if a declaration is found for a symbol that is
1914  already present in the symbol table with a defined interface.
1915  :raises GenerationError: if a set of incompatible Fortran
1916  attributes are found in a symbol declaration.
1917 
1918  '''
1919  # pylint: disable=too-many-arguments
1920  (type_spec, attr_specs, entities) = decl.items
1921 
1922  # Parse the type_spec
1923  base_type, _ = self._process_type_spec_process_type_spec(scope, type_spec)
1924 
1925  # Parse declaration attributes:
1926  # 1) If no dimension attribute is provided, it defaults to scalar.
1927  attribute_shape = []
1928  # 2) Record symbol interface
1929  interface = None
1930  multiple_interfaces = False
1931  # 3) Record initialized constant values
1932  has_constant_value = False
1933  # 4) Whether the declaration has the allocatable attribute
1934  allocatable = False
1935  # 5) Access-specification - this var is only set if the declaration
1936  # has an explicit access-spec (e.g. INTEGER, PRIVATE :: xxx)
1937  decln_access_spec = None
1938  # 6) Whether this declaration has the SAVE attribute.
1939  has_save_attr = False
1940  if attr_specs:
1941  for attr in attr_specs.items:
1942  if isinstance(attr, Fortran2003.Attr_Spec):
1943  normalized_string = str(attr).lower().replace(' ', '')
1944  if normalized_string == "save":
1945  if interface is not None:
1946  multiple_interfaces = True
1947  has_save_attr = True
1948  elif normalized_string == "parameter":
1949  # Flag the existence of a constant value in the RHS
1950  has_constant_value = True
1951  elif normalized_string == "allocatable":
1952  allocatable = True
1953  else:
1954  raise NotImplementedError(
1955  f"Could not process {decl.items}. Unrecognised "
1956  f"attribute '{attr}'.")
1957  elif isinstance(attr, Fortran2003.Intent_Attr_Spec):
1958  (_, intent) = attr.items
1959  normalized_string = \
1960  intent.string.lower().replace(' ', '')
1961  try:
1962  if interface is not None:
1963  multiple_interfaces = True
1964  interface = ArgumentInterface(
1965  INTENT_MAPPING[normalized_string])
1966  except KeyError as info:
1967  message = (
1968  f"Could not process {decl.items}. Unexpected "
1969  f"intent attribute '{attr}'.")
1970  raise InternalError(message) from info
1971  elif isinstance(attr,
1972  (Fortran2003.Dimension_Attr_Spec,
1973  Fortran2003.Dimension_Component_Attr_Spec)):
1974  attribute_shape = \
1975  self._parse_dimensions_parse_dimensions(attr, symbol_table)
1976  elif isinstance(attr, Fortran2003.Access_Spec):
1977  try:
1978  decln_access_spec = _process_access_spec(attr)
1979  except InternalError as err:
1980  raise InternalError(
1981  f"Could not process '{decl.items}': "
1982  f"{err.value}") from err
1983  else:
1984  raise NotImplementedError(
1985  f"Could not process declaration '{decl}'. Unrecognised"
1986  f" attribute type '{type(attr).__name__}'.")
1987 
1988  # There are some combinations of attributes that are not valid
1989  # Fortran but fparser does not check, so we need to check for them
1990  # here.
1991  # TODO fparser/#413 could also fix these issues.
1992  if has_save_attr and has_constant_value:
1993  raise GenerationError(
1994  f"SAVE and PARAMETER attributes are not compatible but "
1995  f"found:\n {decl}")
1996 
1997  # Now we've checked for save and parameter existing
1998  # together, we can allow parameter without save and set it
1999  # to the same interface as save.
2000  if has_constant_value and interface is None:
2001  # We have a parameter so should set its interface to static.
2002  interface = StaticInterface()
2003 
2004  if allocatable and has_constant_value:
2005  raise GenerationError(
2006  f"ALLOCATABLE and PARAMETER attributes are not compatible "
2007  f"but found:\n {decl}")
2008  if isinstance(interface, ArgumentInterface) and has_constant_value:
2009  raise GenerationError(
2010  f"INTENT and PARAMETER attributes are not compatible but"
2011  f" found:\n {decl}")
2012  if multiple_interfaces:
2013  raise GenerationError(
2014  f"Multiple or duplicated incompatible attributes "
2015  f"found in declaration:\n {decl}")
2016 
2017  # Parse declarations RHS and declare new symbol into the
2018  # parent symbol table for each entity found.
2019  for entity in entities.items:
2020  (name, array_spec, char_len, initialisation) = entity.items
2021  init_expr = None
2022 
2023  # If the entity has an array-spec shape, it has priority.
2024  # Otherwise use the declaration attribute shape.
2025  if array_spec is not None:
2026  entity_shape = \
2027  self._parse_dimensions_parse_dimensions(array_spec, symbol_table)
2028  else:
2029  entity_shape = attribute_shape
2030 
2031  if allocatable and not entity_shape:
2032  # We have an allocatable attribute on something that we
2033  # don't recognise as an array - this is not supported.
2034  raise NotImplementedError(
2035  f"Could not process {decl}. The 'allocatable' attribute is"
2036  f" only supported on array declarations.")
2037 
2038  for idx, extent in enumerate(entity_shape):
2039  if extent is None:
2040  if allocatable:
2041  entity_shape[idx] = ArrayType.Extent.DEFERRED
2042  else:
2043  entity_shape[idx] = ArrayType.Extent.ATTRIBUTE
2044  elif not isinstance(extent, ArrayType.Extent) and \
2045  allocatable:
2046  # We have an allocatable array with a defined extent.
2047  # This is invalid Fortran.
2048  raise InternalError(
2049  f"Invalid Fortran: '{decl}'. An array with defined "
2050  f"extent cannot have the ALLOCATABLE attribute.")
2051 
2052  if initialisation:
2053  # If the variable or parameter has an initial value then
2054  # parse its initialization into a dummy Assignment.
2055  dummynode = Assignment(parent=scope)
2056  expr = initialisation.items[1]
2057  self.process_nodesprocess_nodes(parent=dummynode, nodes=[expr])
2058  init_expr = dummynode.children[0].detach()
2059 
2060  if char_len is not None:
2061  raise NotImplementedError(
2062  f"Could not process {decl.items}. Character length "
2063  f"specifications are not supported.")
2064 
2065  sym_name = str(name).lower()
2066 
2067  if decln_access_spec:
2068  visibility = decln_access_spec
2069  else:
2070  # There was no access-spec on the LHS of the decln
2071  if visibility_map is not None:
2072  visibility = visibility_map.get(
2073  sym_name, symbol_table.default_visibility)
2074  else:
2075  visibility = symbol_table.default_visibility
2076 
2077  listed_in_save = "*" in statics_list or sym_name in statics_list
2078  if has_save_attr or listed_in_save:
2079  if has_save_attr and listed_in_save:
2080  raise GenerationError(
2081  f"Invalid Fortran: '{decl}'. Symbol 'sym_name' is "
2082  f"the subject of a SAVE statement but also has a SAVE "
2083  f"attribute on its declaration.")
2084  this_interface = StaticInterface()
2085  elif not interface:
2086  # Interface not explicitly specified, provide a default value.
2087  # This might still be redefined as Argument later if it appears
2088  # in the argument list, but we don't know at this point.
2089  this_interface = (DefaultModuleInterface() if
2090  isinstance(scope, Container) else
2091  AutomaticInterface())
2092  else:
2093  # We use copies of the interface object because we will reuse
2094  # the interface for each entity if there are multiple in the
2095  # same declaration statement.
2096  this_interface = interface.copy()
2097 
2098  if entity_shape:
2099  # array
2100  datatype = ArrayType(base_type, entity_shape)
2101  else:
2102  # scalar
2103  datatype = base_type
2104 
2105  # Make sure the declared symbol exists in the SymbolTable
2106  tag = None
2107  try:
2108  sym = symbol_table.lookup(sym_name, scope_limit=scope)
2109  # pylint: disable=unidiomatic-typecheck
2110  if type(sym) is Symbol:
2111  # This was a generic symbol. We now know what it is
2112  sym.specialise(DataSymbol, datatype=datatype,
2113  visibility=visibility,
2114  interface=this_interface,
2115  is_constant=has_constant_value,
2116  initial_value=init_expr)
2117  else:
2118  if sym is symbol_table.lookup_with_tag(
2119  "own_routine_symbol"):
2120  # In case it is its own function routine
2121  # symbol, Fortran will declare it inside the
2122  # function as a DataSymbol. Remove the
2123  # RoutineSymbol in order to free the exact
2124  # name for the DataSymbol.
2125  symbol_table.remove(sym)
2126  # And trigger the exception path but keeping
2127  # the same tag
2128  tag = "own_routine_symbol"
2129  raise KeyError
2130  if not sym.is_unresolved:
2131  raise SymbolError(
2132  f"Symbol '{sym_name}' already present in "
2133  f"SymbolTable with a defined interface "
2134  f"({sym.interface}).")
2135  except KeyError:
2136  try:
2137  sym = DataSymbol(sym_name, datatype,
2138  visibility=visibility,
2139  is_constant=has_constant_value,
2140  initial_value=init_expr)
2141  except ValueError:
2142  # Error setting initial value have to be raised as
2143  # NotImplementedError in order to create an UnsupportedType
2144  # Therefore, the Error doesn't need raise_from or message
2145  # pylint: disable=raise-missing-from
2146  if tag:
2147  raise InternalError(
2148  f"The fparser2 frontend does not support "
2149  f"declarations where the routine name is of "
2150  f"UnsupportedType, but found this case in "
2151  f"'{sym_name}'.")
2152  raise NotImplementedError()
2153 
2154  symbol_table.add(sym, tag=tag)
2155 
2156  if init_expr:
2157  # In Fortran, an initialisation expression on a declaration of
2158  # a symbol (whether in a routine or a module) implies that the
2159  # symbol is static (endures for the lifetime of the program)
2160  # unless it is a pointer initialisation.
2161  sym.interface = StaticInterface()
2162  else:
2163  sym.interface = this_interface
2164 
2165  def _process_derived_type_decln(self, parent, decl, visibility_map):
2166  '''
2167  Process the supplied fparser2 parse tree for a derived-type
2168  declaration. A DataTypeSymbol representing the derived-type is added
2169  to the symbol table associated with the parent node.
2170 
2171  :param parent: PSyIR node in which to insert the symbols found.
2172  :type parent: :py:class:`psyclone.psyGen.KernelSchedule`
2173  :param decl: fparser2 parse tree of declaration to process.
2174  :type decl: :py:class:`fparser.two.Fortran2003.Type_Declaration_Stmt`
2175  :param visibility_map: mapping of symbol name to visibility (for \
2176  those symbols listed in an accessibility statement).
2177  :type visibility_map: dict with str keys and \
2178  :py:class:`psyclone.psyir.symbols.Symbol.Visibility` values
2179 
2180  :raises SymbolError: if a Symbol already exists with the same name \
2181  as the derived type being defined and it is not a DataTypeSymbol \
2182  or is not of UnresolvedType.
2183 
2184  '''
2185  name = str(walk(decl.children[0], Fortran2003.Type_Name)[0]).lower()
2186  # Create a new StructureType for this derived type
2187  dtype = StructureType()
2188 
2189  # Look for any private-components-stmt (R447) within the type
2190  # decln. In the absence of this, the default visibility of type
2191  # components is public.
2192  private_stmts = walk(decl, Fortran2003.Private_Components_Stmt)
2193  if private_stmts:
2194  default_compt_visibility = Symbol.Visibility.PRIVATE
2195  else:
2196  default_compt_visibility = Symbol.Visibility.PUBLIC
2197 
2198  # The visibility of the symbol representing this derived type
2199  if name in visibility_map:
2200  dtype_symbol_vis = visibility_map[name]
2201  else:
2202  specs = walk(decl.children[0], Fortran2003.Access_Spec)
2203  if specs:
2204  dtype_symbol_vis = _process_access_spec(specs[0])
2205  else:
2206  dtype_symbol_vis = parent.symbol_table.default_visibility
2207 
2208  # We have to create the symbol for this type before processing its
2209  # components as they may refer to it (e.g. for a linked list).
2210  if name in parent.symbol_table:
2211  # An entry already exists for this type.
2212  # Check that it is a DataTypeSymbol
2213  tsymbol = parent.symbol_table.lookup(name)
2214  if not isinstance(tsymbol, DataTypeSymbol):
2215  raise SymbolError(
2216  f"Error processing definition of derived type '{name}'. "
2217  f"The symbol table already contains an entry with this "
2218  f"name but it is a '{type(tsymbol).__name__}' when it "
2219  f"should be a 'DataTypeSymbol' (for the derived-type "
2220  f"definition '{decl}')")
2221  # Since we are processing the definition of this symbol, the only
2222  # permitted type for an existing symbol of this name is Unresolved
2223  if not isinstance(tsymbol.datatype, UnresolvedType):
2224  raise SymbolError(
2225  f"Error processing definition of derived type '{name}'. "
2226  f"The symbol table already contains a DataTypeSymbol with "
2227  f"this name but it is of type "
2228  f"'{type(tsymbol.datatype).__name__}' when it should be "
2229  f"of 'UnresolvedType'")
2230  else:
2231  # We don't already have an entry for this type so create one
2232  tsymbol = DataTypeSymbol(name, dtype, visibility=dtype_symbol_vis)
2233  parent.symbol_table.add(tsymbol)
2234 
2235  # Populate this StructureType by processing the components of
2236  # the derived type
2237  try:
2238  # We don't support derived-types with additional
2239  # attributes e.g. "extends" or "abstract". Note, we do
2240  # support public/private attributes but these are stored
2241  # as Access_Spec, not Type_Attr_Spec.
2242  derived_type_stmt = decl.children[0]
2243  if walk(derived_type_stmt, Fortran2003.Type_Attr_Spec):
2244  raise NotImplementedError(
2245  "Derived-type definition contains unsupported attributes.")
2246 
2247  # We don't yet support derived-type definitions with a CONTAINS
2248  # section.
2249  contains = walk(decl, Fortran2003.Contains_Stmt)
2250  if contains:
2251  raise NotImplementedError(
2252  "Derived-type definition has a CONTAINS statement.")
2253 
2254  # Re-use the existing code for processing symbols
2255  local_table = SymbolTable(
2256  default_visibility=default_compt_visibility)
2257  for child in walk(decl, Fortran2003.Data_Component_Def_Stmt):
2258  self._process_decln_process_decln(parent, local_table, child)
2259  # Convert from Symbols to type information
2260  for symbol in local_table.symbols:
2261  dtype.add(symbol.name, symbol.datatype, symbol.visibility,
2262  symbol.initial_value)
2263 
2264  # Update its type with the definition we've found
2265  tsymbol.datatype = dtype
2266 
2267  except NotImplementedError:
2268  # Support for this declaration is not fully implemented so
2269  # set the datatype of the DataTypeSymbol to UnsupportedFortranType.
2270  tsymbol.datatype = UnsupportedFortranType(str(decl))
2271  tsymbol.interface = UnknownInterface()
2272 
2273  def _get_partial_datatype(self, node, scope, visibility_map):
2274  '''Try to obtain partial datatype information from node by removing
2275  any unsupported properties in the declaration.
2276 
2277  :param node: fparser2 node containing the declaration statement.
2278  :type node: :py:class:`fparser.two.Fortran2008.Type_Declaration_Stmt`
2279  or :py:class:`fparser.two.Fortran2003.Type_Declaration_Stmt`
2280  :param scope: PSyIR node in which to insert the symbols found.
2281  :type scope: :py:class:`psyclone.psyir.nodes.ScopingNode`
2282  :param visibility_map: mapping of symbol names to explicit
2283  visibilities.
2284  :type visibility_map: dict with str keys and values of type
2285  :py:class:`psyclone.psyir.symbols.Symbol.Visibility`
2286 
2287  :returns: a 2-tuple containing a PSyIR datatype, or datatype symbol,
2288  containing partial datatype information for the declaration
2289  statement and the PSyIR for any initialisation expression.
2290  When it is not possible to extract partial datatype information
2291  then (None, None) is returned.
2292  :rtype: Tuple[
2293  Optional[:py:class:`psyclone.psyir.symbols.DataType` |
2294  :py:class:`psyclone.psyir.symbols.DataTypeSymbol`],
2295  Optional[:py:class:`psyclone.psyir.nodes.Node`]]
2296 
2297  '''
2298  # 1: Remove any additional variables.
2299  entity_decl_list = node.children[2]
2300  orig_entity_decl_list = list(entity_decl_list.children[:])
2301  entity_decl_list.items = tuple(entity_decl_list.children[0:1])
2302  entity_decl = entity_decl_list.children[0]
2303  orig_entity_decl_children = list(entity_decl.children[:])
2304 
2305  # 2: Remove any unsupported attributes
2306  unsupported_attribute_names = ["pointer", "target"]
2307  attr_spec_list = node.children[1]
2308  orig_node_children = list(node.children[:])
2309  orig_attr_spec_list_children = (list(node.children[1].children[:])
2310  if attr_spec_list else None)
2311  if attr_spec_list:
2312  entry_list = []
2313  for attr_spec in attr_spec_list.children:
2314  if str(attr_spec).lower() not in unsupported_attribute_names:
2315  entry_list.append(attr_spec)
2316  if not entry_list:
2317  node.items = (node.items[0], None, node.items[2])
2318  else:
2319  node.items[1].items = tuple(entry_list)
2320 
2321  # Try to parse the modified node.
2322  symbol_table = SymbolTable()
2323  try:
2324  self._process_decln_process_decln(scope, symbol_table, node,
2325  visibility_map)
2326  symbol_name = node.children[2].children[0].children[0].string
2327  symbol_name = symbol_name.lower()
2328  new_sym = symbol_table.lookup(symbol_name)
2329  datatype = new_sym.datatype
2330  init_expr = new_sym.initial_value
2331  except NotImplementedError:
2332  datatype = None
2333  init_expr = None
2334 
2335  # Restore the fparser2 parse tree
2336  node.items = tuple(orig_node_children)
2337  if node.children[1]:
2338  node.children[1].items = tuple(orig_attr_spec_list_children)
2339  node.children[2].items = tuple(orig_entity_decl_list)
2340  node.children[2].children[0].items = tuple(orig_entity_decl_children)
2341 
2342  # Return the init_expr detached from the temporal symbol
2343  init_expr = init_expr.detach() if init_expr is not None else None
2344  return datatype, init_expr
2345 
2346  def _process_parameter_stmts(self, nodes, parent):
2347  '''
2348  Examine the supplied list of fparser2 nodes and handle any
2349  PARAMETER statements. This is done separately so that it can be
2350  performed after all the declarations have been processed (since
2351  a PARAMETER statement can come *before* a symbol's declaration.)
2352 
2353  :param nodes: fparser2 AST nodes containing declaration statements.
2354  :type nodes: list of :py:class:`fparser.two.utils.Base`
2355  :param parent: PSyIR node in which to insert the symbols found.
2356  :type parent: :py:class:`psyclone.psyir.nodes.KernelSchedule`
2357 
2358  :raises NotImplementedError: if there are any issues parsing a
2359  parameter statement.
2360 
2361  '''
2362  for node in nodes:
2363  if not isinstance(node, Fortran2003.Implicit_Part):
2364  continue
2365  for stmt in node.children:
2366  if not isinstance(stmt, Fortran2003.Parameter_Stmt):
2367  continue
2368  for parameter_def in stmt.children[1].items:
2369  name, expr = parameter_def.items
2370  try:
2371  symbol = parent.symbol_table.lookup(str(name))
2372  except Exception as err:
2373  # If there is any problem put the whole thing
2374  # in a codeblock (as we presume the original
2375  # code is correct).
2376  raise NotImplementedError(
2377  f"Could not process '{stmt}' because: "
2378  f"{err}.") from err
2379 
2380  if not isinstance(symbol, DataSymbol):
2381  raise NotImplementedError(
2382  f"Could not process '{stmt}' because "
2383  f"'{symbol.name}' is not a DataSymbol.")
2384  if isinstance(symbol.datatype, UnsupportedType):
2385  raise NotImplementedError(
2386  f"Could not process '{stmt}' because "
2387  f"'{symbol.name}' has an UnsupportedType.")
2388 
2389  # Parse its initialization into a dummy Assignment
2390  # (but connected to the parent scope since symbols
2391  # must be resolved)
2392  dummynode = Assignment(parent=parent)
2393  self.process_nodesprocess_nodes(parent=dummynode, nodes=[expr])
2394 
2395  # Add the initialization expression in the symbol
2396  # constant_value attribute
2397  ct_expr = dummynode.children[0].detach()
2398  symbol.initial_value = ct_expr
2399  symbol.is_constant = True
2400  # Ensure the interface to this Symbol is static
2401  symbol.interface = StaticInterface()
2402 
2403  def _process_interface_block(self, node, symbol_table, visibility_map):
2404  '''
2405  Processes a Fortran2003.Interface_Block. If the interface is named
2406  and consists only of [module] procedure :: <procedure-list> then a
2407  GenericInterfaceSymbol is created. Otherwise, a RoutineSymbol of
2408  UnsupportedFortranType is created.
2409 
2410  :param node: the parse tree for the interface block.
2411  :type node: :py:class:`fparser.two.Fortran2003.Interface_Block`
2412  :param symbol_table: the table to which to add new symbols.
2413  :type symbol_table: :py:class:`psyclone.psyir.symbols.SymbolTable`
2414  :param visibility_map: information on any explicit symbol visibilities
2415  in the current scope.
2416  :type visibility_map: dict[
2417  str, :py:class:`psyclone.psyir.symbols.Symbol.Visibility`]
2418 
2419  '''
2420  # Fortran 2003 standard R1203 says that:
2421  # interface-stmt is INTERFACE [ generic-spec ]
2422  # or ABSTRACT INTERFACE
2423  # where generic-spec is either (R1207) a generic-name or one
2424  # of OPERATOR, ASSIGNMENT or dtio-spec.
2425  if not isinstance(node.children[0].children[0],
2426  Fortran2003.Name):
2427  # This interface does not have a name. Therefore we store it as a
2428  # RoutineSymbol with an internal name and with the content of the
2429  # interface being kept within an UnsupportedFortranType. As a
2430  # result the visibility and interface details of the RoutineSymbol
2431  # do not matter.
2432  symbol_table.new_symbol(
2433  root_name="_psyclone_internal_interface",
2434  symbol_type=RoutineSymbol,
2435  datatype=UnsupportedFortranType(str(node).lower()))
2436  return
2437 
2438  # This interface has a name.
2439  name = node.children[0].children[0].string.lower()
2440  vis = visibility_map.get(
2441  name, symbol_table.default_visibility)
2442  # Attempt to work out which routines this interface includes. We
2443  # only support those interfaces which use:
2444  # [MODULE] PROCEDURE :: <name-list>
2445  # to specify these.
2446  rsymbols = []
2447  # This flag will be set to False in the loop below if an unsupported
2448  # feature is found.
2449  supported_interface = True
2450  # Loop over the child nodes of the Interface definition.
2451  for child in node.children:
2452  if isinstance(child, (Fortran2003.Interface_Stmt,
2453  Fortran2003.End_Interface_Stmt)):
2454  continue
2455  if isinstance(child, Fortran2003.Procedure_Stmt):
2456  # Keep track of whether these are module procedures.
2457  is_module = child.children[1] == 'MODULE'
2458  for routine_name in child.children[0].children:
2459  # Can't specify the symbol_type here as that will raise
2460  # an exception if a bare Symbol is found instead of a
2461  # RoutineSymbol.
2462  rsym = symbol_table.find_or_create(
2463  routine_name.string)
2464  if type(rsym) is Symbol:
2465  rsym.specialise(RoutineSymbol)
2466  elif not isinstance(rsym, RoutineSymbol):
2467  raise InternalError(
2468  f"Expected '{rsym.name}' referenced by generic "
2469  f"interface '{name}' to be a Symbol or a "
2470  f"RoutineSymbol but found '{type(rsym).__name__}'")
2471  rsymbols.append((rsym, is_module))
2472  else:
2473  # Interface block contains an unsupported entry so
2474  # we'll create a symbol of UnsupportedFortranType (below).
2475  supported_interface = False
2476 
2477  try:
2478  if supported_interface:
2479  # A named interface block corresponds to a
2480  # GenericInterfaceSymbol. (There will be calls to it
2481  # although there will be no corresponding implementation
2482  # with that name.)
2483  symbol_table.add(GenericInterfaceSymbol(
2484  name, rsymbols, visibility=vis))
2485  else:
2486  # We've not been able to determine the list of
2487  # RoutineSymbols that this interface maps to so we just
2488  # create a RoutineSymbol of UnsupportedFortranType.
2489  symbol_table.add(RoutineSymbol(
2490  name, datatype=UnsupportedFortranType(str(node).lower()),
2491  visibility=vis))
2492  except KeyError:
2493  # This symbol has already been declared. This can happen when
2494  # an interface overloads a constructor for a type (as the interface
2495  # name is then the name of the type). However we still want to
2496  # capture the interface so we store it in the PSyIR as an
2497  # UnsupportedFortranType with an internal name as we do
2498  # for unnamed interfaces.
2499  symbol_table.new_symbol(
2500  root_name=f"_psyclone_internal_{name}",
2501  symbol_type=RoutineSymbol,
2502  datatype=UnsupportedFortranType(str(node).lower()),
2503  visibility=vis)
2504 
2505  def process_declarations(self, parent, nodes, arg_list,
2506  visibility_map=None):
2507  '''
2508  Transform the variable declarations in the fparser2 parse tree into
2509  symbols in the symbol table of the PSyIR parent node. The default
2510  visibility of any new symbol is taken from the symbol table associated
2511  with the `parent` node if necessary. The `visibility_map` provides
2512  information on any explicit symbol visibilities that are specified
2513  for the declarations.
2514 
2515  :param parent: PSyIR node in which to insert the symbols found.
2516  :type parent: :py:class:`psyclone.psyir.nodes.KernelSchedule`
2517  :param nodes: fparser2 AST nodes containing declaration statements.
2518  :type nodes: List[:py:class:`fparser.two.utils.Base`]
2519  :param arg_list: fparser2 AST node containing the argument list.
2520  :type arg_list: :py:class:`fparser.Fortran2003.Dummy_Arg_List`
2521  :param visibility_map: mapping of symbol names to explicit
2522  visibilities.
2523  :type visibility_map: dict[
2524  str, :py:class:`psyclone.psyir.symbols.Symbol.Visibility`]
2525 
2526  :raises GenerationError: if an INCLUDE statement is encountered.
2527  :raises NotImplementedError: the provided declarations contain
2528  attributes which are not supported yet.
2529  :raises GenerationError: if the parse tree for a USE statement does
2530  not have the expected structure.
2531  :raises SymbolError: if a declaration is found for a Symbol that is
2532  already in the symbol table with a defined interface.
2533  :raises InternalError: if the provided declaration is an unexpected
2534  or invalid fparser or Fortran expression.
2535 
2536  '''
2537  if visibility_map is None:
2538  visibility_map = {}
2539 
2540  # Look at any USE statements
2541  self._process_use_stmts_process_use_stmts(parent, nodes, visibility_map)
2542 
2543  # Look at any SAVE statements to determine any static symbols.
2544  statics_list = self._process_save_statements_process_save_statements(nodes, parent)
2545 
2546  # Handle any derived-type declarations/definitions before we look
2547  # at general variable declarations in case any of the latter use
2548  # the former.
2549  for decl in walk(nodes, Fortran2003.Derived_Type_Def):
2550  self._process_derived_type_decln_process_derived_type_decln(parent, decl, visibility_map)
2551 
2552  # INCLUDE statements are *not* part of the Fortran language and
2553  # can appear anywhere. Therefore we have to do a walk to make sure we
2554  # find them if they are present.
2555  incl_nodes = walk(nodes, (Fortran2003.Include_Stmt,
2556  C99Preprocessor.Cpp_Include_Stmt))
2557  if incl_nodes:
2558  # The include_handler just raises an error so we use that to
2559  # reduce code duplication.
2560  self._include_handler_include_handler(incl_nodes[0], parent)
2561 
2562  # Now we've captured any derived-type definitions, proceed to look
2563  # at the variable declarations.
2564  for node in nodes:
2565 
2566  if isinstance(node, Fortran2003.Interface_Block):
2567 
2568  self._process_interface_block_process_interface_block(node, parent.symbol_table,
2569  visibility_map)
2570 
2571  elif isinstance(node, Fortran2003.Type_Declaration_Stmt):
2572  try:
2573  self._process_decln_process_decln(parent, parent.symbol_table, node,
2574  visibility_map, statics_list)
2575  except NotImplementedError:
2576  # Found an unsupported variable declaration. Create a
2577  # DataSymbol with UnsupportedType for each entity being
2578  # declared. Currently this means that any symbols that come
2579  # after an unsupported declaration will also have
2580  # UnsupportedType. This is the subject of Issue #791.
2581  specs = walk(node, Fortran2003.Access_Spec)
2582  if specs:
2583  decln_vis = _process_access_spec(specs[0])
2584  else:
2585  decln_vis = parent.symbol_table.default_visibility
2586 
2587  orig_children = list(node.children[2].children[:])
2588  for child in orig_children:
2589  # Modify the fparser2 parse tree so that it only
2590  # declares the current entity. `items` is a tuple and
2591  # thus immutable so we create a new one.
2592  node.children[2].items = (child,)
2593  symbol_name = str(child.children[0]).lower()
2594  vis = visibility_map.get(symbol_name, decln_vis)
2595 
2596  # Check whether the symbol we're about to add
2597  # corresponds to the routine we're currently inside. If
2598  # it does then we remove the RoutineSymbol in order to
2599  # free the exact name for the DataSymbol, but we keep
2600  # the tag to reintroduce it to the new symbol.
2601  tag = None
2602  try:
2603  routine_sym = parent.symbol_table.lookup_with_tag(
2604  "own_routine_symbol")
2605  if routine_sym.name.lower() == symbol_name:
2606  parent.symbol_table.remove(routine_sym)
2607  tag = "own_routine_symbol" # Keep the tag
2608  except KeyError:
2609  pass
2610 
2611  # Try to extract partial datatype information.
2612  datatype, init = self._get_partial_datatype_get_partial_datatype(
2613  node, parent, visibility_map)
2614 
2615  # If a declaration declares multiple entities, it's
2616  # possible that some may have already been processed
2617  # successfully and thus be in the symbol table.
2618  try:
2619  parent.symbol_table.add(
2620  DataSymbol(
2621  symbol_name, UnsupportedFortranType(
2622  str(node),
2623  partial_datatype=datatype),
2624  interface=UnknownInterface(),
2625  visibility=vis,
2626  initial_value=init),
2627  tag=tag)
2628 
2629  except KeyError as err:
2630  if len(orig_children) == 1:
2631  raise SymbolError(
2632  f"Error while processing unsupported "
2633  f"declaration ('{node}'). An entry for "
2634  f"symbol '{symbol_name}' is already in "
2635  f"the symbol table.") from err
2636  # Restore the fparser2 parse tree
2637  node.children[2].items = tuple(orig_children)
2638 
2639  elif isinstance(node, (Fortran2003.Access_Stmt,
2640  Fortran2003.Save_Stmt,
2641  Fortran2003.Derived_Type_Def,
2642  Fortran2003.Stmt_Function_Stmt,
2643  Fortran2003.Common_Stmt,
2644  Fortran2003.Use_Stmt)):
2645  # These node types are handled separately
2646  pass
2647 
2648  elif isinstance(node, Fortran2003.Implicit_Part):
2649  # Anything other than a PARAMETER statement or an
2650  # IMPLICIT NONE means we can't handle this code.
2651  # Any PARAMETER statements are handled separately by the
2652  # call to _process_parameter_stmts below.
2653  # Any ENTRY statements are checked for in _subroutine_handler.
2654  child_nodes = walk(node, Fortran2003.Format_Stmt)
2655  if child_nodes:
2656  raise NotImplementedError(
2657  f"Error processing implicit-part: Format statements "
2658  f"are not supported but found '{child_nodes[0]}'")
2659  child_nodes = walk(node, Fortran2003.Implicit_Stmt)
2660  if any(imp.children != ('NONE',) for imp in child_nodes):
2661  raise NotImplementedError(
2662  f"Error processing implicit-part: implicit variable "
2663  f"declarations not supported but found '{node}'")
2664 
2665  elif isinstance(node, Fortran2003.Namelist_Stmt):
2666  # Place the declaration statement into the symbol table using
2667  # an internal symbol name. In case that we need more details
2668  # (e.g. to update symbol information), the following code
2669  # loops over namelist and each symbol:
2670  # for namelist_object in node.children:
2671  # for symbol_name in namelist_object[1].items:
2672  parent.symbol_table.new_symbol(
2673  root_name="_PSYCLONE_INTERNAL_NAMELIST",
2674  symbol_type=DataSymbol,
2675  datatype=UnsupportedFortranType(str(node)))
2676  else:
2677  raise NotImplementedError(
2678  f"Error processing declarations: fparser2 node of type "
2679  f"'{type(node).__name__}' not supported")
2680 
2681  # Process the nodes again, looking for PARAMETER statements. This is
2682  # done after the main declarations loop because they modify existing
2683  # symbols and can appear in any order.
2684  self._process_parameter_stmts_process_parameter_stmts(nodes, parent)
2685 
2686  # We process the nodes again looking for common blocks.
2687  # We do this here, after the main declarations loop, because they
2688  # modify the interface of existing symbols and can appear in any order.
2689  self._process_common_blocks_process_common_blocks(nodes, parent)
2690 
2691  if visibility_map is not None:
2692  # Check for symbols named in an access statement but not explicitly
2693  # declared. These must then refer to symbols that have been brought
2694  # into scope by an unqualified use statement.
2695  for name, vis in visibility_map.items():
2696  if name not in parent.symbol_table:
2697  # This call creates a Symbol and inserts it in the
2698  # appropriate symbol table.
2699  _find_or_create_unresolved_symbol(parent, name,
2700  visibility=vis)
2701  try:
2702  arg_symbols = []
2703  # Ensure each associated symbol has the correct interface info.
2704  for arg_name in [x.string.lower() for x in arg_list]:
2705  symbol = parent.symbol_table.lookup(arg_name)
2706  if not symbol.is_argument:
2707  # We didn't previously know that this Symbol was an
2708  # argument (as it had no 'intent' qualifier). Mark
2709  # that it is an argument by specifying its interface.
2710  # Although a Fortran argument has intent(inout) by default,
2711  # specifying this for an argument that is actually read
2712  # only (and is declared as such in the caller) causes
2713  # gfortran to complain. We therefore specify that the
2714  # access is unknown at this stage.
2715  symbol.interface = ArgumentInterface(
2716  ArgumentInterface.Access.UNKNOWN)
2717  arg_symbols.append(symbol)
2718  # Now that we've updated the Symbols themselves, set the
2719  # argument list
2720  parent.symbol_table.specify_argument_list(arg_symbols)
2721  except KeyError as info:
2722  decls_str_list = [str(node) for node in nodes]
2723  arg_str_list = [arg.string.lower() for arg in arg_list]
2724  raise InternalError(
2725  f"The argument list {arg_str_list} for routine '{parent.name}'"
2726  f" does not match the variable declarations:\n"
2727  f"{os.linesep.join(decls_str_list)}\n"
2728  f"(Note that PSyclone does not support implicit declarations.)"
2729  f" Specific PSyIR error is {info}.") from info
2730 
2731  # fparser2 does not always handle Statement Functions correctly, this
2732  # loop checks for Stmt_Functions that should be an array statement
2733  # and recovers them, otherwise it raises an error as currently
2734  # Statement Functions are not supported in PSyIR.
2735  for stmtfn in walk(nodes, Fortran2003.Stmt_Function_Stmt):
2736  (fn_name, arg_list, scalar_expr) = stmtfn.items
2737  try:
2738  symbol = parent.symbol_table.lookup(fn_name.string.lower())
2739  if symbol.is_array:
2740  # This is an array assignment wrongly categorized as a
2741  # statement_function by fparser2.
2742  array_subscript = arg_list.items
2743 
2744  assignment_rhs = scalar_expr
2745 
2746  # Create assignment node
2747  assignment = Assignment(parent=parent)
2748  parent.addchild(assignment)
2749 
2750  # Build lhs
2751  lhs = ArrayReference(symbol, parent=assignment)
2752  self.process_nodesprocess_nodes(parent=lhs, nodes=array_subscript)
2753  assignment.addchild(lhs)
2754 
2755  # Build rhs
2756  self.process_nodesprocess_nodes(parent=assignment,
2757  nodes=[assignment_rhs])
2758  else:
2759  raise InternalError(
2760  f"Could not process '{stmtfn}'. Symbol "
2761  f"'{symbol.name}' is in the SymbolTable but it is not "
2762  f"an array as expected, so it can not be recovered as "
2763  f"an array assignment.")
2764  except KeyError as err:
2765  raise NotImplementedError(
2766  f"Could not process '{stmtfn}'. Statement Function "
2767  f"declarations are not supported.") from err
2768 
2769  @staticmethod
2770  def _process_common_blocks(nodes, psyir_parent):
2771  ''' Process the fparser2 common block declaration statements. This is
2772  done after the other declarations and it will keep the statement
2773  as a UnsupportedFortranType and update the referenced symbols to a
2774  CommonBlockInterface.
2775 
2776  :param nodes: fparser2 AST nodes containing declaration statements.
2777  :type nodes: List[:py:class:`fparser.two.utils.Base`]
2778  :param psyir_parent: the PSyIR Node with a symbol table in which to
2779  add the Common Blocks and update the symbols interfaces.
2780  :type psyir_parent: :py:class:`psyclone.psyir.nodes.ScopingNode`
2781 
2782  :raises NotImplementedError: if one of the Symbols in a common block
2783  has initialisation (including when it is a parameter). This is not
2784  valid Fortran.
2785  :raises NotImplementedError: if it is unable to find one of the
2786  CommonBlock expressions in the symbol table (because it has not
2787  been declared yet or when it is not just the symbol name).
2788 
2789  '''
2790  for node in nodes:
2791  if isinstance(node, Fortran2003.Common_Stmt):
2792  # Place the declaration statement into a UnsupportedFortranType
2793  # (for now we just want to reproduce it). The name of the
2794  # commonblock is not in the same namespace as the variable
2795  # symbols names (and there may be multiple of them in a
2796  # single statement). So we use an internal symbol name.
2797  psyir_parent.symbol_table.new_symbol(
2798  root_name="_PSYCLONE_INTERNAL_COMMONBLOCK",
2799  symbol_type=DataSymbol,
2800  datatype=UnsupportedFortranType(str(node)))
2801 
2802  # Get the names of the symbols accessed with the commonblock,
2803  # they are already defined in the symbol table but they must
2804  # now have a common-block interface.
2805  try:
2806  # Loop over every COMMON block defined in this Common_Stmt
2807  for cb_object in node.children[0]:
2808  for symbol_name in cb_object[1].items:
2809  sym = psyir_parent.symbol_table.lookup(
2810  str(symbol_name))
2811  if sym.initial_value:
2812  # This is C506 of the F2008 standard.
2813  raise NotImplementedError(
2814  f"Symbol '{sym.name}' has an initial value"
2815  f" ({sym.initial_value.debug_string()}) "
2816  f"but appears in a common block. This is "
2817  f"not valid Fortran.")
2818  sym.interface = CommonBlockInterface()
2819  except KeyError as error:
2820  raise NotImplementedError(
2821  f"The symbol interface of a common block variable "
2822  f"could not be updated because of {error}.") from error
2823 
2824  @staticmethod
2825  def _process_precision(type_spec, psyir_parent):
2826  '''Processes the fparser2 parse tree of the type specification of a
2827  variable declaration in order to extract precision
2828  information. Two formats for specifying precision are
2829  supported a) "*N" e.g. real*8 and b) "kind=" e.g. kind=i_def, or
2830  kind=KIND(x).
2831 
2832  :param type_spec: the fparser2 parse tree of the type specification.
2833  :type type_spec: \
2834  :py:class:`fparser.two.Fortran2003.Intrinsic_Type_Spec`
2835  :param psyir_parent: the parent PSyIR node where the new node \
2836  will be attached.
2837  :type psyir_parent: :py:class:`psyclone.psyir.nodes.Node`
2838 
2839  :returns: the precision associated with the type specification.
2840  :rtype: :py:class:`psyclone.psyir.symbols.DataSymbol.Precision` or \
2841  :py:class:`psyclone.psyir.symbols.DataSymbol` or int or NoneType
2842 
2843  :raises NotImplementedError: if a KIND intrinsic is found with an \
2844  argument other than a real or integer literal.
2845  :raises NotImplementedError: if we have `kind=xxx` but cannot find \
2846  a valid variable name.
2847 
2848  '''
2849  symbol_table = psyir_parent.scope.symbol_table
2850 
2851  if not isinstance(type_spec.items[1], Fortran2003.Kind_Selector):
2852  # No precision is specified
2853  return None
2854 
2855  kind_selector = type_spec.items[1]
2856 
2857  if (isinstance(kind_selector.children[0], str) and
2858  kind_selector.children[0] == "*"):
2859  # Precision is provided in the form *N
2860  precision = int(str(kind_selector.children[1]))
2861  return precision
2862 
2863  # Precision is supplied in the form "kind=..."
2864  intrinsics = walk(kind_selector.items,
2865  Fortran2003.Intrinsic_Function_Reference)
2866  if intrinsics and isinstance(intrinsics[0].items[0],
2867  Fortran2003.Intrinsic_Name) and \
2868  str(intrinsics[0].items[0]).lower() == "kind":
2869  # We have kind=KIND(X) where X may be of any intrinsic type. It
2870  # may be a scalar or an array. items[1] is an
2871  # Actual_Arg_Spec_List with the first entry being the argument.
2872  kind_arg = intrinsics[0].items[1].items[0]
2873 
2874  # We currently only support integer and real literals as
2875  # arguments to KIND
2876  if isinstance(kind_arg, (Fortran2003.Int_Literal_Constant,
2877  Fortran2003.Real_Literal_Constant)):
2878  return get_literal_precision(kind_arg, psyir_parent)
2879 
2880  raise NotImplementedError(
2881  f"Only real and integer literals are supported as arguments "
2882  f"to the KIND intrinsic but found "
2883  f"'{type(kind_arg).__name__}' in: {kind_selector}")
2884 
2885  # We have kind=kind-param
2886  kind_names = walk(kind_selector.items, Fortran2003.Name)
2887  if not kind_names:
2888  raise NotImplementedError(
2889  f"Failed to find valid Name in Fortran Kind Selector: "
2890  f"{kind_selector}'")
2891 
2892  return _kind_find_or_create(str(kind_names[0]), symbol_table)
2893 
2894  def process_nodes(self, parent, nodes):
2895  '''
2896  Create the PSyIR of the supplied list of nodes in the
2897  fparser2 AST.
2898 
2899  :param parent: Parent node in the PSyIR we are constructing.
2900  :type parent: :py:class:`psyclone.psyir.nodes.Node`
2901  :param nodes: List of sibling nodes in fparser2 AST.
2902  :type nodes: list[:py:class:`fparser.two.utils.Base`]
2903 
2904  '''
2905  code_block_nodes = []
2906  message = "PSyclone CodeBlock (unsupported code) reason:"
2907  for child in nodes:
2908  try:
2909  psy_child = self._create_child_create_child(child, parent)
2910  except NotImplementedError as err:
2911  # If child type implementation not found, add them on the
2912  # ongoing code_block node list.
2913  message += "\n - " + str(err)
2914  code_block_nodes.append(child)
2915  if not isinstance(parent, Schedule):
2916  # If we're not processing a statement then we create a
2917  # separate CodeBlock for each node in the parse tree.
2918  # (Otherwise it is hard to correctly reconstruct e.g.
2919  # the arguments to a Call.)
2920  self.nodes_to_code_blocknodes_to_code_block(parent, code_block_nodes, message)
2921  message = "PSyclone CodeBlock (unsupported code) reason:"
2922  else:
2923  if psy_child:
2924  self.nodes_to_code_blocknodes_to_code_block(parent, code_block_nodes, message)
2925  message = "PSyclone CodeBlock (unsupported code) reason:"
2926  parent.addchild(psy_child)
2927  # If psy_child is not initialised but it didn't produce a
2928  # NotImplementedError, it means it is safe to ignore it.
2929 
2930  # Complete any unfinished code-block
2931  self.nodes_to_code_blocknodes_to_code_block(parent, code_block_nodes, message)
2932 
2933  def _create_child(self, child, parent=None):
2934  '''
2935  Create a PSyIR node representing the supplied fparser 2 node.
2936 
2937  :param child: node in fparser2 AST.
2938  :type child: :py:class:`fparser.two.utils.Base`
2939  :param parent: Parent node of the PSyIR node we are constructing.
2940  :type parent: :py:class:`psyclone.psyir.nodes.Node`
2941 
2942  :returns: Returns the PSyIR representation of child, which can be a \
2943  single node, a tree of nodes or None if the child can be \
2944  ignored.
2945  :rtype: :py:class:`psyclone.psyir.nodes.Node` or NoneType
2946 
2947  :raises NotImplementedError: if the child node has a label or there \
2948  isn't a handler for the provided child type.
2949 
2950  '''
2951  # We don't support statements with labels.
2952  if isinstance(child, BlockBase):
2953  # An instance of BlockBase describes a block of code (no surprise
2954  # there), so we have to examine the first statement within it. We
2955  # must allow for the case where the block is empty though.
2956  if (child.content and child.content[0] and
2957  child.content[0].item and child.content[0].item.label):
2958  raise NotImplementedError("Unsupported labelled statement")
2959  elif isinstance(child, StmtBase):
2960  if child.item and child.item.label:
2961  raise NotImplementedError("Unsupported labelled statement")
2962 
2963  handler = self.handlershandlers.get(type(child))
2964  if handler is None:
2965  # If the handler is not found then check with the first
2966  # level parent class. This is done to simplify the
2967  # handlers map when multiple fparser2 types can be
2968  # processed with the same handler. (e.g. Subclasses of
2969  # BinaryOpBase: Mult_Operand, Add_Operand, Level_2_Expr,
2970  # ... can use the same handler.)
2971  generic_type = type(child).__bases__[0]
2972  handler = self.handlershandlers.get(generic_type)
2973  if not handler:
2974  raise NotImplementedError(
2975  f"Unsupported statement: {type(child).__name__}")
2976  return handler(child, parent)
2977 
2978  def _ignore_handler(self, *_):
2979  '''
2980  This handler returns None indicating that the associated
2981  fparser2 node can be ignored.
2982 
2983  Note that this method contains ignored arguments to comform with
2984  the handler(node, parent) method interface.
2985 
2986  :returns: None
2987  :rtype: NoneType
2988  '''
2989  return None
2990 
2991  def _include_handler(self, node, parent):
2992  '''
2993  Handler for Fortran and CPP INCLUDE statements. Since these are not
2994  supported by the PSyIR it simply raises an error.
2995 
2996  :param node: node in fparser2 tree.
2997  :type node: :py:class:`fparser.two.Fortran2003.Include_Stmt`
2998  :param parent: parent node of the PSyIR node we are constructing.
2999  :type parent: :py:class:`psyclone.psyir.nodes.Schedule`
3000 
3001  :raises GenerationError: as INCLUDE statements must be handled by \
3002  the parser or pre-processor.
3003  '''
3004  config = Config.get()
3005  # An INCLUDE can appear anywhere so we have to allow for the case
3006  # where we have no enclosing Routine.
3007  unit = parent.ancestor((Routine, Container), include_self=True)
3008  # pylint: disable=unidiomatic-typecheck
3009  if isinstance(unit, Routine):
3010  if unit.is_program:
3011  out_txt = f"program '{unit.name}'. "
3012  else:
3013  out_txt = f"routine '{unit.name}'. "
3014  elif type(unit) is Container:
3015  out_txt = f"module '{unit.name}'. "
3016  else:
3017  out_txt = f"code:\n{str(node.get_root())}\n"
3018  # pylint: enable=unidiomatic-typecheck
3019  filename = node.children[0].string
3020  if isinstance(node, Fortran2003.Include_Stmt):
3021  err_msg = (
3022  f"Found an unresolved Fortran INCLUDE file '{filename}' while "
3023  f"processing {out_txt}This file must be made available by "
3024  f"specifying its location with a -I flag. "
3025  f"(The list of directories to search is currently set to: "
3026  f"{config.include_paths}.)")
3027  else:
3028  # We have a CPP #include.
3029  err_msg = (f"CPP #include statements are not supported but found a"
3030  f" #include of file '{node.children[0].string}' while "
3031  f"processing {out_txt}Such statements must be handled "
3032  f"using a standard pre-processor before the code can "
3033  f"be processed by PSyclone.")
3034  raise GenerationError(err_msg)
3035 
3036  def _allocate_handler(self, node, parent):
3037  '''
3038  Transforms an fparser2 Allocate_Stmt into its PSyIR form.
3039 
3040  :param node: node in fparser2 tree.
3041  :type node: :py:class:`fparser.two.Fortran2003.Allocate_Stmt`
3042  :param parent: parent node of the PSyIR node we are constructing.
3043  :type parent: :py:class:`psyclone.psyir.nodes.Schedule`
3044 
3045  :returns: PSyIR representation of an allocate.
3046  :rtype: :py:class:`psyclone.psyir.nodes.IntrinsicCall`
3047 
3048  :raises NotImplementedError: if the allocate has a type specification \
3049  (e.g. allocate(character(len=10) :: my_var)).
3050 
3051  '''
3052  call = IntrinsicCall(IntrinsicCall.Intrinsic.ALLOCATE, parent=parent)
3053 
3054  type_spec = node.children[0]
3055  if type_spec:
3056  raise NotImplementedError(
3057  "Allocate statements with type specifications cannot be "
3058  "handled in the PSyIR")
3059 
3060  alloc_list = node.children[1].children
3061  # Loop over each 'Allocation' in the 'Allocation_List'
3062  for alloc in alloc_list:
3063  # Currently fparser produces an incorrect parse tree if 'mold' is
3064  # specified - there is no Allocate object, just the bare Name or
3065  # Data_Ref. This is the subject of fparser/#383.
3066  if isinstance(alloc, (Fortran2003.Name, Fortran2003.Data_Ref)):
3067  # If the allocate() has a 'mold' argument then its positional
3068  # argument(s) is/are just references without any shape
3069  # information.
3070  self.process_nodesprocess_nodes(parent=call, nodes=[alloc])
3071  else:
3072  # We have an Allocation(name, Allocate_Shape_Spec_List)
3073  self.process_nodesprocess_nodes(parent=call,
3074  nodes=[alloc.children[0]])
3075  cursor = call.children[-1]
3076  while hasattr(cursor, "member"):
3077  cursor = cursor.member
3078  if isinstance(cursor, Member):
3079  # Convert Member to ArrayMember.
3080  aref = ArrayMember(cursor.name)
3081  else:
3082  # Convert Reference to ArrayReference.
3083  aref = ArrayReference(cursor.symbol)
3084  cursor.replace_with(aref)
3085  # Handle the index expressions (each of which is represented
3086  # by an Allocate_Shape_Spec).
3087  for shape_spec in walk(alloc,
3088  Fortran2003.Allocate_Shape_Spec):
3089  self.process_nodesprocess_nodes(parent=aref, nodes=[shape_spec])
3090 
3091  # Handle any options to the allocate()
3092  opt_list = walk(node, Fortran2003.Alloc_Opt)
3093  for opt in opt_list:
3094  self.process_nodesprocess_nodes(parent=call, nodes=opt.children[1:])
3095  call.append_named_arg(opt.children[0], call.children[-1].detach())
3096 
3097  # Point to the original statement in the parse tree.
3098  call.ast = node
3099 
3100  return call
3101 
3102  def _allocate_shape_spec_handler(self, node, parent):
3103  '''
3104  Creates a Range node describing the supplied Allocate_Shape_Spec.
3105  This is similar to the subscript_triplet handler except that the
3106  default lower bound is unity and the step is also unity.
3107 
3108  :param node: node in fparser2 AST.
3109  :type node: :py:class:`fparser.two.Fortran2003.Allocate_Shape_Spec`
3110  :param parent: parent node of the PSyIR node we are constructing.
3111  :type parent: :py:class:`psyclone.psyir.nodes.Reference`
3112 
3113  :returns: PSyIR of fparser2 node.
3114  :rtype: :py:class:`psyclone.psyir.nodes.Range`
3115 
3116  '''
3117  my_range = Range(parent=parent)
3118  my_range.children = []
3119  integer_type = default_integer_type()
3120 
3121  if node.children[0]:
3122  self.process_nodesprocess_nodes(parent=my_range, nodes=[node.children[0]])
3123  else:
3124  # Default lower bound in Fortran is 1
3125  my_range.addchild(Literal("1", integer_type))
3126 
3127  self.process_nodesprocess_nodes(parent=my_range, nodes=[node.children[1]])
3128 
3129  # Step is always 1.
3130  my_range.addchild(Literal("1", integer_type))
3131 
3132  return my_range
3133 
3134  def _create_loop(self, parent, variable):
3135  '''
3136  Create a Loop instance. This is done outside _do_construct_handler
3137  because some APIs may want to instantiate a specialised Loop.
3138 
3139  :param parent: the parent of the node.
3140  :type parent: :py:class:`psyclone.psyir.nodes.Node`
3141  :param variable: the loop variable.
3142  :type variable: :py:class:`psyclone.psyir.symbols.DataSymbol`
3143 
3144  :return: a new Loop instance.
3145  :rtype: :py:class:`psyclone.psyir.nodes.Loop`
3146 
3147  '''
3148  return Loop(parent=parent, variable=variable)
3149 
3150  def _create_bounded_loop(self, parent, variable, limits_list):
3151  '''
3152  Create a Loop instance with start, stop, step expressions.
3153 
3154  :param parent: the parent of the node.
3155  :type parent: :py:class:`psyclone.psyir.nodes.Node`
3156  :param variable: the loop variable.
3157  :type variable: :py:class:`psyclone.psyir.symbols.DataSymbol`
3158  :param limits_list: a list of fparser expressions reprsenting the
3159  loop bounds.
3160  :type limits_list: List[:py:class:`fparser.two.utils.Base`]
3161 
3162  :return: a new Loop instance.
3163  :rtype: :py:class:`psyclone.psyir.nodes.Loop`
3164 
3165  '''
3166  # Loop variable must be a DataSymbol of integer type.
3167  variable_name = str(variable)
3168  data_symbol = _find_or_create_unresolved_symbol(
3169  parent, variable_name, symbol_type=DataSymbol,
3170  datatype=default_integer_type())
3171 
3172  # The loop node is created with the _create_loop factory method as
3173  # some APIs require a specialised loop node type.
3174  loop = self._create_loop_create_loop(parent, data_symbol)
3175 
3176  # The Loop Limits are:
3177  # [start value expression, end value expression, step expression]
3178  self.process_nodesprocess_nodes(parent=loop, nodes=[limits_list[0]])
3179  self.process_nodesprocess_nodes(parent=loop, nodes=[limits_list[1]])
3180  if len(limits_list) == 3 and limits_list[2] is not None:
3181  self.process_nodesprocess_nodes(parent=loop, nodes=[limits_list[2]])
3182  else:
3183  # Default loop increment is 1. Use the type of the start
3184  # or step nodes once #685 is complete. For the moment use
3185  # the default precision.
3186  default_step = Literal("1", default_integer_type())
3187  loop.addchild(default_step)
3188 
3189  # Create Loop body Schedule
3190  loop_body = Schedule(parent=loop)
3191  loop.addchild(loop_body)
3192  return loop
3193 
3194  def _deallocate_handler(self, node, parent):
3195  '''
3196  Transforms a deallocate() statement into its PSyIR form.
3197 
3198  :param node: node in fparser2 tree.
3199  :type node: :py:class:`fparser.two.Fortran2003.Deallocate_Stmt`
3200  :param parent: parent node of the PSyIR node we are constructing.
3201  :type parent: :py:class:`psyclone.psyir.nodes.Schedule`
3202 
3203  :returns: PSyIR for a deallocate.
3204  :rtype: :py:class:`psyclone.psyir.nodes.IntrinsicCall`
3205 
3206  '''
3207  call = IntrinsicCall(
3208  IntrinsicCall.Intrinsic.DEALLOCATE, parent=parent)
3209  dealloc_list = node.children[0].children
3210  for dealloc in dealloc_list:
3211  self.process_nodesprocess_nodes(parent=call, nodes=[dealloc])
3212 
3213  # Handle any options to the deallocate()
3214  opt_list = walk(node, Fortran2003.Dealloc_Opt)
3215  for opt in opt_list:
3216  self.process_nodesprocess_nodes(parent=call, nodes=opt.children[1:])
3217  call.append_named_arg(opt.children[0], call.children[-1].detach())
3218 
3219  # Point to the original statement in the parse tree.
3220  call.ast = node
3221 
3222  return call
3223 
3224  def _do_construct_handler(self, node, parent):
3225  '''
3226  Transforms a fparser2 Do Construct into its PSyIR form.
3227 
3228  :param node: node in fparser2 tree.
3229  :type node: \
3230  :py:class:`fparser.two.Fortran2003.Block_Nonlabel_Do_Construct`
3231  :param parent: parent node of the PSyIR node we are constructing.
3232  :type parent: :py:class:`psyclone.psyir.nodes.Node`
3233 
3234  :returns: PSyIR representation of node
3235  :rtype: Union[:py:class:`psyclone.psyir.nodes.Loop`, \
3236  :py:class:`psyclone.psyir.nodes.WhileLoop`]
3237 
3238  :raises NotImplementedError: if the fparser2 tree has a named DO \
3239  containing a reference to that name.
3240  '''
3241  nonlabel_do = walk(node.content, Fortran2003.Nonlabel_Do_Stmt)[0]
3242  if nonlabel_do.item is not None:
3243  # If the associated line has a name that is referenced inside the
3244  # loop then it isn't supported , e.g. `EXIT outer_loop`.
3245  if nonlabel_do.item.name:
3246  construct_name = nonlabel_do.item.name
3247  # Check that the construct-name is not referred to inside
3248  # the Loop (but exclude the END DO from this check).
3249  names = walk(node.content[:-1], Fortran2003.Name)
3250  if construct_name in [name.string for name in names]:
3251  raise NotImplementedError(
3252  "Unsupported label reference within DO")
3253 
3254  ctrl = walk(nonlabel_do, Fortran2003.Loop_Control)
3255  # In fparser Loop_Control has 4 children, but just one of the Loop
3256  # types children None is not None, this one defines the loop boundaries
3257  # style: LoopCtrl(While_Loop, Counter_Loop, OptionalDelimiter,
3258  # Concurrent_Loop)
3259  if not ctrl or ctrl[0].items[0] is not None:
3260  # do loops with no condition and do while loops
3261  annotation = ['was_unconditional'] if not ctrl else None
3262  loop = WhileLoop(parent=parent, annotations=annotation)
3263  loop.ast = node
3264  condition = [Fortran2003.Logical_Literal_Constant(".TRUE.")] \
3265  if not ctrl else [ctrl[0].items[0]]
3266  self.process_nodesprocess_nodes(parent=loop, nodes=condition)
3267  # Create Loop body Schedule
3268  loop_body = Schedule(parent=loop)
3269  loop_body.ast = node
3270  loop.addchild(loop_body)
3271  elif ctrl[0].items[1] is not None:
3272  # CounterLoops, its children are: Loop variable and Loop Limits
3273  loop_var, limits_list = ctrl[0].items[1]
3274  loop = self._create_bounded_loop_create_bounded_loop(parent, loop_var, limits_list)
3275  loop.ast = node
3276  loop_body = loop.loop_body
3277  loop_body.ast = node
3278  elif ctrl[0].items[3] is not None:
3279  # The triplet is the var=X:X:X representing the variable with the
3280  # start, stop and step boundaries of the ForAll construct. We use
3281  # a walk because Loop concurrent can have a list of triplets that
3282  # represent nested loops.
3283  triplet = walk(ctrl[0].items[3], Fortran2003.Forall_Triplet_Spec)
3284  loop = None
3285  for expr in triplet:
3286  variable, start, stop, step = expr.items
3287  new_loop = self._create_bounded_loop_create_bounded_loop(parent, variable,
3288  [start, stop, step])
3289  # TODO #2256: We could store the information that it is
3290  # concurrent do, we currently drop this information.
3291  new_loop.ast = node
3292  new_loop.loop_body.ast = node
3293  # If its a new loop, bind it to the loop variable, otherwise
3294  # add it as children of the last loop_body
3295  if loop is None:
3296  loop = new_loop
3297  else:
3298  loop_body.addchild(new_loop)
3299 
3300  # Update loop_body and parent to always reference to the
3301  # innermost schedule
3302  loop_body = new_loop.loop_body
3303  parent = loop_body
3304  else:
3305  raise NotImplementedError("Unsupported Loop")
3306 
3307  # Process loop body (ignore 'do' and 'end do' statements with [1:-1])
3308  self.process_nodesprocess_nodes(parent=loop_body, nodes=node.content[1:-1])
3309 
3310  return loop
3311 
3312  def _if_construct_handler(self, node, parent):
3313  '''
3314  Transforms an fparser2 If_Construct to the PSyIR representation.
3315 
3316  :param node: node in fparser2 tree.
3317  :type node: :py:class:`fparser.two.Fortran2003.If_Construct`
3318  :param parent: Parent node of the PSyIR node we are constructing.
3319  :type parent: :py:class:`psyclone.psyir.nodes.Node`
3320  :returns: PSyIR representation of node
3321  :rtype: :py:class:`psyclone.psyir.nodes.IfBlock`
3322  :raises InternalError: If the fparser2 tree has an unexpected \
3323  structure.
3324  '''
3325 
3326  # Check that the fparser2 parsetree has the expected structure
3327  if not isinstance(node.content[0], Fortran2003.If_Then_Stmt):
3328  raise InternalError(
3329  f"Failed to find opening if then statement in: {node}")
3330  if not isinstance(node.content[-1], Fortran2003.End_If_Stmt):
3331  raise InternalError(
3332  f"Failed to find closing end if statement in: {node}")
3333 
3334  # Search for all the conditional clauses in the If_Construct
3335  clause_indices = []
3336  for idx, child in enumerate(node.content):
3337  if isinstance(child, (Fortran2003.If_Then_Stmt,
3338  Fortran2003.Else_Stmt,
3339  Fortran2003.Else_If_Stmt,
3340  Fortran2003.End_If_Stmt)):
3341  clause_indices.append(idx)
3342 
3343  # Deal with each clause: "if", "else if" or "else".
3344  ifblock = None
3345  currentparent = parent
3346  num_clauses = len(clause_indices) - 1
3347  for idx in range(num_clauses):
3348  start_idx = clause_indices[idx]
3349  end_idx = clause_indices[idx+1]
3350  clause = node.content[start_idx]
3351 
3352  if isinstance(clause, (Fortran2003.If_Then_Stmt,
3353  Fortran2003.Else_If_Stmt)):
3354  # If it's an 'IF' clause just create an IfBlock, otherwise
3355  # it is an 'ELSE' clause and it needs an IfBlock annotated
3356  # with 'was_elseif' inside a Schedule.
3357  newifblock = None
3358  if isinstance(clause, Fortran2003.If_Then_Stmt):
3359  ifblock = IfBlock(parent=currentparent)
3360  ifblock.ast = node # Keep pointer to fpaser2 AST
3361  newifblock = ifblock
3362  else:
3363  elsebody = Schedule(parent=currentparent)
3364  currentparent.addchild(elsebody)
3365  newifblock = IfBlock(parent=elsebody,
3366  annotations=['was_elseif'])
3367  elsebody.addchild(newifblock)
3368 
3369  # Keep pointer to fpaser2 AST
3370  elsebody.ast = node.content[start_idx]
3371  newifblock.ast = node.content[start_idx]
3372 
3373  # Create condition as first child
3374  self.process_nodesprocess_nodes(parent=newifblock,
3375  nodes=[clause.items[0]])
3376 
3377  # Create if-body as second child
3378  ifbody = Schedule(parent=newifblock)
3379  ifbody.ast = node.content[start_idx + 1]
3380  ifbody.ast_end = node.content[end_idx - 1]
3381  newifblock.addchild(ifbody)
3382  self.process_nodesprocess_nodes(parent=ifbody,
3383  nodes=node.content[start_idx + 1:end_idx])
3384 
3385  currentparent = newifblock
3386 
3387  elif isinstance(clause, Fortran2003.Else_Stmt):
3388  if not idx == num_clauses - 1:
3389  raise InternalError(
3390  f"Else clause should only be found next to last "
3391  f"clause, but found {node.content}")
3392  elsebody = Schedule(parent=currentparent)
3393  currentparent.addchild(elsebody)
3394  elsebody.ast = node.content[start_idx]
3395  elsebody.ast_end = node.content[end_idx]
3396  self.process_nodesprocess_nodes(parent=elsebody,
3397  nodes=node.content[start_idx + 1:end_idx])
3398  else:
3399  raise InternalError(
3400  f"Only fparser2 If_Then_Stmt, Else_If_Stmt and Else_Stmt "
3401  f"are expected, but found {clause}.")
3402 
3403  return ifblock
3404 
3405  def _if_stmt_handler(self, node, parent):
3406  '''
3407  Transforms an fparser2 If_Stmt to the PSyIR representation.
3408 
3409  :param node: node in fparser2 AST.
3410  :type node: :py:class:`fparser.two.Fortran2003.If_Stmt`
3411  :param parent: Parent node of the PSyIR node we are constructing.
3412  :type parent: :py:class:`psyclone.psyir.nodes.Node`
3413 
3414  :returns: PSyIR representation of node
3415  :rtype: :py:class:`psyclone.psyir.nodes.IfBlock`
3416 
3417  '''
3418  ifblock = IfBlock(parent=parent, annotations=['was_single_stmt'])
3419  ifblock.ast = node
3420  self.process_nodesprocess_nodes(parent=ifblock, nodes=[node.items[0]])
3421  ifbody = Schedule(parent=ifblock)
3422  ifblock.addchild(ifbody)
3423  self.process_nodesprocess_nodes(parent=ifbody, nodes=[node.items[1]])
3424  return ifblock
3425 
3426  @staticmethod
3427  def _add_target_attribute(var_name, table):
3428  '''Ensure that the datatype of the symbol with the supplied name has a
3429  pointer or target attribute and if not, add the target attribute.
3430 
3431  The datatype is stored as text within an UnsupportedFortranType. We
3432  therefore re-create the datatype as an fparser2 ast, add the attribute
3433  if required and update the UnsupportedFortranType with the new text.
3434 
3435  :param str var_name: the name of the symbol for which we attempt to
3436  modify the datatype.
3437  :param table: a SymbolTable in which to search for the symbol.
3438  :type table: :py:class:`psyclone.psyir.symbols.SymbolTable`
3439 
3440  :raises NotImplementedError: if the variable cannot be found, is
3441  unresolved or is not a DataSymbol.
3442  :raises NotImplementedError: if the variable needs to be given
3443  the target attribute but represents a symbol defined externally
3444  (e.g. a routine argument or an imported symbol).
3445 
3446  '''
3447  try:
3448  symbol = table.lookup(var_name)
3449  except KeyError as err:
3450  raise NotImplementedError(
3451  f"Cannot add TARGET attribute to variable '{var_name}' "
3452  f"because it is unresolved") from err
3453  if symbol.is_unresolved or not isinstance(symbol, DataSymbol):
3454  raise NotImplementedError(
3455  f"Cannot add TARGET attribute to symbol '{symbol}': it must "
3456  f"be resolved and a DataSymbol")
3457 
3458  datatype = symbol.datatype
3459  # Create Fortran text for the supplied datatype from the
3460  # supplied UnsupportedFortranType text, then parse this into an
3461  # fparser2 tree and store the fparser2 representation of the
3462  # datatype in type_decl_stmt.
3463  dummy_code = (
3464  f"subroutine dummy()\n"
3465  f" {datatype.declaration}\n"
3466  f"end subroutine\n")
3467  parser = ParserFactory().create(std="f2008")
3468  reader = FortranStringReader(dummy_code)
3469  fp2_ast = parser(reader)
3470  type_decl_stmt = fp2_ast.children[0].children[1].children[0]
3471 
3472  # Look for a preexisting target or pointer attribute and if
3473  # one does not exist add a target attribute as this allows the
3474  # created pointers to point at the supplied datatype.
3475  attr_spec_list = type_decl_stmt.children[1]
3476  attr_spec_str_list = []
3477 
3478  if attr_spec_list:
3479  for attr_spec in attr_spec_list.children:
3480  attr_spec_str = attr_spec.string
3481  attr_spec_str_list.append(attr_spec_str)
3482  if attr_spec_str.upper() in ["TARGET", "POINTER"]:
3483  # There is already a target or pointer attribute
3484  return
3485 
3486  # TARGET needs to be added as an additional attribute. We cannot
3487  # do this if the Symbol has an interface that means it is defined
3488  # externally.
3489  if not (symbol.is_automatic or symbol.is_modulevar):
3490  raise NotImplementedError(
3491  f"Type-selector variable '{symbol.name}' is defined externally"
3492  f" (has interface '{symbol.interface}') and thus cannot be "
3493  f"given the TARGET attribute")
3494 
3495  if attr_spec_str_list:
3496  # At least one attribute already exists but it is/they are not
3497  # the 'target' or 'pointer' attributes.
3498  attr_spec_str_list.append("TARGET")
3499  attr_spec_list = Fortran2003.Attr_Spec_List(
3500  ", ".join(attr_spec_str_list))
3501  else:
3502  # There are no pre-existing attributes
3503  attr_spec_list = Fortran2003.Attr_Spec_List("TARGET")
3504  type_decl_stmt.items = (
3505  type_decl_stmt.items[0], attr_spec_list,
3506  type_decl_stmt.items[2])
3507  attr_spec_list.parent = type_decl_stmt
3508  # pylint: disable=protected-access
3509  datatype._declaration = str(type_decl_stmt)
3510 
3511  def _create_ifblock_for_select_type_content(
3512  self, parent, select_type, type_string_symbol, pointer_symbols):
3513  '''Use the contents of the supplied SelectTypeInfo instance
3514  to create an if nest to capture the content of the associated
3515  select type construct.
3516 
3517  This allows the PSyIR to 'see' the content of the select type
3518  despite not supporting the select type clause directly in
3519  PSyIR. A Codeblock preceding this condition will capture the
3520  conditional logic of the select type and the chosen type will
3521  be communicated to the if nest at runtime via the
3522  'type_string_symbol'. The if nest created here captures the
3523  content of each branch of the original select type.
3524 
3525  :param parent: the PSyIR parent to which we are going to add
3526  the PSyIR ifblock and any required symbols.
3527  :type parent: :py:class:`psyclone.psyir.nodes.Node`
3528  :param select_type: information on the select type construct.
3529  :type select_type: :py:class:\
3530  `psyclone.psyir.frontend.fparser2.Fparser2Reader.SelectTypeInfo`
3531  :param type_string_symbol: a run-time symbol capturing (as a string)
3532  the value chosen by the select type construct.
3533  :type type_string_symbol: :py:class:`psyclone.psyir.type.DataSymbol`
3534  :param pointer_symbols: a list of symbols that point to the
3535  different select-type types within the select type codeblock.
3536  :type pointer_symbols:
3537  list[Optional[:py:class:`psyclone.psyir.symbols.Symbol`]]
3538 
3539  :returns: the newly created PSyIR IfBlock.
3540  :rtype: :py:class:`psyclone.psyir.nodes.IfBlock`
3541 
3542  :raises NotImplementedError: if there is a CodeBlock that contains a
3543  reference to the type-selector variable.
3544 
3545  '''
3546  outer_ifblock = None
3547  ifblock = None
3548  currentparent = parent
3549  for idx in range(select_type.num_clauses):
3550  if idx == select_type.default_idx:
3551  # This is the index of the 'class default' clause so
3552  # skip this until the end of the if blocks.
3553  continue
3554 
3555  annotation = (
3556  "was_class_is" if select_type.clause_type[idx].upper() ==
3557  "CLASS IS" else "was_type_is")
3558  if ifblock:
3559  # We already have an if so this is an else if.
3560  elsebody = Schedule(parent=currentparent)
3561  ifblock = IfBlock(annotations=[annotation], parent=elsebody)
3562  elsebody.addchild(ifblock)
3563  currentparent.addchild(elsebody)
3564  else:
3565  # We do not yet have an if so this is the 'outer' if block.
3566  ifblock = IfBlock(parent=currentparent,
3567  annotations=[annotation])
3568  outer_ifblock = ifblock
3569 
3570  # Create an if hierarchy that uses the string (stored in
3571  # type_string_symbol) set in an earlier select type
3572  # codeblock to choose the appropriate content in the
3573  # original select type clauses.
3574  clause = BinaryOperation.create(
3575  BinaryOperation.Operator.EQ, Reference(type_string_symbol),
3576  Literal(select_type.guard_type_name[idx], CHARACTER_TYPE))
3577 
3578  ifblock.addchild(clause)
3579  # Add If_body
3580  ifbody = Schedule(parent=ifblock)
3581  self.process_nodesprocess_nodes(parent=ifbody, nodes=select_type.stmts[idx])
3582  # Check that there are no CodeBlocks with references to the type
3583  # selector variable.
3584  for cblock in ifbody.walk(CodeBlock):
3585  names = cblock.get_symbol_names()
3586  if select_type.selector in names:
3587  raise NotImplementedError(
3588  f"CodeBlock contains reference to type-selector "
3589  f"variable '{select_type.selector}'")
3590  # Replace references to the type selector variable with
3591  # references to the appropriate pointer.
3592  for reference in ifbody.walk(Reference):
3593  symbol = reference.symbol
3594  if symbol.name.lower() == select_type.selector:
3595  reference.symbol = pointer_symbols[idx]
3596  ifblock.addchild(ifbody)
3597  currentparent = ifblock
3598 
3599  if select_type.default_idx >= 0:
3600  # There is a class default clause so add this on as an
3601  # else at the end of the IfBlock.
3602  elsebody = Schedule(parent=currentparent)
3603  currentparent.addchild(elsebody)
3604  self.process_nodesprocess_nodes(
3605  parent=elsebody, nodes=select_type.stmts[
3606  select_type.default_idx])
3607 
3608  return outer_ifblock
3609 
3610  @staticmethod
3611  def _create_select_type(
3612  parent, select_type, type_string_name=None):
3613  '''Use the contents of the supplied SelectTypeInfo, `select_type`,
3614  to create a CodeBlock containing a select type to capture its control
3615  logic without capturing its content.
3616 
3617  The 'output' of this CodeBlock is a character variable containing the
3618  'name' of the type that was provided, thus identifying which branch of
3619  the code would be executed. A pointer is also created and assigned
3620  to the type-selection of the 'type is' or 'class is' clause.
3621 
3622  :param parent: the PSyIR parent to which we are going to add
3623  the PSyIR codeblock and any required symbols.
3624  :type parent: :py:class:`psyclone.psyir.nodes.Node`
3625  :param select_type: instance of the SelectTypeInfo dataclass
3626  containing information about the select type construct.
3627  :type select_type: :py:class:`Self.SelectTypeInfo`
3628  :param Optional[str] type_string_name: the base name to use
3629  for the newly created type_string symbol.
3630 
3631  :returns: the DataSymbol representing the character variable which
3632  will hold the 'name' of the type and a list of symbols that
3633  point to the different select-type types within the select
3634  type codeblock.
3635  :rtype: tuple[:py:class:`psyclone.psyir.symbols.DataSymbol`,
3636  list[Optional[:py:class:`psyclone.psyir.symbols.Symbol`]]]
3637 
3638  '''
3639  pointer_symbols = []
3640  # Create a symbol from the supplied base name. Store as an
3641  # UnsupportedFortranType in the symbol table as we do not natively
3642  # support character strings (as opposed to scalars) in the PSyIR at
3643  # the moment.
3644  # TODO #2550 will improve this by using an integer instead.
3645  type_string_name = parent.scope.symbol_table.next_available_name(
3646  type_string_name)
3647  # Length is hardcoded here so could potentially be too short.
3648  # TODO #2550 will improve this by using an integer instead.
3649  type_string_type = UnsupportedFortranType(
3650  f"character(256) :: {type_string_name}")
3651  type_string_symbol = DataSymbol(type_string_name, type_string_type)
3652  parent.scope.symbol_table.add(type_string_symbol)
3653 
3654  # Create text for a select type construct using the information
3655  # captured in the `select_type` SelectTypeInfo instance.
3656  # Also add any required pointer symbols to the symbol table as
3657  # UnsupportedFortranType, as pointers are not natively supported in the
3658  # PSyIR at the moment.
3659  code = "program dummy\n"
3660  code += f"select type({select_type.selector})\n"
3661  for idx in range(select_type.num_clauses):
3662  if idx == select_type.default_idx:
3663  # This is the index of the 'class default' clause so no pointer
3664  # symbol is required (as there is no type selection).
3665  pointer_symbols.append(None)
3666  continue
3667  # Create pointer symbol for the content of this 'type is'
3668  # or 'class is' clause.
3669  pointer_name = parent.scope.symbol_table.next_available_name(
3670  f"ptr_{select_type.guard_type_name[idx]}")
3671  if (select_type.intrinsic_type_name[idx] and
3672  select_type.intrinsic_type_name[idx].upper() ==
3673  "CHARACTER"):
3674  # This is a character string pointer which we always
3675  # declare with a fixed length to allow it to be
3676  # declared locally (otherwise it must be a parameter
3677  # or passed by argument). As length is hardcoded here,
3678  # the string could potentially be too short.
3679 
3680  tmp_type = "CHARACTER(LEN=256)"
3681  # The type spec for a character intrinsic within the
3682  # type is and class is clauses must always be assumed
3683  type_spec = "CHARACTER(LEN = *)"
3684  else:
3685  # Declare pointer in the usual way using the
3686  # guard_type string
3687  tmp_type = f"{select_type.guard_type[idx]}"
3688  if not select_type.intrinsic_type_name[idx]:
3689  # This is a type declaration
3690  tmp_type = f"type({tmp_type})"
3691  # Use the variable declaration for the type spec in
3692  # the type is and class is clauses
3693  type_spec = select_type.guard_type[idx]
3694 
3695  # Create a pointer that points to the specific type from
3696  # the appropriate select type clause so that the specific
3697  # type can be used in a subsequent if block hierarchy
3698  # (otherwise Fortran complains that the type is generic).
3699  pointer_type = UnsupportedFortranType(
3700  f"{tmp_type}, pointer :: {pointer_name} => null()")
3701  pointer_symbol = DataSymbol(pointer_name, pointer_type)
3702  parent.scope.symbol_table.add(pointer_symbol)
3703  pointer_symbols.append(pointer_symbol)
3704  # The situation where 'clause_type' is 'class default' is
3705  # handled separately, so we can assume 'clause_type' is
3706  # either 'type is' or 'class is' which means it will
3707  # always have a valid 'type_spec' value.
3708  code += f" {select_type.clause_type[idx]} ({type_spec})\n"
3709  code += (f" {type_string_name} = "
3710  f"\"{select_type.guard_type_name[idx].lower()}\"\n")
3711  code += (f" {pointer_name} => {select_type.selector}\n")
3712  code += "end select\n"
3713  code += "end program\n"
3714 
3715  # Parse the the created Fortran text to an fparser2 tree and
3716  # store the resulting tree in a PSyIR CodeBlock.
3717  parser = ParserFactory().create(std="f2008")
3718  reader = FortranStringReader(code)
3719  fp2_program = parser(reader)
3720  # Ignore the program part of the fparser2 tree
3721  exec_part = walk(fp2_program, Fortran2003.Execution_Part)
3722  code_block = CodeBlock(exec_part, CodeBlock.Structure.STATEMENT,
3723  parent=parent)
3724 
3725  # Handlers assume a single node is returned and in this
3726  # implementation we create an assignment (see below), a
3727  # CodeBlock (see above) and a nested if statement (see
3728  # later). Therefore we add the assignment and codeblock to the
3729  # parent here and compute and return the if statement in a
3730  # subsequent routine (using the type_string_symbol).
3731  parent.addchild(Assignment.create(
3732  Reference(type_string_symbol), Literal("", CHARACTER_TYPE)))
3733  parent.addchild(code_block)
3734 
3735  return (type_string_symbol, pointer_symbols)
3736 
3737  @staticmethod
3738  def _create_select_type_info(node):
3739  '''Create and return a SelectTypeInfo instance that stores the required
3740  information for a select-type construct to be used by
3741  subsequent methods.
3742 
3743  :param node: fparser2 node from which to extract the select-type
3744  information.
3745  :type node: :py:class:`fparser2.Fortran2003.Select_Type_Construct`
3746 
3747  :returns: instance of the SelectTypeInfo dataclass containing
3748  information about the select-type construct.
3749  :rtype: :py:class:`Self.SelectTypeInfo`
3750 
3751  '''
3752  select_type = Fparser2Reader.SelectTypeInfo()
3753 
3754  select_idx = -1
3755  for child in node.children:
3756  if isinstance(child, Fortran2003.Select_Type_Stmt):
3757  # Found the select type stmt clause ('select type
3758  # (x)')
3759  if child.children[0]:
3760  # The selector variable ('x') is renamed and this
3761  # is not yet supported.
3762  raise NotImplementedError(
3763  f"The selector variable '{child.children[1]}' is "
3764  f"renamed to '{child.children[0]}' in the select "
3765  f"clause '{str(node)}'. This is not yet supported in "
3766  f"the PSyIR.")
3767  # Store the name of the selector variable in our
3768  # dataclass instance.
3769  select_type.selector = child.children[1].string.lower()
3770  elif isinstance(child, Fortran2003.Type_Guard_Stmt):
3771  # Found one of the type stmt guard clauses ('type is',
3772  # 'class is', or 'class default').
3773  select_idx += 1
3774  select_type.stmts.append([])
3775  # Extract the fparser2 Type_Specification
3776  # e.g. 'real(kind=4)'
3777  type_spec = child.children[1]
3778  # Default the intrinsic base name to None
3779  intrinsic_base_name = None
3780  if type_spec is None:
3781  # There is no type information so this is the
3782  # default clause
3783  type_name = None
3784  elif isinstance(type_spec, Fortran2003.Intrinsic_Type_Spec):
3785  # The guard type selector is an intrinsic type
3786  # e.g. 'type is(REAL)'. Set the intrinsic base name
3787  # as there is a base intrinsic type.
3788  intrinsic_base_name = str(type_spec.children[0]).lower()
3789  # Determine type_name for all the different cases
3790  # (must return a string that can be used as a
3791  # variable name).
3792  type_name = intrinsic_base_name
3793  if isinstance(
3794  type_spec.children[1], Fortran2003.Kind_Selector):
3795  # This is a non-character intrinsic type with
3796  # a kind specification
3797  kind_spec_value = type_spec.children[1].children[1]
3798  type_name = f"{type_name}_{kind_spec_value}".lower()
3799  elif walk(type_spec, Fortran2003.Length_Selector):
3800  # This is a character intrinsic type so must
3801  # have an assumed length ('*') which we
3802  # tranform to 'star to allow the creation of a
3803  # valid symbol name.
3804  type_name = f"{type_name}_star".lower()
3805  else:
3806  # The guard type selector is a Class type ('class
3807  # is (mytype)')
3808  type_name = str(type_spec).lower()
3809  select_type.guard_type_name.append(type_name)
3810  if type_spec:
3811  select_type.guard_type.append(str(type_spec).lower())
3812  else:
3813  select_type.guard_type.append(None)
3814  select_type.intrinsic_type_name.append(intrinsic_base_name)
3815 
3816  # Store the index of the class default information
3817  select_type.clause_type.append(child.children[0])
3818  if child.children[0].lower() == "class default":
3819  select_type.default_idx = select_idx
3820  elif isinstance(child, Fortran2003.End_Select_Type_Stmt):
3821  pass
3822  else:
3823  # The next content must be a statement as the content within
3824  # select case or select type clauses in fparser2 are *siblings*
3825  # of the various select case or select-type statements, rather
3826  # than children of them (as one might expect).
3827  select_type.stmts[select_idx].append(child)
3828  select_type.num_clauses = select_idx + 1
3829 
3830  return select_type
3831 
3832  def _select_type_construct_handler(self, node, parent):
3833  '''
3834  Transforms an fparser2 Select_Type_Construct to the PSyIR
3835  representation (consisting of an Assignment, a CodeBlock
3836  and an IfBlock).
3837 
3838  :param node: node in fparser2 tree.
3839  :type node: :py:class:`fparser.two.Fortran2003.Select_Type_Construct`
3840  :param parent: parent node of the PSyIR node we are constructing.
3841  :type parent: :py:class:`psyclone.psyir.nodes.Node`
3842 
3843  :returns: PSyIR representation of the node.
3844  :rtype: :py:class:`psyclone.psyir.nodes.IfBlock`
3845 
3846  :raises NotImplementedError: if the symbol representing the type-
3847  selector variable is not resolved or is not a DataSymbol.
3848 
3849  '''
3850  # Step 1: Search for all the TYPE IS and CLASS IS clauses in
3851  # the Select_Type_Construct and extract the required
3852  # information. This makes for easier code generation later in
3853  # the routine.
3854  insert_index = len(parent.children) - 1
3855  # Create the required type information in a dataclass instance.
3856  select_type = self._create_select_type_info_create_select_type_info(node)
3857 
3858  # We don't immediately add our new nodes into the PSyIR tree in
3859  # case we encounter something we don't support (in which case
3860  # an exception will be raised and a CodeBlock created).
3861  tmp_parent = Schedule(parent=parent)
3862 
3863  # Step 2: Recreate the select type clause within a CodeBlock
3864  # with the content of the clauses being replaced by a string
3865  # capturing the name of the type or class clauses
3866  # ('type_string'). The string symbol is returned for use in
3867  # step 3, as is the list of any pointers to the selector variable.
3868  # TODO #2550 - use an integer instead of a string to encode
3869  # which branch is chosen at run-time.
3870  type_string_symbol, pointer_symbols = self._create_select_type_create_select_type(
3871  tmp_parent, select_type, type_string_name="type_string")
3872 
3873  # Step 3: Create the (potentially nested) if statement that
3874  # replicates the content of the select type options (as select
3875  # type is not supported directly in the PSyIR) allowing the
3876  # PSyIR to 'see' the select type content.
3877  outer_ifblock = self._create_ifblock_for_select_type_content_create_ifblock_for_select_type_content(
3878  parent, select_type, type_string_symbol, pointer_symbols)
3879 
3880  # Step 4: Ensure that the type selector variable declaration
3881  # has the pointer or target attribute as we need to create
3882  # pointers that point to it to get the specific type.
3883  self._add_target_attribute_add_target_attribute(select_type.selector,
3884  outer_ifblock.scope.symbol_table)
3885 
3886  # Step 5: We didn't encounter any problems so finally attach our new
3887  # nodes to the PSyIR tree in the location we saved earlier.
3888  for child in reversed(tmp_parent.pop_all_children()):
3889  parent.addchild(child, index=insert_index)
3890  # Ensure any Symbols are moved over too.
3891  parent.scope.symbol_table.merge(tmp_parent.symbol_table)
3892 
3893  return outer_ifblock
3894 
3895  def _case_construct_handler(self, node, parent):
3896  '''
3897  Transforms an fparser2 Case_Construct to the PSyIR representation.
3898 
3899  :param node: node in fparser2 tree.
3900  :type node: :py:class:`fparser.two.Fortran2003.Case_Construct`
3901  :param parent: parent node of the PSyIR node we are constructing.
3902  :type parent: :py:class:`psyclone.psyir.nodes.Node`
3903 
3904  :returns: PSyIR representation of node
3905  :rtype: :py:class:`psyclone.psyir.nodes.IfBlock`
3906 
3907  :raises InternalError: If the fparser2 tree has an unexpected
3908  structure.
3909  :raises NotImplementedError: If the fparser2 tree contains an
3910  unsupported structure and should be placed in a CodeBlock.
3911 
3912  '''
3913  # Check that the fparser2 parsetree has the expected structure
3914  if not isinstance(node.content[0], Fortran2003.Select_Case_Stmt):
3915  raise InternalError(
3916  f"Failed to find opening case statement in: {node}")
3917  if not isinstance(node.content[-1], Fortran2003.End_Select_Stmt):
3918  raise InternalError(
3919  f"Failed to find closing case statement in: {node}")
3920 
3921  # Search for all the CASE clauses in the Case_Construct. We do this
3922  # because the fp2 parse tree has a flat structure at this point with
3923  # the clauses being siblings of the contents of the clauses. The
3924  # final index in this list will hold the position of the end-select
3925  # statement.
3926  clause_indices = []
3927  selector = None
3928  # The position of the 'case default' clause, if any
3929  default_clause_idx = None
3930  for idx, child in enumerate(node.content):
3931  if isinstance(child, Fortran2003.Select_Case_Stmt):
3932  selector = child.items[0]
3933  if isinstance(child, Fortran2003.Case_Stmt):
3934  if not isinstance(child.items[0], Fortran2003.Case_Selector):
3935  raise InternalError(
3936  f"Unexpected parse tree structure. Expected child of "
3937  f"Case_Stmt to be a Case_Selector but got: "
3938  f"'{type(child.items[0]).__name__}'")
3939  case_expression = child.items[0].items[0]
3940  if case_expression is None:
3941  # This is a 'case default' clause - store its position.
3942  # We do this separately as this clause is special and
3943  # will be added as a final 'else'.
3944  default_clause_idx = idx
3945  clause_indices.append(idx)
3946  if isinstance(child, Fortran2003.End_Select_Stmt):
3947  clause_indices.append(idx)
3948 
3949  # Deal with each Case_Stmt
3950  rootif = None
3951  currentparent = parent
3952  num_clauses = len(clause_indices) - 1
3953  for idx in range(num_clauses):
3954  # Skip the 'default' clause for now because we handle it last
3955  if clause_indices[idx] == default_clause_idx:
3956  continue
3957  start_idx = clause_indices[idx]
3958  end_idx = clause_indices[idx+1]
3959  clause = node.content[start_idx]
3960  case = clause.items[0]
3961 
3962  # If rootif is already initialised we chain the new case in the
3963  # last else branch, otherwise we start a new IfBlock
3964  if rootif:
3965  elsebody = Schedule(parent=currentparent)
3966  currentparent.addchild(elsebody)
3967  ifblock = IfBlock(annotations=['was_case'])
3968  elsebody.addchild(ifblock)
3969  elsebody.ast = node.content[start_idx + 1]
3970  elsebody.ast_end = node.content[end_idx - 1]
3971  else:
3972  ifblock = IfBlock(parent=currentparent,
3973  annotations=['was_case'])
3974  rootif = ifblock
3975 
3976  if idx == 0:
3977  # If this is the first IfBlock then have it point to
3978  # the original SELECT CASE in the parse tree
3979  ifblock.ast = node
3980  else:
3981  # Otherwise, this IfBlock represents a CASE clause in the
3982  # Fortran and so we point to the parse tree of the content
3983  # of the clause.
3984  ifblock.ast = node.content[start_idx + 1]
3985  ifblock.ast_end = node.content[end_idx - 1]
3986 
3987  # Process the logical expression
3988  self._process_case_value_list_process_case_value_list(selector, case.items[0].items,
3989  ifblock)
3990 
3991  # Add If_body
3992  ifbody = Schedule(parent=ifblock)
3993  self.process_nodesprocess_nodes(parent=ifbody,
3994  nodes=node.content[start_idx + 1:
3995  end_idx])
3996  ifblock.addchild(ifbody)
3997  ifbody.ast = node.content[start_idx + 1]
3998  ifbody.ast_end = node.content[end_idx - 1]
3999 
4000  currentparent = ifblock
4001 
4002  if default_clause_idx:
4003  # Finally, add the content of the 'default' clause as a last
4004  # 'else' clause. If the 'default' clause was the only clause
4005  # then 'rootif' will still be None and we don't bother creating
4006  # an enclosing IfBlock at all.
4007  if rootif:
4008  elsebody = Schedule(parent=currentparent)
4009  currentparent.addchild(elsebody)
4010  currentparent = elsebody
4011  start_idx = default_clause_idx + 1
4012  # Find the next 'case' clause that occurs after 'case default'
4013  # (if any)
4014  end_idx = -1
4015  for idx in clause_indices:
4016  if idx > default_clause_idx:
4017  end_idx = idx
4018  break
4019  # Process the statements within the 'default' clause
4020  self.process_nodesprocess_nodes(parent=currentparent,
4021  nodes=node.content[start_idx:end_idx])
4022  if rootif:
4023  elsebody.ast = node.content[start_idx]
4024  elsebody.ast_end = node.content[end_idx - 1]
4025  return rootif
4026 
4027  def _process_case_value_list(self, selector, nodes, parent):
4028  '''
4029  Processes the supplied list of fparser2 nodes representing case-value
4030  expressions and constructs the equivalent PSyIR representation.
4031  e.g. for:
4032 
4033  SELECT CASE(my_flag)
4034  CASE(var1, var2:var3, :var5)
4035  my_switch = .true.
4036  END SELECT
4037 
4038  the equivalent logical expression is:
4039 
4040  my_flag == var1 OR (myflag>=var2 AND myflag <= var3) OR my_flag <= var5
4041 
4042  and the corresponding structure of the PSyIR that we create is:
4043 
4044  OR
4045  / \
4046  EQ OR
4047  / \
4048  AND LE
4049  / \
4050  GE LE
4051 
4052  :param selector: the fparser2 parse tree representing the \
4053  selector_expression in SELECT CASE(selector_expression).
4054  :type selector: sub-class of :py:class:`fparser.two.utils.Base`
4055  :param nodes: the nodes representing the label-list of the current \
4056  CASE() clause.
4057  :type nodes: list of :py:class:`fparser.two.Fortran2003.Name` or \
4058  :py:class:`fparser.two.Fortran2003.Case_Value_Range`
4059  :param parent: parent node in the PSyIR.
4060  :type parent: :py:class:`psyclone.psyir.nodes.Node`
4061 
4062  '''
4063  if len(nodes) == 1:
4064  # Only one item in list so process it
4065  self._process_case_value_process_case_value(selector, nodes[0], parent)
4066  return
4067  # More than one item in list. Create an OR node with the first item
4068  # on the list as one arg then recurse down to handle the remainder
4069  # of the list.
4070  orop = BinaryOperation(BinaryOperation.Operator.OR,
4071  parent=parent)
4072  self._process_case_value_process_case_value(selector, nodes[0], orop)
4073  self._process_case_value_list_process_case_value_list(selector, nodes[1:], orop)
4074  parent.addchild(orop)
4075 
4076  def _process_case_value(self, selector, node, parent):
4077  '''
4078  Handles an individual condition inside a CASE statement. This can
4079  be a single scalar expression (e.g. CASE(1)) or a range specification
4080  (e.g. CASE(lim1:lim2)).
4081 
4082  :param selector: the node in the fparser2 parse tree representing the
4083  'some_expr' of the SELECT CASE(some_expr).
4084  :type selector: sub-class of :py:class:`fparser.two.utils.Base`
4085  :param node: the node representing the case-value expression in the \
4086  fparser2 parse tree.
4087  :type node: sub-class of :py:class:`fparser.two.utils.Base`
4088  :param parent: parent node in the PSyIR.
4089  :type parent: :py:class:`psyclone.psyir.nodes.Node`
4090 
4091  :raises NotImplementedError: If the operator for an equality cannot be
4092  determined (i.e. the statement cannot be
4093  determined to be a logical comparison
4094  or not)
4095  '''
4096  if isinstance(node, Fortran2003.Case_Value_Range):
4097  # The case value is a range (e.g. lim1:lim2)
4098  if node.items[0] and node.items[1]:
4099  # Have lower and upper limits so need a parent AND
4100  aop = BinaryOperation(BinaryOperation.Operator.AND,
4101  parent=parent)
4102  parent.addchild(aop)
4103  new_parent = aop
4104  else:
4105  # No need to create new parent node
4106  new_parent = parent
4107 
4108  if node.items[0]:
4109  # A lower limit is specified
4110  geop = BinaryOperation(BinaryOperation.Operator.GE,
4111  parent=new_parent)
4112  self.process_nodesprocess_nodes(parent=geop, nodes=[selector])
4113  self.process_nodesprocess_nodes(parent=geop, nodes=[node.items[0]])
4114  new_parent.addchild(geop)
4115  if node.items[1]:
4116  # An upper limit is specified
4117  leop = BinaryOperation(BinaryOperation.Operator.LE,
4118  parent=new_parent)
4119  self.process_nodesprocess_nodes(parent=leop, nodes=[selector])
4120  self.process_nodesprocess_nodes(parent=leop, nodes=[node.items[1]])
4121  new_parent.addchild(leop)
4122  else:
4123  # The case value is some scalar expression
4124  fake_parent = Assignment(parent=parent)
4125  self.process_nodesprocess_nodes(parent=fake_parent, nodes=[selector])
4126  self.process_nodesprocess_nodes(parent=fake_parent, nodes=[node])
4127 
4128  for operand in fake_parent.lhs, fake_parent.rhs:
4129  # If any of the operands has a datatype we can distinguish
4130  # between boolean (which in Fortran and PSyIR uses the EQV
4131  # operator) or not-boolean (which uses the EQ operator)
4132  if (hasattr(operand, "datatype") and
4133  isinstance(operand.datatype, ScalarType)):
4134  if (operand.datatype.intrinsic ==
4135  ScalarType.Intrinsic.BOOLEAN):
4136  bop = BinaryOperation(BinaryOperation.Operator.EQV,
4137  parent=parent)
4138  else:
4139  bop = BinaryOperation(BinaryOperation.Operator.EQ,
4140  parent=parent)
4141  parent.addchild(bop)
4142  bop.children.extend(fake_parent.pop_all_children())
4143  break
4144  else:
4145  # If the loop did not encounter a break, we don't know which
4146  # operator is needed, so we use the generic interface instead
4147  cmp_symbol = _find_or_create_psyclone_internal_cmp(parent)
4148  call = Call(parent=parent)
4149  call.addchild(Reference(cmp_symbol))
4150  parent.addchild(call)
4151  call.children.extend(fake_parent.pop_all_children())
4152 
4153  @staticmethod
4154  def _array_notation_rank(node):
4155  '''Check that the supplied candidate array reference uses supported
4156  array notation syntax and return the rank of the sub-section
4157  of the array that uses array notation. e.g. for a reference
4158  "a(:, 2, :)" the rank of the sub-section is 2.
4159 
4160  :param node: the reference to check.
4161  :type node: :py:class:`psyclone.psyir.nodes.ArrayReference` or \
4162  :py:class:`psyclone.psyir.nodes.ArrayMember` or \
4163  :py:class:`psyclone.psyir.nodes.StructureReference`
4164 
4165  :returns: rank of the sub-section of the array.
4166  :rtype: int
4167 
4168  :raises InternalError: if no ArrayMixin node with at least one \
4169  Range in its indices is found.
4170  :raises InternalError: if two or more part references in a \
4171  structure reference contain ranges.
4172  :raises NotImplementedError: if the supplied node is not of a \
4173  supported type.
4174  :raises NotImplementedError: if any ranges are encountered that are \
4175  not for the full extent of the dimension.
4176  '''
4177  if isinstance(node, (ArrayReference, ArrayMember)):
4178  array = node
4179  elif isinstance(node, StructureReference):
4180  array = None
4181  arrays = node.walk((ArrayMember, ArrayOfStructuresMixin))
4182  for part_ref in arrays:
4183  if any(isinstance(idx, Range) for idx in part_ref.indices):
4184  if array:
4185  # Cannot have two or more part references that contain
4186  # ranges - this is not valid Fortran.
4187  raise InternalError(
4188  f"Found a structure reference containing two or "
4189  f"more part references that have ranges: "
4190  f"'{node.debug_string()}'. This is not valid "
4191  f"within a WHERE in Fortran.")
4192  array = part_ref
4193  if not array:
4194  raise InternalError(
4195  f"No array access found in node '{node.name}'")
4196  else:
4197  # This will result in a CodeBlock.
4198  raise NotImplementedError(
4199  f"Expected either an ArrayReference, ArrayMember or a "
4200  f"StructureReference but got '{type(node).__name__}'")
4201 
4202  # Only array refs using basic colon syntax are currently
4203  # supported e.g. (a(:,:)). Each colon is represented in the
4204  # PSyIR as a Range node with first argument being an lbound
4205  # binary operator, the second argument being a ubound operator
4206  # and the third argument being an integer Literal node with
4207  # value 1 i.e. a(:,:) is represented as
4208  # a(lbound(a,1):ubound(a,1):1,lbound(a,2):ubound(a,2):1) in
4209  # the PSyIR.
4210  num_colons = 0
4211  for idx_node in array.indices:
4212  if isinstance(idx_node, Range):
4213  # Found array syntax notation. Check that it is the
4214  # simple ":" format.
4215  if not _is_range_full_extent(idx_node):
4216  raise NotImplementedError(
4217  "Only array notation of the form my_array(:, :, ...) "
4218  "is supported.")
4219  num_colons += 1
4220  return num_colons
4221 
4222  def _array_syntax_to_indexed(self, parent, loop_vars):
4223  '''
4224  Utility function that modifies each ArrayReference object in the
4225  supplied PSyIR fragment so that they are indexed using the supplied
4226  loop variables rather than having colon array notation. This indexing
4227  is always done relative to the declared lower bound of the array being
4228  accessed.
4229 
4230  :param parent: root of PSyIR sub-tree to search for Array
4231  references to modify.
4232  :type parent: :py:class:`psyclone.psyir.nodes.Node`
4233  :param loop_vars: the variable names for the array indices.
4234  :type loop_vars: list of str
4235 
4236  :raises NotImplementedError: if array sections of differing ranks are
4237  found.
4238  '''
4239  assigns = parent.walk(Assignment)
4240  # Check that the LHS of any assignment uses array notation.
4241  # Note that this will prevent any WHERE blocks that contain scalar
4242  # assignments from being handled but is a necessary limitation until
4243  # #717 is done and we interrogate the type of each symbol.
4244  for assign in assigns:
4245  _ = self._array_notation_rank_array_notation_rank(assign.lhs)
4246 
4247  # TODO #717 if the supplied code accidentally omits array
4248  # notation for an array reference on the RHS then we will
4249  # identify it as a scalar and the code produced from the
4250  # PSyIR (using e.g. the Fortran backend) will not
4251  # compile. We need to implement robust identification of the
4252  # types of all symbols in the PSyIR fragment.
4253  table = parent.scope.symbol_table
4254  one = Literal("1", INTEGER_TYPE)
4255  arrays = parent.walk(ArrayMixin)
4256  first_rank = None
4257  for array in arrays:
4258  # Check that this is a supported array reference and that
4259  # all arrays are of the same rank
4260  rank = len([child for child in array.indices if
4261  isinstance(child, Range)])
4262  if rank == 0:
4263  # This is an array reference without any ranges so we can
4264  # ignore it.
4265  continue
4266 
4267  if first_rank:
4268  if rank != first_rank:
4269  raise NotImplementedError(
4270  f"Found array sections of differing ranks within a "
4271  f"WHERE construct: array section of {array.name} has "
4272  f"rank {rank}")
4273  else:
4274  first_rank = rank
4275 
4276  base_ref = _copy_full_base_reference(array)
4277  array_ref = array.ancestor(Reference, include_self=True)
4278  shape = array_ref.datatype.shape
4279  add_op = BinaryOperation.Operator.ADD
4280  sub_op = BinaryOperation.Operator.SUB
4281  # Replace the PSyIR Ranges with appropriate index expressions.
4282  range_idx = 0
4283  for idx, child in enumerate(array.indices):
4284  if not isinstance(child, Range):
4285  continue
4286  # We need the lower bound of the appropriate dimension of this
4287  # array as we will index relative to it. Note that the 'shape'
4288  # of the datatype only gives us extents, not the lower bounds
4289  # of the declaration or slice.
4290  if isinstance(shape[range_idx], ArrayType.Extent):
4291  # We don't know the bounds of this array so we have
4292  # to query using LBOUND.
4293  lbound = IntrinsicCall.create(
4294  IntrinsicCall.Intrinsic.LBOUND,
4295  [base_ref.copy(),
4296  ("dim", Literal(str(idx+1), INTEGER_TYPE))])
4297  else:
4298  if array.is_full_range(idx):
4299  # The access to this index is to the full range of
4300  # the array.
4301  # TODO #949 - ideally we would try to find the lower
4302  # bound of the array by interrogating `array.symbol.
4303  # datatype` but the fparser2 frontend doesn't currently
4304  # support array declarations with explicit lower bounds
4305  lbound = IntrinsicCall.create(
4306  IntrinsicCall.Intrinsic.LBOUND,
4307  [base_ref.copy(),
4308  ("dim", Literal(str(idx+1), INTEGER_TYPE))])
4309  else:
4310  # We need the lower bound of this access.
4311  lbound = child.start.copy()
4312 
4313  # Create the index expression.
4314  symbol = table.lookup(loop_vars[range_idx])
4315  if isinstance(lbound, Literal) and lbound.value == "1":
4316  # Lower bound is just unity so we can use the loop-idx
4317  # directly.
4318  expr2 = Reference(symbol)
4319  else:
4320  # We don't know what the lower bound is so have to
4321  # have an expression:
4322  # idx-expr = array-lower-bound + loop-idx - 1
4323  expr = BinaryOperation.create(
4324  add_op, lbound, Reference(symbol))
4325  expr2 = BinaryOperation.create(sub_op, expr, one.copy())
4326  array.children[idx] = expr2
4327  range_idx += 1
4328 
4329  def _where_construct_handler(self, node, parent):
4330  '''
4331  Construct the canonical PSyIR representation of a WHERE construct or
4332  statement. A construct has the form:
4333 
4334  WHERE(logical-mask)
4335  statements
4336  [ELSE WHERE(logical-mask)
4337  statements]
4338  [ELSE WHERE
4339  statements]
4340  END WHERE
4341 
4342  while a statement is just:
4343 
4344  WHERE(logical-mask) statement
4345 
4346  :param node: node in the fparser2 parse tree representing the WHERE.
4347  :type node: :py:class:`fparser.two.Fortran2003.Where_Construct` |
4348  :py:class:`fparser.two.Fortran2003.Where_Stmt`
4349  :param parent: parent node in the PSyIR.
4350  :type parent: :py:class:`psyclone.psyir.nodes.Node`
4351 
4352  :returns: the top-level Loop object in the created loop nest.
4353  :rtype: :py:class:`psyclone.psyir.nodes.Loop`
4354 
4355  :raises InternalError: if the parse tree does not have the expected
4356  structure.
4357  :raises NotImplementedError: if the parse tree contains a Fortran
4358  intrinsic that performs a reduction but still returns an array.
4359  :raises NotImplementedError: if the logical mask of the WHERE does
4360  not use array notation.
4361 
4362  '''
4363  def _contains_intrinsic_reduction(pnodes):
4364  '''
4365  Utility to check for Fortran intrinsics that perform a reduction
4366  but result in an array.
4367 
4368  :param pnodes: node(s) in the parse tree to check.
4369  :type pnodes: list[:py:class:`fparser.two.utils.Base`] |
4370  :py:class:`fparser.two.utils.Base`
4371 
4372  :returns: whether or not the supplied node(s) in the parse tree
4373  contain a call to an intrinsic that performs a reduction
4374  into an array.
4375  :rtype: bool
4376 
4377  '''
4378  intr_nodes = walk(pnodes, Fortran2003.Intrinsic_Function_Reference)
4379  for intr in intr_nodes:
4380  if (intr.children[0].string in
4381  Fortran2003.Intrinsic_Name.array_reduction_names):
4382  # These intrinsics are only a problem if they return an
4383  # array rather than a scalar.
4384  arg_specs = walk(intr.children[1],
4385  Fortran2003.Actual_Arg_Spec)
4386  if any(spec.children[0].string == 'dim'
4387  for spec in arg_specs):
4388  return True
4389  return False
4390 
4391  if isinstance(node, Fortran2003.Where_Stmt):
4392  # We have a Where statement. Check that the parse tree has the
4393  # expected structure.
4394  if not len(node.items) == 2:
4395  raise InternalError(
4396  f"Expected a Fortran2003.Where_Stmt to have exactly two "
4397  f"entries in 'items' but found {len(node.items)}: "
4398  f"{node.items}")
4399  if not isinstance(node.items[1], Fortran2003.Assignment_Stmt):
4400  raise InternalError(
4401  f"Expected the second entry of a Fortran2003.Where_Stmt "
4402  f"items tuple to be an Assignment_Stmt but found: "
4403  f"{type(node.items[1]).__name__}")
4404  if _contains_intrinsic_reduction(node.items[1]):
4405  raise NotImplementedError(
4406  f"TODO #1960 - WHERE statements which contain array-"
4407  f"reduction intrinsics are not supported but found "
4408  f"'{node}'")
4409  was_single_stmt = True
4410  annotations = ["was_where", "was_single_stmt"]
4411  logical_expr = [node.items[0]]
4412  else:
4413  # We have a Where construct. Check that the first and last
4414  # children are what we expect.
4415  if not isinstance(node.content[0],
4416  Fortran2003.Where_Construct_Stmt):
4417  raise InternalError(f"Failed to find opening where construct "
4418  f"statement in: {node}")
4419  if not isinstance(node.content[-1], Fortran2003.End_Where_Stmt):
4420  raise InternalError(f"Failed to find closing end where "
4421  f"statement in: {node}")
4422  if _contains_intrinsic_reduction(node.content[1:-1]):
4423  raise NotImplementedError(
4424  f"TODO #1960 - WHERE constructs which contain an array-"
4425  f"reduction intrinsic are not supported but found "
4426  f"'{node}'")
4427  was_single_stmt = False
4428  annotations = ["was_where"]
4429  logical_expr = node.content[0].items
4430 
4431  # Examine the logical-array expression (the mask) in order to
4432  # determine the number of nested loops required. The Fortran
4433  # standard allows bare array notation here (e.g. `a < 0.0` where
4434  # `a` is an array) and thus we would need to examine our SymbolTable
4435  # to find out the rank of `a`. For the moment we limit support to
4436  # the NEMO style where the fact that `a` is an array is made
4437  # explicit using the colon notation, e.g. `a(:, :) < 0.0`.
4438 
4439  if _contains_intrinsic_reduction(logical_expr):
4440  raise NotImplementedError(
4441  f"TODO #1960 - WHERE constructs which contain an array-"
4442  f"reduction intrinsic in their logical expression are not "
4443  f"supported but found '{logical_expr}'")
4444 
4445  # For this initial processing of the logical-array expression we
4446  # use a temporary parent as we haven't yet constructed the PSyIR
4447  # for the loop nest and innermost IfBlock. Once we have a valid
4448  # parent for this logical expression we will repeat the processing.
4449  fake_parent = Assignment(parent=parent)
4450  self.process_nodesprocess_nodes(fake_parent, logical_expr)
4451  arrays = fake_parent.walk(ArrayMixin)
4452 
4453  if not arrays:
4454  # If the PSyIR doesn't contain any Arrays then that must be
4455  # because the code doesn't use explicit array syntax. At least one
4456  # variable in the logical-array expression must be an array for
4457  # this to be a valid WHERE().
4458  # TODO #1799. Look-up the shape of the array in the SymbolTable.
4459  raise NotImplementedError(
4460  f"Only WHERE constructs using explicit array notation (e.g. "
4461  f"my_array(:,:)) are supported but found '{logical_expr}'.")
4462 
4463  for array in arrays:
4464  if any(isinstance(idx, Range) for idx in array.indices):
4465  first_array = array
4466  break
4467  else:
4468  raise NotImplementedError(
4469  f"Only WHERE constructs using explicit array notation "
4470  f"including ranges (e.g. 'my_array(1,:)') are supported but "
4471  f"found '{logical_expr}'")
4472 
4473  array_ref = first_array.ancestor(Reference, include_self=True)
4474  mask_shape = array_ref.datatype.shape
4475  # All array sections in a Fortran WHERE must have the same shape so
4476  # just look at that of the mask.
4477  rank = len(mask_shape)
4478  # Create a list to hold the names of the loop variables as we'll
4479  # need them to index into the arrays.
4480  loop_vars = rank*[""]
4481 
4482  symbol_table = parent.scope.symbol_table
4483  integer_type = default_integer_type()
4484 
4485  # Now create a loop nest of depth `rank`
4486  new_parent = parent
4487  for idx in range(rank, 0, -1):
4488 
4489  data_symbol = symbol_table.new_symbol(
4490  f"widx{idx}", symbol_type=DataSymbol, datatype=integer_type)
4491  loop_vars[idx-1] = data_symbol.name
4492 
4493  loop = Loop(parent=new_parent, variable=data_symbol,
4494  annotations=annotations)
4495  # Point to the original WHERE statement in the parse tree.
4496  loop.ast = node
4497 
4498  # This loop is over the *shape* of the mask and thus starts
4499  # at unity. Each individual array access is then adjusted
4500  # according to the lower bound of that array.
4501  loop.addchild(Literal("1", integer_type))
4502  # Add loop upper bound using the shape of the mask.
4503  if isinstance(mask_shape[idx-1], ArrayType.Extent):
4504  # We don't have an explicit value for the upper bound so we
4505  # have to query it using SIZE.
4506  loop.addchild(
4507  IntrinsicCall.create(IntrinsicCall.Intrinsic.SIZE,
4508  [array_ref.copy(),
4509  ("dim", Literal(str(idx),
4510  integer_type))]))
4511  else:
4512  loop.addchild(mask_shape[idx-1].upper.copy())
4513 
4514  # Add loop increment
4515  loop.addchild(Literal("1", integer_type))
4516  # Fourth child of a Loop must be a Schedule
4517  sched = Schedule(parent=loop)
4518  loop.addchild(sched)
4519  # Finally, add the Loop we've constructed to its parent (but
4520  # not into the existing PSyIR tree - that's done in
4521  # process_nodes()).
4522  if new_parent is not parent:
4523  new_parent.addchild(loop)
4524  else:
4525  # Keep a reference to the first loop as that's what this
4526  # handler returns
4527  root_loop = loop
4528  new_parent = sched
4529 
4530  # Now we have the loop nest, add an IF block to the innermost
4531  # schedule
4532  ifblock = IfBlock(parent=new_parent, annotations=annotations)
4533  ifblock.ast = node # Point back to the original WHERE construct
4534  new_parent.addchild(ifblock)
4535 
4536  # We construct the conditional expression from the original
4537  # logical-array-expression of the WHERE. We process_nodes() a
4538  # second time here now that we have the correct parent node in the
4539  # PSyIR (and thus a SymbolTable) to refer to.
4540  self.process_nodesprocess_nodes(ifblock, logical_expr)
4541 
4542  # Each array reference must now be indexed by the loop variables
4543  # of the loops we've just created.
4544  # N.B. we can't use `ifblock.condition` below because the IfBlock is
4545  # not yet fully constructed and therefore the consistency checks
4546  # inside that method fail.
4547  self._array_syntax_to_indexed_array_syntax_to_indexed(ifblock.children[0], loop_vars)
4548 
4549  # Now construct the body of the IF using the body of the WHERE
4550  sched = Schedule(parent=ifblock)
4551  ifblock.addchild(sched)
4552 
4553  if was_single_stmt:
4554  # We only had a single-statement WHERE
4555  self.process_nodesprocess_nodes(sched, node.items[1:])
4556  else:
4557  # We have to allow for potentially multiple ELSE WHERE clauses
4558  clause_indices = []
4559  for idx, child in enumerate(node.content):
4560  if isinstance(child, (Fortran2003.Elsewhere_Stmt,
4561  Fortran2003.Masked_Elsewhere_Stmt,
4562  Fortran2003.End_Where_Stmt)):
4563  clause_indices.append(idx)
4564  num_clauses = len(clause_indices) - 1
4565 
4566  if len(clause_indices) > 1:
4567  # We have at least one elsewhere clause.
4568  # Process the body of the where (up to the first elsewhere).
4569  self.process_nodesprocess_nodes(sched, node.content[1:clause_indices[0]])
4570  current_parent = ifblock
4571 
4572  for idx in range(num_clauses):
4573  start_idx = clause_indices[idx]
4574  end_idx = clause_indices[idx+1]
4575  clause = node.content[start_idx]
4576 
4577  if isinstance(clause, Fortran2003.Masked_Elsewhere_Stmt):
4578  elsebody = Schedule(parent=current_parent)
4579  current_parent.addchild(elsebody)
4580  newifblock = IfBlock(parent=elsebody,
4581  annotations=annotations)
4582  elsebody.addchild(newifblock)
4583 
4584  # Keep pointer to fparser2 AST
4585  elsebody.ast = node.content[start_idx]
4586  newifblock.ast = node.content[start_idx]
4587 
4588  # Create condition as first child
4589  self.process_nodesprocess_nodes(parent=newifblock,
4590  nodes=[clause.items[0]])
4591 
4592  # Create if-body as second child
4593  ifbody = Schedule(parent=newifblock)
4594  ifbody.ast = node.content[start_idx + 1]
4595  ifbody.ast_end = node.content[end_idx - 1]
4596  newifblock.addchild(ifbody)
4597  self.process_nodesprocess_nodes(
4598  parent=ifbody,
4599  nodes=node.content[start_idx+1:end_idx])
4600  current_parent = newifblock
4601 
4602  elif isinstance(clause, Fortran2003.Elsewhere_Stmt):
4603  if idx != num_clauses - 1:
4604  raise InternalError(
4605  f"Elsewhere_Stmt should only be found next to "
4606  f"last clause, but found {node.content}")
4607  elsebody = Schedule(parent=current_parent)
4608  current_parent.addchild(elsebody)
4609  elsebody.ast = node.content[start_idx]
4610  elsebody.ast_end = node.content[end_idx]
4611  self.process_nodesprocess_nodes(
4612  parent=elsebody,
4613  nodes=node.content[start_idx + 1:end_idx])
4614 
4615  else:
4616  raise InternalError(
4617  f"Expected either Fortran2003.Masked_Elsewhere"
4618  f"_Stmt or Fortran2003.Elsewhere_Stmt but found "
4619  f"'{type(clause).__name__}'")
4620  else:
4621  # No elsewhere clauses were found so put the whole body into
4622  # the single if block.
4623  self.process_nodesprocess_nodes(sched, node.content[1:-1])
4624 
4625  # Convert all uses of array syntax to indexed accesses
4626  self._array_syntax_to_indexed_array_syntax_to_indexed(ifblock, loop_vars)
4627  # Return the top-level loop generated by this handler
4628  return root_loop
4629 
4630  def _return_handler(self, node, parent):
4631  '''
4632  Transforms an fparser2 Return_Stmt to the PSyIR representation.
4633 
4634  :param node: node in fparser2 parse tree.
4635  :type node: :py:class:`fparser.two.Fortran2003.Return_Stmt`
4636  :param parent: Parent node of the PSyIR node we are constructing.
4637  :type parent: :py:class:`psyclone.psyir.nodes.Node`
4638 
4639  :return: PSyIR representation of node.
4640  :rtype: :py:class:`psyclone.psyir.nodes.Return`
4641 
4642  '''
4643  rtn = Return(parent=parent)
4644  rtn.ast = node
4645  return rtn
4646 
4647  def _assignment_handler(self, node, parent):
4648  '''
4649  Transforms an fparser2 Assignment_Stmt to the PSyIR representation.
4650 
4651  :param node: node in fparser2 AST.
4652  :type node: :py:class:`fparser.two.Fortran2003.Assignment_Stmt`
4653  :param parent: Parent node of the PSyIR node we are constructing.
4654  :type parent: :py:class:`psyclone.psyir.nodes.Node`
4655 
4656  :returns: PSyIR representation of node.
4657  :rtype: :py:class:`psyclone.psyir.nodes.Assignment`
4658  '''
4659  assignment = Assignment(node, parent=parent)
4660  self.process_nodesprocess_nodes(parent=assignment, nodes=[node.items[0]])
4661  self.process_nodesprocess_nodes(parent=assignment, nodes=[node.items[2]])
4662 
4663  return assignment
4664 
4665  def _structure_accessor_handler(self, node, parent):
4666  '''
4667  Create the PSyIR for structure accessors found in fparser2 Data_Ref and
4668  Procedure_Designator (representing an access to a derived type data and
4669  methods respectively).
4670 
4671  :param node: node in fparser2 parse tree.
4672  :type node: :py:class:`fparser.two.Fortran2003.Data_Ref` |
4673  :py:class:`fparser.two.Fortran2003.Process_Designator`
4674  :param parent: Parent node of the PSyIR node we are constructing.
4675  :type parent: :py:class:`psyclone.psyir.nodes.Node`
4676 
4677  :return: PSyIR representation of node
4678  :rtype: :py:class:`psyclone.psyir.nodes.StructureReference` |
4679  :py:class:`psyclone.psyir.nodes.ArrayOfStructuresReference`
4680 
4681  :raises NotImplementedError: if the parse tree contains unsupported
4682  elements.
4683 
4684  '''
4685  # Fortran 2003 standard R1221 says that:
4686  # procedure-designator is procedure-name
4687  # or proc-component-ref
4688  # or data-ref % binding-name
4689  # and R611 says that:
4690  # data-ref is part-ref [% part-ref]
4691  if isinstance(node, Fortran2003.Procedure_Designator):
4692  # If it is a Procedure_Designator split it in its components.
4693  # Note that this won't fail for procedure-name and proc-component
4694  # -ref, the binding_name will just become None, if we have a
4695  # binding_name we store it to add it as the last structure access
4696  node, _, binding_name = node.children
4697  else:
4698  binding_name = None
4699 
4700  if isinstance(node, Fortran2003.Data_Ref):
4701  # Separate the top_ref, that sets the PSyIR node type and symbol
4702  # from the member nodes, which set the accessors (PSyIR members)
4703  top_ref = node.children[0]
4704  member_nodes = node.children[1:]
4705  else:
4706  top_ref = node
4707  member_nodes = []
4708 
4709  if isinstance(top_ref, Fortran2003.Name):
4710  # Add the structure root reference to the symbol table if its
4711  # not already there.
4712  base_sym = _find_or_create_unresolved_symbol(
4713  parent, top_ref.string.lower(),
4714  symbol_type=DataSymbol, datatype=UnresolvedType())
4715  base_indices = []
4716  base_ref = StructureReference
4717  elif isinstance(top_ref, Fortran2003.Part_Ref):
4718  # Add the structure root reference to the symbol table if its
4719  # not already there.
4720  base_sym = _find_or_create_unresolved_symbol(
4721  parent, top_ref.children[0].string.lower(),
4722  symbol_type=DataSymbol, datatype=UnresolvedType())
4723  # Processing the array-index expressions requires access to the
4724  # symbol table so create an ArrayReference node.
4725  sched = parent.ancestor(Schedule, include_self=True)
4726  aref = ArrayReference(parent=sched, symbol=base_sym)
4727  # The children of this node will represent the indices of the
4728  # ArrayOfStructuresReference.
4729  self.process_nodesprocess_nodes(parent=aref,
4730  nodes=top_ref.children[1].children)
4731  base_indices = aref.pop_all_children()
4732  base_ref = ArrayOfStructuresReference
4733 
4734  else:
4735  # If it's not a plain name or an array, we don't support it.
4736  raise NotImplementedError(str(node))
4737 
4738  # Now construct the list of 'members' making up the derived-type
4739  # accessors and array indices, e.g for "var%region(1)%start" this will
4740  # be: [("region", [Literal("1")]), "start"].
4741  members = []
4742  for child in member_nodes:
4743  if isinstance(child, Fortran2003.Name):
4744  # Members of a structure do not refer to symbols
4745  members.append(child.string)
4746  elif isinstance(child, Fortran2003.Part_Ref):
4747  # In order to use process_nodes() we need a parent node
4748  # through which we can access the symbol table. This is
4749  # because array-index expressions must refer to symbols.
4750  sched = parent.ancestor(Schedule, include_self=True)
4751  # Since the index expressions may refer to the parent
4752  # reference we construct a full reference to the current
4753  # member of the derived type. We include a fake array index
4754  # to ensure that the innermost member is an ArrayMember
4755  # that can accept the real array-index expressions generated
4756  # by process_nodes().
4757  array_name = child.children[0].string
4758  new_ref = _create_struct_reference(
4759  sched, base_ref, base_sym,
4760  members + [(array_name, [Literal("1", INTEGER_TYPE)])],
4761  base_indices)
4762  # 'Chase the pointer' all the way to the bottom of the
4763  # derived-type reference
4764  current_ref = new_ref
4765  while hasattr(current_ref, "member"):
4766  current_ref = current_ref.member
4767  # Remove the fake array index
4768  current_ref.pop_all_children()
4769  # We can now process the child index expressions
4770  self.process_nodesprocess_nodes(parent=current_ref,
4771  nodes=child.children[1].children)
4772  # The resulting children will become part of the structure
4773  # access expression
4774  children = current_ref.pop_all_children()
4775  members.append((array_name, children))
4776  else:
4777  # Found an unsupported entry in the parse tree. This will
4778  # result in a CodeBlock.
4779  raise NotImplementedError(str(node))
4780 
4781  if binding_name:
4782  members.append(str(binding_name))
4783 
4784  # Now we have the list of members, use the `create()` method of the
4785  # appropriate Reference subclass.
4786  return _create_struct_reference(parent, base_ref, base_sym,
4787  members, base_indices)
4788 
4789  def _unary_op_handler(self, node, parent):
4790  '''
4791  Transforms an fparser2 UnaryOpBase to its PSyIR representation.
4792 
4793  :param node: node in fparser2 AST.
4794  :type node: :py:class:`fparser.two.utils.UnaryOpBase`
4795  :param parent: Parent node of the PSyIR node we are constructing.
4796  :type parent: :py:class:`psyclone.psyir.nodes.Node`
4797 
4798  :return: PSyIR representation of node
4799  :rtype: :py:class:`psyclone.psyir.nodes.UnaryOperation`
4800 
4801  :raises NotImplementedError: if the supplied operator is not
4802  supported by this handler.
4803 
4804  '''
4805  operator_str = str(node.items[0]).lower()
4806  try:
4807  operator = Fparser2Reader.unary_operators[operator_str]
4808  except KeyError as err:
4809  # Operator not supported, it will produce a CodeBlock instead
4810  raise NotImplementedError(operator_str) from err
4811 
4812  unary_op = UnaryOperation(operator, parent=parent)
4813  self.process_nodesprocess_nodes(parent=unary_op, nodes=[node.items[1]])
4814  return unary_op
4815 
4816  def _binary_op_handler(self, node, parent):
4817  '''
4818  Transforms an fparser2 BinaryOp to its PSyIR representation.
4819 
4820  :param node: node in fparser2 AST.
4821  :type node: :py:class:`fparser.two.utils.BinaryOpBase`
4822  :param parent: Parent node of the PSyIR node we are constructing.
4823  :type parent: :py:class:`psyclone.psyir.nodes.Node`
4824 
4825  :returns: PSyIR representation of node
4826  :rtype: :py:class:`psyclone.psyir.nodes.BinaryOperation`
4827 
4828  :raises NotImplementedError: if the supplied operator is not supported
4829  by this handler.
4830 
4831  '''
4832  operator_str = node.items[1].lower()
4833  arg_nodes = [node.items[0], node.items[2]]
4834 
4835  try:
4836  operator = Fparser2Reader.binary_operators[operator_str]
4837  except KeyError as err:
4838  # Operator not supported, it will produce a CodeBlock instead
4839  raise NotImplementedError(operator_str) from err
4840 
4841  binary_op = BinaryOperation(operator, parent=parent)
4842  self.process_nodesprocess_nodes(parent=binary_op, nodes=[arg_nodes[0]])
4843  self.process_nodesprocess_nodes(parent=binary_op, nodes=[arg_nodes[1]])
4844  return binary_op
4845 
4846  def _intrinsic_handler(self, node, parent):
4847  '''Transforms an fparser2 Intrinsic_Function_Reference to the PSyIR
4848  representation.
4849 
4850  :param node: node in fparser2 Parse Tree.
4851  :type node:
4852  :py:class:`fparser.two.Fortran2003.Intrinsic_Function_Reference`
4853  :param parent: Parent node of the PSyIR node we are constructing.
4854  :type parent: :py:class:`psyclone.psyir.nodes.Node`
4855 
4856  :returns: PSyIR representation of node
4857  :rtype: :py:class:`psyclone.psyir.nodes.IntrinsicCall`
4858 
4859  :raises NotImplementedError: if an unsupported intrinsic is found.
4860 
4861  '''
4862  try:
4863  intrinsic = IntrinsicCall.Intrinsic[node.items[0].string.upper()]
4864 
4865  if not intrinsic.optional_args:
4866  # Intrinsics with no optional arguments
4867  call = IntrinsicCall(intrinsic, parent=parent)
4868  return self._process_args_process_args(node, call)
4869  if intrinsic.name.lower() in ["minval", "maxval", "sum"]:
4870  # Intrinsics with optional arguments require a
4871  # canonicalise function
4872  call = IntrinsicCall(intrinsic, parent=parent)
4873  return self._process_args_process_args(
4874  node, call, canonicalise=_canonicalise_minmaxsum)
4875  # TODO #2302: We do not canonicalise the order of the
4876  # arguments of the remaining intrinsics, but this means
4877  # PSyIR won't be able to guarantee what each child is.
4878  call = IntrinsicCall(intrinsic, parent=parent)
4879  return self._process_args_process_args(node, call)
4880  except KeyError as err:
4881  raise NotImplementedError(
4882  f"Intrinsic '{node.items[0].string}' is not supported"
4883  ) from err
4884 
4885  def _name_handler(self, node, parent):
4886  '''
4887  Transforms an fparser2 Name to the PSyIR representation. If the parent
4888  is connected to a SymbolTable, it checks the reference has been
4889  previously declared.
4890 
4891  :param node: node in fparser2 AST.
4892  :type node: :py:class:`fparser.two.Fortran2003.Name`
4893  :param parent: Parent node of the PSyIR node we are constructing.
4894  :type parent: :py:class:`psyclone.psyir.nodes.Node`
4895 
4896  :returns: PSyIR representation of node
4897  :rtype: :py:class:`psyclone.psyir.nodes.Reference`
4898 
4899  '''
4900  symbol = _find_or_create_unresolved_symbol(parent, node.string)
4901  return Reference(symbol, parent=parent)
4902 
4903  def _parenthesis_handler(self, node, parent):
4904  '''
4905  Transforms an fparser2 Parenthesis to the PSyIR representation.
4906  This means ignoring the parentheis and process the fparser2 children
4907  inside.
4908 
4909  :param node: node in fparser2 AST.
4910  :type node: :py:class:`fparser.two.Fortran2003.Parenthesis`
4911  :param parent: Parent node of the PSyIR node we are constructing.
4912  :type parent: :py:class:`psyclone.psyir.nodes.Node`
4913  :returns: PSyIR representation of node
4914  :rtype: :py:class:`psyclone.psyir.nodes.Node`
4915  '''
4916  # Use the items[1] content of the node as it contains the required
4917  # information (items[0] and items[2] just contain the left and right
4918  # brackets as strings so can be disregarded.
4919  return self._create_child_create_child(node.items[1], parent)
4920 
4921  def _part_ref_handler(self, node, parent):
4922  '''
4923  Transforms an fparser2 Part_Ref to the PSyIR representation. If the
4924  node is connected to a SymbolTable, it checks the reference has been
4925  previously declared.
4926 
4927  :param node: node in fparser2 AST.
4928  :type node: :py:class:`fparser.two.Fortran2003.Part_Ref`
4929  :param parent: Parent node of the PSyIR node we are constructing.
4930  :type parent: :py:class:`psyclone.psyir.nodes.Node`
4931 
4932  :raises NotImplementedError: if the fparser node represents \
4933  unsupported PSyIR features and should be placed in a CodeBlock.
4934 
4935  :returns: the PSyIR node.
4936  :rtype: :py:class:`psyclone.psyir.nodes.ArrayReference` or \
4937  :py:class:`psyclone.psyir.nodes.Call`
4938 
4939  '''
4940  reference_name = node.items[0].string.lower()
4941  # We can't say for sure that the symbol we create here should be a
4942  # DataSymbol as fparser2 often identifies function calls as
4943  # part-references instead of function-references.
4944  symbol = _find_or_create_unresolved_symbol(parent, reference_name)
4945 
4946  if isinstance(symbol, RoutineSymbol):
4947  call_or_array = Call(parent=parent)
4948  call_or_array.addchild(Reference(symbol))
4949  else:
4950  call_or_array = ArrayReference(symbol, parent=parent)
4951  self.process_nodesprocess_nodes(parent=call_or_array, nodes=node.items[1].items)
4952  return call_or_array
4953 
4954  def _subscript_triplet_handler(self, node, parent):
4955  '''
4956  Transforms an fparser2 Subscript_Triplet to the PSyIR
4957  representation.
4958 
4959  :param node: node in fparser2 AST.
4960  :type node: :py:class:`fparser.two.Fortran2003.Subscript_Triplet`
4961  :param parent: parent node of the PSyIR node we are constructing.
4962  :type parent: :py:class:`psyclone.psyir.nodes.Node`
4963 
4964  :returns: PSyIR of fparser2 node.
4965  :rtype: :py:class:`psyclone.psyir.nodes.Range`
4966 
4967  :raises InternalError: if the supplied parent node is not a sub-class \
4968  of either Reference or Member.
4969  '''
4970  # The PSyIR stores array dimension information for the ArrayMixin
4971  # class in an ordered list. As we are processing the
4972  # dimensions in order, the number of children already added to
4973  # our parent indicates the current array dimension being processed
4974  # (with 0 being the first dimension, 1 being the second etc). Fortran
4975  # specifies the 1st dimension as being 1, the second dimension being
4976  # 2, etc.). We therefore add 1 to the number of children already added
4977  # to our parent to determine the Fortran dimension value. However, we
4978  # do have to take care in case the parent is a member of a structure
4979  # rather than a plain array reference.
4980  if isinstance(parent, (Reference, Member)):
4981  dimension = str(len([kid for kid in parent.children if
4982  not isinstance(kid, Member)]) + 1)
4983  else:
4984  raise InternalError(
4985  f"Expected parent PSyIR node to be either a Reference or a "
4986  f"Member but got '{type(parent).__name__}' when processing "
4987  f"'{node}'")
4988 
4989  integer_type = default_integer_type()
4990  my_range = Range(parent=parent)
4991  my_range.children = []
4992 
4993  if node.children[0]:
4994  self.process_nodesprocess_nodes(parent=my_range, nodes=[node.children[0]])
4995  else:
4996  # There is no lower bound, it is implied. This is not
4997  # supported in the PSyIR so we create the equivalent code
4998  # by using the PSyIR lbound intrinsic function:
4999  # a(:...) becomes a(lbound(a,1):...)
5000  lbound = IntrinsicCall.create(
5001  IntrinsicCall.Intrinsic.LBOUND,
5002  [_copy_full_base_reference(parent),
5003  ("dim", Literal(dimension, integer_type))])
5004  my_range.children.append(lbound)
5005 
5006  if node.children[1]:
5007  self.process_nodesprocess_nodes(parent=my_range, nodes=[node.children[1]])
5008  else:
5009  # There is no upper bound, it is implied. This is not
5010  # supported in the PSyIR so we create the equivalent code
5011  # by using the PSyIR ubound intrinsic function:
5012  # a(...:) becomes a(...:ubound(a,1))
5013  ubound = IntrinsicCall.create(
5014  IntrinsicCall.Intrinsic.UBOUND,
5015  [_copy_full_base_reference(parent),
5016  ("dim", Literal(dimension, integer_type))])
5017  my_range.children.append(ubound)
5018 
5019  if node.children[2]:
5020  self.process_nodesprocess_nodes(parent=my_range, nodes=[node.children[2]])
5021  else:
5022  # There is no step, it is implied. This is not
5023  # supported in the PSyIR so we create the equivalent code
5024  # by using a PSyIR integer literal with the value 1
5025  # a(...:...:) becomes a(...:...:1)
5026  literal = Literal("1", integer_type)
5027  my_range.children.append(literal)
5028  return my_range
5029 
5030  def _number_handler(self, node, parent):
5031  '''
5032  Transforms an fparser2 NumberBase to the PSyIR representation.
5033 
5034  :param node: node in fparser2 parse tree.
5035  :type node: :py:class:`fparser.two.utils.NumberBase`
5036  :param parent: Parent node of the PSyIR node we are constructing.
5037  :type parent: :py:class:`psyclone.psyir.nodes.Node`
5038 
5039  :returns: PSyIR representation of node.
5040  :rtype: :py:class:`psyclone.psyir.nodes.Literal`
5041 
5042  :raises NotImplementedError: if the fparser2 node is not recognised.
5043 
5044  '''
5045  if isinstance(node, Fortran2003.Int_Literal_Constant):
5046  integer_type = ScalarType(ScalarType.Intrinsic.INTEGER,
5047  get_literal_precision(node, parent))
5048  return Literal(str(node.items[0]), integer_type)
5049  if isinstance(node, Fortran2003.Real_Literal_Constant):
5050  real_type = ScalarType(ScalarType.Intrinsic.REAL,
5051  get_literal_precision(node, parent))
5052  # Make sure any exponent is lower case
5053  value = str(node.items[0]).lower()
5054  # Make all exponents use the letter "e". (Fortran also
5055  # allows "d").
5056  value = value.replace("d", "e")
5057  # If the value has a "." without a digit before it then
5058  # add a "0" as the PSyIR does not allow this
5059  # format. e.g. +.3 => +0.3
5060  if value[0] == "." or value[0:1] in ["+.", "-."]:
5061  value = value.replace(".", "0.")
5062  return Literal(value, real_type)
5063  # Unrecognised datatype - will result in a CodeBlock
5064  raise NotImplementedError("Unsupported datatype of literal number")
5065 
5066  def _char_literal_handler(self, node, parent):
5067  '''
5068  Transforms an fparser2 character literal into a PSyIR literal.
5069  Currently does not support the use of a double '' or double "" to
5070  represent a single instance of one of those characters within a string
5071  delimited by the same character.
5072 
5073  :param node: node in fparser2 parse tree.
5074  :type node: :py:class:`fparser.two.Fortran2003.Char_Literal_Constant`
5075  :param parent: parent node of the PSyIR node we are constructing.
5076  :type parent: :py:class:`psyclone.psyir.nodes.Node`
5077 
5078  :returns: PSyIR representation of node.
5079  :rtype: :py:class:`psyclone.psyir.nodes.Literal`
5080 
5081  '''
5082  character_type = ScalarType(ScalarType.Intrinsic.CHARACTER,
5083  get_literal_precision(node, parent))
5084  # fparser issue #295 - the value of the character string currently
5085  # contains the quotation symbols themselves. Once that's fixed this
5086  # check will need to be changed.
5087  char_value = str(node.items[0])
5088  if not ((char_value.startswith("'") and char_value.endswith("'")) or
5089  (char_value.startswith('"') and char_value.endswith('"'))):
5090  raise InternalError(
5091  f"Char literal handler expects a quoted value but got: "
5092  f">>{char_value}<<")
5093  # In Fortran "x""x" or 'x''x' represents a string containing x"x
5094  # or x'x, respectively. (See Note 4.12 in the Fortran 2003 standard.)
5095  # However, checking whether we have e.g. 'that''s a cat''s mat' is
5096  # difficult and so, for now, we don't support it.
5097  if len(char_value) > 2 and ("''" in char_value or '""' in char_value):
5098  raise NotImplementedError("Unsupported Literal")
5099  # Strip the wrapping quotation chars before storing the value.
5100  return Literal(char_value[1:-1], character_type)
5101 
5102  def _bool_literal_handler(self, node, parent):
5103  '''
5104  Transforms an fparser2 logical literal into a PSyIR literal.
5105 
5106  :param node: node in fparser2 parse tree.
5107  :type node: \
5108  :py:class:`fparser.two.Fortran2003.Logical_Literal_Constant`
5109  :param parent: parent node of the PSyIR node we are constructing.
5110  :type parent: :py:class:`psyclone.psyir.nodes.Node`
5111 
5112  :returns: PSyIR representation of node.
5113  :rtype: :py:class:`psyclone.psyir.nodes.Literal`
5114 
5115  '''
5116  boolean_type = ScalarType(ScalarType.Intrinsic.BOOLEAN,
5117  get_literal_precision(node, parent))
5118  value = str(node.items[0]).lower()
5119  if value == ".true.":
5120  return Literal("true", boolean_type)
5121  if value == ".false.":
5122  return Literal("false", boolean_type)
5123  raise GenerationError(
5124  f"Expected to find '.true.' or '.false.' as fparser2 logical "
5125  f"literal, but found '{value}' instead.")
5126 
5127  def _call_handler(self, node, parent):
5128  '''Transforms an fparser2 CALL statement into a PSyIR Call node.
5129 
5130  :param node: node in fparser2 parse tree.
5131  :type node: :py:class:`fparser.two.Fortran2003.Call_Stmt`
5132  :param parent: parent node of the PSyIR node we are constructing.
5133  :type parent: :py:class:`psyclone.psyir.nodes.Node`
5134 
5135  :returns: PSyIR representation of node.
5136  :rtype: :py:class:`psyclone.psyir.nodes.Call`
5137 
5138  :raises GenerationError: if the symbol associated with the
5139  name of the call is an unsupported type.
5140 
5141  '''
5142  call = Call(parent=parent)
5143  self.process_nodesprocess_nodes(parent=call, nodes=[node.items[0]])
5144  routine = call.children[0]
5145  # If it's a plain reference, promote the symbol to a RoutineSymbol
5146  # pylint: disable=unidiomatic-typecheck
5147  if type(routine) is Reference:
5148  routine_symbol = routine.symbol
5149  if type(routine_symbol) is Symbol:
5150  # Specialise routine_symbol from a Symbol to a
5151  # RoutineSymbol
5152  routine_symbol.specialise(RoutineSymbol)
5153  elif isinstance(routine_symbol, RoutineSymbol):
5154  # This symbol is already the expected type
5155  pass
5156  else:
5157  raise GenerationError(
5158  f"Expecting the symbol '{routine_symbol.name}', to be of "
5159  f"type 'Symbol' or 'RoutineSymbol', but found "
5160  f"'{type(routine_symbol).__name__}'.")
5161 
5162  return self._process_args_process_args(node, call)
5163 
5164  def _process_args(self, node, call, canonicalise=None):
5165  '''Processes fparser2 call or intrinsic arguments contained in the
5166  node argument and adds them to the PSyIR Call or IntrinsicCall
5167  contained in the call argument, respectively.
5168 
5169  The optional canonicalise function allows the order of the
5170  call's arguments and its named arguments to be re-ordered and
5171  modified to a canonical form so that the PSyIR does not need
5172  to support the different forms that are allowed in
5173  Fortran.
5174 
5175  For example, both sum(a, dim, mask) and sum(dim=dim,
5176  mask=mask, array=a) are equivalant in Fortran. The canonical
5177  form has all required arguments as positional arguments and
5178  all optional arguments as named arguments, which would result
5179  in sum(a, dim=dim, mask=mask) in this case.
5180 
5181  :param node: an fparser call node representing a call or \
5182  an intrinsic call.
5183  :type node: :py:class:`fparser.two.Fortran2003.Call_Stmt` or \
5184  :py:class:`fparser.two.Fortran2003.Intrinsic_Function_Reference`
5185  :param call: a PSyIR call argument representing a call or an \
5186  intrinsic call.
5187  :type call: :py:class:`psyclone.psyir.nodes.Call` or \
5188  :py:class:`psyclone.psyir.nodes.IntrinsicCall`
5189  :param function canonicalise: a function that canonicalises \
5190  the call arguments.
5191 
5192  :returns: the PSyIR call argument with the PSyIR \
5193  representation of the fparser2 node arguments.
5194  :rtype: :py:class:`psyclone.psyir.nodes.Call` or \
5195  :py:class:`psyclone.psyir.nodes.IntrinsicCall`
5196 
5197  :raises GenerationError: if all named arguments do not follow \
5198  all positional arguments.
5199 
5200  '''
5201  arg_nodes = []
5202  arg_names = []
5203  if node.items[1]:
5204  # Store the names of any named args
5205  arg_nodes, arg_names = _get_arg_names(node.items[1].items)
5206 
5207  # Sanity check that all named arguments follow all positional
5208  # arguments. This should be the case but fparser does not
5209  # currently check and this ordering is assumed by the
5210  # canonicalise function. LFRic invokes can cause this
5211  # exception (as they often use name=xxx before the end of the
5212  # argument list), so to avoid this we only check when a
5213  # canonicalise function is supplied (which we know is not the
5214  # case for invokes as they are calls).
5215  if canonicalise:
5216  index = 0
5217  while index < len(arg_names) and not arg_names[index]:
5218  index += 1
5219  for arg_name in arg_names[index:]:
5220  if not arg_name:
5221  raise GenerationError(
5222  f"In Fortran, all named arguments should follow all "
5223  f"positional arguments, but found '{node}'.")
5224 
5225  # Call the canonicalise function if it is supplied. This
5226  # re-orders arg_nodes and renames arg_names appropriately for
5227  # the particular call to make a canonical version. This is
5228  # required because intrinsics can be written with and without
5229  # named arguments (or combinations thereof) in Fortran.
5230  if canonicalise:
5231  canonicalise(arg_nodes, arg_names, node)
5232 
5233  self.process_nodesprocess_nodes(parent=call, nodes=arg_nodes)
5234 
5235  # Detach the arguments and add them again with the argument
5236  # names
5237  arg_list = call.arguments[:]
5238  for child in arg_list:
5239  child.detach()
5240  for idx, child in enumerate(arg_list):
5241  call.append_named_arg(arg_names[idx], child)
5242 
5243  # Point to the original CALL statement in the parse tree.
5244  call.ast = node
5245 
5246  return call
5247 
5248  def _subroutine_handler(self, node, parent):
5249  '''Transforms an fparser2 Subroutine_Subprogram or Function_Subprogram
5250  statement into a PSyIR Routine node.
5251 
5252  :param node: node in fparser2 parse tree.
5253  :type node: :py:class:`fparser.two.Fortran2003.Subroutine_Subprogram`
5254  or :py:class:`fparser.two.Fortran2003.Function_Subprogram`
5255  :param parent: parent node of the PSyIR node being constructed.
5256  :type parent: subclass of :py:class:`psyclone.psyir.nodes.Node`
5257 
5258  :returns: PSyIR representation of node.
5259  :rtype: :py:class:`psyclone.psyir.nodes.Routine`
5260 
5261 
5262  :raises NotImplementedError: if the node contains a Contains clause.
5263  :raises NotImplementedError: if the node contains an ENTRY statement.
5264  :raises NotImplementedError: if an unsupported prefix is found.
5265  :raises SymbolError: if no explicit type information is available for
5266  the return value of a Function.
5267 
5268  '''
5269  try:
5270  _first_type_match(node.children,
5271  Fortran2003.Internal_Subprogram_Part)
5272  raise NotImplementedError("PSyclone doesn't yet support 'Contains'"
5273  " inside a Subroutine or Function")
5274  except ValueError:
5275  pass
5276 
5277  entry_stmts = walk(node, Fortran2003.Entry_Stmt)
5278  if entry_stmts:
5279  raise NotImplementedError(
5280  f"PSyclone does not support routines that contain one or more "
5281  f"ENTRY statements but found '{entry_stmts[0]}'")
5282 
5283  name = node.children[0].children[1].string
5284  routine = Routine(name, parent=parent)
5285  routine._ast = node
5286 
5287  # Deal with any arguments
5288  try:
5289  sub_spec = _first_type_match(node.content,
5290  Fortran2003.Specification_Part)
5291  decl_list = sub_spec.content
5292  except ValueError:
5293  # Subroutine has no Specification_Part so has no
5294  # declarations. Continue with empty list.
5295  decl_list = []
5296 
5297  # TODO this if test can be removed once fparser/#211 is fixed
5298  # such that routine arguments are always contained in a
5299  # Dummy_Arg_List, even if there's only one of them.
5300  if (isinstance(node, (Fortran2003.Subroutine_Subprogram,
5301  Fortran2003.Function_Subprogram)) and
5302  isinstance(node.children[0].children[2],
5303  Fortran2003.Dummy_Arg_List)):
5304  arg_list = node.children[0].children[2].children
5305  else:
5306  # Routine has no arguments
5307  arg_list = []
5308 
5309  self.process_declarationsprocess_declarations(routine, decl_list, arg_list)
5310 
5311  # Check whether the function-stmt has a prefix specifying the
5312  # return type (other prefixes are handled in
5313  # _process_routine_symbols()).
5314  base_type = None
5315  prefix = node.children[0].children[0]
5316  if prefix:
5317  for child in prefix.children:
5318  if isinstance(child, Fortran2003.Prefix_Spec):
5319  if child.string not in SUPPORTED_ROUTINE_PREFIXES:
5320  raise NotImplementedError(
5321  f"Routine has unsupported prefix: {child.string}")
5322  else:
5323  base_type, _ = self._process_type_spec_process_type_spec(routine, child)
5324 
5325  if isinstance(node, Fortran2003.Function_Subprogram):
5326  # Check whether this function-stmt has a suffix containing
5327  # 'RETURNS'
5328  suffix = node.children[0].children[3]
5329  if suffix:
5330  # Although the suffix can, in principle, contain a proc-
5331  # language-binding-spec (e.g. BIND(C, "some_name")), this is
5332  # only valid in an interface block and we are dealing with a
5333  # function-subprogram here.
5334  return_name = suffix.children[0].string
5335  else:
5336  # Otherwise, the return value of the function is given by
5337  # a symbol of the same name.
5338  return_name = name
5339 
5340  # Ensure that we have an explicit declaration for the symbol
5341  # returned by the function.
5342  keep_tag = None
5343  if return_name in routine.symbol_table:
5344  symbol = routine.symbol_table.lookup(return_name)
5345  # If the symbol table still contains a RoutineSymbol
5346  # for the function name (rather than a DataSymbol)
5347  # then there is no explicit declaration within the
5348  # function of the variable used to hold the return
5349  # value.
5350  if isinstance(symbol, RoutineSymbol):
5351  # Remove the RoutineSymbol ready to replace it with a
5352  # DataSymbol.
5353  routine.symbol_table.remove(symbol)
5354  keep_tag = "own_routine_symbol"
5355 
5356  if return_name not in routine.symbol_table:
5357  # There is no existing declaration for the symbol returned by
5358  # the function (because it is specified by the prefix and
5359  # suffix of the function declaration). We add one rather than
5360  # attempt to recreate the prefix. We have to set shadowing to
5361  # True as there is likely to be a RoutineSymbol for this
5362  # function in any enclosing Container.
5363  if not base_type:
5364  # The type of the return value was not specified in the
5365  # function prefix or in a local declaration and therefore
5366  # we have no explicit type information for it. Since we
5367  # default to adding `implicit none` when generating Fortran
5368  # we can't simply put this function into a CodeBlock as the
5369  # generated code won't compile.
5370  raise SymbolError(
5371  f"No explicit return-type information found for "
5372  f"function '{name}'. PSyclone requires that all "
5373  f"symbols be explicitly typed.")
5374 
5375  # First, update the existing RoutineSymbol with the
5376  # return datatype specified in the function
5377  # declaration.
5378 
5379  # Lookup with the routine name as return_name may be
5380  # declared with its own local name. Be wary that this
5381  # function may not be referenced so there might not be
5382  # a RoutineSymbol.
5383  try:
5384  routine_symbol = routine.symbol_table.lookup(routine.name)
5385  routine_symbol.datatype = base_type
5386  except KeyError:
5387  pass
5388 
5389  routine.symbol_table.new_symbol(return_name,
5390  tag=keep_tag,
5391  symbol_type=DataSymbol,
5392  datatype=base_type,
5393  shadowing=True)
5394 
5395  # Update the Routine object with the return symbol.
5396  routine.return_symbol = routine.symbol_table.lookup(return_name)
5397 
5398  try:
5399  sub_exec = _first_type_match(node.content,
5400  Fortran2003.Execution_Part)
5401  except ValueError:
5402  # Routines without any execution statements are still
5403  # valid.
5404  pass
5405  else:
5406  self.process_nodesprocess_nodes(routine, sub_exec.content)
5407 
5408  return routine
5409 
5410  def _main_program_handler(self, node, parent):
5411  '''Transforms an fparser2 Main_Program statement into a PSyIR
5412  Routine node.
5413 
5414  :param node: node in fparser2 parse tree.
5415  :type node: :py:class:`fparser.two.Fortran2003.Main_Program`
5416  :param parent: parent node of the PSyIR node being constructed.
5417  :type parent: subclass of :py:class:`psyclone.psyir.nodes.Node`
5418 
5419  :returns: PSyIR representation of node.
5420  :rtype: :py:class:`psyclone.psyir.nodes.Routine`
5421 
5422  :raises NotImplementedError: if the node contains a Contains clause.
5423  '''
5424  try:
5425  _first_type_match(node.children,
5426  Fortran2003.Internal_Subprogram_Part)
5427  raise NotImplementedError("PSyclone doesn't yet support 'Contains'"
5428  " inside a Program")
5429  except ValueError:
5430  # The Program does not have a CONTAINS block
5431  pass
5432 
5433  name = node.children[0].children[1].string
5434  routine = Routine(name, parent=parent, is_program=True)
5435  routine._ast = node
5436 
5437  try:
5438  prog_spec = _first_type_match(node.content,
5439  Fortran2003.Specification_Part)
5440  decl_list = prog_spec.content
5441  except ValueError:
5442  # program has no Specification_Part so has no
5443  # declarations. Continue with empty list.
5444  decl_list = []
5445  self.process_declarationsprocess_declarations(routine, decl_list, [])
5446 
5447  try:
5448  prog_exec = _first_type_match(node.content,
5449  Fortran2003.Execution_Part)
5450  except ValueError:
5451  # Routines without any execution statements are still
5452  # valid.
5453  pass
5454  else:
5455  self.process_nodesprocess_nodes(routine, prog_exec.content)
5456 
5457  return routine
5458 
5459  def _module_handler(self, node, parent):
5460  '''Transforms an fparser2 Module statement into a PSyIR Container node.
5461 
5462  :param node: fparser2 representation of a module.
5463  :type node: :py:class:`fparser.two.Fortran2003.Module`
5464  :param parent: parent node of the PSyIR node being constructed.
5465  :type parent: subclass of :py:class:`psyclone.psyir.nodes.Node`
5466 
5467  :returns: PSyIR representation of module.
5468  :rtype: :py:class:`psyclone.psyir.nodes.Container`
5469 
5470  '''
5471  # Create a container to capture the module information
5472  mod_name = str(node.children[0].children[1])
5473  container = Container(mod_name, parent=parent)
5474 
5475  # Search for any accessibility statements (e.g. "PUBLIC :: my_var") to
5476  # determine the default accessibility of symbols as well as identifying
5477  # those that are explicitly declared as public or private.
5478  (default_visibility, visibility_map) = self.process_access_statementsprocess_access_statements(
5479  node)
5480  container.symbol_table.default_visibility = default_visibility
5481 
5482  # Create symbols for all routines defined within this module
5483  _process_routine_symbols(node, container.symbol_table, visibility_map)
5484 
5485  # Parse the declarations if it has any
5486  try:
5487  spec_part = _first_type_match(
5488  node.children, Fortran2003.Specification_Part)
5489  except ValueError:
5490  spec_part = None
5491 
5492  if spec_part is not None:
5493  self.process_declarationsprocess_declarations(container, spec_part.children,
5494  [], visibility_map)
5495 
5496  # Parse any module subprograms (subroutine or function)
5497  # skipping the contains node
5498  try:
5499  subprog_part = _first_type_match(
5500  node.children, Fortran2003.Module_Subprogram_Part)
5501  module_subprograms = \
5502  [subprogram for subprogram in subprog_part.children
5503  if not isinstance(subprogram, Fortran2003.Contains_Stmt)]
5504  if module_subprograms:
5505  self.process_nodesprocess_nodes(parent=container, nodes=module_subprograms)
5506  except SymbolError as err:
5507  raise NotImplementedError(str(err.value))
5508  except ValueError:
5509  pass
5510 
5511  return container
5512 
5513  def _program_handler(self, node, parent):
5514  '''Processes an fparser2 Program statement. Program is the top level
5515  node of a complete fparser2 tree and may contain one or more
5516  program-units. This is captured with a FileContainer node.
5517 
5518  :param node: top level node in fparser2 parse tree.
5519  :type node: :py:class:`fparser.two.Fortran2003.Program`
5520  :param parent: parent node of the PSyIR node we are constructing.
5521  :type parent: :py:class:`psyclone.psyir.nodes.Node`
5522 
5523  :returns: PSyIR representation of the program.
5524  :rtype: subclass of :py:class:`psyclone.psyir.nodes.Node`
5525 
5526  '''
5527  # fparser2 does not keep the original filename (if there was
5528  # one) so this can't be provided as the name of the
5529  # FileContainer.
5530  file_container = FileContainer("None", parent=parent)
5531  if len(node.children) == 1 and node.children[0] is None:
5532  # We have an empty file
5533  return file_container
5534  self.process_nodesprocess_nodes(file_container, node.children)
5535  return file_container
5536 
5537 
5538 # For Sphinx AutoAPI documentation generation
5539 __all__ = ["Fparser2Reader"]
def _process_common_blocks(nodes, psyir_parent)
Definition: fparser2.py:2770
def _process_args(self, node, call, canonicalise=None)
Definition: fparser2.py:5164
def _create_ifblock_for_select_type_content(self, parent, select_type, type_string_symbol, pointer_symbols)
Definition: fparser2.py:3512
def _create_child(self, child, parent=None)
Definition: fparser2.py:2933
def get_routine_schedules(self, name, module_ast)
Definition: fparser2.py:1273
def _create_loop(self, parent, variable)
Definition: fparser2.py:3134
def _process_case_value_list(self, selector, nodes, parent)
Definition: fparser2.py:4027
def _parse_dimensions(dimensions, symbol_table)
Definition: fparser2.py:1341
def _process_interface_block(self, node, symbol_table, visibility_map)
Definition: fparser2.py:2403
def _process_derived_type_decln(self, parent, decl, visibility_map)
Definition: fparser2.py:2165
def _process_case_value(self, selector, node, parent)
Definition: fparser2.py:4076
def _process_decln(self, scope, symbol_table, decl, visibility_map=None, statics_list=())
Definition: fparser2.py:1879
def _create_select_type(parent, select_type, type_string_name=None)
Definition: fparser2.py:3612
def _process_parameter_stmts(self, nodes, parent)
Definition: fparser2.py:2346
def _array_syntax_to_indexed(self, parent, loop_vars)
Definition: fparser2.py:4222
def _create_bounded_loop(self, parent, variable, limits_list)
Definition: fparser2.py:3150
def _process_precision(type_spec, psyir_parent)
Definition: fparser2.py:2825
def _process_use_stmts(parent, nodes, visibility_map=None)
Definition: fparser2.py:1653
def process_declarations(self, parent, nodes, arg_list, visibility_map=None)
Definition: fparser2.py:2506
def _get_partial_datatype(self, node, scope, visibility_map)
Definition: fparser2.py:2273
def nodes_to_code_block(parent, fp2_nodes, message=None)
Definition: fparser2.py:1156
def _process_type_spec(self, parent, type_spec)
Definition: fparser2.py:1790