Reference Guide  2.5.0
fortran.py
1 # BSD 3-Clause License
2 #
3 # Copyright (c) 2021-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 # Author: S. Siso, STFC Daresbury Lab
34 # Modifications: A. R. Porter, N. Nobre and R. W. Ford, STFC Daresbury Lab
35 # -----------------------------------------------------------------------------
36 
37 ''' This module provides the PSyIR Fortran front-end.'''
38 
39 from typing import Optional
40 from fparser.common.readfortran import FortranStringReader, FortranFileReader
41 from fparser.common.sourceinfo import FortranFormat
42 from fparser.two import Fortran2003, pattern_tools
43 from fparser.two.parser import ParserFactory
44 from fparser.two.symbol_table import SYMBOL_TABLES
45 from fparser.two.utils import NoMatchError
46 from psyclone.configuration import Config
47 from psyclone.psyir.frontend.fparser2 import Fparser2Reader
48 from psyclone.psyir.nodes import Schedule, Assignment, Routine
49 from psyclone.psyir.symbols import SymbolTable
50 
51 
52 class FortranReader():
53  ''' PSyIR Fortran frontend. This frontend translates Fortran from a string
54  or a file into PSyIR using the fparser2 utilities.
55 
56  '''
57  # Save parser object across instances to reduce the initialisation time
58  _parser = None
59 
60  def __init__(self):
61  if not self._parser_parser:
62  self._parser_parser = ParserFactory().create(std="f2008")
63  self._processor_processor = Fparser2Reader()
64  SYMBOL_TABLES.clear()
65 
66  @staticmethod
67  def validate_name(name: str):
68  '''
69  Utility method that checks that the supplied name is a valid
70  Fortran name.
71 
72  :param name: the name to check.
73 
74  :raises TypeError: if the name is not a string.
75  :raises ValueError: if this is not a valid name.
76 
77  '''
78  if not isinstance(name, str):
79  raise TypeError(
80  f"A name should be a string, but found "
81  f"'{type(name).__name__}'.")
82  if not pattern_tools.abs_name.match(name):
83  raise ValueError(
84  f"Invalid Fortran name '{name}' found.")
85 
86  def psyir_from_source(self, source_code: str, free_form: bool = True):
87  ''' Generate the PSyIR tree representing the given Fortran source code.
88 
89  :param source_code: text representation of the code to be parsed.
90  :param free_form: If parsing free-form code or not (default True).
91 
92  :returns: PSyIR representing the provided Fortran source code.
93  :rtype: :py:class:`psyclone.psyir.nodes.Node`
94 
95  '''
96  SYMBOL_TABLES.clear()
97  string_reader = FortranStringReader(source_code)
98  # Set reader to free format.
99  string_reader.set_format(FortranFormat(free_form, False))
100  parse_tree = self._parser_parser(string_reader)
101  psyir = self._processor_processor.generate_psyir(parse_tree)
102  return psyir
103 
104  def psyir_from_expression(self, source_code: str,
105  symbol_table: Optional[SymbolTable] = None):
106  '''Generate the PSyIR tree for the supplied Fortran statement. The
107  symbol table is expected to provide all symbols found in the
108  expression.
109 
110  :param source_code: text of the expression to be parsed.
111  :param symbol_table: the SymbolTable in which to search for any
112  symbols that are encountered.
113 
114  :returns: PSyIR representing the provided Fortran expression.
115  :rtype: :py:class:`psyclone.psyir.nodes.Node`
116 
117  :raises TypeError: if no valid SymbolTable is supplied.
118  :raises ValueError: if the supplied source does not represent a
119  Fortran expression.
120 
121  '''
122  if symbol_table is None:
123  symbol_table = SymbolTable()
124  elif not isinstance(symbol_table, SymbolTable):
125  raise TypeError(f"Must be supplied with a valid SymbolTable but "
126  f"got '{type(symbol_table).__name__}'")
127 
128  try:
129  parse_tree = Fortran2003.Expr(source_code)
130  except NoMatchError as err:
131  raise ValueError(
132  f"Supplied source does not represent a Fortran "
133  f"expression: '{source_code}'") from err
134 
135  # Create a fake sub-tree connected to the supplied symbol table so
136  # that we can process the expression and lookup any symbols that it
137  # references. We provide the SymbolTable directly to the private
138  # attribute to avoid the symbol table's node link to connect to the
139  # new Schedule and therefore stealing it from its original scope.
140  # pylint: disable=protected-access
141  fake_parent = Schedule()
142  # pylint: disable=protected-access
143  fake_parent._symbol_table = symbol_table
144  fake_parent.addchild(Assignment())
145  self._processor_processor.process_nodes(fake_parent[0], [parse_tree])
146  return fake_parent[0].children[0].detach()
147 
148  def psyir_from_statement(self, source_code: str,
149  symbol_table: Optional[SymbolTable] = None):
150  '''Generate the PSyIR tree for the supplied Fortran statement. The
151  symbolt table is expected to provide all symbols found in the
152  statement.
153 
154  :param source_code: text of the statement to be parsed.
155  :param symbol_table: the SymbolTable in which to search for any
156  symbols that are encountered.
157 
158  :returns: PSyIR representing the provided Fortran statement.
159  :rtype: :py:class:`psyclone.psyir.nodes.Node`
160 
161  :raises TypeError: if no valid SymbolTable is supplied.
162  :raises ValueError: if the supplied source does not represent a
163  Fortran statement.
164 
165  '''
166  if symbol_table is None:
167  symbol_table = SymbolTable()
168  elif not isinstance(symbol_table, SymbolTable):
169  raise TypeError(f"Must be supplied with a valid SymbolTable but "
170  f"got '{type(symbol_table).__name__}'")
171  string_reader = FortranStringReader(source_code)
172  # Set reader to free format.
173  string_reader.set_format(FortranFormat(True, False))
174  try:
175  exec_part = Fortran2003.Execution_Part(string_reader)
176  except NoMatchError as err:
177  raise ValueError(f"Supplied source does not represent a Fortran "
178  f"statement: '{source_code}'") from err
179 
180  # Create a fake sub-tree connected to the supplied symbol table so
181  # that we can process the statement and lookup any symbols that it
182  # references.
183  try:
184  routine_symbol = symbol_table.lookup_with_tag("own_routine_symbol")
185  routine_name = routine_symbol.name
186  except KeyError:
187  routine_name = "dummy"
188  fake_parent = Routine.create(
189  routine_name, SymbolTable(), [])
190  # pylint: disable=protected-access
191  fake_parent._symbol_table = symbol_table
192 
193  # Process the statement, giving the Routine we've just
194  # created as the parent.
195  self._processor_processor.process_nodes(fake_parent, exec_part.children)
196  return fake_parent[0].detach()
197 
198  def psyir_from_file(self, file_path, free_form=True):
199  ''' Generate the PSyIR tree representing the given Fortran file.
200 
201  :param file_path: path of the file to be read and parsed.
202  :type file_path: str or any Python Path format.
203 
204  :param free_form: If parsing free-form code or not (default True).
205  :type free_form: bool
206 
207  :returns: PSyIR representing the provided Fortran file.
208  :rtype: :py:class:`psyclone.psyir.nodes.Node`
209 
210  '''
211  SYMBOL_TABLES.clear()
212 
213  # Note that this is the main performance hotspot in PSyclone, taking
214  # more than 90% of the runtime in some cases. Therefore this is a good
215  # place to implement caching in order to avoid repeating parsing steps
216  # that have already been done before.
217 
218  # Using the FortranFileReader instead of manually open the file allows
219  # fparser to keep the filename information in the tree
220  reader = FortranFileReader(file_path,
221  include_dirs=Config.get().include_paths)
222  reader.set_format(FortranFormat(free_form, False))
223  parse_tree = self._parser_parser(reader)
224  psyir = self._processor_processor.generate_psyir(parse_tree)
225  return psyir
226 
227 
228 # For Sphinx AutoAPI documentation generation
229 __all__ = ['FortranReader']
def psyir_from_file(self, file_path, free_form=True)
Definition: fortran.py:198
def psyir_from_statement(self, str source_code, Optional[SymbolTable] symbol_table=None)
Definition: fortran.py:149
def psyir_from_expression(self, str source_code, Optional[SymbolTable] symbol_table=None)
Definition: fortran.py:105
def psyir_from_source(self, str source_code, bool free_form=True)
Definition: fortran.py:86