Source code for fortdepend.units

from .preprocessor import FortranPreprocessor
import re

from .smartopen import smart_open

UNIT_REGEX = re.compile(
    r"^\s*(?P<unit_type>module(?!\s+procedure)|program)\s+(?P<modname>\w*)",
    re.IGNORECASE,
)
END_REGEX = re.compile(
    r"^\s*end\s*(?P<unit_type>module|program)\s+(?P<modname>\w*)?", re.IGNORECASE
)
USE_REGEX = re.compile(
    r"""^\s*use
(\s*,\s*intrinsic\s*)?(\s*::\s*|\s+)  # Valid separators between "use" and module name
(?P<moduse>\w*)                       # The module name
\s*(, )?\s*(only)?\s*(:)?.*?$         # Stuff that might follow the name
""",
    re.IGNORECASE | re.VERBOSE,
)


[docs]class FortranFile: """The modules and dependencies of a Fortran source file Args: filename (str): Source file macros (iterable): Dict of preprocessor macros to be expanded readfile (bool): Read and process the file [True] cpp_includes (list of str): List of directories to add to preprocessor search path use_preprocessor (bool): Preprocess the source file [True] """ def __init__( self, filename=None, macros=None, readfile=True, cpp_includes=None, use_preprocessor=True, ): self.filename = filename self.uses = None self.modules = None self.depends_on = None if readfile: with smart_open(self.filename, "r", encoding="utf-8") as f: contents = f.read() preprocessor = FortranPreprocessor() if macros: if isinstance(macros, dict): for k, v in macros.items(): preprocessor.define("{} {}".format(k, v)) else: if not isinstance(macros, list): macros = [macros] for macro in macros: if "=" in macro: temp = macro.split("=") preprocessor.define("{} {}".format(*temp)) else: preprocessor.define(macro) if cpp_includes: if not isinstance(cpp_includes, list): cpp_includes = [cpp_includes] for include_dir in cpp_includes: preprocessor.add_path(include_dir) if use_preprocessor: contents = preprocessor.parse_to_string(contents, source=self.filename) self.modules = self.get_modules(contents.splitlines()) self.uses = self.get_uses() def __str__(self): return self.filename def __repr__(self): return "FortranFile('{}')".format(self.filename)
[docs] def get_modules(self, contents, macros=None): """Return all the modules or programs that are in the file Args: contents (str): Contents of the source file """ contains = {} found_units = [] starts = [] ends = [] for num, line in enumerate(contents): unit = re.match(UNIT_REGEX, line) end = re.match(END_REGEX, line) if unit: found_units.append(unit) starts.append(num) if end: ends.append(num) if found_units: if (len(found_units) != len(starts)) or (len(starts) != len(ends)): error_string = ( "Unmatched start/end of modules in {} ({} begins/{} ends)".format( self.filename, len(starts), len(ends) ) ) raise ValueError(error_string) for unit, start, end in zip(found_units, starts, ends): name = unit.group("modname") mod = FortranModule( unit_type=unit.group("unit_type"), name=name, source_file=self, text=(contents, start, end), macros=macros, ) contains[mod.name] = mod # Remove duplicates before returning return contains
[docs] def get_uses(self): """Return a sorted list of the modules this file USEs""" if self.modules is None: return [] # flatten list of lists return sorted( set([mod for module in self.modules.values() for mod in module.uses]) )
[docs]class FortranModule: """A Fortran Module or Program Args: unit_type (str): 'module' or 'program' name (str): Name of the module/program source_file (str): Name of the file containing the module/program text (tuple): Tuple containing source_file contents, and start and end lines of the module macros (dict): Any defined macros """ def __init__(self, unit_type, name, source_file=None, text=None, macros=None): self.unit_type = unit_type.strip().lower() self.name = name.strip().lower() if self.unit_type == "module" else name.strip() if source_file is not None: self.source_file = source_file self.defined_at = text[1] self.end = text[2] self.uses = self.get_uses(text[0], macros) else: self.source_file = FortranFile(filename="empty", readfile=False) def __str__(self): return self.name def __repr__(self): return "FortranModule({}, '{}', '{}')".format( self.unit_type, self.name, self.source_file.filename )
[docs] def get_uses(self, contents, macros=None): """Return which modules are used in the file after expanding macros Args: contents (str): Contents of the source file macros (dict): Dict of preprocessor macros to be expanded """ uses = [] for line in contents[self.defined_at : self.end]: found = re.match(USE_REGEX, line) if found: uses.append(found.group("moduse").strip().lower()) # Remove duplicates uniq_mods = list(set(uses)) if macros is not None: for i, mod in enumerate(uniq_mods): for k, v in macros.items(): if re.match(k, mod, re.IGNORECASE): uniq_mods[i] = mod.replace(k, v) return uniq_mods