fparser Reference Guide  0.0.14
parser.py
1 # Modified work Copyright (c) 2018-2022 Science and Technology
2 # Facilities Council.
3 # Original work Copyright (c) 1999-2008 Pearu Peterson
4 
5 # All rights reserved.
6 
7 # Modifications made as part of the fparser project are distributed
8 # under the following license:
9 
10 # Redistribution and use in source and binary forms, with or without
11 # modification, are permitted provided that the following conditions are
12 # met:
13 
14 # 1. Redistributions of source code must retain the above copyright
15 # notice, this list of conditions and the following disclaimer.
16 
17 # 2. Redistributions in binary form must reproduce the above copyright
18 # notice, this list of conditions and the following disclaimer in the
19 # documentation and/or other materials provided with the distribution.
20 
21 # 3. Neither the name of the copyright holder nor the names of its
22 # contributors may be used to endorse or promote products derived from
23 # this software without specific prior written permission.
24 
25 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
26 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
27 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
28 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
29 # HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
30 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
31 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
32 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
33 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
34 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
35 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
36 
37 # --------------------------------------------------------------------
38 
39 # The original software (in the f2py project) was distributed under
40 # the following license:
41 
42 # Redistribution and use in source and binary forms, with or without
43 # modification, are permitted provided that the following conditions are met:
44 
45 # a. Redistributions of source code must retain the above copyright notice,
46 # this list of conditions and the following disclaimer.
47 # b. Redistributions in binary form must reproduce the above copyright
48 # notice, this list of conditions and the following disclaimer in the
49 # documentation and/or other materials provided with the distribution.
50 # c. Neither the name of the F2PY project nor the names of its
51 # contributors may be used to endorse or promote products derived from
52 # this software without specific prior written permission.
53 
54 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
55 # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
56 # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
57 # ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
58 # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
59 # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
60 # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
61 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
62 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
63 # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
64 # DAMAGE.
65 
66 """This file provides utilities to create a Fortran parser suitable
67 for a particular standard."""
68 # pylint: disable=eval-used
69 
70 import inspect
71 import sys
72 from fparser.two.symbol_table import SYMBOL_TABLES
73 
74 
75 def get_module_classes(input_module):
76  """Return all classes local to a module.
77 
78  :param module input_module: the module containing the classes.
79  :return: a `list` of tuples each containing a class name and a \
80  class.
81 
82  """
83  module_cls_members = []
84  module_name = input_module.__name__
85  # first find all classes in the module. This includes imported
86  # classes.
87  all_cls_members = inspect.getmembers(sys.modules[module_name], inspect.isclass)
88  # next only keep classes that are specified in the module.
89  for cls_member in all_cls_members:
90  if cls_member[1].__module__ == module_name:
91  module_cls_members.append(cls_member)
92  return module_cls_members
93 
94 
96  """Creates a parser suitable for the specified Fortran standard."""
97 
98  def create(self, std=None):
99  """Creates a class hierarchy suitable for the specified Fortran
100  standard. Also sets-up the list of classes that define scoping
101  regions in the global SymbolTables object and clears any existing
102  symbol table information.
103 
104  :param str std: the Fortran standard. Choices are 'f2003' or \
105  'f2008'. 'f2003' is the default.
106  :return: a Program class (not object) for use with the Fortran reader
107  :rtype: :py:class:`fparser.two.Fortran2003.Program`
108 
109  :raises ValueError: if the supplied value for the std parameter \
110  is invalid
111 
112  For example:
113 
114  >>> from fparser.two.parser import ParserFactory
115  >>> f2003_parser = ParserFactory().create()
116  >>> f2003_parser = ParserFactory().create(std='f2003')
117  >>> f2008_parser = ParserFactory().create(std='f2008')
118  >>> # Assuming that a reader has already been created ...
119  >>> ast = f2008_parser(reader)
120  >>> print(ast)
121 
122  """
123  # Clear any existing symbol tables.
124  SYMBOL_TABLES.clear()
125 
126  # find all relevant classes in our Fortran2003 file as we
127  # always need these.
128  # pylint: disable=import-outside-toplevel
129  from fparser.two import Fortran2003
130 
131  f2003_cls_members = get_module_classes(Fortran2003)
132  if not std:
133  # default to f2003.
134  std = "f2003"
135 
136  if std == "f2003":
137  # we already have our required list of classes so call _setup
138  # to setup our class hierarchy.
139  self._setup(f2003_cls_members)
140  # We can now specify which classes are taken as defining new
141  # scoping regions. Programs without the optional program-stmt
142  # are handled separately in the Fortran2003.Main_Program0 class.
143  SYMBOL_TABLES.scoping_unit_classes = [
148  ]
149  # the class hierarchy has been set up so return the top
150  # level class that we start from when parsing Fortran code.
151  return Fortran2003.Program
152  if std == "f2008":
153  # we need to find all relevent classes in our Fortran2003
154  # and Fortran2008 files and then ensure that where classes
155  # have the same name we return the Fortran2008 class
156  # i.e. where Fortran2008 extends Fortran2003 we return
157  # Fortran2008.
158  # First find all Fortran2008 classes.
159  from fparser.two import Fortran2008
160 
161  f2008_cls_members = get_module_classes(Fortran2008)
162  # next add in Fortran2003 classes if they do not already
163  # exist as a Fortran2008 class.
164  f2008_class_names = [i[0] for i in f2008_cls_members]
165  for local_cls in f2003_cls_members:
166  if local_cls[0] not in f2008_class_names:
167  f2008_cls_members.append(local_cls)
168  # we now have our required list of classes so call _setup
169  # to setup our class hierarchy.
170  self._setup(f2008_cls_members)
171  # We can now specify which classes are taken as defining new
172  # scoping regions. Programs without the optional program-stmt
173  # are handled separately in the Fortran2003.Main_Program0 class.
174  SYMBOL_TABLES.scoping_unit_classes = [
180  ]
181  # the class hierarchy has been set up so return the top
182  # level class that we start from when parsing Fortran
183  # code. Fortran2008 does not extend the top level class so
184  # we return the Fortran2003 one.
185  return Fortran2003.Program
186 
187  raise ValueError(f"'{std}' is an invalid standard")
188 
189  def _setup(self, input_classes):
190  """Perform some Python magic to create the connections between classes
191  and populate the baseclass with this information. This has
192  been lifted from the original implementation and no attempt
193  has been made to tidy up the code, other than making it
194  conformant to the coding rules.
195 
196  :param list input_classes: a list of tuples each containing a \
197  class name and a class.
198 
199  """
200 
201  __autodoc__ = []
202  base_classes = {}
203 
204  import logging
206 
207  class_type = type(fparser.two.Fortran2003.Base)
208 
209  # Reset subclasses dictionary in case this function has been
210  # called before. If this is not done then multiple calls to
211  # the ParserFactory create method may not work correctly.
212  fparser.two.Fortran2003.Base.subclasses = {}
213 
214  for clsinfo in input_classes:
215  clsname = "{0}.{1}".format(clsinfo[1].__module__, clsinfo[0])
216  cls = eval(clsname)
217  # ?? classtype is set to Base so why have issubclass?
218  if (
219  isinstance(cls, class_type)
220  and issubclass(cls, fparser.two.Fortran2003.Base)
221  and not cls.__name__.endswith("Base")
222  ):
223  base_classes[cls.__name__] = cls
224  if len(__autodoc__) < 10:
225  __autodoc__.append(cls.__name__)
226 
227  #
228  # OPTIMIZE subclass_names tree.
229  #
230 
231  if 1: # Optimize subclass tree:
232 
233  def _rpl_list(clsname):
234  if clsname not in base_classes:
235  error_string = "Not implemented: {0}".format(clsname)
236  logging.getLogger(__name__).debug(error_string)
237  return []
238  # remove this code when all classes are implemented.
239  cls = base_classes[clsname]
240  if hasattr(cls, "match"):
241  return [clsname]
242  bits = []
243  for names in getattr(cls, "subclass_names", []):
244  list1 = _rpl_list(names)
245  for names1 in list1:
246  if names1 not in bits:
247  bits.append(names1)
248  return bits
249 
250  for cls in list(base_classes.values()):
251  if not hasattr(cls, "subclass_names"):
252  continue
253  opt_subclass_names = []
254  for names in cls.subclass_names:
255  for names1 in _rpl_list(names):
256  if names1 not in opt_subclass_names:
257  opt_subclass_names.append(names1)
258  if not opt_subclass_names == cls.subclass_names:
259  cls.subclass_names[:] = opt_subclass_names
260 
261  # Initialize Base.subclasses dictionary:
262  for clsname, cls in list(base_classes.items()):
263  subclass_names = getattr(cls, "subclass_names", None)
264  if subclass_names is None:
265  message = "%s class is missing subclass_names list" % (clsname)
266  logging.getLogger(__name__).debug(message)
267  continue
268  try:
269  bits = fparser.two.Fortran2003.Base.subclasses[clsname]
270  except KeyError:
271  fparser.two.Fortran2003.Base.subclasses[clsname] = bits = []
272  for name in subclass_names:
273  if name in base_classes:
274  bits.append(base_classes[name])
275  else:
276  message = "{0} not implemented needed by {1}".format(name, clsname)
277  logging.getLogger(__name__).debug(message)
278 
279  if 1:
280  for cls in list(base_classes.values()):
281  # subclasses = fparser.two.Fortran2003.Base.subclasses.get(
282  # cls.__name__, [])
283  # subclasses_names = [c.__name__ for c in subclasses]
284  subclass_names = getattr(cls, "subclass_names", [])
285  use_names = getattr(cls, "use_names", [])
286  # for name in subclasses_names:
287  # break
288  # if name not in subclass_names:
289  # message = ('%s needs to be added to %s '
290  # 'subclasses_name list'
291  # % (name, cls.__name__))
292  # logging.getLogger(__name__).debug(message)
293  # for name in subclass_names:
294  # break
295  # if name not in subclasses_names:
296  # message = '%s needs to be added to %s '
297  # 'subclass_name list' % (name, cls.__name__)
298  # logging.getLogger(__name__).debug(message)
299  for name in use_names + subclass_names:
300  if name not in base_classes:
301  message = "%s not defined used " "by %s" % (name, cls.__name__)
302  logging.getLogger(__name__).debug(message)
def _setup(self, input_classes)
Definition: parser.py:189
def create(self, std=None)
Definition: parser.py:98