Reference Guide  2.5.0
fortran.py
1 # -----------------------------------------------------------------------------
2 # BSD 3-Clause License
3 #
4 # Copyright (c) 2019-2024, Science and Technology Facilities Council.
5 # All rights reserved.
6 #
7 # Redistribution and use in source and binary forms, with or without
8 # modification, are permitted provided that the following conditions are met:
9 #
10 # * Redistributions of source code must retain the above copyright notice, this
11 # list of conditions and the following disclaimer.
12 #
13 # * Redistributions in binary form must reproduce the above copyright notice,
14 # this list of conditions and the following disclaimer in the documentation
15 # and/or other materials provided with the distribution.
16 #
17 # * Neither the name of the copyright holder nor the names of its
18 # contributors may be used to endorse or promote products derived from
19 # this software without specific prior written permission.
20 #
21 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24 # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
25 # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
27 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
29 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
31 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32 # POSSIBILITY OF SUCH DAMAGE.
33 # -----------------------------------------------------------------------------
34 # Authors R. W. Ford and S. Siso, STFC Daresbury Lab
35 # Modified J. Henrichs, Bureau of Meteorology
36 # Modified A. R. Porter, A. B. G. Chalk and N. Nobre, STFC Daresbury Lab
37 # Modified J. Remy, Université Grenoble Alpes, Inria
38 
39 '''PSyIR Fortran backend. Implements a visitor that generates Fortran code
40 from a PSyIR tree. '''
41 
42 # pylint: disable=too-many-lines
43 from psyclone.core import Signature
44 from psyclone.errors import GenerationError, InternalError
45 from psyclone.psyir.backend.language_writer import LanguageWriter
46 from psyclone.psyir.backend.visitor import VisitorError
48  Fparser2Reader, TYPE_MAP_FROM_FORTRAN)
49 from psyclone.psyir.nodes import (
50  BinaryOperation, Call, CodeBlock, DataNode, IntrinsicCall, Literal,
51  Operation, Range, Routine, Schedule, UnaryOperation)
52 from psyclone.psyir.symbols import (
53  ArgumentInterface, ArrayType, ContainerSymbol, DataSymbol, DataTypeSymbol,
54  GenericInterfaceSymbol, IntrinsicSymbol, PreprocessorInterface,
55  RoutineSymbol, ScalarType, StructureType, Symbol, SymbolTable,
56  UnresolvedInterface, UnresolvedType, UnsupportedFortranType,
57  UnsupportedType, )
58 
59 
60 # Mapping from PSyIR types to Fortran data types. Simply reverse the
61 # map from the frontend, removing the special case of "double
62 # precision", which is captured as a REAL intrinsic in the PSyIR.
63 TYPE_MAP_TO_FORTRAN = {}
64 for key, item in TYPE_MAP_FROM_FORTRAN.items():
65  if key != "double precision":
66  TYPE_MAP_TO_FORTRAN[item] = key
67 
68 
69 def gen_intent(symbol):
70  '''Given a DataSymbol instance as input, determine the Fortran intent that
71  the DataSymbol should have and return the value as a string.
72 
73  :param symbol: the symbol instance.
74  :type symbol: :py:class:`psyclone.psyir.symbols.DataSymbol`
75 
76  :returns: the Fortran intent of the symbol instance in lower case, \
77  or None if the access is unknown or if this is a local variable.
78  :rtype: str or NoneType
79 
80  '''
81  mapping = {ArgumentInterface.Access.UNKNOWN: None,
82  ArgumentInterface.Access.READ: "in",
83  ArgumentInterface.Access.WRITE: "out",
84  ArgumentInterface.Access.READWRITE: "inout"}
85 
86  if symbol.is_argument:
87  try:
88  return mapping[symbol.interface.access]
89  except KeyError as excinfo:
90  raise VisitorError(
91  f"Unsupported access '{excinfo}' found.") from excinfo
92  return None # non-Arguments do not have intent
93 
94 
95 def gen_datatype(datatype, name):
96  '''Given a DataType instance as input, return the Fortran datatype
97  of the symbol including any specific precision properties.
98 
99  :param datatype: the DataType or DataTypeSymbol describing the type of \
100  the declaration.
101  :type datatype: :py:class:`psyclone.psyir.symbols.DataType` or \
102  :py:class:`psyclone.psyir.symbols.DataTypeSymbol`
103  :param str name: the name of the symbol being declared (only used for \
104  error messages).
105 
106  :returns: the Fortran representation of the symbol's datatype \
107  including any precision properties.
108  :rtype: str
109 
110  :raises NotImplementedError: if the symbol has an unsupported \
111  datatype.
112  :raises VisitorError: if the symbol specifies explicit precision \
113  and this is not supported for the datatype.
114  :raises VisitorError: if the size of the explicit precision is not \
115  supported for the datatype.
116  :raises VisitorError: if the size of the symbol is specified by \
117  another variable and the datatype is not one that supports the \
118  Fortran KIND option.
119  :raises NotImplementedError: if the type of the precision object \
120  is an unsupported type.
121 
122  '''
123  if isinstance(datatype, DataTypeSymbol):
124  # Symbol is of derived type
125  return f"type({datatype.name})"
126 
127  if (isinstance(datatype, ArrayType) and
128  isinstance(datatype.intrinsic, DataTypeSymbol)):
129  # Symbol is an array of derived types
130  return f"type({datatype.intrinsic.name})"
131 
132  try:
133  fortrantype = TYPE_MAP_TO_FORTRAN[datatype.intrinsic]
134  except KeyError as error:
135  raise NotImplementedError(
136  f"Unsupported datatype '{datatype.intrinsic}' for symbol '{name}' "
137  f"found in gen_datatype().") from error
138 
139  precision = datatype.precision
140 
141  if isinstance(precision, int):
142  if fortrantype not in ['real', 'integer', 'logical']:
143  raise VisitorError(f"Explicit precision not supported for datatype"
144  f" '{fortrantype}' in symbol '{name}' in "
145  f"Fortran backend.")
146  if fortrantype == 'real' and precision not in [4, 8, 16]:
147  raise VisitorError(
148  f"Datatype 'real' in symbol '{name}' supports fixed precision "
149  f"of [4, 8, 16] but found '{precision}'.")
150  if fortrantype in ['integer', 'logical'] and precision not in \
151  [1, 2, 4, 8, 16]:
152  raise VisitorError(
153  f"Datatype '{fortrantype}' in symbol '{name}' supports fixed "
154  f"precision of [1, 2, 4, 8, 16] but found '{precision}'.")
155  # Precision has an an explicit size. Use the "type*size" Fortran
156  # extension for simplicity. We could have used
157  # type(kind=selected_int|real_kind(size)) or, for Fortran 2008,
158  # ISO_FORTRAN_ENV; type(type64) :: MyType.
159  return f"{fortrantype}*{precision}"
160 
161  if isinstance(precision, ScalarType.Precision):
162  # The precision information is not absolute so is either
163  # machine specific or is specified via the compiler. Fortran
164  # only distinguishes relative precision for single and double
165  # precision reals.
166  if fortrantype.lower() == "real" and \
167  precision == ScalarType.Precision.DOUBLE:
168  return "double precision"
169  # This logging warning can be added when issue #11 is
170  # addressed.
171  # import logging
172  # logging.warning(
173  # "Fortran does not support relative precision for the '%s' "
174  # "datatype but '%s' was specified for variable '%s'.",
175  # datatype, str(symbol.precision), symbol.name)
176  return fortrantype
177 
178  if isinstance(precision, DataSymbol):
179  if fortrantype not in ["real", "integer", "logical"]:
180  raise VisitorError(
181  f"kind not supported for datatype '{fortrantype}' in symbol "
182  f"'{name}' in Fortran backend.")
183  # The precision information is provided by a parameter, so use KIND.
184  return f"{fortrantype}(kind={precision.name})"
185 
186  raise VisitorError(
187  f"Unsupported precision type '{type(precision).__name__}' found for "
188  f"symbol '{name}' in Fortran backend.")
189 
190 
191 def precedence(fortran_operator):
192  '''Determine the relative precedence of the supplied Fortran operator.
193  Relative Operator precedence is taken from the Fortran 2008
194  specification document and encoded as a list.
195 
196  :param str fortran_operator: the supplied Fortran operator.
197 
198  :returns: an integer indicating the relative precedence of the \
199  supplied Fortran operator. The higher the value, the higher \
200  the precedence.
201 
202  :raises KeyError: if the supplied operator is not in the \
203  precedence list.
204 
205  '''
206  # The index of the fortran_precedence list indicates relative
207  # precedence. Strings within sub-lists have the same precedence
208  # apart from the following two caveats. 1) unary + and - have
209  # a higher precedence than binary + and -, e.g. -(a-b) !=
210  # -a-b and 2) floating point operations are not actually
211  # associative due to rounding errors, e.g. potentially (a * b) / c
212  # != a * (b / c). Therefore, if a particular ordering is specified
213  # then it should be respected. These issues are dealt with in the
214  # binaryoperation handler.
215  fortran_precedence = [
216  ['.EQV.', '.NEQV.'],
217  ['.OR.'],
218  ['.AND.'],
219  ['.NOT.'],
220  ['.EQ.', '.NE.', '.LT.', '.LE.', '.GT.', '.GE.', '==', '/=', '<',
221  '<=', '>', '>='],
222  ['//'],
223  ['+', '-'],
224  ['*', '/'],
225  ['**']]
226  for oper_list in fortran_precedence:
227  if fortran_operator in oper_list:
228  return fortran_precedence.index(oper_list)
229  raise KeyError()
230 
231 
232 def add_accessibility_to_unsupported_declaration(symbol):
233  '''
234  Utility that manipulates the UnsupportedFortranType declaration for the
235  supplied Symbol so as to ensure that it has the correct accessibility
236  specifier.
237  (This is required because we capture an UnsupportedFortranType declaration
238  as is and this may or may not include accessibility information.)
239 
240  :param symbol: the symbol for which the declaration is required.
241  :type symbol: :py:class:`psyclone.psyir.symbols.Symbol`
242 
243  :returns: Fortran declaration of the supplied symbol with accessibility \
244  information included (public/private).
245  :rtype: str
246 
247  :raises TypeError: if the supplied argument is not a Symbol of \
248  UnsupportedFortranType.
249  :raises InternalError: if the declaration associated with the Symbol is \
250  empty.
251  :raises NotImplementedError: if the original declaration does not use \
252  '::' to separate the entity name from its type.
253  :raises InternalError: if the declaration stored for the supplied symbol \
254  contains accessibility information which does not match the \
255  visibility of the supplied symbol.
256 
257  '''
258  if not isinstance(symbol, Symbol):
259  raise TypeError(f"Expected a Symbol but got '{type(symbol).__name__}'")
260 
261  if not isinstance(symbol.datatype, UnsupportedFortranType):
262  raise TypeError(f"Expected a Symbol of UnsupportedFortranType but "
263  f"symbol '{symbol.name}' has type '{symbol.datatype}'")
264 
265  if not symbol.datatype.declaration:
266  raise InternalError(
267  f"Symbol '{symbol.name}' is of UnsupportedFortranType but the "
268  f"associated declaration text is empty.")
269 
270  # The original declaration text is obtained from fparser2 and will
271  # already have had any line-continuation symbols removed.
272  first_line = symbol.datatype.declaration.split("\n")[0]
273  if "::" not in first_line:
274  raise NotImplementedError(
275  f"Cannot add accessibility information to an UnsupportedFortran"
276  f"Type that does not have '::' in its original declaration: "
277  f"'{symbol.datatype.declaration}'")
278 
279  parts = symbol.datatype.declaration.split("::")
280  first_part = parts[0].lower()
281  if symbol.visibility == Symbol.Visibility.PUBLIC:
282  if "public" not in first_part:
283  if "private" in first_part:
284  raise InternalError(
285  f"Symbol '{symbol.name}' of UnsupportedFortranType has "
286  f"public visibility but its associated declaration "
287  f"specifies that it is private: "
288  f"'{symbol.datatype.declaration}'")
289  first_part = first_part.rstrip() + ", public "
290  else:
291  if "private" not in first_part:
292  if "public" in first_part:
293  raise InternalError(
294  f"Symbol '{symbol.name}' of UnsupportedFortranType has "
295  f"private visibility but its associated declaration "
296  f"specifies that it is public: "
297  f"'{symbol.datatype.declaration}'")
298  first_part = first_part.rstrip() + ", private "
299  return "::".join([first_part]+parts[1:])
300 
301 
303  # pylint: disable=too-many-public-methods
304  '''Implements a PSyIR-to-Fortran back end for PSyIR kernel code (not
305  currently PSyIR algorithm code which has its own gen method for
306  generating Fortran).
307 
308  :param bool skip_nodes: If skip_nodes is False then an exception \
309  is raised if a visitor method for a PSyIR node has not been \
310  implemented, otherwise the visitor silently continues. This is an \
311  optional argument which defaults to False.
312  :param str indent_string: Specifies what to use for indentation. This \
313  is an optional argument that defaults to two spaces.
314  :param int initial_indent_depth: Specifies how much indentation to \
315  start with. This is an optional argument that defaults to 0.
316  :param bool check_global_constraints: whether or not to validate all \
317  global constraints when walking the tree. Defaults to True.
318 
319  '''
320  _COMMENT_PREFIX = "! "
321 
322  def __init__(self, skip_nodes=False, indent_string=" ",
323  initial_indent_depth=0, check_global_constraints=True):
324  # Construct the base class using () as array parenthesis, and
325  # % as structure access symbol
326  super().__init__(("(", ")"), "%", skip_nodes,
327  indent_string,
328  initial_indent_depth,
329  check_global_constraints)
330  # Reverse the Fparser2Reader maps that are used to convert from
331  # Fortran operator names to PSyIR operator names.
332  self._operator_2_str_operator_2_str = {}
333  self._reverse_map_reverse_map(self._operator_2_str_operator_2_str,
334  Fparser2Reader.unary_operators)
335  self._reverse_map_reverse_map(self._operator_2_str_operator_2_str,
336  Fparser2Reader.binary_operators)
337 
338  # Create and store a CallTreeUtils instance for use when ordering
339  # parameter declarations. Have to import it here as CallTreeUtils
340  # also uses this Fortran backend.
341  # pylint: disable=import-outside-toplevel
342  from psyclone.psyir.tools.call_tree_utils import CallTreeUtils
343  self._call_tree_utils_call_tree_utils = CallTreeUtils()
344 
345  @staticmethod
346  def _reverse_map(reverse_dict, op_map):
347  '''
348  Reverses the supplied fortran2psyir mapping to make a psyir2fortran
349  mapping. Any key that does already exist in `reverse_dict`
350  is not overwritten, only new keys are added.
351 
352  :param reverse_dict: the dictionary to which the new mapping of \
353  operator to string is added.
354  :type reverse_dict: dict from \
355  :py:class:`psyclone.psyir.nodes.BinaryOperation`, \
356  :py:class:`psyclone.psyir.nodes.NaryOperation` or \
357  :py:class:`psyclone.psyir.nodes.UnaryOperation` to str
358 
359  :param op_map: mapping from string representation of operator to \
360  enumerated type.
361  :type op_map: :py:class:`collections.OrderedDict`
362 
363  '''
364  for operator in op_map:
365  mapping_key = op_map[operator]
366  mapping_value = operator
367  # Only choose the first mapping value when there is more
368  # than one.
369  if mapping_key not in reverse_dict:
370  reverse_dict[mapping_key] = mapping_value.upper()
371 
372  def get_operator(self, operator):
373  '''Determine the Fortran operator that is equivalent to the provided
374  PSyIR operator. This is achieved by reversing the Fparser2Reader
375  maps that are used to convert from Fortran operator names to PSyIR
376  operator names.
377 
378  :param operator: a PSyIR operator.
379  :type operator: :py:class:`psyclone.psyir.nodes.Operation.Operator`
380 
381  :returns: the Fortran operator.
382  :rtype: str
383 
384  :raises KeyError: if the supplied operator is not known.
385 
386  '''
387  return self._operator_2_str_operator_2_str[operator]
388 
389  def gen_indices(self, indices, var_name=None):
390  '''Given a list of PSyIR nodes representing the dimensions of an
391  array, return a list of strings representing those array dimensions.
392  This is used both for array references and array declarations. Note
393  that 'indices' can also be a shape in case of Fortran.
394 
395  :param indices: list of PSyIR nodes.
396  :type indices: list of :py:class:`psyclone.psyir.symbols.Node`
397  :param str var_name: name of the variable for which the dimensions \
398  are created. Not used in the Fortran implementation.
399 
400  :returns: the Fortran representation of the dimensions.
401  :rtype: list of str
402 
403  :raises NotImplementedError: if the format of the dimension is not \
404  supported.
405 
406  '''
407  dims = []
408  for index in indices:
409  if isinstance(index, (DataNode, Range)):
410  # literal constant, symbol reference, or computed
411  # dimension
412  expression = self._visit_visit(index)
413  dims.append(expression)
414  elif isinstance(index, ArrayType.ArrayBounds):
415  # Lower and upper bounds of an array declaration specified
416  # by literal constant, symbol reference, or computed dimension
417  lower_expression = self._visit_visit(index.lower)
418  if isinstance(index.upper, ArrayType.Extent):
419  # We have an assumed-shape array (R514) where only the
420  # lower bound is specified.
421  upper_expression = ""
422  else:
423  upper_expression = self._visit_visit(index.upper)
424  if lower_expression == "1":
425  # Lower bound of 1 is the default in Fortran
426  dims.append(upper_expression)
427  else:
428  dims.append(lower_expression+":"+upper_expression)
429  elif isinstance(index, ArrayType.Extent):
430  # unknown extent
431  dims.append(":")
432  else:
433  raise NotImplementedError(
434  f"unsupported gen_indices index '{index}'")
435  return dims
436 
437  def gen_use(self, symbol, symbol_table):
438  ''' Performs consistency checks and then creates and returns the
439  Fortran use statement(s) for this ContainerSymbol as required for
440  the supplied symbol table.
441 
442  :param symbol: the container symbol instance.
443  :type symbol: :py:class:`psyclone.psyir.symbols.ContainerSymbol`
444  :param symbol_table: the symbol table containing this container symbol.
445  :type symbol_table: :py:class:`psyclone.psyir.symbols.SymbolTable`
446 
447  :returns: the Fortran use statement(s) as a string.
448  :rtype: str
449 
450  :raises VisitorError: if the symbol argument is not a ContainerSymbol.
451  :raises VisitorError: if the symbol_table argument is not a \
452  SymbolTable.
453  :raises VisitorError: if the supplied symbol is not in the supplied \
454  SymbolTable.
455  :raises VisitorError: if the supplied symbol has the same name as an \
456  entry in the SymbolTable but is a different object.
457  '''
458  if not isinstance(symbol, ContainerSymbol):
459  raise VisitorError(
460  f"gen_use() expects a ContainerSymbol as its first argument "
461  f"but got '{type(symbol).__name__}'")
462  if not isinstance(symbol_table, SymbolTable):
463  raise VisitorError(
464  f"gen_use() expects a SymbolTable as its second argument but "
465  f"got '{type(symbol_table).__name__}'")
466  if symbol.name not in symbol_table:
467  raise VisitorError(
468  f"gen_use() - the supplied symbol ('{symbol.name}') is not"
469  f" in the supplied SymbolTable.")
470  if symbol_table.lookup(symbol.name) is not symbol:
471  raise VisitorError(
472  f"gen_use() - the supplied symbol ('{symbol.name}') is not "
473  f"the same object as the entry with that name in the supplied "
474  f"SymbolTable.")
475 
476  # Construct the list of symbol names for the ONLY clause
477  only_list = []
478  for dsym in symbol_table.symbols_imported_from(symbol):
479  if dsym.interface.orig_name:
480  # This variable is renamed on import. Use Fortran's
481  # 'new_name=>orig_name' syntax to reflect this.
482  only_list.append(f"{dsym.name}=>{dsym.interface.orig_name}")
483  else:
484  # This variable is not renamed.
485  only_list.append(dsym.name)
486 
487  # Finally construct the use statements for this Container (module)
488  if not only_list and not symbol.wildcard_import:
489  # We have a "use xxx, only:" - i.e. an empty only list
490  return f"{self._nindent}use {symbol.name}, only :\n"
491  if only_list and not symbol.wildcard_import:
492  return f"{self._nindent}use {symbol.name}, only : " + \
493  ", ".join(sorted(only_list)) + "\n"
494 
495  return f"{self._nindent}use {symbol.name}\n"
496 
497  def gen_vardecl(self, symbol, include_visibility=False):
498  '''Create and return the Fortran variable declaration for this Symbol
499  or derived-type member.
500 
501  :param symbol: the symbol or member instance.
502  :type symbol: :py:class:`psyclone.psyir.symbols.DataSymbol` or
503  :py:class:`psyclone.psyir.nodes.MemberReference`
504  :param bool include_visibility: whether to include the visibility of
505  the symbol in the generated declaration (default False).
506 
507  :returns: the Fortran variable declaration as a string.
508  :rtype: str
509 
510  :raises VisitorError: if the symbol is of UnresolvedType.
511  :raises VisitorError: if the symbol is of UnsupportedType other than
512  UnsupportedFortranType.
513  :raises VisitorError: if the symbol is of known type but does not
514  specify a variable declaration (it is not a local declaration or
515  an argument declaration).
516  :raises VisitorError: if the symbol is a runtime constant but does not
517  have a StaticInterface.
518  :raises InternalError: if the symbol is a ContainerSymbol or an import.
519  :raises InternalError: if the symbol is a RoutineSymbol other than
520  UnsupportedFortranType.
521  :raises InternalError: if visibility is to be included but is not
522  either PUBLIC or PRIVATE.
523 
524  '''
525  # pylint: disable=too-many-branches
526  if isinstance(symbol.datatype, UnresolvedType):
527  raise VisitorError(f"Symbol '{symbol.name}' has a UnresolvedType "
528  f"and we can not generate a declaration for "
529  f"UnresolvedTypes.")
530  if isinstance(symbol, ContainerSymbol) or \
531  isinstance(symbol, Symbol) and symbol.is_import:
532  raise InternalError(f"Symbol '{symbol.name}' is brought into scope"
533  f" from a Fortran USE statement and should be "
534  f"generated by 'gen_use' instead of "
535  f"'gen_vardecl'.")
536  if (isinstance(symbol, RoutineSymbol) and
537  not isinstance(symbol.datatype, UnsupportedFortranType)):
538  raise InternalError(f"Symbol '{symbol.name}' is a RoutineSymbol "
539  f"which is not imported or of "
540  f"UnsupportedFortranType. This is already "
541  f"implicitly declared by the routine itself "
542  f"and should not be provided to 'gen_vardecl'."
543  )
544 
545  # Whether we're dealing with an array declaration and, if so, the
546  # shape of that array.
547  if isinstance(symbol.datatype, ArrayType):
548  array_shape = symbol.datatype.shape
549  else:
550  array_shape = []
551 
552  if isinstance(symbol.datatype, UnsupportedType):
553  if isinstance(symbol.datatype, UnsupportedFortranType):
554 
555  if (include_visibility and
556  not isinstance(symbol, RoutineSymbol) and
557  not symbol.name.startswith("_PSYCLONE_INTERNAL")):
558  # We don't attempt to add accessibility to RoutineSymbols
559  # or to those created by PSyclone to handle named common
560  # blocks appearing in SAVE statements.
561  decln = add_accessibility_to_unsupported_declaration(
562  symbol)
563  return f"{self._nindent}{decln}\n"
564 
565  decln = symbol.datatype.declaration
566  return f"{self._nindent}{decln}\n"
567  # The Fortran backend only handles UnsupportedFortranType
568  # declarations.
569  raise VisitorError(
570  f"{type(symbol).__name__} '{symbol.name}' is of "
571  f"'{type(symbol.datatype).__name__}' type. This is not "
572  f"supported by the Fortran backend.")
573 
574  datatype = gen_datatype(symbol.datatype, symbol.name)
575  result = f"{self._nindent}{datatype}"
576 
577  if ArrayType.Extent.DEFERRED in array_shape:
578  # A 'deferred' array extent means this is an allocatable array
579  result += ", allocatable"
580 
581  # Specify Fortran attributes
582  if array_shape:
583  dims = self.gen_indicesgen_indicesgen_indices(array_shape)
584  result += ", dimension(" + ",".join(dims) + ")"
585 
586  if isinstance(symbol, DataSymbol) and symbol.is_argument:
587  intent = gen_intent(symbol)
588  if intent:
589  result += f", intent({intent})"
590 
591  if isinstance(symbol, DataSymbol) and symbol.is_constant:
592  result += ", parameter"
593  elif isinstance(symbol, DataSymbol) and symbol.is_static:
594  # This condition is an elif because SAVE and PARAMETER are
595  # incompatible, but we let PARAMETER take precedence because
596  # a parameter is already behaving like a static value
597  result += ", save"
598 
599  if include_visibility:
600  if symbol.visibility == Symbol.Visibility.PRIVATE:
601  result += ", private"
602  elif symbol.visibility == Symbol.Visibility.PUBLIC:
603  result += ", public"
604  else:
605  raise InternalError(
606  f"A Symbol must be either public or private but symbol "
607  f"'{symbol.name}' has visibility '{symbol.visibility}'")
608 
609  # Specify name
610  result += f" :: {symbol.name}"
611 
612  # Specify initialisation expression
613  if (isinstance(symbol, StructureType.ComponentType) and
614  symbol.initial_value):
615  result += " = " + self._visit_visit(symbol.initial_value)
616  elif isinstance(symbol, DataSymbol) and symbol.initial_value:
617  if not symbol.is_static:
618  raise VisitorError(
619  f"{type(symbol).__name__} '{symbol.name}' has an initial "
620  f"value ({self._visit(symbol.initial_value)}) and "
621  f"therefore (in Fortran) must have a StaticInterface. "
622  f"However it has an interface of '{symbol.interface}'.")
623  result += " = " + self._visit_visit(symbol.initial_value)
624 
625  return result + "\n"
626 
627  def gen_interfacedecl(self, symbol):
628  '''
629  Generate the declaration for a generic interface.
630 
631  Since a GenericInterfaceSymbol is a subclass of RoutineSymbol, any
632  necessary accessibility statement will be generated in
633  gen_access_stmts().
634 
635  :param symbol: the GenericInterfaceSymbol to be declared.
636  :type symbol: :py:class:`psyclone.psyir.symbols.GenericInterfaceSymbol`
637 
638  :returns: the corresponding Fortran declaration.
639  :rtype: str
640 
641  :raises InternalError: if passed something that is not a
642  GenericInterfaceSymbol.
643 
644  '''
645  if not isinstance(symbol, GenericInterfaceSymbol):
646  raise InternalError(
647  f"gen_interfacedecl only supports 'GenericInterfaceSymbol's "
648  f"but got '{type(symbol).__name__}'")
649 
650  decln = f"{self._nindent}interface {symbol.name}\n"
651  self._depth_depth += 1
652  # Any module procedures.
653  routines = ", ".join([rsym.name for rsym in symbol.container_routines])
654  if routines:
655  decln += f"{self._nindent}module procedure :: {routines}\n"
656  # Any other (external) procedures.
657  routines = ", ".join([rsym.name for rsym in symbol.external_routines])
658  if routines:
659  decln += f"{self._nindent}procedure :: {routines}\n"
660  self._depth_depth -= 1
661  decln += f"{self._nindent}end interface {symbol.name}\n"
662 
663  return decln
664 
665  def gen_typedecl(self, symbol, include_visibility=True):
666  '''
667  Creates a derived-type declaration for the supplied DataTypeSymbol.
668 
669  :param symbol: the derived-type to declare.
670  :type symbol: :py:class:`psyclone.psyir.symbols.DataTypeSymbol`
671  :param bool include_visibility: whether or not to include visibility
672  information in the declaration. (Default is True.)
673 
674  :returns: the Fortran declaration of the derived type.
675  :rtype: str
676 
677  :raises VisitorError: if the supplied symbol is not a DataTypeSymbol.
678  :raises VisitorError: if the datatype of the symbol is of
679  UnsupportedType but is not of UnsupportedFortranType.
680  :raises InternalError: if include_visibility is True and the
681  visibility of the symbol is not of the correct type.
682  :raises VisitorError: if the supplied symbol is of UnresolvedType.
683 
684  '''
685  if not isinstance(symbol, DataTypeSymbol):
686  raise VisitorError(
687  f"gen_typedecl expects a DataTypeSymbol as argument but "
688  f"got: '{type(symbol).__name__}'")
689 
690  if isinstance(symbol.datatype, UnsupportedType):
691  if isinstance(symbol.datatype, UnsupportedFortranType):
692  # This is a declaration of UnsupportedType. We have to ensure
693  # that its visibility is correctly specified though.
694  if include_visibility:
695  decln = add_accessibility_to_unsupported_declaration(
696  symbol)
697  else:
698  decln = symbol.datatype.declaration
699  return f"{self._nindent}{decln}\n"
700 
701  raise VisitorError(
702  f"Fortran backend cannot generate code for symbol "
703  f"'{symbol.name}' of type '{type(symbol.datatype).__name__}'")
704 
705  result = f"{self._nindent}type"
706 
707  if include_visibility:
708  if symbol.visibility == Symbol.Visibility.PRIVATE:
709  result += ", private"
710  elif symbol.visibility == Symbol.Visibility.PUBLIC:
711  result += ", public"
712  else:
713  raise InternalError(
714  f"A Symbol's visibility must be one of Symbol.Visibility."
715  f"PRIVATE/PUBLIC but '{symbol.name}' has visibility of "
716  f"type '{type(symbol.visibility).__name__}'")
717  result += f" :: {symbol.name}\n"
718 
719  if isinstance(symbol.datatype, UnresolvedType):
720  raise VisitorError(
721  f"Local Symbol '{symbol.name}' is of UnresolvedType and "
722  f"therefore no declaration can be created for it. Should it "
723  f"have an ImportInterface?")
724 
725  self._depth_depth += 1
726 
727  for member in symbol.datatype.components.values():
728  # We can only specify the visibility of components within
729  # a derived type if the declaration is within the specification
730  # part of a module.
731  result += self.gen_vardeclgen_vardecl(member,
732  include_visibility=include_visibility)
733  self._depth_depth -= 1
734 
735  result += f"{self._nindent}end type {symbol.name}\n"
736  return result
737 
738  def gen_default_access_stmt(self, symbol_table):
739  '''
740  Generates the access statement for a module - either "private" or
741  "public". Although the PSyIR captures the visibility of every Symbol
742  explicitly, this information is required in order
743  to ensure the correct visibility of symbols that have been imported
744  into the current module from another one using a wildcard import
745  (i.e. a `use` without an `only` clause) and also for those Symbols
746  that are of UnsupportedFortranType (because their declaration may or
747  may not include visibility information).
748 
749  :returns: text containing the access statement line.
750  :rtype: str
751 
752  :raises InternalError: if the symbol table has an invalid default \
753  visibility.
754  '''
755  # If no default visibility has been set then we use the Fortran
756  # default of public.
757  if symbol_table.default_visibility in [None, Symbol.Visibility.PUBLIC]:
758  return self._nindent_nindent + "public\n"
759  if symbol_table.default_visibility == Symbol.Visibility.PRIVATE:
760  return self._nindent_nindent + "private\n"
761 
762  raise InternalError(
763  f"Unrecognised visibility ('{symbol_table.default_visibility}') "
764  f"found when attempting to generate access statement. Should be "
765  f"either 'Symbol.Visibility.PUBLIC' or "
766  f"'Symbol.Visibility.PRIVATE'\n")
767 
768  def gen_access_stmts(self, symbol_table):
769  '''
770  Creates the accessibility statements (R518) for any routine or
771  imported symbols in the supplied symbol table.
772 
773  :param symbol_table: the symbol table for which to generate \
774  accessibility statements.
775  :type symbol_table: :py:class:`psyclone.psyir.symbols.SymbolTable`
776 
777  :returns: the accessibility statements for any routine or imported \
778  symbols.
779  :rtype: str
780 
781  '''
782  # Find the symbol that represents itself, this one will not need
783  # an accessibility statement
784  try:
785  itself = symbol_table.lookup_with_tag('own_routine_symbol')
786  except KeyError:
787  itself = None
788 
789  public_symbols = []
790  private_symbols = []
791  for symbol in symbol_table.symbols:
792  if (isinstance(symbol, RoutineSymbol) or
793  symbol.is_unresolved or symbol.is_import):
794 
795  # Skip the symbol representing the routine where these
796  # declarations belong
797  if isinstance(symbol, RoutineSymbol) and symbol is itself:
798  continue
799 
800  # It doesn't matter whether this symbol has a local or import
801  # interface - its accessibility in *this* context is determined
802  # by the local accessibility statements. e.g. if we are
803  # dealing with the declarations in a given module which itself
804  # uses a public symbol from some other module, the
805  # accessibility of that symbol is determined by the
806  # accessibility statements in the current module.
807  if (symbol_table.default_visibility in
808  [None, Symbol.Visibility.PUBLIC]):
809  if symbol.visibility == Symbol.Visibility.PRIVATE:
810  # Default vis. is public but this symbol is private
811  private_symbols.append(symbol.name)
812  else:
813  if symbol.visibility == Symbol.Visibility.PUBLIC:
814  # Default vis. is private but this symbol is public
815  public_symbols.append(symbol.name)
816 
817  result = "\n"
818  if public_symbols:
819  result += f"{self._nindent}public :: {', '.join(public_symbols)}\n"
820  if private_symbols:
821  result += (f"{self._nindent}private :: "
822  f"{', '.join(private_symbols)}\n")
823 
824  if len(result) > 1:
825  return result
826  return ""
827 
828  # pylint: disable=too-many-branches
829  def _gen_parameter_decls(self, symbol_table, is_module_scope=False):
830  ''' Create the declarations of all parameters present in the supplied
831  symbol table. Declarations are ordered so as to satisfy any inter-
832  dependencies between them.
833 
834  :param symbol_table: the SymbolTable instance.
835  :type symbol: :py:class:`psyclone.psyir.symbols.SymbolTable`
836  :param bool is_module_scope: whether or not the declarations are in \
837  a module scoping unit. Default is False.
838 
839  :returns: Fortran code declaring all parameters.
840  :rtype: str
841 
842  :raises VisitorError: if there is no way of resolving \
843  interdependencies between parameter declarations.
844 
845  '''
846  declarations = ""
847  local_constants = []
848  for sym in symbol_table.datasymbols:
849  if sym.is_import or sym.is_unresolved:
850  continue # Skip, these don't need declarations
851  if sym.is_constant:
852  local_constants.append(sym)
853 
854  # There may be dependencies between these constants so setup a dict
855  # listing the required inputs for each one.
856  decln_inputs = {}
857  # Avoid circular dependency
858  # pylint: disable=import-outside-toplevel
859  from psyclone.psyir.tools.read_write_info import ReadWriteInfo
860  for symbol in local_constants:
861  decln_inputs[symbol.name] = set()
862  read_write_info = ReadWriteInfo()
863  self._call_tree_utils_call_tree_utils.get_input_parameters(read_write_info,
864  symbol.initial_value)
865  # The dependence analysis tools do not include symbols used to
866  # define precision so check for those here.
867  for lit in symbol.initial_value.walk(Literal):
868  if isinstance(lit.datatype.precision, DataSymbol):
869  read_write_info.add_read(
870  Signature(lit.datatype.precision.name))
871  # If the precision of the Symbol being declared is itself defined
872  # by a Symbol then include that as an 'input'.
873  if isinstance(symbol.datatype.precision, DataSymbol):
874  read_write_info.add_read(
875  Signature(symbol.datatype.precision.name))
876  # Remove any 'inputs' that are not local since these do not affect
877  # the ordering of local declarations.
878  for sig in read_write_info.signatures_read:
879  if symbol_table.lookup(sig.var_name) in local_constants:
880  decln_inputs[symbol.name].add(sig)
881  # We now iterate over the declarations, declaring those that have their
882  # inputs satisfied. Creating a declaration for a given symbol removes
883  # that symbol as a dependence from any outstanding declarations.
884  declared = set()
885  while local_constants:
886  for symbol in local_constants[:]:
887  inputs = decln_inputs[symbol.name]
888  if inputs.issubset(declared):
889  # All inputs are satisfied so this declaration can be added
890  declared.add(Signature(symbol.name))
891  local_constants.remove(symbol)
892  declarations += self.gen_vardeclgen_vardecl(
893  symbol, include_visibility=is_module_scope)
894  break
895  else:
896  # We looped through all of the variables remaining to be
897  # declared and none had their dependencies satisfied.
898  raise VisitorError(
899  f"Unable to satisfy dependencies for the declarations of "
900  f"{[sym.name for sym in local_constants]}")
901  return declarations
902 
903  def gen_decls(self, symbol_table, is_module_scope=False):
904  '''Create and return the Fortran declarations for the supplied
905  SymbolTable.
906 
907  :param symbol_table: the SymbolTable instance.
908  :type symbol_table: :py:class:`psyclone.psyir.symbols.SymbolTable`
909  :param bool is_module_scope: whether or not the declarations are in
910  a module scoping unit. Default is False.
911 
912  :returns: the Fortran declarations for the table.
913  :rtype: str
914 
915  :raises VisitorError: if one of the symbols is a RoutineSymbol which
916  does not have an ImportInterface or UnresolvedInterface (
917  representing named and unqualified imports respectively) or
918  ModuleDefaultInterface (representing routines declared in the
919  same module) or is not a Fortran intrinsic.
920  :raises VisitorError: if args_allowed is False and one or more
921  argument declarations exist in symbol_table.
922  :raises VisitorError: if there are any symbols (other than
923  RoutineSymbols) in the supplied table that do not have an
924  explicit declaration (UnresolvedInterface) and there are no
925  wildcard imports or unknown interfaces.
926 
927  '''
928  # pylint: disable=too-many-branches
929  declarations = ""
930 
931  # Get all symbols local to this symbol table
932  all_symbols = symbol_table.symbols
933 
934  # Before processing the declarations we remove:
935  for sym in all_symbols[:]:
936  # Everything that is a container or imported (because it should
937  # already be done by the gen_use() method before)
938  if isinstance(sym, ContainerSymbol):
939  all_symbols.remove(sym)
940  continue
941  if sym.is_import:
942  all_symbols.remove(sym)
943  continue
944  # All the IntrinsicSymbols and RoutineSymbols with an
945  # UresolvedInterface (Fortran can have Calls which are
946  # only resolved at link time)
947  if isinstance(sym, IntrinsicSymbol) or (
948  isinstance(sym, RoutineSymbol) and
949  isinstance(sym.interface, UnresolvedInterface)):
950  all_symbols.remove(sym)
951  # We ignore all symbols with a PreprocessorInterface
952  if isinstance(sym.interface, PreprocessorInterface):
953  all_symbols.remove(sym)
954 
955  # If the symbol table contains any symbols with an
956  # UnresolvedInterface interface (they are not explicitly
957  # declared), we need to check that we have at least one
958  # wildcard import which could be bringing them into this
959  # scope, or an unknown interface which could be declaring
960  # them.
961  unresolved_symbols = []
962  for sym in all_symbols[:]:
963  if isinstance(sym.interface, UnresolvedInterface):
964  unresolved_symbols.append(sym)
965  all_symbols.remove(sym)
966  try:
967  internal_interface_symbol = symbol_table.lookup(
968  "_psyclone_internal_interface")
969  except KeyError:
970  internal_interface_symbol = None
971  if unresolved_symbols and not (
972  symbol_table.wildcard_imports() or internal_interface_symbol):
973  symbols_txt = ", ".join(
974  ["'" + sym.name + "'" for sym in unresolved_symbols])
975  raise VisitorError(
976  f"The following symbols are not explicitly declared or "
977  f"imported from a module and there are no wildcard "
978  f"imports which could be bringing them into scope: "
979  f"{symbols_txt}")
980 
981  # As a convention, we will declare the variables in the following
982  # order:
983 
984  # 1: Routine declarations and interfaces. (Note that accessibility
985  # statements are generated in gen_access_stmts().)
986  for sym in all_symbols[:]:
987  if not isinstance(sym, RoutineSymbol):
988  continue
989  # Interfaces can be GenericInterfaceSymbols or RoutineSymbols
990  # of UnsupportedFortranType.
991  if isinstance(sym, GenericInterfaceSymbol):
992  declarations += self.gen_interfacedeclgen_interfacedecl(sym)
993  elif isinstance(sym.datatype, UnsupportedType):
994  declarations += self.gen_vardeclgen_vardecl(
995  sym, include_visibility=is_module_scope)
996  elif not (sym.is_modulevar or sym.is_automatic):
997  raise VisitorError(
998  f"Routine symbol '{sym.name}' has '{sym.interface}'. "
999  f"This is not supported by the Fortran back-end.")
1000  all_symbols.remove(sym)
1001 
1002  # 2: Constants.
1003  declarations += self._gen_parameter_decls_gen_parameter_decls(symbol_table,
1004  is_module_scope)
1005  for sym in all_symbols[:]:
1006  if isinstance(sym, DataSymbol) and sym.is_constant:
1007  all_symbols.remove(sym)
1008 
1009  # 3: Argument variable declarations
1010  if symbol_table.argument_datasymbols and is_module_scope:
1011  raise VisitorError(
1012  f"Arguments are not allowed in this context but this symbol "
1013  f"table contains argument(s): "
1014  f"'{[sym.name for sym in symbol_table.argument_datasymbols]}'."
1015  )
1016  # We use symbol_table.argument_datasymbols because it has the
1017  # symbol order that we need
1018  for symbol in symbol_table.argument_datasymbols:
1019  declarations += self.gen_vardeclgen_vardecl(
1020  symbol, include_visibility=is_module_scope)
1021  all_symbols.remove(symbol)
1022 
1023  # 4: Derived-type declarations. These must come before any declarations
1024  # of symbols of these types.
1025  for symbol in all_symbols[:]:
1026  if isinstance(symbol, DataTypeSymbol):
1027  declarations += self.gen_typedeclgen_typedecl(
1028  symbol, include_visibility=is_module_scope)
1029  all_symbols.remove(symbol)
1030 
1031  # 5: The rest of the symbols
1032  for symbol in all_symbols:
1033  declarations += self.gen_vardeclgen_vardecl(
1034  symbol, include_visibility=is_module_scope)
1035 
1036  return declarations
1037 
1038  def filecontainer_node(self, node):
1039  '''This method is called when a FileContainer instance is found in
1040  the PSyIR tree.
1041 
1042  A file container node requires no explicit text in the Fortran
1043  back end.
1044 
1045  :param node: a Container PSyIR node.
1046  :type node: :py:class:`psyclone.psyir.nodes.FileContainer`
1047 
1048  :returns: the Fortran code as a string.
1049  :rtype: str
1050 
1051  :raises VisitorError: if the attached symbol table contains \
1052  any data symbols.
1053  :raises VisitorError: if more than one child is a Routine Node \
1054  with is_program set to True.
1055 
1056  '''
1057  if node.symbol_table.symbols:
1058  raise VisitorError(
1059  f"In the Fortran backend, a file container should not have "
1060  f"any symbols associated with it, but found "
1061  f"{len(node.symbol_table.symbols)}.")
1062 
1063  program_nodes = len([child for child in node.children if
1064  isinstance(child, Routine) and child.is_program])
1065  if program_nodes > 1:
1066  raise VisitorError(
1067  f"In the Fortran backend, a file container should contain at "
1068  f"most one routine node that is a program, but found "
1069  f"{program_nodes}.")
1070 
1071  result = ""
1072  for child in node.children:
1073  result += self._visit_visit(child)
1074  return result
1075 
1076  def container_node(self, node):
1077  '''This method is called when a Container instance is found in
1078  the PSyIR tree.
1079 
1080  A container node is mapped to a module in the Fortran back end.
1081 
1082  :param node: a Container PSyIR node.
1083  :type node: :py:class:`psyclone.psyir.nodes.Container`
1084 
1085  :returns: the Fortran code as a string.
1086  :rtype: str
1087 
1088  :raises VisitorError: if the name attribute of the supplied \
1089  node is empty or None.
1090  :raises VisitorError: if any of the children of the supplied \
1091  Container node are not Routines or CodeBlocks.
1092 
1093  '''
1094  if not node.name:
1095  raise VisitorError("Expected Container node name to have a value.")
1096 
1097  # All children must be either Routines or CodeBlocks as modules within
1098  # modules are not supported.
1099  if not all(isinstance(child, (Routine, CodeBlock)) for
1100  child in node.children):
1101  raise VisitorError(
1102  f"The Fortran back-end requires all children of a Container "
1103  f"to be either CodeBlocks or sub-classes of Routine but found:"
1104  f" {[type(child).__name__ for child in node.children]}.")
1105 
1106  result = f"{self._nindent}module {node.name}\n"
1107 
1108  self._depth_depth += 1
1109 
1110  # Generate module imports
1111  imports = ""
1112  for symbol in node.symbol_table.containersymbols:
1113  imports += self.gen_usegen_use(symbol, node.symbol_table)
1114 
1115  # Declare the Container's data
1116  declarations = self.gen_declsgen_decls(node.symbol_table, is_module_scope=True)
1117 
1118  # Generate the access statement (PRIVATE or PUBLIC)
1119  declarations += self.gen_default_access_stmtgen_default_access_stmt(node.symbol_table)
1120 
1121  # Accessibility statements for imported and routine symbols
1122  declarations += self.gen_access_stmtsgen_access_stmts(node.symbol_table)
1123 
1124  # Get the subroutine statements.
1125  subroutines = ""
1126  for child in node.children:
1127  subroutines += self._visit_visit(child)
1128 
1129  result += (
1130  f"{imports}"
1131  f"{self._nindent}implicit none\n"
1132  f"{declarations}\n"
1133  f"{self._nindent}contains\n"
1134  f"{subroutines}\n")
1135 
1136  self._depth_depth -= 1
1137  result += f"{self._nindent}end module {node.name}\n"
1138  return result
1139 
1140  def routine_node(self, node):
1141  '''This method is called when a Routine node is found in
1142  the PSyIR tree.
1143 
1144  :param node: a Routine PSyIR node.
1145  :type node: :py:class:`psyclone.psyir.nodes.Routine`
1146 
1147  :returns: the Fortran code for this node.
1148  :rtype: str
1149 
1150  :raises VisitorError: if the name attribute of the supplied \
1151  node is empty or None.
1152 
1153  '''
1154  if not node.name:
1155  raise VisitorError("Expected node name to have a value.")
1156 
1157  if node.is_program:
1158  result = f"{self._nindent}program {node.name}\n"
1159  routine_type = "program"
1160  else:
1161  args = [symbol.name for symbol in node.symbol_table.argument_list]
1162  suffix = ""
1163  if node.return_symbol:
1164  # This Routine has a return value and is therefore a Function
1165  routine_type = "function"
1166  if node.return_symbol.name.lower() != node.name.lower():
1167  suffix = f" result({node.return_symbol.name})"
1168  else:
1169  routine_type = "subroutine"
1170  result = f"{self._nindent}{routine_type} {node.name}("
1171  result += ", ".join(args) + f"){suffix}\n"
1172 
1173  self._depth_depth += 1
1174 
1175  if self._DISABLE_LOWERING_DISABLE_LOWERING:
1176  # If we are not lowering we don't have a deep_copied tree so it
1177  # should NOT make any modifications to the provided node or
1178  # symbol table.
1179  whole_routine_scope = node.symbol_table
1180  else:
1181  # The PSyIR has nested scopes but Fortran only supports declaring
1182  # variables at the routine level scope. For this reason, at this
1183  # point we have to unify all declarations and resolve possible name
1184  # clashes that appear when merging the scopes. Make sure we use
1185  # the same SymbolTable class used in the base class to get an
1186  # API-specific table here:
1187  whole_routine_scope = type(node.symbol_table)()
1188 
1189  for schedule in node.walk(Schedule):
1190  sched_table = schedule.symbol_table
1191  # We can't declare a routine inside itself so make sure we
1192  # skip any RoutineSymbol representing this routine.
1193  try:
1194  rsym = sched_table.lookup_with_tag("own_routine_symbol")
1195  skip = [rsym] if isinstance(rsym, RoutineSymbol) else []
1196  except KeyError:
1197  skip = []
1198  whole_routine_scope.merge(sched_table, skip)
1199  if schedule is node:
1200  # Replace the Routine's symbol table as soon as we've
1201  # merged it into the new one. This ensures that the new
1202  # table has full information on outer scopes which is
1203  # important when merging.
1204  node.symbol_table.detach()
1205  whole_routine_scope.attach(node)
1206 
1207  # Generate module imports
1208  imports = ""
1209  for symbol in whole_routine_scope.containersymbols:
1210  imports += self.gen_usegen_use(symbol, whole_routine_scope)
1211 
1212  # Generate declaration statements
1213  declarations = self.gen_declsgen_decls(whole_routine_scope)
1214 
1215  # Get the executable statements.
1216  exec_statements = ""
1217  for child in node.children:
1218  exec_statements += self._visit_visit(child)
1219  result += (
1220  f"{imports}"
1221  f"{declarations}\n"
1222  f"{exec_statements}\n")
1223 
1224  self._depth_depth -= 1
1225  result += f"{self._nindent}end {routine_type} {node.name}\n"
1226 
1227  return result
1228 
1229  def assignment_node(self, node):
1230  '''This method is called when an Assignment instance is found in the
1231  PSyIR tree.
1232 
1233  :param node: an Assignment PSyIR node.
1234  :type node: :py:class:`psyclone.psyir.nodes.Assignment``
1235 
1236  :returns: the Fortran code as a string.
1237  :rtype: str
1238 
1239  '''
1240  lhs = self._visit_visit(node.lhs)
1241  rhs = self._visit_visit(node.rhs)
1242  result = f"{self._nindent}{lhs} = {rhs}\n"
1243  return result
1244 
1245  def binaryoperation_node(self, node):
1246  '''This method is called when a BinaryOperation instance is found in
1247  the PSyIR tree.
1248 
1249  :param node: a BinaryOperation PSyIR node.
1250  :type node: :py:class:`psyclone.psyir.nodes.BinaryOperation`
1251 
1252  :returns: the Fortran code as a string.
1253  :rtype: str
1254 
1255  '''
1256  lhs = self._visit_visit(node.children[0])
1257  rhs = self._visit_visit(node.children[1])
1258  try:
1259  fort_oper = self.get_operatorget_operator(node.operator)
1260  parent = node.parent
1261  if isinstance(parent, Operation):
1262  # We may need to enforce precedence
1263  parent_fort_oper = self.get_operatorget_operator(parent.operator)
1264  if precedence(fort_oper) < precedence(parent_fort_oper):
1265  # We need brackets to enforce precedence
1266  return f"({lhs} {fort_oper} {rhs})"
1267  if precedence(fort_oper) == precedence(parent_fort_oper):
1268  # We still may need to enforce precedence
1269  if (isinstance(parent, UnaryOperation) or
1270  (isinstance(parent, BinaryOperation) and
1271  parent.children[1] == node)):
1272  # We need brackets to enforce precedence
1273  # as a) a unary operator is performed
1274  # before a binary operator and b) floating
1275  # point operations are not actually
1276  # associative due to rounding errors.
1277  return f"({lhs} {fort_oper} {rhs})"
1278  return f"{lhs} {fort_oper} {rhs}"
1279  except KeyError as error:
1280  raise VisitorError(
1281  f"Unexpected binary op '{node.operator}'.") from error
1282 
1283  def range_node(self, node):
1284  '''This method is called when a Range instance is found in the PSyIR
1285  tree.
1286 
1287  :param node: a Range PSyIR node.
1288  :type node: :py:class:`psyclone.psyir.nodes.Range`
1289 
1290  :returns: the Fortran code as a string.
1291  :rtype: str
1292 
1293  '''
1294  if node.parent and node.parent.is_lower_bound(
1295  node.parent.indices.index(node)):
1296  # The range starts for the first element in this
1297  # dimension. This is the default in Fortran so no need to
1298  # output anything.
1299  start = ""
1300  else:
1301  start = self._visit_visit(node.start)
1302 
1303  if node.parent and node.parent.is_upper_bound(
1304  node.parent.indices.index(node)):
1305  # The range ends with the last element in this
1306  # dimension. This is the default in Fortran so no need to
1307  # output anything.
1308  stop = ""
1309  else:
1310  stop = self._visit_visit(node.stop)
1311  result = f"{start}:{stop}"
1312 
1313  if isinstance(node.step, Literal) and \
1314  node.step.datatype.intrinsic == ScalarType.Intrinsic.INTEGER and \
1315  node.step.value == "1":
1316  # Step is 1. This is the default in Fortran so no need to
1317  # output any text.
1318  pass
1319  else:
1320  step = self._visit_visit(node.step)
1321  result += f":{step}"
1322  return result
1323 
1324  def literal_node(self, node):
1325  '''This method is called when a Literal instance is found in the PSyIR
1326  tree.
1327 
1328  :param node: a Literal PSyIR node.
1329  :type node: :py:class:`psyclone.psyir.nodes.Literal`
1330 
1331  :returns: the Fortran code for the literal.
1332  :rtype: str
1333 
1334  '''
1335  # pylint: disable=too-many-branches
1336  precision = node.datatype.precision
1337 
1338  if node.datatype.intrinsic == ScalarType.Intrinsic.BOOLEAN:
1339  # Booleans need to be converted to Fortran format
1340  result = '.' + node.value + '.'
1341  elif node.datatype.intrinsic == ScalarType.Intrinsic.CHARACTER:
1342  # Need to take care with which quotation symbol to use since a
1343  # character string may include quotation marks, e.g. a format
1344  # specifier: "('hello',3A)". The outermost quotation marks are
1345  # not stored so we have to decide whether to use ' or ".
1346  if "'" not in node.value:
1347  # No single quotes in the string so use those
1348  quote_symbol = "'"
1349  else:
1350  # There are single quotes in the string so we use double
1351  # quotes (after verifying that there aren't both single *and*
1352  # double quotes in the string).
1353  if '"' in node.value:
1354  raise NotImplementedError(
1355  f"Character literals containing both single and double"
1356  f" quotes are not supported but found >>{node.value}<<"
1357  )
1358  quote_symbol = '"'
1359  result = f"{quote_symbol}{node.value}{quote_symbol}"
1360  elif (node.datatype.intrinsic == ScalarType.Intrinsic.REAL and
1361  precision == ScalarType.Precision.DOUBLE):
1362  # The PSyIR stores real scalar values using the standard 'e'
1363  # notation. If the scalar is in fact double precision then this
1364  # 'e' must be replaced by 'd' for Fortran.
1365  result = node.value.replace("e", "d", 1)
1366  else:
1367  result = node.value
1368 
1369  if isinstance(precision, DataSymbol):
1370  # A KIND variable has been specified
1371  if node.datatype.intrinsic == ScalarType.Intrinsic.CHARACTER:
1372  result = f"{precision.name}_{result}"
1373  else:
1374  result = f"{result}_{precision.name}"
1375  if isinstance(precision, int):
1376  # A KIND value has been specified
1377  if node.datatype.intrinsic == ScalarType.Intrinsic.CHARACTER:
1378  result = f"{precision}_{result}"
1379  else:
1380  result = f"{result}_{precision}"
1381 
1382  return result
1383 
1384  def ifblock_node(self, node):
1385  '''This method is called when an IfBlock instance is found in the
1386  PSyIR tree.
1387 
1388  :param node: an IfBlock PSyIR node.
1389  :type node: :py:class:`psyclone.psyir.nodes.IfBlock`
1390 
1391  :returns: the Fortran code as a string.
1392  :rtype: str
1393 
1394  '''
1395  condition = self._visit_visit(node.children[0])
1396 
1397  self._depth_depth += 1
1398  if_body = ""
1399  for child in node.if_body:
1400  if_body += self._visit_visit(child)
1401  else_body = ""
1402  # node.else_body is None if there is no else clause.
1403  if node.else_body:
1404  for child in node.else_body:
1405  else_body += self._visit_visit(child)
1406  self._depth_depth -= 1
1407 
1408  if else_body:
1409  result = (
1410  f"{self._nindent}if ({condition}) then\n"
1411  f"{if_body}"
1412  f"{self._nindent}else\n"
1413  f"{else_body}"
1414  f"{self._nindent}end if\n")
1415  else:
1416  result = (
1417  f"{self._nindent}if ({condition}) then\n"
1418  f"{if_body}"
1419  f"{self._nindent}end if\n")
1420  return result
1421 
1422  def whileloop_node(self, node):
1423  '''This method is called when a WhileLoop instance is found in the
1424  PSyIR tree.
1425 
1426  :param node: a WhileLoop PSyIR node.
1427  :type node: :py:class:`psyclone.psyir.nodes.WhileLoop`
1428 
1429  :returns: the Fortran code.
1430  :rtype: str
1431 
1432  '''
1433  condition = self._visit_visit(node.condition)
1434 
1435  self._depth_depth += 1
1436  body = ""
1437  for child in node.loop_body:
1438  body += self._visit_visit(child)
1439  self._depth_depth -= 1
1440 
1441  result = (
1442  f"{self._nindent}do while ({condition})\n"
1443  f"{body}"
1444  f"{self._nindent}end do\n")
1445  return result
1446 
1447  def loop_node(self, node):
1448  '''This method is called when a Loop instance is found in the
1449  PSyIR tree.
1450 
1451  :param node: a Loop PSyIR node.
1452  :type node: :py:class:`psyclone.psyir.nodes.Loop`
1453 
1454  :returns: the loop node converted into a (language specific) string.
1455  :rtype: str
1456 
1457  '''
1458  start = self._visit_visit(node.start_expr)
1459  stop = self._visit_visit(node.stop_expr)
1460  step = self._visit_visit(node.step_expr)
1461 
1462  self._depth_depth += 1
1463  body = ""
1464  for child in node.loop_body:
1465  body += self._visit_visit(child)
1466  self._depth_depth -= 1
1467 
1468  # A generation error is raised if variable is not defined. This
1469  # happens in LFRic kernel that iterate over a domain.
1470  try:
1471  variable_name = node.variable.name
1472  except GenerationError:
1473  # If a kernel iterates over a domain - there is
1474  # no loop. But the loop node is maintained since it handles halo
1475  # exchanges. So just return the body in this case
1476  return body
1477 
1478  return (
1479  f"{self._nindent}do {variable_name} = {start}, {stop}, {step}\n"
1480  f"{body}"
1481  f"{self._nindent}enddo\n")
1482 
1483  def unaryoperation_node(self, node):
1484  '''This method is called when a UnaryOperation instance is found in
1485  the PSyIR tree.
1486 
1487  :param node: a UnaryOperation PSyIR node.
1488  :type node: :py:class:`psyclone.psyir.nodes.UnaryOperation`
1489 
1490  :returns: the Fortran code as a string.
1491  :rtype: str
1492 
1493  :raises VisitorError: if an unexpected Unary op is encountered.
1494 
1495  '''
1496  content = self._visit_visit(node.children[0])
1497  try:
1498  fort_oper = self.get_operatorget_operator(node.operator)
1499  # If the parent node is a UnaryOperation or a BinaryOperation
1500  # such as '-' or '**' then we need parentheses. This ensures we
1501  # don't generate invalid Fortran such as 'a ** -b', 'a - -b' or
1502  # '-a ** b' which is '-(a ** b)' instead of intended '(-a) ** b'.
1503  # Also consider the grandparent node to avoid generating invalid
1504  # Fortran such as 'a + -b * c' instead of intended 'a + (-b) * c'.
1505  parent = node.parent
1506  if isinstance(parent, UnaryOperation):
1507  return f"({fort_oper}{content})"
1508  if isinstance(parent, BinaryOperation):
1509  parent_fort_oper = self.get_operatorget_operator(parent.operator)
1510  if (node is parent.children[1] or
1511  (parent_fort_oper == "**" and fort_oper == "-")):
1512  return f"({fort_oper}{content})"
1513  grandparent = parent.parent
1514  # Case: 'a op1 (-b) op2 c'
1515  # and precedence(op2) > precedence(op1)
1516  # implying that '(-b) op2 c' is not parenthesized.
1517  if isinstance(grandparent, BinaryOperation):
1518  grandparent_fort_oper = self.get_operatorget_operator(
1519  grandparent.operator
1520  )
1521  if (parent is grandparent.children[1]
1522  and node is parent.children[0]
1523  and (precedence(parent_fort_oper)
1524  > precedence(grandparent_fort_oper))
1525  and fort_oper == "-"):
1526  return f"({fort_oper}{content})"
1527  return f"{fort_oper}{content}"
1528 
1529  except KeyError as error:
1530  raise VisitorError(
1531  f"Unexpected unary op '{node.operator}'.") from error
1532 
1533  def return_node(self, _):
1534  '''This method is called when a Return instance is found in
1535  the PSyIR tree.
1536 
1537  :param node: a Return PSyIR node.
1538  :type node: :py:class:`psyclone.psyir.nodes.Return`
1539 
1540  :returns: the Fortran code as a string.
1541  :rtype: str
1542 
1543  '''
1544  return f"{self._nindent}return\n"
1545 
1546  def codeblock_node(self, node):
1547  '''This method is called when a CodeBlock instance is found in the
1548  PSyIR tree. It returns the content of the CodeBlock as a
1549  Fortran string, indenting as appropriate.
1550 
1551  :param node: a CodeBlock PSyIR node.
1552  :type node: :py:class:`psyclone.psyir.nodes.CodeBlock`
1553 
1554  :returns: the Fortran code as a string.
1555  :rtype: str
1556 
1557  '''
1558  result = ""
1559  if node.structure == CodeBlock.Structure.STATEMENT:
1560  # indent and newlines required
1561  for ast_node in node.get_ast_nodes:
1562  # Using tofortran() ensures we get any label associated
1563  # with this statement.
1564  for line in ast_node.tofortran().split("\n"):
1565  result += f"{self._nindent}{line}\n"
1566  elif node.structure == CodeBlock.Structure.EXPRESSION:
1567  for ast_node in node.get_ast_nodes:
1568  result += str(ast_node)
1569  else:
1570  raise VisitorError(
1571  f"Unsupported CodeBlock Structure '{node.structure}' found.")
1572  return result
1573 
1574  def operandclause_node(self, node):
1575  '''This method is called when a OperandClause is
1576  found in the PSyIR tree. It returns the clause and its children
1577  as a string.
1578 
1579  :param node: an OperandClause PSyIR node.
1580  :type node: :py:class:`psyclone.psyir.nodes.OperandClause`
1581 
1582  :returns: the Fortran code for this node.
1583  :rtype: str
1584 
1585  '''
1586  if len(node.children) == 0:
1587  return ""
1588 
1589  result = node.clause_string
1590 
1591  result = result + "(" + node.operand + ": "
1592 
1593  child_list = []
1594  for child in node.children:
1595  child_list.append(self._visit_visit(child))
1596 
1597  result = result + ",".join(child_list) + ")"
1598 
1599  return result
1600 
1601  def regiondirective_node(self, node):
1602  '''This method is called when a RegionDirective instance is found in
1603  the PSyIR tree. It returns the opening and closing directives, and
1604  the statements in between as a string.
1605 
1606  :param node: a RegionDirective PSyIR node.
1607  :type node: :py:class:`psyclone.psyir.nodes.RegionDirective`
1608 
1609  :returns: the Fortran code for this node.
1610  :rtype: str
1611 
1612  '''
1613  result = f"{self._nindent}!${node.begin_string()}"
1614 
1615  clause_list = []
1616  for clause in node.clauses:
1617  val = self._visit_visit(clause)
1618  # Some clauses return empty strings if they should not
1619  # generate any output (e.g. private clause with no children).
1620  if val != "":
1621  clause_list.append(val)
1622  # Add a space only if there are clauses
1623  if len(clause_list) > 0:
1624  result = result + " "
1625  result = result + ", ".join(clause_list)
1626  result = result + "\n"
1627 
1628  for child in node.dir_body:
1629  result = result + self._visit_visit(child)
1630 
1631  end_string = node.end_string()
1632  if end_string:
1633  result = result + f"{self._nindent}!${end_string}\n"
1634  return result
1635 
1636  def standalonedirective_node(self, node):
1637  '''This method is called when a StandaloneDirective instance is found
1638  in the PSyIR tree. It returns the directive as a string.
1639 
1640  :param node: a StandaloneDirective PSyIR node.
1641  :type node: :py:class:`psyclone.psyir.nodes.StandloneDirective`
1642 
1643  :returns: the Fortran code for this node.
1644  :rtype: str
1645 
1646  '''
1647  result = f"{self._nindent}!${node.begin_string()}"
1648 
1649  clause_list = []
1650  # Currently no standalone directives have clauses associated
1651  # so this code is left commented out. If a standalone directive
1652  # is added with clauses, this should be added in.
1653  # for clause in node.clauses:
1654  # clause_list.append(self._visit(clause))
1655  # Add a space only if there are clauses
1656  # if len(clause_list) > 0:
1657  # result = result + " "
1658  result = result + ", ".join(clause_list)
1659  result = result + "\n"
1660 
1661  return result
1662 
1663  def _gen_arguments(self, node):
1664  '''Utility function that check that all named args occur after all
1665  positional args. This is a Fortran restriction, not a PSyIR
1666  restriction. And if they are valid, it returns the whole list of
1667  arguments.
1668 
1669  :param node: the node to check.
1670  :type node: :py:class:`psyclone.psyir.nodes.Call`
1671  :returns: string representation of the complete list of arguments.
1672  :rtype: str
1673 
1674  raises TypeError: if the provided node is not a Call.
1675  raises VisitorError: if the all of the positional arguments are
1676  not before all of the named arguments.
1677 
1678  '''
1679  if not isinstance(node, Call):
1680  raise TypeError(
1681  f"The _gen_arguments utility function expects a "
1682  f"Call node, but found '{type(node).__name__}'.")
1683 
1684  found_named_arg = False
1685  for name in node.argument_names:
1686  if found_named_arg and not name:
1687  raise VisitorError(
1688  f"Fortran expects all named arguments to occur after all "
1689  f"positional arguments but this is not the case for "
1690  f"{str(node)}")
1691  if name:
1692  found_named_arg = True
1693 
1694  # All arguments have been validated, proceed to generate them
1695  result_list = []
1696  for idx, child in enumerate(node.arguments):
1697  if node.argument_names[idx]:
1698  result_list.append(
1699  f"{node.argument_names[idx]}={self._visit(child)}")
1700  else:
1701  result_list.append(self._visit_visit(child))
1702  return ", ".join(result_list)
1703 
1704  def call_node(self, node):
1705  '''Translate the PSyIR call node to Fortran.
1706 
1707  :param node: a Call PSyIR node.
1708  :type node: :py:class:`psyclone.psyir.nodes.Call`
1709 
1710  :returns: the equivalent Fortran code.
1711  :rtype: str
1712 
1713  '''
1714  args = self._gen_arguments_gen_arguments(node)
1715  if isinstance(node, IntrinsicCall) and node.routine.name in [
1716  "ALLOCATE", "DEALLOCATE"]:
1717  # An allocate/deallocate doesn't have 'call'.
1718  return f"{self._nindent}{node.routine.name}({args})\n"
1719  if not node.parent or isinstance(node.parent, Schedule):
1720  return f"{self._nindent}call {self._visit(node.routine)}({args})\n"
1721 
1722  # Otherwise it is inside-expression function call
1723  return f"{self._visit(node.routine)}({args})"
1724 
1725  def kernelfunctor_node(self, node):
1726  '''
1727  Translate the Kernel functor into Fortran.
1728 
1729  :param node: the PSyIR node to translate.
1730  :type node: :py:class:`psyclone.domain.common.algorithm.KernelFunctor`
1731 
1732  :returns: the equivalent Fortran code.
1733  :rtype: str
1734 
1735  '''
1736  result_list = []
1737  for child in node.children:
1738  result_list.append(self._visit_visit(child))
1739  args = ", ".join(result_list)
1740  return f"{node.name}({args})"
def gen_default_access_stmt(self, symbol_table)
Definition: fortran.py:738
def gen_decls(self, symbol_table, is_module_scope=False)
Definition: fortran.py:903
def gen_indices(self, indices, var_name=None)
Definition: fortran.py:389
def _gen_parameter_decls(self, symbol_table, is_module_scope=False)
Definition: fortran.py:829
def gen_typedecl(self, symbol, include_visibility=True)
Definition: fortran.py:665
def gen_use(self, symbol, symbol_table)
Definition: fortran.py:437
def gen_vardecl(self, symbol, include_visibility=False)
Definition: fortran.py:497
def gen_access_stmts(self, symbol_table)
Definition: fortran.py:768
def _reverse_map(reverse_dict, op_map)
Definition: fortran.py:346