import AutoRPE.UtilsRPE.Classes.Procedure as Procedure
import AutoRPE.UtilsRPE.Classes.Subprogram as Subprogram
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.Error as Error
import AutoRPE.UtilsRPE.RegexPattern as RegexPattern
import AutoRPE.UtilsRPE.VariablePrecision as VariablePrecision
import AutoRPE.UtilsRPE.Getter as Getter
import warnings
import re
from os.path import join, isfile
[docs]
def insert_use_rp_emulator_in_function(func_name: str, module: 'Module'):
"""
Inserts a 'USE rp_emulator' statement after the function declaration in the module.
Parameters:
func_name (str): The name of the function to search for.
module (Module): The module to modify.
"""
for idx, line in enumerate(module.lines):
if re.search(RegexPattern.subprogram_name_declaration % func_name, line, flags=re.IGNORECASE):
module.lines.insert(idx + 1, "USE rp_emulator")
return
[docs]
def insert_use_statement(vault: 'Vault'):
"""
Adds 'USE rp_emulator' and 'USE read_precisions' to all modules and abstract interfaces
that contain a variable of type 'rpe_var'.
Parameters:
vault (Vault): The vault containing the modules and interfaces to modify.
"""
# Adding module rp_emulator (in theory adding this to par_kind.f90 should be enough,
# but theory fails and compiler complains so I'll add it everywhere
for module in vault.modules.values():
module.lines.insert(1, "\nUSE rp_emulator\nUSE read_precisions\n")
module.update_module_lines()
# Add USE rp_emulator to the abstract interfaces with rpe_var
for interface in vault.interfaces_dictionary.values():
if interface.is_abstract:
# Check the different subprograms and variables
for subprogram in interface.subprogram:
for v in subprogram.variables:
if v.type == 'rpe_var':
# Add the statement
insert_use_rp_emulator_in_function(subprogram.name, interface.module)
interface.module.update_module_lines()
break
[docs]
def create_read_precisions_module(path_to_sources: str, vault: 'Vault'):
"""
Generates a Fortran module to read precision namelist and writes it to a file.
Parameters:
path_to_sources (str): The directory path where the module should be saved.
vault (Vault): The vault containing the variables to be processed.
"""
from os.path import dirname, abspath
print("\n\nCreating module to read precision namelist...")
# Get the number of rpe variables
rpe_vars = [var for var in vault.variables if var.id is not None]
rpe_vars.sort(key=lambda x: x.id)
rpe_vars_number = len(rpe_vars)
# Store information to be printed in used variables
variables_info = ["\tvariable_info( % i) = \"!\t%s\t%s\t%s\"" %
(v.id, v.name, v.procedure.name, v.module.name) for v in rpe_vars]
# Path to template is hardcoded
package_directory = dirname(abspath(__file__))
filename = package_directory + "/../AdditionalFiles/read_precisions_template"
with open(filename, "r") as input_file:
template = input_file.read()
module = template.replace("%NUMBER_OF_RPE_VARS%", str(rpe_vars_number))
module = module.replace("%VARIABLES_INFO%", "\n".join(variables_info))
with open(join(path_to_sources, "read_precisions.f90"), "w") as output_file:
output_file.write(module)
[docs]
def add_sbits_to_rpe_variables(vault: 'Vault'):
"""
Assigns significant bits to RPE variables, truncating them based on their attributes.
Parameters:
vault (Vault): The vault containing the variables to be processed and truncated.
"""
print("\n\nAssigning significant bits...")
def condition(v):
return v.type == "rpe_var" and not v.is_parameter
# List rpe variables
rpe_variables = [var for var in vault.variables if condition(var)]
# Split between allocatable and non allocatable, since they have to be truncate in different places
allocatable, non_allocatable = split_vars_by_allocatable(rpe_variables)
# # List types with allocatable members
# type_with_allocatable_member = list(set([a.is_member_of.lower() for a in allocatable if a.is_member_of]))
#
# # List variables with allocatable members
# var_with_rpe_allocatable_member = [var for var in vault.variables if
# var.type.lower() in type_with_allocatable_member]
# Remove allocatable members from the list of allocatable
allocatable = [a for a in allocatable if not a.is_member_of]
# List non allocatable that appear in a namelist
na_in_namelist, na_not_in_namelist = split_by_appearance_in_namelist(non_allocatable)
# Truncate variables with allocatable rpe members
# for variable in var_with_rpe_allocatable_member:
# insert_variable_precision_specification_to_allocatable_member(variable, var_counter, vault)
# List var not declared as global, and divide between simple var and derived type with rpe member
not_main_rpe_member, not_main, main = split_vars_by_main(na_not_in_namelist, vault)
# List of all rpe var
var_to_truncate = allocatable + na_in_namelist + not_main + main
# Sort the list alphabetically so that namelist ordering won't depend on the platform
var_to_truncate.sort(key=lambda x: x.name + x.procedure.name + x.module.name)
# Assign a progressive id
for index_var, var in enumerate(var_to_truncate):
var.id = index_var
# Now that id have been assigned, fill dictionary ids
vault.index_variables()
# Insert truncation statement
# Truncate allocatable
for variable in allocatable:
insert_variable_precision_specification_to_allocatable(variable)
# Truncate non allocatable that appear in a namelist
for variable in na_in_namelist:
insert_variable_precision_specification_to_namelist_parameter(variable, vault)
# # Truncate variable of custom data type with rpe member
# for variable in not_main_rpe_member:
# if not isinstance(variable.procedure, BasicStructures.DerivedType) and variable.intent != "in":
# insert_variable_precision_to_output_dummy_with_rpe_member(variable, var_counter, vault)
# Truncate subroutine dummies with intent out (cut just after declaration)
for variable in not_main:
if not isinstance(variable.procedure, Procedure.DerivedType) and variable.intent != "in":
insert_variable_precision_to_output_dummy(variable)
# Truncate after subroutine calls for actual arguments with intent out
insert_variable_precision_after_subroutine_call(vault)
# Insert a function for each module, where module variables will be assigned and id and a precision
insert_variable_precision_to_main_variable(main, vault)
def _insert_check(if_condition, module_line, var_id, check=True):
"""Once all the unused variables are set to 3 bits,
this will stop if encounters once such variable, since it means it ended up in the analysis by mistake"""
# This function is intended for debug only, usually we don't want these lines to be in the code
if not check:
module_line
# If an if condition is present, add the check to this
if if_condition.strip():
# Remove the last parenthesis
if_condition = if_condition.strip()[:-1]
# Complete if statement and add print
if_condition += " .and. emulator_variable_precisions(%i) == 3)" % var_id
else:
# Create if condition
if_condition = " IF(emulator_variable_precisions(%i) == 3)" % var_id
# Insert conditional check and print
module_line += "\n" + if_condition + " ERROR STOP %i" % var_id
return module_line
def _add_argument_truncation(subprogram_call):
# Check the intent and type of the arguments
variables_to_truncate = []
for arg, dummy_a in zip(subprogram_call.arguments, subprogram_call.dummy_arguments):
# Do not check non real variables or var that correspond to an intent in dummy
if arg.type != 'rpe_var' or dummy_a.intent == 'in':
continue
# The var can not be None: since the intent is out, must be a single var
# TODO remove this check, it has been substituted by the check_argument_consistency
if arg.variable is None:
raise Error.IntentOutError("The dummy argument %s of procedure %s has intent %s but is used"
"like %s " % (dummy_a.name, dummy_a.procedure.name, dummy_a.intent, arg.name))
# If id is not yet assigned at this point, it means it does not enter the analysis, skip
if arg.variable.id is not None:
variables_to_truncate.append([arg.variable, arg.variable.is_pointer])
call_to_subroutine = subprogram_call.line_info.line
for var, is_pointer in variables_to_truncate:
# if is_pointer:
# # Remove indexing to avoid
# # error #7835: Record fields or array elements or sections of pointers are not themselves pointers.
# if var.count("%"):
# indexed_member = var.split("%")[-1]
# member_name = BasicFunctions.remove_indexing(indexed_member)
# deindexed_var = var.replace(indexed_member, member_name)
# else:
# deindexed_var = BasicFunctions.remove_indexing(var)
# call_to_subroutine += "if(associated(%s)) then\n" % deindexed_var
call_to_subroutine = insert_variable_precision_specification(var,
call_to_subroutine,
subprogram_call.line_info.if_condition,
apply_truncation=True)
# if is_pointer:
# call_to_subroutine += "\nend if"
return call_to_subroutine
[docs]
def insert_variable_precision_specification(variable: 'Variable', module_line: str,
if_condition: str="",
member_name: str=None,
apply_truncation: bool=False):
"""
Adds variable precision specification to a module line, with optional truncation and condition checks.
Parameters:
variable (Variable): The variable whose precision is to be specified.
module_line (str): The module line to which the specification is added.
if_condition (str, optional): A condition to be applied before the specification (default is "").
member_name (str, optional): The name of the member to use, if the variable is part of a structure (default is None).
apply_truncation (bool, optional): Whether to apply truncation to the variable (default is False).
Returns:
str: The updated module line with the precision specification.
"""
# Set the variable to mutable
variable.is_mutable = True
# Get name of variable, or of member if specified
var_name = member_name if member_name else variable.name
var_id = variable.id
# if variable.is_pointer and not variable.is_allocatable:
# module.lines[insertion_line] += "\nif(associated(%s)) then" % var_name
# Add variable usage specification
module_line += "\n" + if_condition + " variable_is_used(%i) = .true." % var_id
# Add sbits
module_line += "\n" + if_condition + " %s%%sbits = emulator_variable_precisions(%i)" % (var_name, var_id)
# Insert a check, that ensures that all the truncated variables have been assigned a precision
# module_line = _insert_check(if_condition, module_line, var_id)
# Add apply_truncation if is variable declaration or allocation
if apply_truncation:
module_line += "\n" + if_condition + " CALL apply_truncation(%s)" % var_name
# if variable.is_pointer and not variable.is_allocatable:
# module.lines[insertion_line] += "\nend if"
return module_line
def _get_member_name(arg, module, line_index, vault, variable):
# Remove leading spaces
arg = arg.replace(" ", "").strip()
# Get variable name without indexing (foo(n)%var --> foo%var)
clean_contents = BasicFunctions.remove_indexing(arg)
member_type = Getter.get_type_of_contents(clean_contents, module.line_info[line_index].block, vault)
# Get the variable
if clean_contents.count("%"):
var = Getter.get_structure_member(clean_contents, module.line_info[line_index].block, vault)
else:
var = vault.get_variable_by_name(clean_contents, module.line_info[line_index].block)
# Fix only if it's an rpe_var and it's the "same" variable
if member_type != 'rpe_var' or not variable.is_equal_to(var):
return None
return arg.split(variable.name)[0] + variable.name
[docs]
def insert_variable_precision_specification_to_allocatable(variable: 'Variable'):
"""
Searches for the allocation of a variable and applies truncation afterward.
Parameters:
variable (Variable): The allocatable variable for which precision specification is to be inserted.
Returns:
None
"""
def find_variable(var, li, l, m):
if re.search(RegexPattern.allocation_variable_name % var.name, l, re.I):
# Check that we did not match a member with the same name: a_i will also match %a_i
if re.search(RegexPattern.allocation_member_name % var.name, l.replace(" ", ""), re.I):
return False
m.lines[li] = insert_variable_precision_specification(var, m.lines[line_index], apply_truncation=True)
m.update_lineinfo()
return True
if variable.procedure.name == 'main':
# Search in all modules
for module in [variable.module] + variable.procedure.module.used_by:
for line_index, line in enumerate(module.lines):
# Variables can be allocated just inside subprograms
if not module.line_info[line_index].block.name == 'main':
if find_variable(variable, line_index, line, module):
return
else:
# Just search where the variable is defined
module = variable.module
lines = [[i, l.unconditional_line] for i, l in enumerate(module.line_info)
if l.block.name == variable.procedure.name]
for line_index, line in lines:
if find_variable(variable, line_index, line, module):
return
def _find_member_allocation(_module_lines, _var_name, _member_name):
for index, line in enumerate(_module_lines):
if re.search(RegexPattern.allocation_variable_name % _var_name, line, re.I):
# Get the arguments from ALLOCATE, avoiding any possible extra call or IF statement inline
arguments = CallManager.find_call_arguments(BasicFunctions.remove_if_condition(line))
for arg in arguments:
cleaned_arg = BasicFunctions.remove_indexing(arg)
if re.search(RegexPattern.name_occurrence % _member_name, cleaned_arg, re.I):
# Return line and allocation removing index of the member
return index, arg.split(_member_name)[0] + _member_name
return None, None
[docs]
def insert_variable_precision_specification_to_allocatable_member(variable: 'Variable', counter_object, vault: 'Vault'):
"""
Searches for allocatable members of a variable's type and applies precision truncation to them.
Parameters:
variable (Variable): The variable whose allocatable members will be processed.
counter_object: The counter for assigning unique IDs to members.
vault (Vault): The vault containing the variables and modules.
Returns:
None
"""
def _condition(var, structure):
# Is an rpe member of the encompassing structure with the pointer or allocatable attribute
if var.type == 'rpe_var':
if var.is_member_of and var.is_member_of.lower() == structure and (var.is_allocatable or var.is_pointer):
return True
return False
allocatable_members = [var for var in vault.variables if _condition(var, variable.type.lower())]
for member in allocatable_members:
for module in [variable.module] + variable.procedure.module.used_by:
line_index, member_allocation = _find_member_allocation(module.lines, variable.name, member.name)
if line_index:
# Set member id if it was not set yet
if member.id is None:
member.id = counter_object.count
counter_object.up()
# Cut member precision
module.lines[line_index] = insert_variable_precision_specification(member, module.lines[line_index],
member_name=member_allocation,
apply_truncation=True)
module.update_lineinfo()
# Search for next member allocation
break
[docs]
def insert_variable_precision_specification_to_namelist_parameter(variable: 'Variable', vault: 'Vault'):
"""
Inserts variable precision specification to a namelist parameter in the corresponding module.
Parameters:
variable (Variable): The variable to be processed.
vault (Vault): The vault containing the modules and variables.
Returns:
None
"""
module = vault.modules[variable.appears_in_namelist[0]]
for index, line in enumerate(module.lines):
pattern3 = r"read *\(.*\b%s\b" % variable.appears_in_namelist[1]
if re.search(pattern3, line, re.I):
break
module.lines[index] = insert_variable_precision_specification(variable, module.lines[index], apply_truncation=True)
module.update_lineinfo()
def _find_name_to_cut(line, variable, member):
# Case in which a variable was inside a do loop or a where statement
if variable.dimension == 0 or not line.count(variable.name):
return variable.name + "%" + member.name
# Try to find de exact variable call
start_of_name = re.search(RegexPattern.name_occurrence % variable.name, line)
if start_of_name:
start_of_name = start_of_name.start()
else:
raise Error.ExceptionNotManaged("Name should be here")
var_name = line[start_of_name:].split(member.name)[0]
return var_name + member.name
[docs]
def insert_variable_precision_to_output_dummy_with_rpe_member(variable, counter_object, vault):
"""
Inserts variable precision specification for an rpe member in the output dummy.
Parameters:
variable (Variable): The variable to be processed.
counter_object: The counter for assigning ids.
vault (Vault): The vault containing the variables and modules.
Returns:
None
"""
def _condition(var, struct):
# Is an rpe member of the encompassing struct with the pointer or allocatable attribute
if var.type == 'rpe_var':
if var.is_member_of and var.is_member_of.lower() == struct and not (var.is_allocatable or var.is_pointer):
return True
return False
rpe_members = [var for var in vault.variables if _condition(var, variable.type.lower())]
for member in rpe_members:
module, insert_line, condition = find_first_use_of_member(variable, member)
if module:
# Check that this member was not assigned when used in another variable
if member.id is None:
member.id = counter_object.count
counter_object.up()
first_use_line = module.lines[insert_line + 1]
member_name = _find_name_to_cut(first_use_line, variable, member)
# Cut member precision
module.lines[insert_line] = insert_variable_precision_specification(member, module.lines[insert_line],
condition,
member_name=member_name,
apply_truncation=True)
module.update_lineinfo()
else:
warnings.warn("Variable %s not found %s in routine %s" % (
variable.name, variable.procedure.module.name, variable.procedure.name))
[docs]
def insert_variable_precision_after_subroutine_call(vault: 'Vault'):
"""
Inserts truncation statements after each subroutine call for variables with intent 'out'.
Parameters:
vault (Vault): The vault containing the subprogram calls.
Returns:
None
"""
for subprogram_call in vault.dictionary_of_calls.values():
# No need to check function, inputs are ~copied, and sbits member will stay constant
if isinstance(subprogram_call[0].subprogram, Subprogram.Function):
continue
for call in subprogram_call:
module = call.block.module
line_number = call.line_info.line_number
# Update calls
line_plus_truncation = _add_argument_truncation(call)
if line_plus_truncation is not None:
module.lines[line_number] = line_plus_truncation
[docs]
def add_val_to_argument(argument: str, string: str):
"""
Replaces occurrences of the argument with its '%val' version in the string.
Parameters:
argument (str): The argument to be replaced.
string (str): The string where the replacement occurs.
Returns:
str: The string with the replaced argument.
"""
string = replace_variable_exact_match(argument, argument + "%val", string)
return string
[docs]
def add_rpe_real_member_to_argument(argument: str, string: str):
"""
Replaces occurrences of the argument with its '%val' version in the string, handling hardcoded arrays.
Parameters:
argument (str): The argument to be replaced.
string (str): The string where the replacement occurs.
Returns:
str: The string with the replaced argument.
"""
hardcoded_array = []
if NatureDeterminer.is_hardcoded_array(argument, hardcoded_array):
for arg in hardcoded_array:
string = replace_variable_exact_match(arg, arg + "%val", string)
return string
return replace_variable_exact_match(argument, argument + "%val", string)
[docs]
def add_rpe_to_argument(argument: str, string: str, is_pointer: bool=False):
"""
Adds an 'rpe' or 'rpe_ptr' cast to the argument in the string if not already cast.
Parameters:
argument (str): The argument to be cast.
string (str): The string where the replacement occurs.
is_pointer (bool, optional): Whether to use 'rpe_ptr' for pointer types. Defaults to False.
Returns:
str: The string with the casted argument.
"""
if is_pointer:
cast_rpe = 'rpe_ptr(%s)'
else:
cast_rpe = 'rpe(%s)'
# Check we are not casting more than once
if not string.count(cast_rpe % argument) == string.count(argument):
if NatureDeterminer.is_hardcoded_array(argument):
string = string.replace(argument, add_rpe_to_array_arguments(argument))
else:
string = replace_variable_exact_match(argument, cast_rpe % argument, string)
return string
# Transforms a number in an rpe variables => 1.0 in rpe( 52, 1.0). Used to fix parameters assignation
[docs]
def add_rpe_type_constructor(argument: str, string: str):
"""
Adds an 'rpe' constructor to the argument in the string, handling reshapes and arrays.
Parameters:
argument (str): The argument to be cast.
string (str): The string where the replacement occurs.
Returns:
str: The string with the rpe constructor applied.
"""
# Deal with cases of array or reshape functions
if argument.count("RESHAPE") or re.search(r"= *\[.*\]", argument):
# List the arguments inside the RESHAPE function and avoid adding the cast to dimensions
argument = CallManager.find_call_arguments(argument)[0]
return replace_variable_exact_match(argument, add_rpe_to_array_arguments(argument), string)
if argument.count("(/"):
# Add rpe constructor to all numbers inside the array
return replace_variable_exact_match(argument, add_rpe_to_array_arguments(argument), string)
return replace_variable_exact_match(argument, "rpe_var( 52, " + argument + ")", string)
[docs]
def add_real_to_argument(argument: str, string: str, precision: str=VariablePrecision.real_id["wp"]):
"""
Adds 'real' type casting with specified precision to the argument in the string if not already cast.
Parameters:
argument (str): The argument to be cast.
string (str): The string where the replacement occurs.
precision (str, optional): The precision to apply. Defaults to VariablePrecision.real_id["wp"].
Returns:
str: The string with the real-cast argument.
"""
# Check we are not casting more than once or fixing an arg already fixed
precision = ", " + precision
if string.count("real(" + argument + precision + ")") == string.count(argument):
return string
# # Avoid add real to all array element if is an array with a loop
# if NatureDeterminer.is_an_hardcoded_array(argument) and \
# not NatureDeterminer.is_an_hardcoded_array_with_loop(argument):
# new_arg = add_real_to_array_arguments(argument)
#
# else:
new_arg = "real(" + argument + precision + ")"
string = replace_variable_exact_match(argument, new_arg, string)
return string
[docs]
def add_real_to_array_arguments(argument: str):
"""
Casts each element of the array argument to 'real' with 'wp' precision.
Parameters:
argument (str): The array argument to be cast.
Returns:
str: The array argument with each element cast to 'real(wp)'.
"""
simple_array = argument.replace("(/", "")
simple_array = simple_array.replace("/)", "")
clean_numbers = CallManager.find_call_arguments("(" + simple_array + ")")
# Open the array
ret_val = "(/"
for number in clean_numbers:
# Cast all array numbers
ret_val += "real(" + number + " , wp), "
# Remove last comma and closes array
ret_val = ret_val[:-2] + "/)"
return ret_val
[docs]
def add_rpe_to_array_arguments(argument: str):
"""
Casts each element of the array argument to 'rpe_var( 52, ... )'.
Parameters:
argument (str): The array argument to be cast.
Returns:
str: The array argument with each element cast to 'rpe_var( 52, ... )'.
"""
if re.search(RegexPattern.hardcoded_array_parenthesis, argument): arg1, arg2 = "(/", "/)"
if re.search(RegexPattern.hardcoded_array_brackets, argument): arg1, arg2 = "[", "]"
simple_array = argument.replace(arg1, "")
simple_array = simple_array.replace(arg2, "")
clean_numbers = CallManager.find_call_arguments("(" + simple_array + ")")
# Open the array
ret_val = arg1
for number in clean_numbers:
# Cast all array numbers
ret_val += "rpe_var( 52, " + number + " ), "
# Remove last comma and closes array
ret_val = ret_val[:-2] + arg2
return ret_val
[docs]
def replace_variable_exact_match(piece_to_replace: str, replacement: str, string: str):
"""
Replaces exact occurrences of a variable in the string, ensuring no internal replacements occur.
Parameters:
piece_to_replace (str): The variable to be replaced.
replacement (str): The replacement for the variable.
string (str): The string where the substitution occurs.
Returns:
str: The string with the exact replacement applied.
"""
# Before performing the substitution mask strings to avoid substitution inside them
dict_of_substitutions = {}
string = MaskCreator.mask_literal(string, dict_of_substitutions)
# Mask the keyword if the argument is passed with the same name as the keyword
string = MaskCreator.mask_keyword(string, piece_to_replace, dict_of_substitutions)
# Perform substitution, escaping the string
string = re.sub(RegexPattern.escape_string(piece_to_replace), replacement, string)
# Substitute back masked objects
string = MaskCreator.unmask_from_dict(string, dict_of_substitutions)
if not string.count(replacement):
pass
# raise Error.ExceptionNotManaged("Impossible substitution: %s in line %s" % (replacement, piece_to_replace))
return string
[docs]
def find_subprogram_lines(subprogram: Subprogram, module: 'Module'=None):
"""
Finds the lines in a module corresponding to the start and end of a subprogram.
Parameters:
subprogram (Subprogram): The subprogram whose lines are to be found.
module (Module, optional): The module to search in. Defaults to None, in which case the subprogram's module is used.
Returns:
tuple: A tuple containing the module and a list with the start and end line indices of the subprogram.
Raises:
Error.ProcedureNotFound: If the subprogram is not found in the module or its header files.
"""
indices = []
if module is None:
module = subprogram.module
begin_pattern = RegexPattern.subprogram_name_declaration % subprogram.name
end_pattern = RegexPattern.subprogram_name_end_declaration % subprogram.name
# First find the subprogram body inside the module
for index, line in enumerate(module.lines):
# If it's the start or end of the subprogram
line = BasicFunctions.remove_comments(line)
if re.search(begin_pattern, line, re.I):
indices.append(index)
# End of the program was hit
if re.search(end_pattern, line, re.I):
break
if not len(indices):
for h in module.header_files:
try:
return find_subprogram_lines(subprogram, h)
except Error.ProcedureNotFound:
continue
raise Error.ProcedureNotFound(" Procedure %s not found" % subprogram.name)
# In case more than one start/end index was found:
#
# #if defined key_agrif
# RECURSIVE SUBROUTINE stp_MLF( )
# INTEGER :: kstp ! ocean time-step index
# #else
# SUBROUTINE stp_MLF( kstp )
# INTEGER, INTENT(in) :: kstp ! ocean time-step index
# #endif
#
# return the broader range
return module, [indices[0], indices[-1]]
## PRIVATE
[docs]
def insert_variable_precision_to_main_variable(in_main: list['Variable'], vault: 'Vault'):
"""
Inserts truncation for all global variables with no first use tracking.
For each module that contains global variables, it creates a subroutine to assign
precision truncation and adds a call to this subroutine in the main program.
Parameters:
in_main (list): List of global variables in the main program.
vault (Vault): The vault containing all modules and variables.
Updates:
Modifies the lines of relevant modules to include truncation subroutines
and appropriate calls to these subroutines in the main program.
Note:
This function handles the insertion of the `assign_sbits` subroutine and
ensures that necessary `USE` statements and `CALL` statements are added.
"""
subroutine_calls = ""
used_module = ""
for module in vault.modules.values():
module_variables = [v for v in in_main if v.module == module]
# There are no rpe global variable in this module: skip
if not module_variables:
continue
# Update line info, not updated after insert truncation to sbr
module.update_lineinfo()
# Name of the subroutine must be different for each module
subroutine_name = "assign_sbits_" + module.name
subroutine_text = "\n implicit none"
for var in module_variables:
# Add the assignation in the subroutine call
subroutine_text += insert_variable_precision_specification(var, "", apply_truncation=True)
subroutine = "SUBROUTINE " + subroutine_name + "".join(subroutine_text) + "\nEND SUBROUTINE " + subroutine_name
contain_index = len(module.lines)
for line_index, line in enumerate(module.lines):
# Insert the subroutine as first subprogram of the module
if re.search(RegexPattern.name_occurrence % "contains", line, re.I):
contain_index = line_index
module.lines[contain_index+1] = subroutine + "\n" + module.lines[contain_index+1]
break
# If the module just contains variables declaration add contain statement before the module ends
if contain_index == len(module.lines):
contain_index = -1
module.lines[contain_index] = "CONTAINS\n" + subroutine + "\n" + module.lines[contain_index]
# executable_part_index = module.main.executable_part()
# Just before the contains statement include the declaration of function as public
#module.lines[contain_index] = "PUBLIC " + subroutine_name + "\n" + module.lines[contain_index]
# module.lines[executable_part_index] = "PUBLIC " + subroutine_name + "\n" + module.lines[contain_index]
module.update_module_lines()
# Store all subroutine names that will be call in nemogcm
subroutine_calls += "CALL " + subroutine_name + "\n"
# Store modules names, they will be added to the use statement of nemogcm
used_module += "USE " + module.name + "\n"
# insert_module = vault.modules['nemogcm']
#Trying to find the main module:
unused_modules = [mod for mod in vault.modules.values() if not mod.used_by]
if len(unused_modules) == 1:
insert_module = unused_modules[0]
#TODO: Find a better non hardcoded way of solving this with NEMO.
elif [m for m in unused_modules if m.name == "nemogcm"]:
insert_module = vault.modules['nemogcm']
# If the preprocessed files include the main program nemo, then nemogcm is not unused anymore. Adding it here just to be safe.
elif [m for m in unused_modules if m.name == "nemo"]:
insert_module = vault.modules['nemo']
else:
insert_module = vault.modules['dummy_module']
# Here, we try to find the proper place to insert the calls to asign sbits
# This should happen after all variable declarations
declaration_lines = [i for i, l in enumerate(insert_module.lines) if re.search(RegexPattern.variable_declaration , l, re.I)]
if declaration_lines:
line_index = declaration_lines[-1]
else:
line_index = 1
# implicit_nones = [i for i, l in enumerate(insert_module.lines) if l.lower().count("implicit none")]
# if implicit_nones:
# line_index = implicit_nones[0]
# else:
# line_index = 1
subroutine_calls = "CALL read_variable_precisions\n" + subroutine_calls
insert_module.lines[line_index] += "\n" + subroutine_calls
# Find first use statement (no need to rebuild text, this is before all the calls)
# TODO: Is this necessary?
implicit_nones = [i for i, l in enumerate(insert_module.lines) if l.lower().count("implicit none")]
if implicit_nones:
line_index = implicit_nones[0]
else:
line_index = 1
# insert_module.lines[line_index] += "\n" + used_module
# TODO: this it does not take into account the new sbr, so after this call all lineinfo will be wrong,
# here info lines should be recreated,
# but this is very expensive, and we are not going to use it anymore at this point
insert_module.update_module_lines()
[docs]
def insert_variable_precision_to_output_dummy(variable: 'Variable'):
"""
Inserts truncation for dummy arguments in the output, searching for the first line of code after variable declarations.
Adds truncation logic to the first line of code in the subprogram where no variables are declared, handling optional variables
with a conditional check for their presence.
Parameters:
variable (Variable): The variable for which truncation is to be added.
Returns:
None
"""
module = variable.procedure.module
lines = [[i, l.unconditional_line] for i, l in enumerate(module.line_info)
if l.block.name == variable.procedure.name]
# Insert the use statement just after variables declaration
line_number, insertion_line = [[i, l] for i, l in lines if l.count("::")][-1]
if variable.is_optional:
condition = "IF ( PRESENT(%s) )" % variable.name
else:
condition = ""
module.lines[line_number] = insert_variable_precision_specification(variable, module.lines[line_number], condition,
apply_truncation=True)
module.update_lineinfo()
[docs]
def find_first_use_of_member(variable: 'Variable', member: 'Variable'):
"""
Finds the first use of a member variable in a subprogram, skipping declarations, implicit statements, and use statements.
Searches through the subprogram lines after the declaration to find the first occurrence of the member variable,
considering loops and where statements to avoid incorrect matches.
Parameters:
variable (Variable): The parent variable whose member's usage is being tracked.
member (Variable): The member variable to search for.
Returns:
tuple: The module and line index where the member is first used, along with any condition string if present.
"""
# Is it possible that var.module != subprogram.module?!
subprogram = variable.procedure
module = subprogram.module
# pattern = '(^|\W+)%s(\W+|$)' % variable.name
loop_level = 0
loop_start = None
where_level = 0
where_statement_start = None
# Find start and end of subprogram declaration
module, indices = find_subprogram_lines(subprogram)
# Search for first use, avoiding subprogram declaration and end lines
# We will need to add indices[0] - 1 to the line_index return value since that's our starting index
for line_index, line in enumerate(module.lines[indices[0] + 1:indices[1]]):
# Skip all declarations to avoids false positive that will block compilation.
if line.count("::") or line.lower().count("implicit none") or line.find("use ") == 0:
continue
# Scroll subprogram's lines searching for the first use
if re.search(RegexPattern.name_occurrence % variable.name, line, re.I):
# # Check the variable is not mentioned in a ".NOT.PRESENT" or in a =PRESENT(var) statement
# if _check_if_condition(line, variable.name):
# continue
if re.search(RegexPattern.name_occurrence % member.name, line, re.I):
# Avoid lines in which de var is used to select a case
if re.search(r"SELECT CASE", line, re.I):
continue
if re.search(r'^\s*if\s*\(', line, re.I):
condition = BasicFunctions.close_brackets(line)
else:
condition = ""
# The variable is found: : return line -1, because it will be inserted at the end of that line
if where_level > 0:
# Inside a where statement, return line before where statement starts
return module, indices[0] + where_statement_start, condition
else:
# In a (nested) loop, return the line before loop starts
if loop_level > 0:
return module, indices[0] + loop_start, condition
# On a normal line
else:
return module, indices[0] + line_index, condition
# Whenever a 'do loop' starts increment nesting index
elif re.search(RegexPattern.do_loop, line, re.IGNORECASE):
if loop_level == 0:
loop_start = line_index
loop_level += 1
# Whenever a 'do loop' ends decrement nesting index
# This way match bot the expression 'end do' and 'enddo' but excludes matches like 'enddo loop_name'
elif re.search(RegexPattern.end_do_loop, line, re.IGNORECASE):
loop_level -= 1
# Do the same for where statement
elif line.lower().strip().find("where") == 0:
if where_level == 0:
where_statement_start = line_index
where_level += 1
elif line.lower().strip().find("end where") == 0:
where_level -= 1
# The variable was not found in the subroutine
return None, None, None
def _check_if_condition(line, var_name):
condition, line = BasicFunctions.split_if_condition(line)
if condition:
arg = CallManager.find_call_arguments(condition)
for i_arg in arg:
if re.search(r"\.NOT\.\s*PRESENT\s*\(\s*%s" % var_name, i_arg, re.I):
return True
if re.search(r'=\s*PRESENT\s*\(\s*%s\s*\)' % var_name, line, re.I):
return True
return False
def _in_which_type_definition(_line, type_def):
_line = _line.strip().lower()
_line = MaskCreator.remove_literals(_line)
_pattern_type = "^ *type[^(].*::(.*)"
_pattern_type_alt = "^ *type[^(](.*)"
_pattern_type_end = "^ *end *type"
# We are changing block
if type_def is None:
m = re.match(_pattern_type, _line, re.I)
if m:
type_name = m.group(1).strip()
return type_name
n = re.match(_pattern_type_alt, _line, re.I)
if n:
type_name = n.group(1).strip()
return type_name
else:
m = re.search(_pattern_type_end, _line, re.I)
if m:
return None
return type_def
[docs]
def split_vars_by_allocatable(variables: list['Variable']):
"""
Splits the variables into two lists: one with allocatable or pointer variables, and the other
with the remaining variables.
Parameters:
variables (list[Variable]): List of variables to split.
Returns:
tuple: Two lists, the first containing allocatable or pointer variables, and the second containing the rest.
"""
allocatables = [var for var in variables if (var.is_allocatable or var.is_pointer)]
return allocatables, list(set(variables) - set(allocatables))
[docs]
def split_by_appearance_in_namelist(variables: list['Variable']):
"""
Splits the variables into two lists: one with variables that appear in a namelist and the other with those that do not.
Parameters:
variables (list[Variable]): List of variables to split.
Returns:
tuple: Two lists, the first containing variables in a namelist, and the second containing those not in a namelist.
"""
in_namelist = [var for var in variables if var.appears_in_namelist]
not_in_namelist = [var for var in variables if not var.appears_in_namelist]
return in_namelist, not_in_namelist
[docs]
def split_vars_by_main(variables: list['Variable'], vault: 'Vault'):
"""
Splits variables based on whether they are in the 'main' procedure or not, and further categorizes non-main variables by
their association with derived types that contain RPE members.
Parameters:
variables (list[Variable]): List of variables to split.
vault (Vault): Object containing the global variables.
Returns:
tuple: Three lists, the first containing non-main variables with RPE members,
the second containing non-main variables without RPE members,
and the third containing variables in the main procedure.
"""
import AutoRPE.UtilsRPE.Classes.Procedure as Procedure
# Get all variables not declared global
not_in_main = [var for var in variables if var.procedure.name != "main"]
# At this point all variables in main with allocatable or namelist have been excluded
in_main = [var for var in variables if var.procedure.name == "main"]
# Get all the structures not declared in main, that has a rpe member
type_with_rpe_member = list(set([a.is_member_of.lower() for a in not_in_main if a.is_member_of]))
# Get all the variables, non global, that has such a type
not_in_main_rpe_member = [var for var in vault.variables if var.type.lower() in type_with_rpe_member
and not var.procedure.name == 'main']
# Remove members from non global variables
not_in_main = [v for v in not_in_main if not v.is_member_of and
v.intent != "in" and
not isinstance(v.procedure, Procedure.DerivedType)]
return not_in_main_rpe_member, not_in_main, in_main