Commit 3ee17b6a authored by sparonuz's avatar sparonuz
Browse files

# This is a combination of 69 commits.

[refactor_parseSources]: The idea is to always read the sources into load_source_files routine, and remove a lot of useless steps

[refactor_parseSources]: Managed case in which two function with the same name exist

[develop]: moved and improved  regex for capturing subprogram calls

[develop]: Fix get_procedure_by_name that was a bit of a mess

[develop]: mv source_file to source_file_info + fixed storage of interface (everything was doubled) + imports

[develop]: Now basic structures have module info

[develop]: Intruduced function to get derived type constructor

[develop]: Now call_to_function does not recognize structures as functions

[develop]: New regex for  split_read_arguments

[develop]: Introduced get_procedure function: is meant to replace calls_to_subprogram and the great number of fixed within. It does not identify vectors as funcions.

[develop]: _fix_functions has been refactored, it was a mess of useless call

[develop]: Now module also have the lines saved in it, and a function to rebuild text

[develop]: Save vault added as a function to vault

[develop]: Moved load vault and save vault to Vault file. Now parse source is called inside the Vault constructor

[develop]: Added function to find var used in namelist, function split_by_appearance_in_namelist was taking around 3/4 minutes. Now we parse this info in parse sources, and this function takes around 1 sec

[develop]: removed old function + simplified regex for finding function/srb declaration

[develop]: Moved function clean_contents from cleaner to Getter

[develop]: Simplified function to detect subrutine calls

[develop]: Refactored function fix subroutine

[develop]: Added function to fix externals

[develop]: Refactored ImplementRPE, now this class is used just for Implementing RPE

[develop]: Added function to store external subroutine used

[develop]: Removed functions fix_functions fix_subroutines fix_external_subroutines , and replaced by fix_subprogram

[develop]: Now also used external functions are stored in vault. Bettered regex for call_to_functions

[develop]: Removed functions to parse implicit functions, that are no more used, anyway they are store in https://earth.bsc.es/gitlab/otinto/AutoRPE/-/snippets/73

[develop]: Removed from Inserter all the duplicated function that were searching variables in sbr call to truncate then the var.

[Issue#107]: Removed DerivedTypeDealer, and moved functions elsewhere

[Issue#107]: Now the get_real_in_line uses the get_procedure, and does a check on the pointer. Removed _clean_from_intrinsics, now uses strip_from_intrinsics.
parent 2fcdfdf5
......@@ -40,7 +40,7 @@ def fix_casts(source_path, vault_path, list_filename=None):
arguments = find_call_arguments(call)
procedure_name = re.search(call_pattern, call, re.I).group(1)
try:
procedure = vault.get_procedure_by_name(procedure_name)
procedure = vault.get_procedure_by_name(procedure_name, module_name)
except ProcedureNotFound:
continue
# If it is an interface, find the specific procedure being used
......
......@@ -21,7 +21,7 @@
############################################
from AutoRPE.UtilsRPE.SourceManager import expand_sources
from AutoRPE.UtilsRPE.SourceManager import load_vault
from AutoRPE.UtilsRPE.ImplementRPE import ImplementRPE
from AutoRPE.UtilsRPE.Preprocessor import Preprocessor
import AutoRPE.UtilsRPE.CurrentBlock as CurrentBlock
from AutoRPE.UtilsRPE.Inserter import find_call_in_original_file
from AutoRPE.UtilsRPE.Getter import get_real_in_line
......@@ -39,7 +39,7 @@ def code_check_file(filename, vault, fix=False, original_sources=None):
except AttributeError:
warnings.warn("No module %s" % filename)
return
f = ImplementRPE(filename, None)
f = Preprocessor(filename, None)
if original_sources:
original_filename = original_sources[filename]
......@@ -84,7 +84,7 @@ def code_check_file(filename, vault, fix=False, original_sources=None):
else:
# If not, look if the code is entering or exiting a procedure block
current_block = current_block.in_which_block(line, vault=vault)
current_block.in_which_block(line, vault=vault)
if __name__ == "__main__":
......
......@@ -3,9 +3,11 @@
# This script can be used to remove all the contents of a Fortran code that can make it harder to parse.
# It will merge split lines, remove comments and empty lines.
############################################
from AutoRPE.UtilsRPE.SourceManager import preprocess_sources
from AutoRPE.UtilsRPE.Preprocessor import preprocess_sources
from AutoRPE.UtilsRPE.Error import PathType
from os import mkdir
from os.path import isdir
from shutil import rmtree
def get_command_line_arguments():
import argparse
......@@ -26,5 +28,9 @@ if __name__ == "__main__":
path_to_input_sources = args.input_sources
path_to_output_sources = args.output_sources
# Create path to output sources
if isdir(path_to_output_sources):
rmtree(path_to_output_sources)
mkdir(path_to_output_sources)
preprocess_sources(path_to_input_sources, path_to_output_sources)
......@@ -13,7 +13,7 @@ from AutoRPE.UtilsRPE.SourceManager import expand_sources, load_vault, store_var
from AutoRPE.UtilsRPE.BasicStructures import SubRoutine, Interface
from AutoRPE.UtilsRPE.CallManager import call_to_subroutine, find_called_subroutine, find_call_arguments
from AutoRPE.UtilsRPE.Getter import get_variable
from AutoRPE.UtilsRPE.ImplementRPE import ImplementRPE
from AutoRPE.UtilsRPE.Preprocessor import Preprocessor
import AutoRPE.UtilsRPE.CurrentBlock as CurrentBlock
from AutoRPE.UtilsRPE.Error import ProcedureNotFound
import AutoRPE.UtilsRPE.VariablePrecision as VariablePrecision
......@@ -54,10 +54,10 @@ def find_variables_used_in_external_calls(files, vault, exceptions):
module = vault[module_name]
if module is None:
continue
file_info = ImplementRPE(filepath, vault)
file_info = Preprocessor(filepath)
file_info.pre_process()
current_block = CurrentBlock.CurrentBlock(module.main())
current_block = CurrentBlock.CurrentBlock(module.main)
for index, line in enumerate(file_info.lines):
print("\r %04i/%04i %50s" % (index + 1, len(file_info.lines), module_name), end="")
try:
......@@ -71,7 +71,7 @@ def find_variables_used_in_external_calls(files, vault, exceptions):
# Find the procedure called (object)
try:
subroutine = vault.get_procedure_by_name(called_subroutine)
subroutine = vault.get_procedure_by_name(called_subroutine, current_block.module_name)
except ProcedureNotFound:
subroutine = None
pass
......@@ -108,7 +108,7 @@ def find_variables_used_in_external_calls(files, vault, exceptions):
if variable.type in VariablePrecision.real_types:
variables.append(variable.id)
else:
current_block = current_block.in_which_block(line, vault=vault)
current_block.in_which_block(line, vault=vault)
variables = list(set(variables))
return variables
......
############################################
# Purpose:
# This script takes a vault and creates a namelist_precisions for that vault.
# Takes as input clean sources and substitutes all real declaration with RPE variables.
# Produces a vault with the info on these new sources produced.
############################################
import AutoRPE.UtilsRPE.SourceManager as SourceManager
import AutoRPE.UtilsRPE.Inserter as Inserter
from AutoRPE.UtilsRPE.ImplementRPE import Implement_RPE
from AutoRPE.UtilsRPE.Error import PathType
from os import mkdir
......@@ -35,12 +35,6 @@ if __name__ == "__main__":
output_vault = args.output_vault
# Paths to source folders
path_to_preprocessed_sources = "./tmp_processedsources/"
if isdir(path_to_preprocessed_sources):
rmtree(path_to_preprocessed_sources)
mkdir(path_to_preprocessed_sources)
if isdir(path_to_output_sources):
rmtree(path_to_output_sources)
mkdir(path_to_output_sources)
......@@ -51,34 +45,6 @@ if __name__ == "__main__":
preprocess_blacklist = SourceManager.load_list_of_files_to_keep_unmodified()
# Format text + replace real declarations with type(rpe_var)
SourceManager.replace_real_with_RPE_declaration(path_to_input_sources,
path_to_preprocessed_sources,
preprocess_blacklist)
# Obtain source information
vault = SourceManager.parse_sources(path_to_preprocessed_sources, save_vault=True)
# We load the code information into the vault
try:
vault
except NameError:
vault = SourceManager.load_vault()
# Process the sources to fix call to functions, read/write, ...
SourceManager.postprocess_sources(path_to_preprocessed_sources, path_to_output_sources, vault=vault)
# Insert precision specification
Inserter.add_sbits_to_rpe_variables(path_to_output_sources, vault=vault)
# Assign ids to variables that still don't have any (can't have it's precision changed online)
# and set them as mutable=False
vault.assign_ids_to_immutable_variables()
# Add the read_precisions module into a file (should be accessible for all the other modules)
file_to_add_read_precisions_module = None
Inserter.insert_read_precisions_module_to_file(path_to_output_sources, file_to_add_read_precisions_module,
vault=vault)
vault = Implement_RPE(path_to_input_sources, path_to_output_sources, preprocess_blacklist)
# Deleting temporary folders
if isdir(path_to_preprocessed_sources):
rmtree(path_to_preprocessed_sources)
vault.save(output_vault)
import AutoRPE.UtilsRPE.VariablePrecision as VariablePrecision
from AutoRPE.UtilsRPE.SourceManager import parse_sources, load_vault
from AutoRPE.UtilsRPE.TrackDependency import track_dependencies, propagate_dependencies
from os.path import isdir, abspath
from os import listdir, mkdir
from shutil import rmtree
import networkx as nx
import AutoRPE.UtilsRPE.Vault as Vault
import AutoRPE.UtilsRPE.TrackDependency as TrackDependency
from os.path import abspath
def get_command_line_arguments():
"""
Returns a list of files that have been provided as a command line argument
:return: list of files
"""
# Parse and assert command line options
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("-v", "--vault", default=None,
help="name of the input vault pkl file, if any")
parser.add_argument("-w", "--working-precision", default=23, type=int,
help="The precision used for real numbers in the parsed code")
parser.add_argument("-i", "--input-source", default=None, required=True,
help="Path to input fortran files")
parser.add_argument("-o", "--output-vault", default="vault.pkl",
help="Name of the output vault file")
parser.add_argument("-t", "--omit-tracking", default=True, action="store_false",
help="Whether or not you are going to track dependency")
parser.add_argument("-g", "--export-graph", default=False, action="store_true",
help="Whether or not you want to store the created graph")
parser.add_argument("-i", "--input-source", default=None,
help="Path to input fortran files")
parser.add_argument("-w", "--working-precision", default=23, type=int,
help="The precision used for real numbers in the parsed code")
args = parser.parse_args()
if args.input_source is None and args.vault_path is None:
parser.error("One of the arguments -s/--input-source or -v/--vault is mandatory")
return args
......@@ -42,47 +24,19 @@ if __name__ == "__main__":
args = get_command_line_arguments()
vault_path = args.vault
wp = args.working_precision
output_vault = args.output_vault
path_to_input_sources = args.input_source
export_graph = args.export_graph
track = args.omit_tracking
output_vault = args.output_vault
wp = args.working_precision
# Set the proper working precision
VariablePrecision.wp = VariablePrecision.precision2type[wp]
print("Creating a database from sources found in:\n %s\n\n" % path_to_input_sources)
# In case no vault has been provided it will create it from the sources in the input sources folder
if vault_path is None:
# Obtain source information
vault = parse_sources(path_to_input_sources, save_vault=False)
else:
vault = load_vault(vault_path)
# Parse input sources and stores info in vault
print("Creating a database from sources found in:\n %s" % path_to_input_sources)
vault = Vault.Vault(path_to_input_sources)
real_variables = [v for v in vault.variables if v.type in VariablePrecision.real_types]
length = len(real_variables)
# Assigning id and precision
for index, var in enumerate(real_variables):
var.id = index
var.precision = VariablePrecision.type2precision[var.type]
if track:
# Track dependencies and propagate it
# Create empty graph
graph = nx.DiGraph()
track_dependencies(path_to_input_sources, vault, graph=graph)
if export_graph:
nx.readwrite.gexf.write_gexf(graph, "./graph.gexf")
propagate_dependencies(vault, graph=graph)
# Store the dependencies btw variables, and create a graph for visualization
TrackDependency.propagate_dependencies(vault, "./graph.gexf")
# Save the vault with the variable precisions updated.
# This workaround is needed to allow pickle to write the vault file
import sys
import pickle
sys.setrecursionlimit(20000)
pickle.dump(vault, open(output_vault, "wb"))
vault.save(output_vault)
print("Vault successfully dumped to ", abspath(output_vault))
import unittest
import AutoRPE.UtilsRPE.Error as Error
class TestFunctionMethods(unittest.TestCase):
def test_type_of_real_cast_with_double_specification(self):
......@@ -100,6 +101,11 @@ class TestFunctionMethods(unittest.TestCase):
for case in cases:
self.assertFalse(NatureDeterminer.has_operations(case))
def test_has_intrinsic(self):
import AutoRPE.UtilsRPE.NatureDeterminer as NatureDeterminer
string = 'zsum(:,:,:), ish'
self.assertFalse(NatureDeterminer.has_intrinsics(string))
def test_dimension_of_contents(self):
import AutoRPE.UtilsRPE.Getter as Getter
cases = [
......@@ -135,6 +141,37 @@ class TestFunctionMethods(unittest.TestCase):
solution = "CALL iom_rstput( iter, nitrst, numriw, 'sxice' , REAL(sxice, dp) )"
self.assertEqual(fun_out, solution)
def test_replace_variable_exact_match_3(self):
import AutoRPE.UtilsRPE.Inserter as Inserter
# This is the line of code from NEMO
fun_in = 'IF (ln_tide_ramp) zramp = MIN(MAX( (z_arg - REAL(nit000,wp)*rn_Dt)/(rn_tide_ramp_dt*rday),0.),1.)'
# This is the argument that we want to replace and its replacement
piece_to_replace = '(z_arg - REAL(nit000,wp)*rn_Dt)/(rn_tide_ramp_dt*rday)'
replacement = "real(" + piece_to_replace + ")"
# This is what the function actually gives
fun_out = Inserter.replace_variable_exact_match(piece_to_replace, replacement, fun_in)
# This is what it should give
solution = 'IF (ln_tide_ramp) zramp = MIN(MAX(real((z_arg - REAL(nit000,wp)*rn_Dt)/(rn_tide_ramp_dt*rday)),0.),1.)'
self.assertEqual(fun_out, solution)
def test_strip_from_intrinsic(self):
import AutoRPE.UtilsRPE.NatureDeterminer as NatureDeterminer
# This is the line of code from NEMO
piece_to_strip = 'MOD( iiauper, 2 )'
fun_out = NatureDeterminer.strip_from_intrinsic(piece_to_strip)
# This is what it should give
solution = ['iiauper', '2']
self.assertEqual(fun_out, solution)
def test_strip_from_intrinsic2(self):
import AutoRPE.UtilsRPE.NatureDeterminer as NatureDeterminer
# This is the line of code from NEMO
piece_to_strip = 'min((ht_0(:,:) + psshb1(:,:) - rn_wdmin1)/(rn_wdmin0 - rn_wdmin1),1.0_wp)'
fun_out = NatureDeterminer.strip_from_intrinsic(piece_to_strip)
# This is what it should give
solution = ['(ht_0(:,:) + psshb1(:,:) - rn_wdmin1)/(rn_wdmin0 - rn_wdmin1)', '1.0_wp']
self.assertEqual(fun_out, solution)
def test_find_function_type_from_header_1(self):
import AutoRPE.UtilsRPE.Finder as Finder
header = 'RECURSIVE FUNCTION nodal_factort( kformula ) RESULT( zf )'
......@@ -149,7 +186,11 @@ class TestFunctionMethods(unittest.TestCase):
def test_find_function_type_from_header_2(self):
import AutoRPE.UtilsRPE.Finder as Finder
header = ' type(rpe_var) function bdy_segs_surf(phu, phv)'
var_declaration = ['type(rpe_var), DIMENSION(jpi,jpj), INTENT(in) :: phu', 'type(rpe_var), DIMENSION(jpi,jpj), INTENT(in) :: phv', 'INTEGER :: igrd', 'INTEGER :: ib_bdy', 'INTEGER :: ib', 'INTEGER , POINTER :: nbi', 'INTEGER , POINTER :: nbj', 'type(rpe_var), POINTER :: zflagu', 'type(rpe_var), POINTER :: zflagv']
var_declaration = ['type(rpe_var), DIMENSION(jpi,jpj), INTENT(in) :: phu',
'type(rpe_var), DIMENSION(jpi,jpj), INTENT(in) :: phv', 'INTEGER :: igrd',
'INTEGER :: ib_bdy', 'INTEGER :: ib', 'INTEGER , POINTER :: nbi',
'INTEGER , POINTER :: nbj', 'type(rpe_var), POINTER :: zflagu',
'type(rpe_var), POINTER :: zflagv']
ret_type, ret_val = Finder.find_function_type_from_header(header, var_declaration)
expected_retval = ['rpe_var', 'bdy_segs_surf']
self.assertEqual([ret_type, ret_val], expected_retval)
......@@ -225,7 +266,7 @@ class TestFunctionMethods(unittest.TestCase):
import re
string = 'REAL(wp) FUNCTION bdy_segs_surf(phu, phv)'
if re.search(RegexPattern.general_real_declaration, string, flags=re.I):
match = re.search(RegexPattern.real_with_precision_declaration, string,flags=re.I)
match = re.search(RegexPattern.real_with_precision_declaration, string, flags=re.I)
self.assertEqual(match.group(), 'REAL(wp) FUNCTION')
def test_regex_real_with_precision_declaration_4(self):
......@@ -246,5 +287,24 @@ class TestFunctionMethods(unittest.TestCase):
match = re.search(RegexPattern.general_real_declaration, string, flags=re.I)
self.assertEqual(match, "")
def test_regex_is_call_to_function1(self):
import AutoRPE.UtilsRPE.RegexPattern as RegexPattern
import re
string = ' SUBROUTINE asm_inc_init( Kbb, Kmm, Krhs )'
self.assertIsNone(re.search(RegexPattern.call_to_function, string, flags=re.I))
def test_regex_is_call_to_function1(self):
import AutoRPE.UtilsRPE.RegexPattern as RegexPattern
import re
string = ' call asm_inc_init( Kbb, Kmm, Krhs )'
self.assertIsNone(re.search(RegexPattern.call_to_function, string, flags=re.I))
def test_regex_is_call_to_function1(self):
import AutoRPE.UtilsRPE.RegexPattern as RegexPattern
import re
string = ' asm_inc_init( Kbb, Kmm, Krhs )'
self.assertEqual(re.search(RegexPattern.call_to_function, string, flags=re.I).group(), 'asm_inc_init(')
if __name__ == '__main__':
unittest.main()
import AutoRPE.UtilsRPE.VariablePrecision as VariablePrecision
import AutoRPE.UtilsRPE.RegexPattern as RegexPattern
import re
def literal_mask(string):
......@@ -52,6 +54,26 @@ def remove_literals(string):
return modified_string
# Replace all the structures with placeholders
def replace_structures(string):
array = re.findall(RegexPattern.array, string, re.I)
purged_string = string
if array:
array_store = []
for index, a in enumerate(array):
array_store.append(a)
purged_string = purged_string.replace(a, "ARRAY" + str(index))
structure = re.findall(RegexPattern.structure, purged_string, re.I)
if structure:
for s in structure:
# Don't replace with a word to avoid regex is_call_to_functio to match
purged_string = purged_string.replace(s, "########")
for index, a in enumerate(array):
purged_string = purged_string.replace("ARRAY" + str(index), array_store[index])
return purged_string
return string
def close_brackets(string):
started = False
bracket_counter = 0
......@@ -94,8 +116,7 @@ def create_type_combinations(case_types):
raise AssertionError("Too much arguments to use a combinatorial approach.")
import itertools
import itertools
real_types = ["single", "double"]
syn_types = ["real" if x in real_types else x for x in case_types]
syn_types = ["real" if x in VariablePrecision.real_types else x for x in case_types]
number_of_reals = syn_types.count("real")
......@@ -112,7 +133,8 @@ def create_type_combinations(case_types):
def resulting_type(type_a, type_b):
types = ["rpe_var", "complex", VariablePrecision.dp, VariablePrecision.sp, VariablePrecision.wp, "integer", "char", "logical"]
types = ["rpe_var", "complex", VariablePrecision.dp, VariablePrecision.sp, VariablePrecision.wp, "integer", "char",
"logical"]
for t in types:
if type_a == t:
return t
......@@ -120,6 +142,32 @@ def resulting_type(type_a, type_b):
return t
def find_next_end(lines, _index, end_statement):
import re
"""
Given a bunch of lines and a line number, looks for next line with the string contained in end_statement argument.
Input:
_index: integer with line number
end_statement: string that indicates next end_statement
Output: integer with the line number of the next line containing the end_statement
"""
_i = _index + 1
begin_statement = end_statement.replace('end', '')
nested_declaration = 0
while _i < len(lines):
_l = lines[_i]
if re.match('^\s*(' + begin_statement + ')', _l, re.I):
nested_declaration += 1
if _l.lower().count(end_statement):
if nested_declaration == 0:
return _i
else:
nested_declaration -= 1
_i += 1
return len(lines)
def is_all_inside_parenthesis(string):
if string.strip()[0] == "(":
try:
......@@ -132,3 +180,129 @@ def is_all_inside_parenthesis(string):
else:
return False
def remove_comments(string):
# Trying to use a rarely used character ({) for the temporal replacement
if string.count("!"):
if string.count('$OMP'):
return string
# In the case where there's quotes in the text, we should ignore the exclamations inside
if not string.count("'") and not string.count('"'):
string = string[:string.find("!")]
else:
mask = literal_mask(string)
# Looking for
splitted_string = [c for c in string]
splitted_string = ["{" if c == "!" and mask[i] else c for i, c in enumerate(splitted_string)]
string = "".join(splitted_string)
if string.find("!") >= 0:
string = string[:string.find("!")]
string = string.replace("{", "!")
return string
def remove_if_condition(_line):
if _line.replace(" ", "").lower().strip().find("if(") == 0:
condition = close_brackets(_line)
_line = _line.replace(condition, "")
return _line
def split_if_condition(_line):
if _line.replace(" ", "").lower().strip().find("if(") == 0:
condition = close_brackets(_line)
_line = _line.replace(condition, "")
else:
condition = ""
return [condition, _line]
def remove_outer_brackets(string):
string = string.strip()
if is_all_inside_parenthesis(string):
return string[1:-1].strip()
else:
return string
def remove_empty_lines(_lines):
"""
Remove empty strings from list of strings
"""
return [_l for _l in _lines if _l.strip()]
# Used in split_elements_in_operations
def remove_brackets(string):
replacement_index = 0
arrays = True
replacement_list = []
if string.strip()[0] == "[" and string.strip()[-1] == "]":
string = string.strip()[1:-1]
# Checks for all array pattern, and substitute with string
while arrays:
arrays = re.findall(RegexPattern.array, string)
for array in arrays:
string = string.replace(array, "REPLACEMENT" + str(replacement_index), 1)
replacement_list.append(array)
replacement_index += 1
# Substitute structures with string
structures = re.findall(r"\w+%\w+", string)
for struct in structures:
string = string.replace(struct, "REPLACEMENT" + str(replacement_index))
replacement_list.append(struct)
replacement_index += 1
arrays = True
functions = re.search(r"\w+\s*\(", string)
if functions:
matches = re.finditer(r"\w+\s*\(", string, flags=re.I)
matches_start = [m.start(0) for m in matches]
for start in matches_start:
func = close_brackets(string[start:])
string = string.replace(func, "REPLACEMENT" + str(replacement_index))
replacement_list.append(func)
replacement_index += 1
# Now is safe to discard brackets
string = string.replace("(", " ")
string = string.replace(")", " ")
# Substitute back the original structures
for index, replacement in reversed(list(enumerate(replacement_list))):
string = string.replace("REPLACEMENT" + str(index), replacement)
return string
def merge_lines(_lines):
"""
Given a file, puts back on the same line commands split with a &
"""
for _index, _line in enumerate(_lines):
line = _lines[_index]
if not line:
continue
if line[0] == "#":
continue
if line.count("&"):
i = 1
while line and line.strip()[-1] == "&":
line1 = line
line2 = _lines[_index + i]
line = line1.replace("&", "").rstrip() + line2
i += 1
line = line.replace("&", "")
_lines[_index] = line
# # A commands to delete unnecessary spaces (gives some problems right now)
# line = re.sub(", +", ", ", line)
# line = re.sub(": +", ": ", line)
# _lines[_index] = line
for j in range(1, i):
_lines[_index + j] = ""
_lines = remove_empty_lines(_lines)
return _lines
import AutoRPE.UtilsRPE.Error as Error
from AutoRPE.UtilsRPE.ReadSourceFile import ReadSourceFile
class Variable:
def __init__(self, procedure, _name, _type, _dimension, _module=None):
self.id = None
......@@ -13,6 +17,7 @@ class Variable:
self.is_pointer = False
self.position = None