Reference Guide  2.5.0
psyclone.parse.algorithm.Parser Class Reference

Public Member Functions

def __init__ (self, api="", invoke_name="invoke", kernel_paths=None, line_length=False)
 
def parse (self, alg_filename)
 
def invoke_info (self, alg_parse_tree)
 
def create_invoke_call (self, statement)
 
def create_kernel_call (self, argument)
 
def create_builtin_kernel_call (self, kernel_name, args)
 
def create_coded_kernel_call (self, kernel_name, args)
 
def update_arg_to_module_map (self, statement)
 
def check_invoke_label (self, argument)
 

Detailed Description

Supports the parsing of PSyclone conformant algorithm code within a
file and extraction of relevant information for any 'invoke' calls
contained within the code.

:param str api: the PSyclone API to use when parsing the code.
:param str invoke_name: the expected name of the invocation calls in the \
    algorithm code.
:param kernel_paths: the paths to search for kernel source files \
    (if different from the location of the algorithm source). \
    Defaults to None.
:type kernel_paths: list of str or NoneType
:param bool line_length: a logical flag specifying whether we care \
    about line lengths being longer than 132 characters. If so, \
    the input (algorithm and kernel) code is checked to make sure \
    that it conforms and an error raised if not. The default is \
    False.

For example:

>>> from psyclone.parse.algorithm import Parser
>>> parser = Parser(api="gocean1.0")
>>> ast, info = parser.parse(SOURCE_FILE)

Definition at line 114 of file algorithm.py.

Member Function Documentation

◆ check_invoke_label()

def psyclone.parse.algorithm.Parser.check_invoke_label (   self,
  argument 
)
Takes the parse tree of an invoke argument containing an invoke
label. Raises an exception if this label has already been used
by another invoke in the same algorithm code. If all is well
it returns the label as a string.

:param argument: Parse tree of an invoke argument. This \
should contain a "name=xxx" argument.
:type argument: :py:class:`fparser.two.Actual_Arg_Spec`
:returns: the label as a string.
:rtype: str
:raises ParseError: if this label has already been used by \
another invoke in this algorithm code.

Definition at line 497 of file algorithm.py.

497  def check_invoke_label(self, argument):
498  '''Takes the parse tree of an invoke argument containing an invoke
499  label. Raises an exception if this label has already been used
500  by another invoke in the same algorithm code. If all is well
501  it returns the label as a string.
502 
503  :param argument: Parse tree of an invoke argument. This \
504  should contain a "name=xxx" argument.
505  :type argument: :py:class:`fparser.two.Actual_Arg_Spec`
506  :returns: the label as a string.
507  :rtype: str
508  :raises ParseError: if this label has already been used by \
509  another invoke in this algorithm code.
510 
511  '''
512  invoke_label = get_invoke_label(argument, self._alg_filename)
513  if invoke_label in self._unique_invoke_labels:
514  raise ParseError(
515  f"Found multiple named invoke()'s with the same "
516  f"label ('{invoke_label}') when parsing {self._alg_filename}")
517  self._unique_invoke_labels.append(invoke_label)
518  return invoke_label
519 
520 # pylint: enable=too-many-arguments
521 # pylint: enable=too-many-instance-attributes
522 
523 # Section 2: Support functions
524 
525 

References psyclone.parse.algorithm.Parser._alg_filename, and psyclone.parse.algorithm.Parser._unique_invoke_labels.

Here is the caller graph for this function:

◆ create_builtin_kernel_call()

def psyclone.parse.algorithm.Parser.create_builtin_kernel_call (   self,
  kernel_name,
  args 
)
Takes the builtin kernel name and a list of Arg objects which
capture information about the builtin call arguments and
returns a BuiltinCall instance with content specific to the
particular API (as specified in self._api).

:param str kernel_name: the name of the builtin kernel being \
called
:param args: a list of 'Arg' instances containing the required \
information for the arguments being passed from the algorithm \
layer. The list order is the same as the argument order.
:type arg: list of :py:class:`psyclone.parse.algorithm.Arg`
:returns: a BuiltInCall instance with information specific to \
the API.
:rtype: :py:class:`psyclone.parse.algorithm.BuiltInCall`
:raises ParseError: if the builtin is specified in a use \
statement in the algorithm layer

Definition at line 401 of file algorithm.py.

401  def create_builtin_kernel_call(self, kernel_name, args):
402  '''Takes the builtin kernel name and a list of Arg objects which
403  capture information about the builtin call arguments and
404  returns a BuiltinCall instance with content specific to the
405  particular API (as specified in self._api).
406 
407  :param str kernel_name: the name of the builtin kernel being \
408  called
409  :param args: a list of 'Arg' instances containing the required \
410  information for the arguments being passed from the algorithm \
411  layer. The list order is the same as the argument order.
412  :type arg: list of :py:class:`psyclone.parse.algorithm.Arg`
413  :returns: a BuiltInCall instance with information specific to \
414  the API.
415  :rtype: :py:class:`psyclone.parse.algorithm.BuiltInCall`
416  :raises ParseError: if the builtin is specified in a use \
417  statement in the algorithm layer
418 
419  '''
420  if kernel_name.lower() in self._arg_name_to_module_name:
421  raise ParseError(
422  f"A built-in cannot be named in a use statement but "
423  f"'{kernel_name}' is used from module "
424  f"'{self._arg_name_to_module_name[kernel_name.lower()]}' in "
425  f"file {self._alg_filename}")
426 
427  return BuiltInCall(BuiltInKernelTypeFactory(api=self._api).create(
428  self._builtin_name_map.keys(), self._builtin_defs_file,
429  name=kernel_name.lower()), args)
430 

References psyclone.configuration.Config._api, psyclone.parse.algorithm.Parser._api, psyclone.parse.algorithm.Parser._arg_name_to_module_name, and psyclone.parse.algorithm.Parser._builtin_defs_file.

Here is the caller graph for this function:

◆ create_coded_kernel_call()

def psyclone.parse.algorithm.Parser.create_coded_kernel_call (   self,
  kernel_name,
  args 
)
Takes a coded kernel name and a list of Arg objects which
capture information about the coded call arguments and
returns a KernelCall instance with content specific to the
particular API (as specified in self._api).

:param str kernel_name: the name of the coded kernel being \
called
:param args: a list of 'Arg' instances containing the required \
information for the arguments being passed from the algorithm \
layer. The list order is the same as the argument order.
:type arg: list of :py:class:`psyclone.parse.algorithm.Arg`
:returns: a KernelCall instance with information specific to \
the API.
:rtype: :py:class:`psyclone.parse.algorithm.KernelCall`
:raises ParseError: if the kernel is not specified in a use \
statement in the algorithm layer

Definition at line 431 of file algorithm.py.

431  def create_coded_kernel_call(self, kernel_name, args):
432  '''Takes a coded kernel name and a list of Arg objects which
433  capture information about the coded call arguments and
434  returns a KernelCall instance with content specific to the
435  particular API (as specified in self._api).
436 
437  :param str kernel_name: the name of the coded kernel being \
438  called
439  :param args: a list of 'Arg' instances containing the required \
440  information for the arguments being passed from the algorithm \
441  layer. The list order is the same as the argument order.
442  :type arg: list of :py:class:`psyclone.parse.algorithm.Arg`
443  :returns: a KernelCall instance with information specific to \
444  the API.
445  :rtype: :py:class:`psyclone.parse.algorithm.KernelCall`
446  :raises ParseError: if the kernel is not specified in a use \
447  statement in the algorithm layer
448 
449  '''
450  try:
451  module_name = self._arg_name_to_module_name[kernel_name.lower()]
452  except KeyError as info:
453  message = (
454  f"kernel call '{kernel_name.lower()}' must either be named in "
455  f"a use statement (found "
456  f"{list(self._arg_name_to_module_name.values())}) or be a "
457  f"recognised built-in (one of "
458  f"'{list(self._builtin_name_map.keys())}' for "
459  f"this API)")
460  raise ParseError(message) from info
461 
462  modast = get_kernel_ast(module_name, self._alg_filename,
463  self._kernel_paths, self._line_length)
464  return KernelCall(module_name,
465  KernelTypeFactory(api=self._api).create(
466  modast, name=kernel_name), args)
467 

References psyclone.parse.algorithm.Parser._alg_filename, psyclone.configuration.Config._api, psyclone.parse.algorithm.Parser._api, psyclone.parse.algorithm.Parser._arg_name_to_module_name, psyclone.parse.algorithm.Parser._kernel_paths, psyclone.line_length.FortLineLength._line_length, and psyclone.parse.algorithm.Parser._line_length.

Here is the caller graph for this function:

◆ create_invoke_call()

def psyclone.parse.algorithm.Parser.create_invoke_call (   self,
  statement 
)
Takes the part of a parse tree containing an invoke call and
returns an InvokeCall object which captures the required
information about the invoke.

:param statement: Parse tree of the invoke call.
:type statement: :py:class:`fparser.two.Fortran2003.Call_Stmt`
:returns: An InvokeCall object which contains relevant \
information about the invoke call.
:rtype: :py:class:`psyclone.parse.algorithm.InvokeCall`
:raises ParseError: if more than one invoke argument contains \
'name=xxx'.
:raises ParseError: if an unknown or unsupported invoke \
argument is found.

Definition at line 323 of file algorithm.py.

323  def create_invoke_call(self, statement):
324  '''Takes the part of a parse tree containing an invoke call and
325  returns an InvokeCall object which captures the required
326  information about the invoke.
327 
328  :param statement: Parse tree of the invoke call.
329  :type statement: :py:class:`fparser.two.Fortran2003.Call_Stmt`
330  :returns: An InvokeCall object which contains relevant \
331  information about the invoke call.
332  :rtype: :py:class:`psyclone.parse.algorithm.InvokeCall`
333  :raises ParseError: if more than one invoke argument contains \
334  'name=xxx'.
335  :raises ParseError: if an unknown or unsupported invoke \
336  argument is found.
337 
338  '''
339  # Extract argument list.
340  argument_list = statement.items[1].items
341 
342  invoke_label = None
343  kernel_calls = []
344 
345  for argument in argument_list:
346 
347  if isinstance(argument, Actual_Arg_Spec):
348  # This should be the invoke label.
349  if invoke_label:
350  raise ParseError(
351  f"algorithm.py:Parser():create_invoke_call: An invoke "
352  f"must contain one or zero 'name=xxx' arguments but "
353  f"found more than one in: {statement} in file "
354  f"{self._alg_filename}")
355  invoke_label = self.check_invoke_label(argument)
356 
357  elif isinstance(
358  argument, (Data_Ref, Part_Ref, Structure_Constructor)):
359  # This should be a kernel call.
360  kernel_call = self.create_kernel_call(argument)
361  kernel_calls.append(kernel_call)
362 
363  else:
364  # Unknown and/or unsupported argument type
365  raise ParseError(
366  f"algorithm.py:Parser():create_invoke_call: Expecting "
367  f"argument to be of the form 'name=xxx' or a Kernel call "
368  f"but found '{argument}' in file '{self._alg_filename}'.")
369 
370  return InvokeCall(kernel_calls, name=invoke_label)
371 

References psyclone.parse.algorithm.Parser.check_invoke_label(), and psyclone.parse.algorithm.Parser.create_kernel_call().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ create_kernel_call()

def psyclone.parse.algorithm.Parser.create_kernel_call (   self,
  argument 
)
Takes the parse tree of an invoke argument containing a
reference to a kernel or a builtin and returns the kernel or
builtin object respectively which contains the required
information.

:param argument: Parse tree of an invoke argument. This \
    should contain a kernel name and associated arguments.
:type argument: :py:class:`fparser.two.Fortran2003.Part_Ref` or \
    :py:class:`fparser.two.Fortran2003.Structure_Constructor`

:returns: A builtin or coded kernel call object which contains \
    relevant information about the Kernel.
:rtype: :py:class:`psyclone.parse.algorithm.KernelCall` or \
    :py:class:`psyclone.parse.algorithm.BuiltInCall`

Definition at line 372 of file algorithm.py.

372  def create_kernel_call(self, argument):
373  '''Takes the parse tree of an invoke argument containing a
374  reference to a kernel or a builtin and returns the kernel or
375  builtin object respectively which contains the required
376  information.
377 
378  :param argument: Parse tree of an invoke argument. This \
379  should contain a kernel name and associated arguments.
380  :type argument: :py:class:`fparser.two.Fortran2003.Part_Ref` or \
381  :py:class:`fparser.two.Fortran2003.Structure_Constructor`
382 
383  :returns: A builtin or coded kernel call object which contains \
384  relevant information about the Kernel.
385  :rtype: :py:class:`psyclone.parse.algorithm.KernelCall` or \
386  :py:class:`psyclone.parse.algorithm.BuiltInCall`
387 
388  '''
389  kernel_name, args = get_kernel(argument, self._alg_filename,
390  self._arg_type_defns)
391  if kernel_name.lower() in self._builtin_name_map:
392  # This is a builtin kernel
393  kernel_call = self.create_builtin_kernel_call(
394  kernel_name, args)
395  else:
396  # This is a coded kernel
397  kernel_call = self.create_coded_kernel_call(
398  kernel_name, args)
399  return kernel_call
400 

References psyclone.parse.algorithm.Parser._alg_filename, psyclone.parse.algorithm.Parser._arg_type_defns, psyclone.parse.algorithm.Parser.create_builtin_kernel_call(), and psyclone.parse.algorithm.Parser.create_coded_kernel_call().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ invoke_info()

def psyclone.parse.algorithm.Parser.invoke_info (   self,
  alg_parse_tree 
)
Takes an fparser2 representation of a PSyclone-conformant algorithm
code as input and returns an object containing information
about the 'invoke' calls in the algorithm file and any
associated kernels within the invoke calls. Also captures the
type and precision of every variable declaration within the
parse tree.

:param alg_parse_tree: the fparser2 representation of the \
    algorithm code.
:type: :py:class:`fparser.two.Fortran2003.Program`

:returns: an object holding details of the algorithm \
    code and the invokes found within it.
:rtype: :py:class:`psyclone.parse.FileInfo`

:raises ParseError: if a program, module, subroutine or \
    function is not found in the fparser2 tree.
:raises InternalError: if the fparser2 tree representing the \
    type declaration statements is not in the expected form.
:raises NotImplementedError: if the algorithm code contains \
    two different datatypes with the same name.

Definition at line 207 of file algorithm.py.

207  def invoke_info(self, alg_parse_tree):
208  '''Takes an fparser2 representation of a PSyclone-conformant algorithm
209  code as input and returns an object containing information
210  about the 'invoke' calls in the algorithm file and any
211  associated kernels within the invoke calls. Also captures the
212  type and precision of every variable declaration within the
213  parse tree.
214 
215  :param alg_parse_tree: the fparser2 representation of the \
216  algorithm code.
217  :type: :py:class:`fparser.two.Fortran2003.Program`
218 
219  :returns: an object holding details of the algorithm \
220  code and the invokes found within it.
221  :rtype: :py:class:`psyclone.parse.FileInfo`
222 
223  :raises ParseError: if a program, module, subroutine or \
224  function is not found in the fparser2 tree.
225  :raises InternalError: if the fparser2 tree representing the \
226  type declaration statements is not in the expected form.
227  :raises NotImplementedError: if the algorithm code contains \
228  two different datatypes with the same name.
229 
230  '''
231  # Find the first program, module, subroutine or function in the
232  # parse tree. The assumption here is that the first is the one
233  # that is required. See issue #307.
234  container_name = None
235  for child in alg_parse_tree.content:
236  if isinstance(child, (Main_Program, Module, Subroutine_Subprogram,
237  Function_Subprogram)):
238  container_name = str(child.content[0].items[1])
239  break
240 
241  if not container_name:
242  # Nothing relevant found.
243  raise ParseError(
244  "algorithm.py:parser:parse: Program, module, function or "
245  "subroutine not found in fparser2 parse tree.")
246 
247  self._unique_invoke_labels = []
248  self._arg_name_to_module_name = OrderedDict()
249  # Dict holding a 2-tuple consisting of type and precision
250  # information for each variable declared in the algorithm
251  # file, indexed by variable name.
252  self._arg_type_defns = {}
253  invoke_calls = []
254 
255  # Find all invoke calls and capture information about
256  # them. Also find information about use statements and find
257  # all declarations within the supplied parse
258  # tree. Declarations will include the definitions of any
259  # components of derived types that are defined within the
260  # code.
261  for statement in walk(alg_parse_tree.content,
262  types=(Type_Declaration_Stmt,
263  Data_Component_Def_Stmt,
264  Use_Stmt, Call_Stmt)):
265  if isinstance(statement,
266  (Type_Declaration_Stmt, Data_Component_Def_Stmt)):
267  # Capture datatype information for the variable
268  spec = statement.children[0]
269  if isinstance(spec, Declaration_Type_Spec):
270  # This is a type declaration
271  my_type = spec.children[1].string.lower()
272  my_precision = None
273  elif isinstance(spec, Intrinsic_Type_Spec):
274  # This is an intrinsic declaration
275  my_type = spec.children[0].lower()
276  my_precision = None
277  if isinstance(spec.children[1], Kind_Selector):
278  my_precision = \
279  spec.children[1].children[1].string.lower()
280  else:
281  raise InternalError(
282  f"Expected first child of Type_Declaration_Stmt or "
283  f"Data_Component_Def_Stmt to be Declaration_Type_Spec "
284  f"or Intrinsic_Type_Spec but found "
285  f"'{type(spec).__name__}'")
286  for decl in walk(statement.children[2], (
287  Entity_Decl, Component_Decl)):
288  # Determine the variables names. Note that if a
289  # variable declaration is a component of a derived
290  # type, its name is stored 'as is'. This means
291  # that e.g. real :: a will clash with a
292  # derived-type definition if the latter has a
293  # component named 'a' and their datatypes differ.
294  my_var_name = decl.children[0].string.lower()
295  if my_var_name in self._arg_type_defns and (
296  self._arg_type_defns[my_var_name][0] != my_type or
297  self._arg_type_defns[my_var_name][1] !=
298  my_precision):
299  raise NotImplementedError(
300  f"The same symbol '{my_var_name}' is used for "
301  f"different datatypes, "
302  f"'{self._arg_type_defns[my_var_name][0]}, "
303  f"{self._arg_type_defns[my_var_name][1]}' and "
304  f"'{my_type}, {my_precision}'. This is not "
305  f"currently supported.")
306  # Store the variable name and information about its type
307  self._arg_type_defns[my_var_name] = (my_type, my_precision)
308 
309  if isinstance(statement, Use_Stmt):
310  # found a Fortran use statement
311  self.update_arg_to_module_map(statement)
312 
313  if isinstance(statement, Call_Stmt):
314  # found a Fortran call statement
315  call_name = str(statement.items[0])
316  if call_name.lower() == self._invoke_name.lower():
317  # The call statement is an invoke
318  invoke_call = self.create_invoke_call(statement)
319  invoke_calls.append(invoke_call)
320 
321  return FileInfo(container_name, invoke_calls)
322 

References psyclone.parse.algorithm.Parser._arg_name_to_module_name, psyclone.parse.algorithm.Parser._arg_type_defns, psyclone.alg_gen.Alg._invoke_name, psyclone.parse.algorithm.Parser._invoke_name, psyclone.parse.algorithm.Parser._unique_invoke_labels, psyclone.parse.algorithm.Parser.create_invoke_call(), and psyclone.parse.algorithm.Parser.update_arg_to_module_map().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ parse()

def psyclone.parse.algorithm.Parser.parse (   self,
  alg_filename 
)
Takes a PSyclone conformant algorithm file as input and outputs a
parse tree of the code contained therein and an object
containing information about the 'invoke' calls in the
algorithm file and any associated kernels within the invoke
calls. If the NEMO API is being used then the parsed code is
returned without any additional information about the code.

:param str alg_filename: The file containing the algorithm code.

:returns: 2-tuple consisting of the fparser2 parse tree of the \
    algorithm code and an object holding details of the \
    algorithm code and the invokes found within it, unless it \
    is the NEMO API, where the first entry of the tuple is \
    None and the second is the fparser2 parse tree of the \
    code.
:rtype: (:py:class:`fparser.two.Fortran2003.Program`, \
    :py:class:`psyclone.parse.FileInfo`) or (NoneType, \
    :py:class:`fparser.two.Fortran2003.Program`)

Definition at line 173 of file algorithm.py.

173  def parse(self, alg_filename):
174  '''Takes a PSyclone conformant algorithm file as input and outputs a
175  parse tree of the code contained therein and an object
176  containing information about the 'invoke' calls in the
177  algorithm file and any associated kernels within the invoke
178  calls. If the NEMO API is being used then the parsed code is
179  returned without any additional information about the code.
180 
181  :param str alg_filename: The file containing the algorithm code.
182 
183  :returns: 2-tuple consisting of the fparser2 parse tree of the \
184  algorithm code and an object holding details of the \
185  algorithm code and the invokes found within it, unless it \
186  is the NEMO API, where the first entry of the tuple is \
187  None and the second is the fparser2 parse tree of the \
188  code.
189  :rtype: (:py:class:`fparser.two.Fortran2003.Program`, \
190  :py:class:`psyclone.parse.FileInfo`) or (NoneType, \
191  :py:class:`fparser.two.Fortran2003.Program`)
192 
193  '''
194  self._alg_filename = alg_filename
195  if self._line_length:
196  # Make sure the code conforms to the line length limit.
197  check_line_length(alg_filename)
198  alg_parse_tree = parse_fp2(alg_filename)
199 
200  if self._api == "nemo":
201  # For this API we just parse the NEMO code and return the resulting
202  # fparser2 AST with None for the Algorithm AST.
203  return None, alg_parse_tree
204 
205  return alg_parse_tree, self.invoke_info(alg_parse_tree)
206 

References psyclone.parse.algorithm.Parser._alg_filename, psyclone.configuration.Config._api, psyclone.parse.algorithm.Parser._api, psyclone.line_length.FortLineLength._line_length, psyclone.parse.algorithm.Parser._line_length, and psyclone.parse.algorithm.Parser.invoke_info().

Here is the call graph for this function:

◆ update_arg_to_module_map()

def psyclone.parse.algorithm.Parser.update_arg_to_module_map (   self,
  statement 
)
Takes a use statement and adds its contents to the internal
arg_name_to_module_name map. This map associates names
specified in the 'only' list with the corresponding use name.

:param statement: A use statement
:type statement: :py:class:`fparser.two.Fortran2003.Use_Stmt`
:raises InternalError: if the statement being passed is not an \
fparser use statement.

Definition at line 468 of file algorithm.py.

468  def update_arg_to_module_map(self, statement):
469  '''Takes a use statement and adds its contents to the internal
470  arg_name_to_module_name map. This map associates names
471  specified in the 'only' list with the corresponding use name.
472 
473  :param statement: A use statement
474  :type statement: :py:class:`fparser.two.Fortran2003.Use_Stmt`
475  :raises InternalError: if the statement being passed is not an \
476  fparser use statement.
477 
478  '''
479  # make sure statement is a use
480  if not isinstance(statement, Use_Stmt):
481  raise InternalError(
482  f"algorithm.py:Parser:update_arg_to_module_map: Expected "
483  f"a use statement but found instance of "
484  f"'{type(statement)}'.")
485 
486  use_name = str(statement.items[2])
487 
488  # Extract 'only' list.
489  if statement.items[4]:
490  only_list = statement.items[4].items
491  else:
492  only_list = []
493 
494  for item in only_list:
495  self._arg_name_to_module_name[str(item).lower()] = use_name
496 

References psyclone.parse.algorithm.Parser._arg_name_to_module_name.

Here is the caller graph for this function:

The documentation for this class was generated from the following file: