Reference Guide  2.5.0
psyir.py
1 # -----------------------------------------------------------------------------
2 # BSD 3-Clause License
3 #
4 # Copyright (c) 2022-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 # Author: R. W. Ford, STFC Daresbury Lab
35 # Modified: A. R. Porter and S. Siso, STFC Daresbury Lab
36 
37 '''This module contains PSyclone Kernel-layer-specific PSyIR classes
38 for the GOcean API.
39 
40 '''
41 import re
42 
43 from fparser.common.readfortran import FortranStringReader
44 from fparser.two import Fortran2003
45 from fparser.two.parser import ParserFactory
46 from fparser.two.utils import walk, get_child
47 
48 from psyclone.configuration import Config
49 from psyclone.domain.gocean import GOceanConstants
50 from psyclone.errors import InternalError
51 from psyclone.parse.utils import ParseError
52 from psyclone.psyir.frontend.fortran import FortranReader
53 from psyclone.psyir.nodes import Container
54 from psyclone.psyir.symbols import DataTypeSymbol, UnsupportedFortranType
55 
56 
57 class GOceanContainer(Container):
58  '''A GOcean-specific Container. This specialises the generic Container node
59  and adds in any domain-specific information.
60 
61  :param str name: the name of the container.
62  :param metadata: the metadata object.
63  :type metadata: :py:class:`psyclone.domain.gocean.kernel.psyir.\
64  GOceanKernelMetadata`
65  :param parent: optional parent node of this Container in the PSyIR.
66  :type parent: :py:class:`psyclone.psyir.nodes.Node`
67  :param symbol_table: initialise the node with a given symbol table.
68  :type symbol_table: \
69  Optional[:py:class:`psyclone.psyir.symbols.SymbolTable`]
70 
71  '''
72  def __init__(self, name, metadata, **kwargs):
73  super().__init__(name, **kwargs)
74  # The metadata object capturing GOcean kernel metadata.
75  self._metadata_metadata = metadata
76 
77  @classmethod
78  def create(cls, name, metadata, symbol_table, children):
79  '''Create a GOceanContainer instance given a name, metadata, a symbol
80  table and a list of child nodes. A GOcean-specific kernel is
81  created with the metadata describing the kernel interface for
82  a single kernel routine within the container.
83 
84  :param str name: the name of the Container.
85  :param symbol_table: the symbol table associated with this \
86  Container.
87  :type symbol_table: :py:class:`psyclone.psyir.symbols.SymbolTable`
88  :param metadata: the metadata object.
89  :type metadata: :py:class:`psyclone.domain.gocean.kernel.psyir.\
90  GOceanKernelMetadata`
91  :param children: a list of PSyIR nodes contained in the \
92  Container. These must be Containers or Routines.
93  :type children: List[:py:class:`psyclone.psyir.nodes.Container` \
94  | :py:class:`psyclone.psyir.nodes.Routine`]
95 
96  :returns: an instance of `cls`.
97  :rtype: :py:class:`psyclone.psyir.nodes.Container` or subclass
98  thereof
99 
100  '''
101  return cls(name, metadata, children=children,
102  symbol_table=symbol_table.detach())
103 
104  @property
105  def metadata(self):
106  '''
107  :returns the GOcean metadata object.
108  :rtype: :py:class:`psyclone.domain.gocean.kernel.psyir.\
109  GOceanKernelMetadata`
110  '''
111  return self._metadata_metadata
112 
114  '''Lower this GOcean-specific container to language level psyir.
115 
116  :returns: the lowered version of this node.
117  :rtype: :py:class:`psyclone.psyir.node.Node`
118 
119  '''
120  # Create metadata symbol and add it to the container symbol
121  # table.
122  data_symbol = self.metadatametadata.lower_to_psyir()
123  self.symbol_table.add(data_symbol)
124 
125  # Replace this gocean container with a generic container
126  children = self.pop_all_children()
127  generic_container = Container.create(
128  self.name, self.symbol_table.detach(), children)
129  self.replace_with(generic_container)
130  return generic_container
131 
132 
134  '''Contains GOcean kernel metadata. This class supports kernel
135  metadata creation, modification, loading from a fortran string,
136  writing to a fortran string, raising from existing language-level
137  PSyIR and lowering to language-level psyir.
138 
139  :param iterates_over: the name of the quantity that this kernel is \
140  intended to iterate over.
141  :type iterates_over: Optional[str]
142  :param index_offset: the name of the quantity that specifies the \
143  index offset (how different field indices relate to each \
144  other).
145  :type index_offset: Optional[str]
146  :param meta_args: a list of 'meta_arg' objects which capture the \
147  metadata values of the kernel arguments.
148  :type meta_args: Optional[List[:py:class:`GridArg` | :py:class:`FieldArg` \
149  | :py:class:`ScalarArg`]]
150  :param procedure_name: the name of the kernel procedure to call.
151  :type procedure_name: Optional[str]
152  :param name: the name of the symbol to use for the metadata in \
153  language-level PSyIR.
154  :type name: Optional[str]
155 
156  '''
157  def __init__(self, iterates_over=None, index_offset=None, meta_args=None,
158  procedure_name=None, name=None):
159  # Validate values using setters if they are not None
160  self._iterates_over_iterates_over = None
161  if iterates_over is not None:
162  self.iterates_overiterates_overiterates_overiterates_over = iterates_over
163  self._index_offset_index_offset = None
164  if index_offset is not None:
165  self.index_offsetindex_offsetindex_offsetindex_offset = index_offset
166  if meta_args is None:
167  self._meta_args_meta_args = []
168  else:
169  if not isinstance(meta_args, list):
170  raise TypeError(f"meta_args should be a list but found "
171  f"{type(meta_args).__name__}.")
172  for entry in meta_args:
173  if not isinstance(entry,
177  raise TypeError(
178  f"meta_args should be a list of FieldArg, GridArg or "
179  f"ScalarArg objects, but found "
180  f"{type(entry).__name__}.")
181  self._meta_args_meta_args = meta_args
182  self._procedure_name_procedure_name = None
183  if procedure_name is not None:
184  self.procedure_nameprocedure_nameprocedure_nameprocedure_name = procedure_name
185  self._name_name = None
186  if name is not None:
187  self.namenamenamename = name
188 
189  def lower_to_psyir(self):
190  ''' Lower the metadata to language-level PSyIR.
191 
192  :returns: metadata as stored in language-level PSyIR.
193  :rtype: :py:class:`psyclone.psyir.symbols.DataTypeSymbol`
194 
195  '''
196  return DataTypeSymbol(
197  str(self.namenamenamename), UnsupportedFortranType(self.fortran_stringfortran_string()))
198 
199  @staticmethod
200  def create_from_psyir(symbol):
201  '''Create a new instance of GOceanKernelMetadata populated with
202  metadata from a kernel in language-level PSyIR.
203 
204  :param symbol: the symbol in which the metadata is stored \
205  in language-level PSyIR.
206  :type symbol: :py:class:`psyclone.psyir.symbols.DataTypeSymbol`
207 
208  :returns: an instance of GOceanKernelMetadata.
209  :rtype: :py:class:`psyclone.domain.gocean.kernel.psyir.\
210  GOceanKernelMetadata`
211 
212  :raises TypeError: if the symbol argument is not the expected \
213  type.
214  :raises InternalError: if the datatype of the provided symbol \
215  is not the expected type.
216 
217  '''
218  if not isinstance(symbol, DataTypeSymbol):
219  raise TypeError(
220  f"Expected a DataTypeSymbol but found a "
221  f"{type(symbol).__name__}.")
222 
223  datatype = symbol.datatype
224 
225  if not isinstance(datatype, UnsupportedFortranType):
226  raise InternalError(
227  f"Expected kernel metadata to be stored in the PSyIR as "
228  f"an UnsupportedFortranType, but found "
229  f"{type(datatype).__name__}.")
230 
231  # In an UnsupportedFortranType, the declaration is stored as a
232  # string, so use create_from_fortran_string()
233  return GOceanKernelMetadata.create_from_fortran_string(
234  datatype.declaration)
235 
236  @staticmethod
237  def create_from_fortran_string(fortran_string):
238  '''Create a new instance of GOceanKernelMetadata populated with
239  metadata stored in a fortran string.
240 
241  :param str fortran_string: the metadata stored as Fortran.
242 
243  :returns: an instance of GOceanKernelMetadata.
244  :rtype: :py:class:`psyclone.domain.gocean.kernel.psyir.\
245  GOceanKernelMetadata`
246 
247  :raises ValueError: if the string does not contain a fortran \
248  derived type.
249  :raises ParseError: if the metadata has an unexpected format.
250 
251  '''
252  kernel_metadata = GOceanKernelMetadata()
253 
254  # Ensure the Fortran2003 parser is initialised.
255  _ = ParserFactory().create(std="f2003")
256  reader = FortranStringReader(fortran_string)
257  try:
258  spec_part = Fortran2003.Derived_Type_Def(reader)
259  except Fortran2003.NoMatchError:
260  # pylint: disable=raise-missing-from
261  raise ValueError(
262  f"Expected kernel metadata to be a Fortran derived type, but "
263  f"found '{fortran_string}'.")
264 
265  kernel_metadata.name = spec_part.children[0].children[1].tostr()
266 
267  const = GOceanConstants()
268  # Extract and store the required 'iterates_over',
269  # 'index_offset' and 'code' properties from the parse tree
270 
271  # the value of iterates over (go_all_pts, ...)
272  value = GOceanKernelMetadata._get_property(
273  spec_part, "iterates_over").string
274  kernel_metadata.iterates_over = value
275 
276  # the value of index offset (NE, ...)
277  value = GOceanKernelMetadata._get_property(
278  spec_part, "index_offset").string
279  kernel_metadata.index_offset = value
280 
281  # the name of the procedure that this metadata refers to.
282  kernel_metadata.procedure_name = GOceanKernelMetadata._get_property(
283  spec_part, "code").string
284 
285  # meta_args contains arguments which have
286  # properties. Therefore create appropriate (GridArg, ScalarArg
287  # or FieldArg) instances to capture this information.
288  meta_args = GOceanKernelMetadata._get_property(
289  spec_part, "meta_args")
290  args = walk(meta_args, Fortran2003.Ac_Value_List)
291  if not args:
292  raise ParseError(
293  f"meta_args should be a list, but found "
294  f"'{str(meta_args)}' in '{spec_part}'.")
295 
296  for meta_arg in args[0].children:
297  if len(meta_arg.children[1].children) == 2:
298  # Grid args have 2 arguments
299  kernel_metadata.meta_args.append(
300  GOceanKernelMetadata.GridArg(meta_arg, kernel_metadata))
301  elif len(meta_arg.children[1].children) == 3:
302  # scalar and field args have 3 arguments
303  arg2 = meta_arg.children[1].children[1].string.lower()
304  if arg2 in const.VALID_FIELD_GRID_TYPES:
305  kernel_metadata.meta_args.append(
307  meta_arg, kernel_metadata))
308  elif arg2 in const.VALID_SCALAR_TYPES:
309  kernel_metadata.meta_args.append(
311  meta_arg, kernel_metadata))
312  else:
313  raise ParseError(
314  f"Expected a 'meta_arg' entry with 3 arguments to "
315  f"either be a field or a scalar, but found '{arg2}' "
316  f"as the second argument instead of "
317  f"'{const.VALID_FIELD_GRID_TYPES}' (fields) or "
318  f"'{const.VALID_SCALAR_TYPES}' (scalars).")
319  else:
320  raise ParseError(
321  f"'meta_args' should have either 2 or 3 arguments, but "
322  f"found {len(meta_arg.children[1].children)} in "
323  f"{str(meta_arg)}.")
324 
325  return kernel_metadata
326 
327  @staticmethod
328  def _get_property(spec_part, property_name):
329  '''Internal utility that gets the property 'property_name' from an
330  fparser2 tree capturing gocean metadata. It is assumed that
331  the code property is part of a type bound procedure and that
332  the other properties are part of the data declarations.
333 
334  :param spec_part: the fparser2 parse tree containing the metadata.
335  :type spec_part: :py:class:`fparser.two.Fortran2003.Derived_Type_Def`
336  :param str property_name: the name of the property whose value \
337  is being extracted from the metadata.
338 
339  :returns: the value of the property.
340  :rtype: :py:class:`fparser.two.Fortran2003.Name | \
341  :py:class:`fparser.two.Fortran2003.Array_Constructor`
342 
343  :raises ParseError: if the property name is not found in the \
344  metadata.
345 
346  '''
347  if property_name.lower() == "code":
348  # The value of 'code' should be found in a type bound
349  # procedure (after the contains keyword)
350  type_bound_procedure = get_child(
351  spec_part, Fortran2003.Type_Bound_Procedure_Part)
352  if not type_bound_procedure:
353  raise ParseError(
354  f"No type-bound procedure found within a 'contains' "
355  f"section in '{spec_part}'.")
356  if len(type_bound_procedure.children) != 2:
357  raise ParseError(
358  f"Expecting a type-bound procedure, but found "
359  f"'{spec_part}'.")
360  specific_binding = type_bound_procedure.children[1]
361  if not isinstance(specific_binding, Fortran2003.Specific_Binding):
362  raise ParseError(
363  f"Expecting a specific binding for the type-bound "
364  f"procedure, but found '{specific_binding}' in "
365  f"'{spec_part}'.")
366  binding_name = specific_binding.children[3]
367  procedure_name = specific_binding.children[4]
368  if binding_name.string.lower() != "code" and procedure_name:
369  raise ParseError(
370  f"Expecting the type-bound procedure binding-name to be "
371  f"'code' if there is a procedure name, but found "
372  f"'{str(binding_name)}' in '{spec_part}'.")
373  if not procedure_name:
374  # Support the alternative metadata format that does
375  # not include 'code =>'
376  procedure_name = binding_name
377  return procedure_name
378 
379  # The 'property_name' will be declared within Component_Part.
380  component_part = get_child(spec_part, Fortran2003.Component_Part)
381  if not component_part:
382  raise ParseError(
383  f"No declarations were found in the kernel metadata: "
384  f"'{spec_part}'.")
385  # Each name/value pair will be contained within a Component_Decl
386  for component_decl in walk(component_part, Fortran2003.Component_Decl):
387  # Component_Decl(Name('name') ...)
388  name = component_decl.children[0].string
389  if name.lower() == property_name.lower():
390  # The value will be contained in a Component_Initialization
391  comp_init = get_child(
392  component_decl, Fortran2003.Component_Initialization)
393  if not comp_init:
394  raise ParseError(
395  f"No value for property {property_name} was found "
396  f"in '{spec_part}'.")
397  # Component_Initialization('=', Name('name'))
398  return comp_init.children[1]
399  raise ParseError(
400  f"'{property_name}' was not found in {str(spec_part)}.")
401 
402  def fortran_string(self):
403  '''
404  :returns: the metadata represented by this instance as Fortran.
405  :rtype: str
406  '''
407  go_args = []
408  for go_arg in self.meta_argsmeta_args:
409  go_args.append(go_arg.fortran_string())
410  go_args_str = ", &\n".join(go_args)
411  result = (
412  f"TYPE, EXTENDS(kernel_type) :: {self.name}\n"
413  f" TYPE(go_arg), DIMENSION({len(self.meta_args)}) :: "
414  f"meta_args = (/ &\n{go_args_str}/)\n"
415  f" INTEGER :: ITERATES_OVER = {self.iterates_over}\n"
416  f" INTEGER :: INDEX_OFFSET = {self.index_offset}\n"
417  f" CONTAINS\n"
418  f" PROCEDURE, NOPASS :: code => {self.procedure_name}\n"
419  f"END TYPE {self.name}\n")
420  return result
421 
422  @staticmethod
423  def _validate_iterates_over(value):
424  '''Check that 'value' is a valid 'iterates_over' value (go_all_pts,
425  ...).
426 
427  :param str value: the value to check.
428 
429  :raises ValueError: if the supplied value is invalid.
430 
431  '''
432  const = GOceanConstants()
433  if value.lower() not in const.VALID_ITERATES_OVER:
434  raise ValueError(
435  f"Expected one of {str(const.VALID_ITERATES_OVER)} for "
436  f"'iterates_over' metadata, but found '{value}'.")
437 
438  @property
439  def name(self):
440  '''
441  :returns: the name of the symbol that will contain the \
442  metadata when lowering.
443  :rtype: str
444 
445  '''
446  return self._name_name
447 
448  @name.setter
449  def name(self, value):
450  '''
451  :param str value: set the name of the symbol that will contain \
452  the metadata when lowering.
453 
454  :raises ValueError: if the name is not valid.
455 
456  '''
457  FortranReader.validate_name(value)
458  self._name_name = value
459 
460  @property
461  def iterates_over(self):
462  '''
463  :returns: the name of the quantity that this kernel is intended to \
464  iterate over.
465  :rtype: str
466  '''
467  return self._iterates_over_iterates_over
468 
469  @iterates_over.setter
470  def iterates_over(self, value):
471  '''
472  :param str value: set the iterates_over metadata to the \
473  specified value.
474  '''
475  self._validate_iterates_over_validate_iterates_over(value)
476  self._iterates_over_iterates_over = value
477 
478  @staticmethod
479  def _validate_index_offset(value):
480  '''Check that 'value' is a valid 'index_offset' value (go_offset_ne,
481  ...).
482 
483  :param str value: the value to check.
484 
485  :raises ValueError: if the supplied value is invalid.
486 
487  '''
488  const = GOceanConstants()
489  if value.lower() not in const.SUPPORTED_OFFSETS:
490  raise ValueError(
491  f"Expected one of {str(const.SUPPORTED_OFFSETS)} for "
492  f"'index_offset' metadata, but found '{value}'.")
493 
494  @property
495  def index_offset(self):
496  '''
497  :returns: the name of the quantity that specifies the index \
498  offset (how different field indices relate to each other).
499  :rtype: str
500  '''
501  return self._index_offset_index_offset
502 
503  @index_offset.setter
504  def index_offset(self, value):
505  '''
506  :param str value: set the index_offset metadata to the \
507  specified value.
508  '''
509  self._validate_index_offset_validate_index_offset(value)
510  self._index_offset_index_offset = value
511 
512  @property
513  def meta_args(self):
514  '''
515  :returns: a list of 'meta_arg' objects which capture the \
516  metadata values of the kernel arguments.
517  :rtype: List[:py:class:`psyclone.psyir.common.kernel.\
518  KernelMetadataSymbol.KernelMetadataArg`]
519  '''
520  return self._meta_args_meta_args
521 
522  @property
523  def procedure_name(self):
524  '''
525  :returns: the kernel procedure name specified by the metadata.
526  :rtype: str
527  '''
528  return self._procedure_name_procedure_name
529 
530  @procedure_name.setter
531  def procedure_name(self, value):
532  '''
533  :param str value: set the procedure name specified in the
534  metadata to the specified value.
535 
536  :raises ValueError: if the supplied procedure name is invalid.
537 
538  '''
539  try:
540  FortranReader.validate_name(value)
541  except (TypeError, ValueError) as err:
542  raise ValueError(
543  f"Expected procedure_name to be a valid value but found "
544  f"'{value}'.") from err
545  self._procedure_name_procedure_name = value
546 
547  class GridArg():
548  '''Internal class to capture Kernel metadata argument information for
549  a grid property.
550 
551  :param meta_arg: an fparser2 tree representation of the metadata.
552  :type meta_arg: :py:class:`fparser.two.Fortran2003.Part_Ref`
553  :param parent: a KernelMetadataSymbol instance that captures \
554  other parts of the metadata and references this instance.
555  :type parent: :py:class`psyclone.psyir.common.kernel. \
556  KernelMetadataSymbol`
557 
558  :raises ParseError: if the metadata does not contain two \
559  arguments.
560 
561  '''
562  def __init__(self, meta_arg, parent):
563  self._parent_parent = parent
564 
565  arg_list = meta_arg.children[1]
566  if len(arg_list.children) != 2:
567  raise ParseError(
568  f"There should be 2 kernel metadata arguments for a grid "
569  f"property but found {len(arg_list.children)} in "
570  f"{str(meta_arg)}")
571 
572  # access descriptor (read, write, ...)
573  access = arg_list.children[0].string
574  self.accessaccessaccessaccess = access
575 
576  # name of the grid property (grid_mask_t, ...)
577  name = arg_list.children[1].string
578  self.namenamenamename = name
579 
580  def fortran_string(self):
581  '''
582  :returns: the metadata represented by this class as a \
583  Fortran string.
584  :rtype: str
585  '''
586  return f"go_arg({self.access}, {self.name})"
587 
588  @staticmethod
589  def _validate_access(value):
590  '''Check that 'value' is a valid 'access' value (go_read, ...).
591 
592  :param str value: the value to check.
593 
594  :raises ValueError: if the supplied value is invalid.
595 
596  '''
597  const = GOceanConstants()
598  if value.lower() not in const.get_valid_access_types():
599  raise ValueError(
600  f"The first metadata entry for a grid property argument "
601  f"should be a valid access descriptor (one of "
602  f"{const.get_valid_access_types()}), but found '{value}'.")
603 
604  @property
605  def access(self):
606  '''
607  :returns: the value of the access descriptor. This \
608  specifies how the grid property is accessed (read, write, \
609  readwrite).
610  :rtype: str
611  '''
612  return self._access_access
613 
614  @access.setter
615  def access(self, value):
616  '''
617  :param str value: set the access descriptor for this grid \
618  property to the specified value.
619  '''
620  self._validate_access_validate_access(value)
621  self._access_access = value
622 
623  @staticmethod
624  def _validate_name(value):
625  '''Check that 'value' is a valid 'name' value for a GOcean grid
626  property (go_grid_mask_t, ...).
627 
628  :param str value: the value to check.
629 
630  :raises ValueError: if the supplied value is invalid.
631 
632  '''
633  config = Config.get()
634  api_config = config.api_conf("gocean1.0")
635  grid_property_names = list(api_config.grid_properties.keys())
636  if value.lower() not in grid_property_names:
637  raise ValueError(
638  f"The second metadata entry for a grid property argument "
639  f"should have a valid name (one of "
640  f"{grid_property_names}), but found '{value}'.")
641 
642  @property
643  def name(self):
644  '''
645  :returns: the grid property name as specified by the metadata.
646  :rtype: str
647  '''
648  return self._name_name
649 
650  @name.setter
651  def name(self, value):
652  '''
653  :param str value: set the grid property name to the \
654  specified value.
655  '''
656  self._validate_name_validate_name(value)
657  self._name_name = value
658 
659  class FieldArg():
660  '''Internal class to capture Kernel metadata information for
661  a field argument.
662 
663  :param meta_arg: an fparser2 tree representation of the metadata.
664  :type meta_arg: :py:class:`fparser.two.Fortran2003.Part_Ref`
665 
666  :param parent: a KernelMetadataSymbol instance that captures \
667  other parts of the metadata and references this instance.
668  :type parent: :py:class`psyclone.psyir.common.kernel. \
669  KernelMetadataSymbol`
670 
671  :raises ParseError: if the metadata does not contain three \
672  arguments.
673 
674  '''
675  def __init__(self, meta_arg, parent):
676  self._parent_parent = parent
677 
678  arg_list = meta_arg.children[1]
679  if len(arg_list.children) != 3:
680  raise ParseError(
681  f"There should be 3 kernel metadata entries for a field "
682  f"argument, but found {len(arg_list.children)} in "
683  f"{str(meta_arg)}.")
684 
685  # access descriptor (go_read, go_write, ...)
686  access = arg_list.children[0].string
687  self.accessaccessaccessaccess = access
688 
689  # grid point type (go_ct, ...)
690  grid_point_type = arg_list.children[1].string
691  self.grid_point_typegrid_point_typegrid_point_typegrid_point_type = grid_point_type
692 
693  if isinstance(arg_list.children[2], Fortran2003.Name):
694  # form of access (go_pointwise, ...)
695  form = arg_list.children[2].string
696  self.formformformform = form
697  self._stencil_stencil = None
698  else: # Stencil form (go_stencil) and stencil value
699  # (e.g. [000, 111, 000])
700  name = arg_list.children[2].children[0].string
701  self._validate_stencil_name_validate_stencil_name(name)
702  self.formformformform = name
703  stencil = []
704  for stencil_dim in arg_list.children[2].children[1].children:
705  stencil.append(stencil_dim.children[0])
706  self._stencil_stencil = stencil
707 
708  def fortran_string(self):
709  '''
710  :returns: the metadata represented by this class as a \
711  Fortran string.
712  :rtype: str
713  '''
714  if self.stencilstencilstencil:
715  return (f"go_arg({self.access}, {self.grid_point_type}, "
716  f"{self.form}({', '.join(self.stencil)}))")
717  return (f"go_arg({self.access}, {self.grid_point_type}, "
718  f"{self.form})")
719 
720  @staticmethod
721  def _validate_access(value):
722  '''Check that 'value' is a valid 'access' value (go_read, ...).
723 
724  :param str value: the value to check.
725 
726  :raises ValueError: if the supplied value is invalid.
727 
728  '''
729  const = GOceanConstants()
730  if value.lower() not in const.get_valid_access_types():
731  raise ValueError(
732  f"The first metadata entry for a field argument should "
733  f"be a recognised access descriptor (one of "
734  f"{const.get_valid_access_types()}), but found '{value}'.")
735 
736  @property
737  def access(self):
738  '''
739  :returns: the access descriptor for this field \
740  argument.
741  :rtype: str
742  '''
743  return self._access_access
744 
745  @access.setter
746  def access(self, value):
747  '''
748  :param str value: set the access descriptor to the \
749  specified value.
750  '''
751  self._validate_access_validate_access(value)
752  self._access_access = value
753 
754  @staticmethod
755  def _validate_grid_point_type(value):
756  '''Check that 'value' is a valid 'grid_point_type' value
757  (go_ct, ...).
758 
759  :param str value: the value to check.
760 
761  :raises ValueError: if the supplied value is invalid.
762 
763  '''
764  const = GOceanConstants()
765  if value.lower() not in const.VALID_FIELD_GRID_TYPES:
766  raise ValueError(
767  f"The second metadata entry for a field argument should "
768  f"be a recognised grid-point type descriptor (one of "
769  f"{const.VALID_FIELD_GRID_TYPES}), but found '{value}'.")
770 
771  @property
772  def grid_point_type(self):
773  '''
774  :returns: the value of the grid point type (go_ct, ...) \
775  for the field argument.
776  :rtype: str
777  '''
778  return self._grid_point_type_grid_point_type
779 
780  @grid_point_type.setter
781  def grid_point_type(self, value):
782  '''
783  :param str value: set the field grid point type (ct, ...) \
784  to the specified value.
785  '''
786  self._validate_grid_point_type_validate_grid_point_type(value)
787  self._grid_point_type_grid_point_type = value
788 
789  @staticmethod
790  def _validate_form(value):
791  '''Check that 'value' is a valid 'form' value (go_pointwise, ...).
792 
793  :param str value: the value to check.
794 
795  :raises ValueError: if the supplied value is invalid.
796 
797  '''
798  const = GOceanConstants()
799  if value.lower() not in const.VALID_STENCIL_NAMES + ["go_stencil"]:
800  raise ValueError(
801  f"The third metadata entry for a field should "
802  f"be a recognised stencil descriptor (one of "
803  f"{const.VALID_STENCIL_NAMES} or 'go_stencil'), "
804  f"but found '{value}'.")
805 
806  @property
807  def form(self):
808  '''
809  :returns: the form of access for the field (pointwise, \
810  stencil, ...).
811  :rtype: str
812  '''
813  return self._form_form
814 
815  @form.setter
816  def form(self, value):
817  '''
818  :param str value: set the form of access for the field to \
819  the specified value.
820  '''
821  self._validate_form_validate_form(value)
822  self._form_form = value
823 
824  @staticmethod
825  def _validate_stencil_name(value):
826  '''Check that value is the expected stencil name (go_stencil).
827 
828  :raises ValueError: if an invalid stencil name is found.
829 
830  '''
831  const = GOceanConstants()
832  if value.lower() != const.VALID_STENCIL_NAME:
833  raise ValueError(
834  f"The third metadata entry for a field should "
835  f"be {const.VALID_STENCIL_NAME}(...) if it contains "
836  f"arguments, but found '{value}'.")
837 
838  @staticmethod
839  def _validate_stencil(value_list):
840  '''Check that 'value_list' is a valid list of 'stencil' elements
841  (e.g. [000, 111, 000]).
842 
843  :param value_list: the values to check.
844  :type value_list: List[str]
845 
846  :raises TypeError: if the supplied argument is not a list.
847  :raises ValueError: if the supplied list is not of size 3.
848  :raises TypeError: if any of the list entries are not strings.
849  :raises ValueError: if any of the list entries do not \
850  conform to the expected format.
851 
852  '''
853  if not isinstance(value_list, list):
854  raise TypeError(
855  f"Stencil entries should be provided as a list, but found "
856  f"'{type(value_list).__name__}'.")
857  if len(value_list) != 3:
858  raise ValueError(
859  f"If the third metadata entry is a stencil, it should "
860  f"contain 3 arguments, but found "
861  f"{len(value_list)} in {value_list}.")
862  # Match 3 integers which can each have the value 0 or 1
863  # (e.g. 000, 010, 111, ...)
864  pattern = re.compile("[01]{3,3}")
865  for value in value_list:
866  if not isinstance(value, str):
867  raise TypeError(
868  f"Stencil entries should be strings, but found "
869  f"'{type(value).__name__}'.")
870  if not pattern.match(value):
871  raise ValueError(
872  f"Stencil entries should follow the regular "
873  f"expression [01]{{3,3}}, but found '{value}'.")
874 
875  @property
876  def stencil(self):
877  '''
878  :returns: the stencil value, or None if there is no stencil.
879  :rtype: Optional[List[str]]
880  '''
881  return self._stencil_stencil
882 
883  @stencil.setter
884  def stencil(self, value_list):
885  '''
886  :param value_list: set the new stencil value, encoded as \
887  three strings, each of three digits (0 or 1), see the \
888  `psyclone user guide <https://psyclone.readthedocs.io/en/\
889 stable/gocean1p0.html#argument-metadata-meta-args>` \
890  for more details.
891  :type value_list: List[str]
892 
893  '''
894  self._validate_stencil_validate_stencil(value_list)
895  self._stencil_stencil = value_list
896  # If form was not GO_STENCIL, we need to set it to
897  # GO_STENCIL now that we are providing a stencil value as
898  # the format is GO_STENCIL(stencil) which is _form (
899  # _stencil ).
900  if self._form_form.upper() != "GO_STENCIL":
901  self._form_form = "GO_STENCIL"
902 
903  class ScalarArg():
904  '''Internal class to capture Kernel metadata information for
905  a scalar argument.
906 
907  :param meta_arg: an fparser2 tree representation of the metadata.
908  :type meta_arg: :py:class:`fparser.two.Fortran2003.Part_Ref`
909 
910  :param parent: a KernelMetadataSymbol instance that captures \
911  other parts of the metadata and references this instance.
912  :type parent: :py:class`psyclone.psyir.common.kernel. \
913  KernelMetadataSymbol`
914 
915  :raises ParseError: if the metadata does not contain three \
916  arguments.
917 
918  '''
919  def __init__(self, meta_arg, parent):
920  self._parent_parent = parent
921 
922  arg_list = meta_arg.children[1]
923  if len(arg_list.children) != 3:
924  raise ParseError(
925  f"There should be 3 kernel metadata entries for a scalar "
926  f"argument, but found {len(arg_list.children)} in "
927  f"{str(meta_arg)}.")
928 
929  # We do not use the setters here for setting the values of
930  # access, datatype and form as the setters update the
931  # underlying string representation and we don't want/need
932  # to do that as we are actually using the value of the
933  # string representation to set these values.
934 
935  # access descriptor (read, write, ...)
936  access = arg_list.children[0].string
937  self._validate_access_validate_access(access)
938  self._access_access = access
939 
940  # datatype (real, integer, ...)
941  datatype = arg_list.children[1].string
942  self._validate_datatype_validate_datatype(datatype)
943  self._datatype_datatype = datatype
944 
945  # form of access (pointwise)
946  form = arg_list.children[2].string
947  self._validate_form_validate_form(form)
948  self._form_form = form
949 
950  def fortran_string(self):
951  '''
952  :returns: the metadata represented by this class as a \
953  Fortran string.
954  :rtype: str
955  '''
956  return f"go_arg({self.access}, {self.datatype}, {self.form})"
957 
958  @staticmethod
959  def _validate_access(value):
960  '''Check that 'value' is a valid 'access' value (go_read, ...).
961 
962  :param str value: the value to check.
963 
964  :raises ValueError: if the supplied value is invalid.
965 
966  '''
967  const = GOceanConstants()
968  if value.lower() not in const.get_valid_access_types():
969  raise ValueError(
970  f"The first metadata entry for a scalar argument should "
971  f"be a recognised access descriptor (one of "
972  f"{const.get_valid_access_types()}), but found '{value}'.")
973 
974  @property
975  def access(self):
976  '''
977  :returns: the access descriptor for this scalar argument.
978  :rtype: str
979  '''
980  return self._access_access
981 
982  @access.setter
983  def access(self, value):
984  '''
985  :param str value: set the access descriptor for this
986  scalar argument to the specified value.
987  '''
988  self._validate_access_validate_access(value)
989  self._access_access = value
990 
991  @staticmethod
992  def _validate_datatype(value):
993  '''Check that 'value' is a valid scalar 'datatype' value
994  (go_r_scalar, ...).
995 
996  :param str value: the value to check.
997 
998  :raises ValueError: if the supplied value is invalid.
999 
1000  '''
1001  const = GOceanConstants()
1002  if value.lower() not in const.VALID_SCALAR_TYPES:
1003  raise ValueError(
1004  f"The second metadata entry for a scalar argument should "
1005  f"be a recognised name (one of "
1006  f"{const.VALID_SCALAR_TYPES}), but found '{value}'.")
1007 
1008  @property
1009  def datatype(self):
1010  '''
1011  :returns: the datatype of the scalar argument.
1012  :rtype: str
1013  '''
1014  return self._datatype_datatype
1015 
1016  @datatype.setter
1017  def datatype(self, value):
1018  '''
1019  :param str value: set the scalar datatype to the specified \
1020  value.
1021  '''
1022  self._validate_datatype_validate_datatype(value)
1023  self._datatype_datatype = value
1024 
1025  @staticmethod
1026  def _validate_form(value):
1027  '''Check that 'value' is a valid 'form' value (go_pointwise).
1028 
1029  :param str value: the value to check.
1030 
1031  :raises ValueError: if the supplied value is invalid.
1032 
1033  '''
1034  const = GOceanConstants()
1035  if value.lower() not in const.VALID_STENCIL_NAMES:
1036  raise ValueError(
1037  f"The third metadata entry for a scalar should "
1038  f"be a recognised name (one of "
1039  f"{const.VALID_STENCIL_NAMES}), but found '{value}'.")
1040 
1041  @property
1042  def form(self):
1043  '''
1044  :returns: the form of access for the scalar (pointwise ...).
1045  :rtype: str
1046  '''
1047  return self._form_form
1048 
1049  @form.setter
1050  def form(self, value):
1051  '''
1052  :param str value: set the form of access for the scalar to \
1053  the specified value.
1054  '''
1055  self._validate_form_validate_form(value)
1056  self._form_form = value
1057 
1058 
1059 __all__ = ['GOceanContainer', 'GOceanKernelMetadata']
def create(cls, name, metadata, symbol_table, children)
Definition: psyir.py:78