Source code for AutoRPE.UtilsRPE.Getter

from typing import List, TYPE_CHECKING
import AutoRPE.UtilsRPE.Classes.Interface as Interface
from AutoRPE.UtilsRPE.Classes.Procedure import Procedure
import AutoRPE.UtilsRPE.BasicFunctions as BasicFunctions
import AutoRPE.UtilsRPE.MaskCreator as MaskCreator
import AutoRPE.UtilsRPE.CallManager as CallManager
import AutoRPE.UtilsRPE.NatureDeterminer as NatureDeterminer
import AutoRPE.UtilsRPE.VariablePrecision as VariablePrecision
import AutoRPE.UtilsRPE.SourceManager as SourceManager
import AutoRPE.UtilsRPE.Error as Error
import AutoRPE.UtilsRPE.RegexPattern as RegexPattern

import re

# Add line to facilitate type checking without causing circular import
if TYPE_CHECKING:
    from AutoRPE.UtilsRPE.Classes.Vault import Vault


[docs] def get_variable(contents: str, procedure: Procedure=None, vault: 'Vault'=None, clean:bool =False): """ Retrieves a variable based on its string representation, considering context. Parameters: contents (str): The string representation of the variable. procedure (Procedure, optional): The procedure context to search within. vault (Vault, optional): The vault containing metadata about the code. clean (bool): Whether to clean the string of operations and intrinsics. Returns: Variable: The variable object corresponding to the input string. Raises: VariableNotFound: If the variable is not found or the string does not represent a valid variable. """ contents = contents.replace(" ", "").strip() contents = MaskCreator.remove_literals(contents) if clean: contents = BasicFunctions.remove_unary_operator(contents) if NatureDeterminer.has_intrinsics(contents): if clean: contents_no_intrinsic = MaskCreator.mask_intrinsics(contents) ret_val = [] for c_no_i in contents_no_intrinsic: if not NatureDeterminer.is_a_number(c_no_i): ret_val.append(get_variable(c_no_i, procedure, vault=vault)) return ret_val else: raise Error.VariableNotFound("The string %s is not a variable" % contents) if NatureDeterminer.is_hardcoded_array(contents): raise Error.VariableNotFound("The string %s is not a variable" % contents) if NatureDeterminer.is_logical_comparison(contents): raise Error.VariableNotFound("The string %s is not a variable" % contents) split_elements = [] if NatureDeterminer.has_operations(contents, split_elements): if clean: ret_val = [] for el in split_elements: if not NatureDeterminer.is_a_number(el): ret_val.append(get_variable(el, procedure, vault=vault)) return ret_val else: raise Error.VariableNotFound("The string %s is not a variable" % contents) # Remove the indexing from array and DT: # ght_abl <-- ght_abl(2:jpka) # todelay%z1d <-- todelay(ji)%z1d(:) contents = BasicFunctions.remove_indexing(contents) if re.search(RegexPattern.variable, contents): try: return vault.get_variable_by_name(var_name=contents, procedure=procedure) except Error.VariableNotFound: raise Error.VariableNotFound if contents.count("%"): if contents.count("%val"): contents = contents.replace("%val", "") return get_variable(contents, procedure, vault) # Search for all others members try: return get_structure_member(contents, procedure, vault) except Error.MemberNotFound: raise Error.VariableNotFound if NatureDeterminer.is_keyword_argument(contents): # Removing before first = contents = contents[contents.find("=") + 1:].strip() return get_variable(contents, procedure, vault) raise Error.VariableNotFound("Variable %s: not found" % contents)
[docs] def get_pointer_assignment(line: str, vault: 'Vault', procedure: Procedure, var_type=VariablePrecision.real_id): """ Finds pointer assignments in a line of code and retrieves the pointer variable and its target. Parameters: line (str): The line of code to analyze. vault (Vault): The vault containing metadata about the code. procedure (Procedure): The procedure context to search within. var_type (dict): A dictionary of valid types for the pointer. Returns: list[list]: A list containing the pointer and its target if found, or None. """ if NatureDeterminer.is_pointer_assignment(line): ptr, target = _get_pointer_target(line, procedure, vault) if [ptr, target] != [None, None] and ptr.type in var_type: return [[ptr, target]]
def _get_pointer_target(line: str, procedure: Procedure, vault: 'Vault'): # Ignore the if condition. This is not an ideal solution, but the easier for the moment line = BasicFunctions.remove_if_condition(line) if line.count("::"): line = line.split("::")[1] pointer, target = line.strip().split("=>") pointer = pointer.strip() target = target.strip() # Sometimes indexing uses SIZE intrinsic than makes it impossible to find the variable pt4d(1:SIZE(ptab, dim=1)) if NatureDeterminer.has_intrinsics(pointer): pointer = MaskCreator.mask_intrinsics(pointer)[0] if target.lower().replace(" ", "") == "null()": return [None, None] else: ptr = get_variable(pointer, procedure, vault) # Is a pointer to procedure # TODO this try except is outdated if ptr.type == 'procedure': try: target = vault.get_block_by_name(target.strip(), procedure.module.name) # It is a pointer to function, i.e. a variable is associated with it return ptr, target except Error.ProcedureNotFound: # It is a pointer to subroutine return [None, None] else: target = get_variable(target, procedure, vault) return ptr, target
[docs] def get_structure_member(contents: str, procedure: Procedure, vault: 'Vault'): """ Retrieves the last member of a structure based on its string representation. Parameters: contents (str): The string representation of the structure. procedure (Procedure): The procedure context to search within. vault (Vault): The vault containing metadata about the code. Returns: Variable: The variable object corresponding to the last member of the structure. Raises: MemberNotFound: If the structure member is not found. """ # The first part will be the root split_contents = contents.split("%", 1) # Find root variable root_variable = vault.get_variable_by_name(split_contents[0], procedure) # Search for root derived type definition root_dt = [dt for dt in vault.derived_types if dt.name.lower() == root_variable.type.lower()][0] if root_dt.is_external: return None # Iterate while it can be split by '%' while len(split_contents) > 1: # Check if it's a function call (procedure pointer) match = re.match(RegexPattern.call_to_function, split_contents[1]) if match: member = match.group(1) else: split_contents = split_contents[1].split("%", 1) member = BasicFunctions.remove_indexing(split_contents[0]) try: # Iterate the derived type variables and it's extension variables (type, extends(extended_t) :: my_t) search_array = root_dt.members + (root_dt.extension.members if root_dt.extension is not None else []) layer = [v for v in search_array if v.name.lower() == member.lower()][0] except IndexError: # We couldn't found the member raise Error.MemberNotFound # If type is procedure, we can return if layer.type == 'procedure': return layer try: # Search if the member is another dt root_dt = [dt for dt in vault.derived_types if dt.name.lower() == layer.type.lower()][0] except IndexError: # If split contents it's 1, it means that it's the final layer if len(split_contents) == 1 or layer.type == 'procedure': return layer else: raise Error.VariableNotFound("Variable %s not found", contents) return layer
[docs] def get_type_of_contents(contents: str, procedure: Procedure, vault: 'Vault'): """ Determines the type of a given string of code. Parameters: contents (str): The string representation of the code. procedure (Procedure): The procedure context to analyze. vault (Vault): The vault containing metadata about the code. Returns: str: The type of the contents. Raises: TypeOfContent: If the type of the contents cannot be determined. """ contents = contents.replace(" ", "").strip() # Try to facilitate checks contents = BasicFunctions.remove_unary_operator(contents) if contents.find("rpe_var(") == 0: return "rpe_var" # Check if is a call to function, including intrinsic if CallManager.is_call_to_function(contents, procedure, vault): return get_function_type(contents, procedure, vault) if BasicFunctions.contain_literals(contents, procedure.module.dictionary_of_masked_string): return "char" if NatureDeterminer.is_logical_comparison(contents): return "logical" # Check if is an hardcoded array, and return the content type hardcoded_array = [] if NatureDeterminer.is_hardcoded_array(contents, hardcoded_array): contents = hardcoded_array[0] return get_type_of_contents(contents, procedure, vault) # After checking it is not an array we remove parenthesis contents = BasicFunctions.remove_outer_brackets(contents) split_elements = [] if NatureDeterminer.has_operations(contents, split_elements): types = [] for element in split_elements: types.append(get_type_of_contents(element, procedure, vault)) # Remove any possible None types = set([t for t in types if (t is not None and t != "external")]) if len(types) > 1: # Different types were used in the operation return BasicFunctions.combine_types(types) else: # Operands were all of the same type return types.pop() if NatureDeterminer.is_keyword_argument(contents): # Removing before first = contents = contents[contents.find("=") + 1:].strip() return get_type_of_contents(contents, procedure, vault) # Checks if it is a number num_type = NatureDeterminer.is_a_number(contents) if num_type: return num_type # # This function is vault agnostic, but the possibiliy of a function call # array = NatureDeterminer.is_an_array(contents) # if array: # # Now the array will match with a simple variable # contents = array # Remove the indexing from array and DT: # ght_abl(2:jpka+b) --> ght_abl # todelay(ji)%z1d(:) --> todelay%z1d clean_contents = BasicFunctions.remove_indexing(contents) # Remove exponentiation, so to identify variables :sla**(8.0_wp/3.0_wp) => sla clean_contents = MaskCreator.remove_pow(clean_contents) # Search if is a simple variable if re.search(RegexPattern.variable, clean_contents): var = vault.get_variable_by_name(clean_contents, procedure) return var.type # Check if is a derived data type if NatureDeterminer.is_struct_element(clean_contents): if clean_contents.split("%")[-1] == "val": return VariablePrecision.real_id['dp'] member = get_structure_member(clean_contents, procedure, vault) if member: # If it's a procedure we need to make it recursive to get the function type if member.type == 'procedure': try: # First it's a string contents = member.is_pointer + contents.split(member.name, 1)[1] except TypeError: # Later is a list of procedures contents = member.is_pointer[0].name + contents.split(member.name, 1)[1] # Make it recursive for pointers to procedures return get_type_of_contents(contents, procedure, vault) else: return member.type else: # Case of an external data type return None raise Error.TypeOfContent(f"Type of content for the argument {contents!r}: not found")
[docs] def get_intrinsic_type(function_call: str, procedure: Procedure, vault: 'Vault'): """ Determines the type of an intrinsic function call. Parameters: function_call (str): The string representation of the intrinsic function call. procedure (Procedure): The procedure context to analyze. vault (Vault): The vault containing metadata about the code. Returns: str: The type of the intrinsic function call. Raises: ValueError: If the intrinsic function type is not defined in the metadata. """ function_name = CallManager.find_called_function(function_call) try: intrinsic_type = SourceManager.list_of_intrinsics[function_name.lower()] except KeyError: return None if intrinsic_type == 'content_type': if function_name.lower() == 'real': return get_type_of_real_cast(function_call) return get_type_of_contents(CallManager.find_call_arguments(function_call)[0], procedure, vault) elif intrinsic_type == 'wp': return VariablePrecision.real_id['wp'] elif intrinsic_type in [None, 'None']: raise ValueError("Please add the type specification for this intrinsic: %s" % function_name) else: return intrinsic_type
[docs] def get_function_type(function_call, procedure: Procedure, vault: 'Vault'): """ Determines the return type of a function call. Parameters: function_call (str): The string representation of the function call. procedure (Procedure): The procedure context to analyze. vault (Vault): The vault containing metadata about the code. Returns: str: The return type of the function. """ # Check if is an intrinsic intrinsic = NatureDeterminer.has_intrinsics(function_call) # Avoid to match with function that has intrinsic as argument: iom_axis(size(rd3,3)) is NOT an intrinsic if intrinsic == CallManager.find_called_function(function_call): return get_intrinsic_type(function_call, procedure, vault) else: # Must be a function stored in vault function_name = CallManager.find_called_function(function_call) function = vault.get_subprogram_by_name(function_name, block_type=['Function', 'Interface']) if isinstance(function, Interface.Interface): called_arguments = CallManager.find_call_arguments(function_call) function = get_interface(called_arguments, function, procedure, vault) return function.type
[docs] def get_dimension_of_contents(contents: str, block: Procedure, vault: 'Vault'): """ Determines the dimensionality of a given variable or expression. Parameters: contents (str): The string representation of the variable or expression. block (Procedure): The procedure context to analyze. vault (Vault): The vault containing metadata about the code. Returns: int: The dimensionality of the contents. """ # Matches arrays in the form of (/a,b,c/) or [a,b,c] if NatureDeterminer.is_hardcoded_array(contents): return 1 # Remove outer brackets contents = BasicFunctions.remove_outer_brackets(contents) # Its a Char if BasicFunctions.contain_literals(contents, block.module.dictionary_of_masked_string): return 0 # Its a Bool if contents.lower().count(".false.") or contents.lower().count(".true."): return 0 if NatureDeterminer.is_keyword_argument(contents): # Split using only the first equal _, argument = contents.split("=", 1) return get_dimension_of_contents(argument, block, vault) # Remove possible +/- before variables contents = BasicFunctions.remove_unary_operator(contents) # If a function is passed, check is an intrinsic if CallManager.is_call_to_function(contents, block, vault): fun = CallManager.find_called_function(contents) # These intrinsic ret val dimension, depends on the argument if fun.lower() in ["real", "rpe", "sqrt", "min", "max", "abs", "log", "aimag"]: return get_dimension_of_contents(CallManager.find_call_arguments(contents)[0], block, vault) # For these check if the dim keyword is passed elif fun.lower() in ['sum', 'maxval']: if contents.lower().count("dim="): dim = get_dimension_of_contents(CallManager.find_call_arguments(contents)[0], block, vault) if dim: return dim - 1 else: return dim else: contents = CallManager.find_call_arguments(contents)[0] # COUNT returns a number elif fun.lower() in ["count", 'size']: return 0 else: raise Error.ExceptionNotManaged("Something went wrong finding the dimension of %s" % contents) # Check if has operations split_elements = [] if NatureDeterminer.has_operations(contents, split_elements): dimensions = [get_dimension_of_contents(el, block, vault) for el in split_elements] dimensions = [d for d in dimensions if d is not None] if not dimensions: dimensions = [0] return max(dimensions) # Simple number if NatureDeterminer.is_a_number(contents): return 0 if NatureDeterminer.is_logical_comparison(contents): # Split the logical comparison and keep only the first element contents, _ = BasicFunctions.split_comparison(contents) return get_dimension_of_contents(contents=contents, block=block, vault=vault) # Must be a single variable var = get_variable(contents, procedure=block, vault=vault) # If is a sliced array, count how many slice are considered if contents.count(":"): if var.type == 'char': return var.dimension return contents.count(":") # If we have the var + index specification, like sdjf%fdta(2,2,1,2) or psgn(jf) then is a scalar if re.search(r"%s *\(.*\)" % var.name, contents): return 0 return var.dimension
[docs] def get_interface(arguments: List[str], interface: Interface, block: Procedure, vault: 'Vault', uniform_intent=None): """ Determines the specific interface subroutine that matches the given arguments. Parameters: arguments (list[str]): A list of arguments passed to the interface. interface (Interface): The interface object to search within. block (Procedure): The procedure context to analyze. vault (Vault): The vault containing metadata about the code. uniform_intent (str, optional): An intent to filter by (e.g., 'in', 'out'). Returns: Subprogram: The subprogram within the interface that matches the arguments. Raises: InterfaceNotFound: If no matching routine is found. """ optional_da = [] for subprogram in interface.subprogram: for da in subprogram.dummy_arguments: if da.is_optional and da.name not in optional_da: optional_da.append(da.name) # Remove optional parameters specified by keyword keyword_arguments = [_x.split("=")[0].strip() if NatureDeterminer.is_keyword_argument(_x) else False for _x in arguments] mandatory_arguments = [] for index, kwa in enumerate(keyword_arguments): if kwa in optional_da: continue elif kwa in mandatory_arguments: new_index = [da.mandatory_position for da in interface.subprogram[0].mandatory_da if da.name == kwa][0] if len(mandatory_arguments) != new_index: raise Error.ExceptionNotManaged("The argument should be inserted in position new_index") else: mandatory_arguments.append(arguments[index]) else: mandatory_arguments.append(arguments[index]) argument_dimension = [get_dimension_of_contents(argument, block, vault) for argument in mandatory_arguments] argument_types = [get_type_of_contents(argument, block, vault) for argument in mandatory_arguments] return get_interface_from_mandatory_arguments(interface, argument_types, argument_dimension, uniform_intent)
[docs] def get_interface_from_mandatory_arguments(interface: Interface, argument_types: List[str], argument_dimensions: List[int], uniform_intent): """ Matches a specific subprogram in an interface based on mandatory arguments. Parameters: interface (Interface): The interface to analyze. argument_types (list[str]): The types of the mandatory arguments. argument_dimensions (list[int]): The dimensions of the mandatory arguments. uniform_intent (str, optional): An intent to filter by. Returns: Subprogram: The matching subprogram. Raises: InterfaceNotFound: If no matching subprogram is found. """ # Sort the interface by number of mandatory arguments interface.subprogram.sort(key=lambda x: len(x.mandatory_da), reverse=True) # Search among those subroutines that have enough arguments to match subprogram = [s for s in interface.subprogram if len(argument_types) <= len(s.dummy_arguments)] for s in subprogram: len_arg = len(s.mandatory_da) # Exclude interface, that expects more arguments than the ones used if len_arg > len(argument_types): continue argument_intent = [] if uniform_intent is not None: argument_intent = [da.intent for da in s.mandatory_da] # Some of the actual arguments, ca be optional not specified via keyword key = Interface.generate_dict_key(argument_types[:len_arg], argument_dimensions[:len_arg], argument_intent, uniform_intent) if uniform_intent is not None: if key in interface.agnostic_key[uniform_intent]: # Return the list of names key_list = interface.agnostic_key[uniform_intent][key] if len(key_list): return interface[key_list[0]] else: raise Error.InterfaceNotFound( "More than a matching routine found for interface : %s" % interface.name) else: try: subprogram_name = interface.key[key] except KeyError: continue # Return corresponding subprogram return interface[subprogram_name] raise Error.InterfaceNotFound("No matching routine found for interface : %s" % interface.name)
[docs] def get_type_of_real_cast(string: str): """ Determines the type of a real cast operation based on its arguments. Parameters: string (str): The string representation of the cast operation. Returns: str: The type of the real cast (e.g., 'sp', 'dp', etc.). """ # Leave only the arguments arguments = CallManager.find_call_arguments(string) if len(arguments) == 1: return VariablePrecision.real_id['wp'] elif len(arguments) == 2: _, arg_type = arguments # In case the type is specified along with keyword arg_type = re.sub(r'kind=', '', arg_type, flags=re.I) # TODO: Handling an edge case in NEMO 4.2.0 that was not present anymore in NEMO 4.2.2 or NEMO 5. # Must find a better solution but might be that is not necessary if arg_type == "2*wp": arg_type = "dp" return VariablePrecision.lookup_table[arg_type.lower()]