Source code for fparser.one.block_statements

# -*- coding: utf-8 -*-
# Modified work Copyright (c) 2017-2018 Science and Technology
# Facilities Council.
# Original work Copyright (c) 1999-2008 Pearu Peterson.

# All rights reserved.

# Modifications made as part of the fparser project are distributed
# under the following license:

# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:

# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.

# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.

# 3. Neither the name of the copyright holder nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.

# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

# --------------------------------------------------------------------

# The original software (in the f2py project) was distributed under
# the following license:

# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:

#   a. Redistributions of source code must retain the above copyright notice,
#      this list of conditions and the following disclaimer.
#   b. Redistributions in binary form must reproduce the above copyright
#      notice, this list of conditions and the following disclaimer in the
#      documentation and/or other materials provided with the distribution.
#   c. Neither the name of the F2PY project nor the names of its
#      contributors may be used to endorse or promote products derived from
#      this software without specific prior written permission.

# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
# DAMAGE.

"""
Fortran block statements.

"""

import logging
import re
import sys

import fparser.one.statements as statements
from fparser.one.statements import *
import fparser.one.typedecl_statements as typedecl_statements
from fparser.one.typedecl_statements import *

from fparser.common.base_classes import (
    BeginStatement,
    EndStatement,
    Statement,
    AttributeHolder,
    ProgramBlock,
    Variable,
)
from fparser.common.utils import (
    split_comma,
    filter_stmts,
    parse_bind,
    parse_result,
    AnalyzeError,
    is_name,
)

__all__ = [
    "BeginSource",
    "Module",
    "PythonModule",
    "Program",
    "BlockData",
    "Interface",
    "Subroutine",
    "Function",
    "SelectCase",
    "SelectType",
    "WhereConstruct",
    "ForallConstruct",
    "IfThen",
    "If",
    "Do",
    "Associate",
    "TypeDecl",
    "Enum",
    "EndSource",
    "EndModule",
    "EndPythonModule",
    "EndProgram",
    "EndBlockData",
    "EndInterface",
    "EndSubroutine",
    "EndFunction",
    "EndSelect",
    "EndWhere",
    "EndForall",
    "EndIfThen",
    "EndDo",
    "EndAssociate",
    "EndType",
    "EndEnum",
]
__all__.extend(statements.__all__)
__all__.extend(typedecl_statements.__all__)


class HasImplicitStmt:
    """
    Class encapsulating information about any Implicit statements
    contained within a scoping block.
    """

    a = AttributeHolder(implicit_rules={})

    def get_type_by_name(self, name):
        """
        Returns an object of the correct type (Integer or Real) using
        Fortran's implicit typing rules for the supplied variable name.

        :param str name: The variable name.
        :returns: Object describing the variable.
        :rtype: Either :py:class:`fparser.one.typedecl_statements.Real` \
                or :py:class:`fparser.one.typedecl_statements.Integer`.

        """
        # The implicit_rules dict is populated by the analyze() method
        # of one.typedecl_statements.Implicit
        implicit_rules = self.a.implicit_rules
        if implicit_rules is None:
            raise AnalyzeError(
                "Implicit rules mapping is null " "while getting %r type" % (name)
            )
        line = name[0].lower()
        if line in implicit_rules:
            return implicit_rules[line]
        # default rules:
        if line in "ijklmn":
            line = "default_integer"
        else:
            line = "default_real"
        var = implicit_rules.get(line, None)
        if var is None:
            if line[8:] == "real":
                implicit_rules[line] = var = Real(self, self.item.copy("real"))
            else:
                implicit_rules[line] = var = Integer(self, self.item.copy("integer"))
        return var

    def topyf(self, tab="  "):
        """
        Constructs a pyf representation of this class.

        :param str tab: White space to prepend to output.
        :returns: pyf code for this implicit statement.
        :rtype: str
        """
        implicit_rules = self.a.implicit_rules
        if implicit_rules is None:
            return tab + "IMPLICIT NONE\n"
        # Construct a dict where the keys are types and the items are
        # the list of initial letters mapped to that type
        items = {}
        for char, itype in list(implicit_rules.items()):
            if char.startswith("default"):
                continue
            type_str = itype.tostr()
            if type_str in items:
                items[type_str].append(char)
            else:
                items[type_str] = [char]
        if not items:
            return tab + "! default IMPLICIT rules apply\n"
        stmt = "IMPLICIT"
        impl_list = []
        for itype, letter_list in list(items.items()):
            letter_list.sort()
            impl_list.append(itype + " (%s)" % (", ".join(letter_list)))
        stmt += " " + ", ".join(impl_list)
        return tab + stmt + "\n"


class HasUseStmt:
    a = AttributeHolder(use={}, use_provides={})

    def get_entity(self, name):
        for modname, modblock in list(self.top.a.module.items()):
            for stmt in modblock.content:
                if getattr(stmt, "name", "") == name:
                    return stmt
        return

    def topyf(self, tab="  "):
        sys.stderr.write("HasUseStmt.topyf not implemented\n")
        return ""


class AccessSpecs:
    a = AttributeHolder(private_id_list=[], public_id_list=[])

    def topyf(self, tab="  "):
        private_list = self.a.private_id_list
        public_list = self.a.public_id_list
        lines = []
        if "" in private_list:
            lines.append(tab + "PRIVATE\n")
        if "" in public_list:
            lines.append(tab + "PUBLIC\n")
        for a in private_list:
            if not a:
                continue
            lines.append(tab + "PRIVATE :: %s\n" % (a))
        for a in public_list:
            if not a:
                continue
            lines.append(tab + "PUBLIC :: %s\n" % (a))
        return "".join(lines)


class HasVariables:
    a = AttributeHolder(
        variables={}, variable_names=[]  # defines the order of declarations
    )

    def get_variable_by_name(self, name):
        variables = self.a.variables
        if name in variables:
            var = variables[name]
        else:
            var = variables[name] = Variable(self, name)
            self.a.variable_names.append(name)
        return var

    def topyf(self, tab="", only_variables=None):
        s = ""
        if only_variables is None:
            only_variables = list(self.a.variables.keys())
        for name in only_variables:
            var = self.a.variables[name]
            s += tab + str(var) + "\n"
        return s


class HasTypeDecls:
    a = AttributeHolder(type_decls={})

    def topyf(self, tab=""):
        s = ""
        for name, stmt in list(self.a.type_decls.items()):
            s += stmt.topyf(tab="  " + tab)
        return s

    def get_type_decl_by_kind(self, kind):
        type_decls = self.a.type_decls
        type_decl = type_decls.get(kind, None)
        if type_decl is None:
            return self.get_entity(kind)
        return type_decl


class HasAttributes:
    known_attributes = []
    a = AttributeHolder(attributes=[])

    def topyf(self, tab=""):
        s = ""
        for attr in self.a.attributes:
            s += tab + attr + "\n"
        return s

    def update_attributes(self, *attrs):
        attributes = self.a.attributes
        known_attributes = self.known_attributes
        if len(attrs) == 1 and isinstance(attrs[0], (tuple, list)):
            attrs = attrs[0]
        for attr in attrs:
            uattr = attr.upper()
            if uattr not in attributes:
                if isinstance(known_attributes, (list, tuple)):
                    if uattr not in known_attributes:
                        self.warning("unknown attribute %r" % (attr))
                elif not known_attributes(uattr):
                    self.warning("unknown attribute %r" % (attr))
                attributes.append(uattr)
            else:
                self.warning("multiple specification of attribute %r" % (attr))
        return


class HasModuleProcedures:
    a = AttributeHolder(module_procedures=[])


# File block


[docs] class EndSource(EndStatement): """ Dummy End statement for BeginSource. """
[docs] match = staticmethod(lambda s: False)
[docs] class BeginSource(BeginStatement): """ Fortran source content. """
[docs] match = staticmethod(lambda s: True)
[docs] end_stmt_cls = EndSource
[docs] a = AttributeHolder(module={}, external_subprogram={}, blockdata={})
[docs] def tofortran(self, isfix=None): if isfix: tab = "C" else: tab = self.get_indent_tab(isfix=isfix) + "!" return tab + BeginStatement.tofortran(self, isfix=isfix)
[docs] def tostr(self): return self.blocktype.upper() + " " + self.name
[docs] def process_item(self): self.name = self.reader.name self.top = self self.fill(end_flag=True) return
[docs] def analyze(self): for stmt in self.content: if isinstance(stmt, Module): stmt.analyze() self.a.module[stmt.name] = stmt elif isinstance(stmt, SubProgramStatement): stmt.analyze() self.a.external_subprogram[stmt.name] = stmt elif isinstance(stmt, BlockData): stmt.analyze() self.a.blockdata[stmt.name] = stmt else: stmt.analyze() return
[docs] def get_classes(self): if self.reader.format.is_pyf: return [PythonModule] + program_unit return program_unit
[docs] def process_subitem(self, item): # MAIN block does not define start/end line conditions, # so it should never end until all lines are read. # However, sometimes F77 programs lack the PROGRAM statement, # and here we fix that: if self.reader.format.is_f77: line = item.get_line() if line == "end": message = item.reader.format_message( "WARNING", "assuming the end of undefined PROGRAM statement", item.span[0], item.span[1], ) logger.warning(message) # print >> sys.stderr, message p = Program(self) p.content.extend(self.content) p.content.append(EndProgram(p, item)) self.content[:] = [p] return return BeginStatement.process_subitem(self, item)
[docs] def topyf(self, tab=""): # XXXX s = "" for name, stmt in list(self.a.module.items()): s += stmt.topyf(tab=tab) for name, stmt in list(self.a.external_subprogram.items()): s += stmt.topyf(tab=tab) for name, stmt in list(self.a.blockdata.items()): s += stmt.topyf(tab=tab) return s
# Module
[docs] class EndModule(EndStatement):
[docs] match = re.compile(r"end(\s*module\s*\w*|)\Z", re.I).match
[docs] class Module( BeginStatement, HasAttributes, HasImplicitStmt, HasUseStmt, HasVariables, HasTypeDecls, AccessSpecs, ): """ MODULE <name> .. END [MODULE [name]] """
[docs] match = re.compile(r"module\s*\w+\Z", re.I).match
[docs] end_stmt_cls = EndModule
[docs] a = AttributeHolder( module_subprogram={}, module_provides={}, # all symbols that are public and # so can be imported via USE # statement by other blocks module_interface={}, )
[docs] known_attributes = ["PUBLIC", "PRIVATE"]
[docs] def get_classes(self): return access_spec + specification_part + module_subprogram_part
[docs] def process_item(self): name = self.item.get_line().replace(" ", "") name = name[len(self.blocktype) :].strip() self.name = name return BeginStatement.process_item(self)
[docs] def get_provides(self): return self.a.module_provides
[docs] def get_interface(self): return self.a.module_interface
[docs] def analyze(self): content = self.content[:] while content: stmt = content.pop(0) if isinstance(stmt, Contains): for stmt in filter_stmts(content, SubProgramStatement): stmt.analyze() self.a.module_subprogram[stmt.name] = stmt stmt = content.pop(0) while isinstance(stmt, Comment): stmt = content.pop(0) if not isinstance(stmt, EndModule): stmt.error("Expected END MODULE statement (analyzer).") continue stmt.analyze() if content: logger.info("Not analyzed content: %s" % content) # self.show_message('Not analyzed content: %s' % content) module_provides = self.a.module_provides for name, var in list(self.a.variables.items()): if var.is_public(): if name in module_provides: message = ( "module data object name conflict with %s, " + "overriding." ) self.warning(message % (name)) module_provides[name] = var return
[docs] def topyf(self, tab=""): s = tab + "MODULE " + self.name + "\n" s += HasImplicitStmt.topyf(self, tab=tab + " ") s += AccessSpecs.topyf(self, tab=tab + " ") s += HasAttributes.topyf(self, tab=tab + " ") s += HasTypeDecls.topyf(self, tab=tab + " ") s += HasVariables.topyf(self, tab=tab + " ") for name, stmt in list(self.a.module_interface.items()): s += stmt.topyf(tab=tab + " ") s += tab + " CONTAINS\n" for name, stmt in list(self.a.module_subprogram.items()): s += stmt.topyf(tab=tab + " ") s += tab + "END MODULE " + self.name + "\n" return s
[docs] def check_private(self, name): if name in self.a.public_id_list: return False if name in self.a.private_id_list: return True if "" in self.a.public_id_list: return False if "" in self.a.private_id_list: return True # todo: handle generic-spec-s in id-lists. return
# Python Module
[docs] class EndPythonModule(EndStatement):
[docs] match = re.compile(r"end(\s*python\s*module\s*\w*|)\Z", re.I).match
[docs] class PythonModule(BeginStatement, HasImplicitStmt, HasUseStmt, HasVariables): """ PYTHON MODULE <name> .. END [PYTHON MODULE [name]] """
[docs] modes = ["pyf"]
[docs] match = re.compile(r"python\s*module\s*\w+\Z", re.I).match
[docs] end_stmt_cls = EndPythonModule
[docs] def get_classes(self): return [Interface, Function, Subroutine, Module]
[docs] def process_item(self): self.name = self.item.get_line().replace(" ", "") self.name = self.name[len(self.blocktype) :].strip() return BeginStatement.process_item(self)
# Program
[docs] class EndProgram(EndStatement): """ END [PROGRAM [name]] """
[docs] match = re.compile(r"end(\s*program\s*\w*|)\Z", re.I).match
[docs] class Program( BeginStatement, ProgramBlock, # HasAttributes, # XXX: why Program needs .attributes? HasVariables, HasImplicitStmt, HasUseStmt, AccessSpecs, ): """PROGRAM [name]"""
[docs] match = re.compile(r"program\s*\w*\Z", re.I).match
[docs] end_stmt_cls = EndProgram
[docs] def get_classes(self): return specification_part + execution_part + internal_subprogram_part
[docs] def process_item(self): if self.item is not None: name = self.item.get_line().replace(" ", "") name = name[len(self.blocktype) :].strip() if name: self.name = name return BeginStatement.process_item(self)
# BlockData
[docs] class EndBlockData(EndStatement): """ END [BLOCK DATA [<block-data-name>]] """
[docs] match = re.compile(r"end(\s*block\s*data\s*\w*|)\Z", re.I).match
[docs] blocktype = "blockdata"
[docs] class BlockData(BeginStatement, HasImplicitStmt, HasUseStmt, HasVariables, AccessSpecs): """ BLOCK DATA [<block-data-name>] """
[docs] end_stmt_cls = EndBlockData
[docs] match = re.compile(r"block\s*data\s*\w*\Z", re.I).match
[docs] def process_item(self): self.name = self.item.get_line()[5:].lstrip()[4:].lstrip() return BeginStatement.process_item(self)
[docs] def get_classes(self): return specification_part
# Interface
[docs] class EndInterface(EndStatement):
[docs] match = re.compile(r"end\s*interface\s*(\w+\s*\(.*\)|\w*)\Z", re.I).match
[docs] blocktype = "interface"
[docs] class Interface( BeginStatement, HasAttributes, HasImplicitStmt, HasUseStmt, HasModuleProcedures, AccessSpecs, ): """ INTERFACE [<generic-spec>] | ABSTRACT INTERFACE END INTERFACE [<generic-spec>] <generic-spec> = <generic-name> | OPERATOR ( <defined-operator> ) | ASSIGNMENT ( = ) | <dtio-generic-spec> <dtio-generic-spec> = READ ( FORMATTED ) | READ ( UNFORMATTED ) | WRITE ( FORMATTED ) | WRITE ( UNFORMATTED ) """
[docs] modes = ["free", "fix", "pyf"]
[docs] pattern = r"(interface\s*(\w+\s*\(.*\)|\w*)|abstract\s*interface)\Z"
[docs] match = re.compile(pattern, re.I).match
[docs] end_stmt_cls = EndInterface
[docs] blocktype = "interface"
[docs] a = AttributeHolder(interface_provides={})
[docs] def get_classes(self): line = intrinsic_type_spec + interface_specification if self.reader.format.mode == "pyf": return [Subroutine, Function] + line return line
[docs] def process_item(self): line = self.item.get_line() line = self.item.apply_map(line) self.isabstract = line.startswith("abstract") if self.isabstract: self.generic_spec = "" else: self.generic_spec = line[len(self.blocktype) :].strip() self.name = self.generic_spec # XXX return BeginStatement.process_item(self)
[docs] def tostr(self): if self.isabstract: return "ABSTRACT INTERFACE" return "INTERFACE " + str(self.generic_spec)
[docs] def analyze(self): content = self.content[:] while content: stmt = content.pop(0) if isinstance(stmt, self.end_stmt_cls): break stmt.analyze() if content: logger.info("Not analyzed content: %s" % content) # self.show_message('Not analyzed content: %s' % content) if self.name in self.parent.a.variables: var = self.parent.a.variables.pop(self.name) self.update_attributes(var.attributes) if isinstance(self.parent, Module): # XXX parent_interface = self.parent.get_interface() if self.name in parent_interface: p = parent_interface[self.name] last = p.content.pop() assert isinstance(last, EndInterface), repr(last.__class__) p.content += self.content p.update_attributes(self.a.attributes) else: parent_interface[self.name] = self return
[docs] def topyf(self, tab=""): s = tab + self.tostr() + "\n" s += HasImplicitStmt.topyf(self, tab=tab + " ") s += HasAttributes.topyf(self, tab=tab + " ") s += HasUseStmt.topyf(self, tab=tab + " ") s += tab + "END" + self.tostr() + "\n" return s
# Subroutine class SubProgramStatement( BeginStatement, ProgramBlock, HasImplicitStmt, HasAttributes, HasUseStmt, HasVariables, HasTypeDecls, AccessSpecs, ): """ [<prefix>] <FUNCTION|SUBROUTINE> <name> [( <args> )] [<suffix>] """ a = AttributeHolder(internal_subprogram={}) known_attributes = ["RECURSIVE", "PURE", "ELEMENTAL"] def process_item(self): clsname = self.__class__.__name__.lower() item = self.item line = item.get_line() m = self.match(line) i = line.lower().find(clsname) assert i != -1, repr((clsname, line)) self.prefix = line[:i].rstrip() self.name = line[i : m.end()].lstrip()[len(clsname) :].strip() line = line[m.end() :].lstrip() args = [] if line.startswith("("): i = line.find(")") assert i != -1, repr(line) line2 = item.apply_map(line[: i + 1]) for a in line2[1:-1].split(","): a = a.strip() if not a: continue args.append(a) line = line[i + 1 :].lstrip() suffix = item.apply_map(line) self.bind, suffix = parse_bind(suffix, item) self.result = None if isinstance(self, Function): self.result, suffix = parse_result(suffix, item) if suffix: assert self.bind is None, repr(self.bind) self.bind, suffix = parse_result(suffix, item) if self.result is None: self.result = self.name assert not suffix, repr(suffix) self.args = args self.typedecl = None return BeginStatement.process_item(self) def tostr(self): clsname = self.__class__.__name__.upper() s = "" if self.prefix: s += self.prefix + " " if self.typedecl is not None: assert isinstance(self, Function), repr(self.__class__.__name__) s += self.typedecl.tostr() + " " s += clsname suf = "" if self.result and self.result != self.name: suf += " RESULT ( %s )" % (self.result) if self.bind: suf += " BIND ( %s )" % (", ".join(self.bind)) return "%s %s(%s)%s" % (s, self.name, ", ".join(self.args), suf) def get_classes(self): return ( f2py_stmt + specification_part + execution_part + internal_subprogram_part ) def analyze(self): content = self.content[:] if self.prefix: self.update_attributes(self.prefix.upper().split()) variables = self.a.variables for a in self.args: assert a not in variables if is_name(a): variables[a] = Variable(self, a) elif a == "*": variables[a] = Variable(self, a) # XXX: fix me appropriately else: message = "argument must be a name or * but got %r" raise AnalyzeError(message % (a)) if isinstance(self, Function): var = variables[self.result] = Variable(self, self.result) if self.typedecl is not None: var.set_type(self.typedecl) while content: stmt = content.pop(0) if isinstance(stmt, Contains): for stmt in filter_stmts(content, SubProgramStatement): stmt.analyze() self.a.internal_subprogram[stmt.name] = stmt stmt = content.pop(0) while isinstance(stmt, Comment): stmt = content.pop(0) assert isinstance(stmt, self.end_stmt_cls), repr(stmt) elif isinstance(stmt, self.end_stmt_cls): continue else: if hasattr(stmt, "analyze"): stmt.analyze() else: message = "Failed to parse: {0}" raise AnalyzeError(message.format(str(stmt))) if content: logger.info("Not analyzed content: %s" % content) # self.show_message('Not analyzed content: %s' % content) parent_provides = self.parent.get_provides() if parent_provides is not None: if self.name in parent_provides: message = "module subprogram name conflict with %s, " + "overriding." self.warning(message % (self.name)) if self.is_public(): parent_provides[self.name] = self if self.is_recursive() and self.is_elemental(): message = ( "C1241 violation: prefix cannot specify both " + "ELEMENTAL and RECURSIVE" ) self.warning(message) return def topyf(self, tab=""): s = tab + self.__class__.__name__.upper() s += " " + self.name + " (%s)" % (", ".join(self.args)) if isinstance(self, Function) and self.result != self.name: s += " RESULT (%s)" % (self.result) s += "\n" s += HasImplicitStmt.topyf(self, tab=tab + " ") s += AccessSpecs.topyf(self, tab=tab + " ") s += HasTypeDecls.topyf(self, tab=tab + " ") s += HasVariables.topyf(self, tab=tab + " ", only_variables=self.args) s += tab + "END " + self.__class__.__name__.upper() + " " + self.name + "\n" return s def is_public(self): return not self.is_private() def is_private(self): return self.parent.check_private(self.name) def is_recursive(self): return "RECURSIVE" in self.a.attributes def is_pure(self): return "PURE" in self.a.attributes def is_elemental(self): return "ELEMENTAL" in self.a.attributes
[docs] class EndSubroutine(EndStatement): """ END [SUBROUTINE [name]] """
[docs] match = re.compile( r"end\s*(?:subroutine\s*(?:(?P<name>\w+)\s*)?)?$", re.IGNORECASE ).match
[docs] class Subroutine(SubProgramStatement): """ [<prefix>] SUBROUTINE <name> [( [<dummy-arg-list>] ) [<proc-language-binding-spec>]] """
[docs] end_stmt_cls = EndSubroutine
[docs] pattern = r"(recursive|pure|elemental|\s)*subroutine\s*\w+"
[docs] match = re.compile(pattern, re.I).match
[docs] _repr_attr_names = ["prefix", "bind", "suffix", "args"] + Statement._repr_attr_names
# Function
[docs] class EndFunction(EndStatement): """ END [FUNCTION [name]] """
[docs] match = re.compile(r"end(\s*function\s*\w*|)\Z", re.I).match
[docs] class Function(SubProgramStatement): """ :: [<prefix>] FUNCTION <name> ( [<dummy-arg-list>] ) [<suffix>] <prefix> = <prefix-spec> [<prefix-spec>]... <prefix-spec> = <declaration-type-spec> | RECURSIVE | PURE | ELEMENTAL <suffix> = <proc-language-binding-spec> [RESULT ( <result-name> )] | RESULT ( <result-name> ) [<proc-language-binding-spec>] """
[docs] end_stmt_cls = EndFunction
[docs] pattern = r"(recursive|pure|elemental|\s)*function\s*\w+"
[docs] match = re.compile(pattern, re.I).match
[docs] _repr_attr_names = [ "prefix", "bind", "suffix", "args", "typedecl", ] + Statement._repr_attr_names
[docs] def subroutine_wrapper_code(self): name = "f2pywrap_" + self.name args = ["f2pyvalue_" + self.result] + self.args var = self.a.variables[self.result] typedecl = var.get_typedecl().astypedecl() lines = [] tab = " " * 6 lines.append("%sSUBROUTINE %s(%s)" % (tab, name, ", ".join(args))) if isinstance(self.parent, Module): lines.append("%s USE %s" % (tab, self.parent.name)) else: if isinstance(typedecl, TypeStmt): type_decl = typedecl.get_type_decl(typedecl.name) if type_decl.parent is self: for line in str(type_decl).split("\n"): lines.append("%s %s" % (tab, line.lstrip())) lines.append("%s EXTERNAL %s" % (tab, self.name)) lines.append("%s %s %s" % (tab, str(typedecl).lstrip(), self.name)) lines.append("%s %s %s" % (tab, str(typedecl).lstrip(), args[0])) lines.append("!f2py intent(out) %s" % (args[0])) for a in self.args: v = self.a.variables[a] lines.append("%s %s" % (tab, str(v).lstrip())) lines.append( "%s %s = %s(%s)" % (tab, args[0], self.name, ", ".join(self.args)) ) lines.append("%sEND SUBROUTINE %s" % (tab, name)) return "\n".join(lines)
[docs] def subroutine_wrapper(self): code = self.subroutine_wrapper_code() from .api import parse block = parse(code) # XXX: set include_dirs while len(block.content) == 1: block = block.content[0] return block
# Handle subprogram prefixes class SubprogramPrefix(Statement): """ <prefix> <declaration-type-spec> <function|subroutine> ... """ match = re.compile(r"(pure|elemental|recursive|\s)+\b", re.I).match def process_item(self): line = self.item.get_line() m = self.match(line) prefix = line[: m.end()].rstrip() rest = self.item.get_line()[m.end() :].lstrip() if rest: self.parent.put_item(self.item.copy(prefix)) self.item.clone(rest) self.isvalid = False return if self.parent.__class__ not in [Function, Subroutine]: self.isvalid = False return prefix = prefix + " " + self.parent.prefix self.parent.prefix = prefix.strip() self.ignore = True return # SelectCase
[docs] class EndSelect(EndStatement):
[docs] match = re.compile(r"end\s*select\s*\w*\Z", re.I).match
[docs] blocktype = "select"
class Select(BeginStatement): """ Base class for the Select (case/type) statement """ end_stmt_cls = EndSelect name = "" def process_item(self): """Populate the state of this Select object by parsing the associated line of code""" item = self.item # TODO make the following more robust, particularly to the # presence of a name at the beginning # (e.g. "a_name: select case(...)") line = item.get_line()[6:].lstrip()[4:].lstrip()[1:-1].strip() self.expr = item.apply_map(line) self.construct_name = self.item.name return BeginStatement.process_item(self)
[docs] class SelectCase(Select): """ [<case-construct-name> :] SELECT CASE ( <case-expr> ) """
[docs] match = re.compile(r"select\s*case\s*\(.*\)\Z", re.I).match
[docs] def tostr(self): return "SELECT CASE ( %s )" % (self.expr)
[docs] def get_classes(self): """Return the list of classes that this instance may have as children""" return [Case] + execution_part_construct
[docs] class SelectType(Select): """ [<case-construct-name> :] SELECT TYPE ( <case-expr> ) """
[docs] match = re.compile(r"select\s*type\s*\(.*\)\Z", re.I).match
[docs] def tostr(self): return "SELECT TYPE ( %s )" % (self.expr)
[docs] def get_classes(self): """Return the list of classes that this instance may have as children""" return [TypeIs, ClassIs] + execution_part_construct
# Where
[docs] class EndWhere(EndStatement): """ END WHERE [<where-construct-name>] """
[docs] match = re.compile(r"end\s*\where\s*\w*\Z", re.I).match
class Where(BeginStatement): """ [<where-construct-name> :] WHERE ( <mask-expr> ) <mask-expr> = <logical-expr> """ match = re.compile(r"where\s*\([^)]*\)\Z", re.I).match end_stmt_cls = EndWhere name = "" def tostr(self): return f"WHERE ( {self.item.apply_map(self.expr)} )" def process_item(self): self.expr = self.item.get_line()[5:].lstrip()[1:-1].strip() self.construct_name = self.item.name return BeginStatement.process_item(self) def get_classes(self): return [Assignment, WhereStmt, WhereConstruct, ElseWhere]
[docs] WhereConstruct = Where
# Forall
[docs] class EndForall(EndStatement): """ END FORALL [<forall-construct-name>] """
[docs] match = re.compile(r"end\s*forall\s*\w*\Z", re.I).match
class Forall(BeginStatement): """ [<forall-construct-name> :] FORALL <forall-header> [<forall-body-construct>]... <forall-body-construct> = <forall-assignment-stmt> | <where-stmt> | <where-construct> | <forall-construct> | <forall-stmt> <forall-header> = ( <forall-triplet-spec-list> [, <scalar-mask-expr>] ) <forall-triplet-spec> = <index-name> = <subscript> : <subscript> [: <stride>] <subscript|stride> = <scalar-int-expr> <forall-assignment-stmt> = <assignment-stmt> | <pointer-assignment-stmt> """ end_stmt_cls = EndForall match = re.compile(r"forall\s*\(.*\)\Z", re.I).match name = "" def process_item(self): self.specs = self.item.get_line()[6:].lstrip()[1:-1].strip() return BeginStatement.process_item(self) def tostr(self): return "FORALL (%s)" % (self.specs) def get_classes(self): return [ GeneralAssignment, WhereStmt, WhereConstruct, ForallConstruct, ForallStmt, ]
[docs] ForallConstruct = Forall
# IfThen
[docs] class EndIfThen(EndStatement): """ END IF [<if-construct-name>] """
[docs] match = re.compile(r"end\s*if\s*\w*\Z", re.I).match
[docs] blocktype = "if"
[docs] class IfThen(BeginStatement): """ [<if-construct-name> :] IF ( <scalar-logical-expr> ) THEN IfThen instance has the following attributes: expr """
[docs] match = re.compile(r"if\s*\(.*\)\s*then\Z", re.I).match
[docs] end_stmt_cls = EndIfThen
[docs] name = ""
[docs] def tostr(self): return "IF (%s) THEN" % (self.expr)
[docs] def process_item(self): item = self.item line = item.get_line()[2:-4].strip() assert line[0] == "(" and line[-1] == ")", repr(line) self.expr = item.apply_map(line[1:-1].strip()) self.construct_name = item.name return BeginStatement.process_item(self)
[docs] def get_classes(self): return [Else, ElseIf] + execution_part_construct
[docs] class If(BeginStatement): """ IF ( <scalar-logical-expr> ) action-stmt """
[docs] match = re.compile(r"if\s*\(", re.I).match
[docs] def process_item(self): item = self.item mode = self.reader.format.mode classes = self.get_classes() classes = [cls for cls in classes if mode in cls.modes] line = item.get_line()[2:].lstrip() i = line.find(")") expr = line[1:i].strip() line = line[i + 1 :].strip() if line.lower() == "then": self.isvalid = False return self.expr = item.apply_map(expr) if not line: newitem = self.get_item() else: newitem = item.copy(line, apply_map=True) newline = newitem.get_line() for cls in classes: if cls.match(newline): stmt = cls(self, newitem) if stmt.isvalid: self.content.append(stmt) return if not line: self.put_item(newitem) self.isvalid = False return
[docs] def tostr(self): assert len(self.content) == 1, repr(self.content) return "IF (%s) %s" % (self.expr, str(self.content[0]).lstrip())
[docs] def tofortran(self, isfix=None): return self.get_indent_tab(isfix=isfix) + self.tostr()
[docs] def get_classes(self): return action_stmt
# Do
[docs] class EndDo(EndStatement): """ END DO [<do-construct-name>] """
[docs] match = re.compile(r"end\s*do\s*(?:(?P<name>\w+)\s*)?\Z", re.IGNORECASE).match
[docs] blocktype = "do"
[docs] def process_item(self): """ Parses the next line assuming it is an "End do" statement. Overrides method in `EndStatement`. """ item = self.item line = item.get_line() matched = self.match(line) # Check for matching labels found_label = getattr(self.item, "label", None) expected_label = getattr(self.parent, "endlabel", None) if expected_label: if found_label: if found_label != expected_label: message = ( 'When entering the "do" block {start} was' + " given as the end label but {end} was found." ) self.warning(message.format(start=expected_label, end=found_label)) self.isvalid = False else: message = ( 'A label was specified when entering the "do" ' + " block ({label}) but none was found at the end." ) self.warning(message.format(label=expected_label)) self.isvalid = False # Check for matching names found_name = matched.group("name") or None expected_name = self.parent.construct_name if expected_name: if found_name: if found_name != expected_name: message = ( 'The "do" block was specified with the name' + ' "{open}" but was closed with the name' + ' "{close}".' ) self.warning(message.format(open=expected_name, close=found_name)) self.isvalid = False else: message = ( 'A name ("{name}") was specified for the "do" ' + "block but was not given when closing the block." ) self.warning(message.format(name=expected_name)) self.isvalid = False else: if found_name: message = ( 'The name "{name}" was used when closing a "do"' + "block but none was specified when opening it." ) self.warning(message.format(name=found_name)) self.isvalid = False return EndStatement.process_item(self)
[docs] class Do(BeginStatement): """ [<do-construct-name> :] DO label [loopcontrol] [<do-construct-name> :] DO [loopcontrol] """
[docs] match = re.compile(r"do\b\s*\d*", re.I).match
[docs] pattern = r"do\b\s*(?:(?P<label>\d+)\s*)?(?:,\s*)?(?P<loopcontrol>.+)?\Z"
[docs] item_re = re.compile(pattern, re.IGNORECASE).match
[docs] end_stmt_cls = EndDo
[docs] name = ""
[docs] def tostr(self): lst = ["DO"] for part in [self.endlabel, self.loopcontrol]: if part: lst.append(str(part)) return " ".join(lst)
[docs] def process_item(self): """ Parses the next line assuming it is a "Do" statement. Overrides method in `BeginStatement`. """ item = self.item line = item.get_line() matched = self.item_re(line) if matched.group("label"): self.endlabel = int(matched.group("label").strip()) else: self.endlabel = None self.construct_name = item.name if matched.group("loopcontrol"): self.loopcontrol = item.apply_map(matched.group("loopcontrol").strip()) else: self.loopcontrol = None return BeginStatement.process_item(self)
[docs] def process_subitem(self, item): result = False if self.endlabel: label = item.label if label == self.endlabel: result = True if isinstance(self.parent, Do) and label == self.parent.endlabel: # the same item label may be used for different block ends self.put_item(item) return BeginStatement.process_subitem(self, item) or result
[docs] def get_classes(self): return execution_part_construct
# Associate
[docs] class EndAssociate(EndStatement): """ END ASSOCIATE [<associate-construct-name>] """
[docs] match = re.compile(r"end\s*associate\s*\w*\Z", re.I).match
[docs] class Associate(BeginStatement): """ [<associate-construct-name> :] ASSOCIATE ( <association-list> ) <block> <association> = <associate-name> => <selector> <selector> = <expr> | <variable> """
[docs] match = re.compile(r"associate\s*\(.*\)\Z", re.I).match
[docs] end_stmt_cls = EndAssociate
[docs] def process_item(self): line = self.item.get_line()[9:].lstrip() self.associations = line[1:-1].strip() return BeginStatement.process_item(self)
[docs] def tostr(self): return "ASSOCIATE (%s)" % (self.associations)
[docs] def get_classes(self): return execution_part_construct
# Type
[docs] class EndType(EndStatement): """ END TYPE [<type-name>] """
[docs] match = re.compile(r"end\s*type\s*\w*\Z", re.I).match
[docs] blocktype = "type"
class Type( BeginStatement, HasVariables, HasAttributes, HasModuleProcedures, AccessSpecs ): """ TYPE [[, <typ-attr-spec-list>] ::] <type-name> [( <type-param-name-list> )] <typ-attr-spec> = <access-spec> | EXTENDS ( <parent-type-name> ) | ABSTRACT | BIND(C) """ match = re.compile(r"type\b\s*").match end_stmt_cls = EndType a = AttributeHolder( extends=None, parameters={}, component_names=[], # Order for sequence types components={}, ) pattern = r"\A(PUBLIC|PRIVATE|SEQUENCE|ABSTRACT|BIND\s*\(.*\))\Z" known_attributes = re.compile(pattern, re.I).match def process_item(self): line = self.item.get_line()[4:].lstrip() if line.startswith("("): self.isvalid = False return specs = [] i = line.find("::") if i != -1: for s in line[:i].split(","): s = s.strip() if s: specs.append(s) line = line[i + 2 :].lstrip() self.specs = specs i = line.find("(") if i != -1: self.name = line[:i].rstrip() assert line[-1] == ")", repr(line) self.params = split_comma(line[i + 1 : -1].lstrip()) else: self.name = line self.params = [] if not is_name(self.name): self.isvalid = False return return BeginStatement.process_item(self) def tostr(self): s = "TYPE" if self.specs: s += ", ".join([""] + self.specs) + " ::" s += " " + self.name if self.params: s += " (" + ", ".join(self.params) + ")" return s def get_classes(self): return ( [Integer] + private_or_sequence + component_part + type_bound_procedure_part ) def analyze(self): for spec in self.specs: i = spec.find("(") if i != -1: assert spec.endswith(")"), repr(spec) s = spec[:i].rstrip().upper() n = spec[i + 1 : -1].strip() if s == "EXTENDS": self.a.extends = n continue elif s == "BIND": args, rest = parse_bind(spec) assert not rest, repr(rest) spec = "BIND(%s)" % (", ".join(args)) else: spec = "%s(%s)" % (s, n) else: spec = spec.upper() self.update_attributes(spec) component_names = self.a.component_names content = self.content[:] while content: stmt = content.pop(0) if isinstance(stmt, self.end_stmt_cls): break stmt.analyze() if content: message = "Not analyzed content: %s" % content logging.getLogger(__class__).info(message) parameters = self.a.parameters components = self.a.components component_names = self.a.component_names for name in self.a.variable_names: var = self.a.variables[name] if name in self.params: parameters[name] = var else: component_names.append(name) components[name] = var self.parent.a.type_decls[self.name] = self parent_provides = self.parent.get_provides() if parent_provides is not None: if self.is_public(): if self.name in parent_provides: message = "type declaration name conflict with {}, " + "overriding." self.warning(message.format(self.name)) parent_provides[self.name] = self return def topyf(self, tab=""): s = tab + "TYPE" if self.a.extends is not None: s += ", EXTENDS(%s) ::" % (self.a.extends) s += " " + self.name if self.a.parameters: s += " (%s)" % (", ".join(self.a.parameters)) s += "\n" s += AccessSpecs.topyf(self, tab=tab + " ") s += HasAttributes.topyf(self, tab=tab + " ") s += HasVariables.topyf(self, tab=tab + " ") s += tab + "END TYPE " + self.name + "\n" return s # Wrapper methods: def is_public(self): return not self.is_private() def is_private(self): if "PUBLIC" in self.a.attributes: return False if "PRIVATE" in self.a.attributes: return True return self.parent.check_private(self.name)
[docs] TypeDecl = Type
# Enum
[docs] class EndEnum(EndStatement): """ END ENUM """
[docs] match = re.compile(r"end\s*enum\Z", re.I).match
[docs] blocktype = "enum"
[docs] class Enum(BeginStatement): """ ENUM , BIND(C) <enumerator-def-stmt> [<enumerator-def-stmt>]... """
[docs] blocktype = "enum"
[docs] end_stmt_cls = EndEnum
[docs] match = re.compile(r"enum\s*,\s*bind\s*\(\s*c\s*\)\Z", re.I).match
[docs] def process_item(self): return BeginStatement.process_item(self)
[docs] def get_classes(self): return [Enumerator]
############################################################################## f2py_stmt = [Threadsafe, FortranName, Depend, Check, CallStatement, CallProtoArgument] access_spec = [Public, Private] interface_specification = [Function, Subroutine, ModuleProcedure] module_subprogram_part = [Contains, Function, Subroutine] specification_stmt = access_spec + [ Allocatable, Asynchronous, Bind, Common, Data, Dimension, Equivalence, External, Intent, Intrinsic, Namelist, Optional, Pointer, Protected, Save, Target, Volatile, Value, ] intrinsic_type_spec = [ SubprogramPrefix, Integer, Real, DoublePrecision, Complex, DoubleComplex, Character, Logical, Byte, ] derived_type_spec = [] type_spec = intrinsic_type_spec + derived_type_spec declaration_type_spec = intrinsic_type_spec + [TypeStmt, Class] type_declaration_stmt = declaration_type_spec private_or_sequence = [Private, Sequence] component_part = declaration_type_spec + [ModuleProcedure] proc_binding_stmt = [SpecificBinding, GenericBinding, FinalBinding] type_bound_procedure_part = [Contains, Private] + proc_binding_stmt # R214 action_stmt = [ Allocate, GeneralAssignment, Assign, Backspace, Call, Close, Continue, Cycle, Deallocate, Endfile, Exit, Flush, ForallStmt, Goto, If, Inquire, Nullify, Open, Print, Read, Return, Rewind, Stop, Wait, WhereStmt, Write, ArithmeticIf, ComputedGoto, AssignedGoto, Pause, ] # GeneralAssignment = Assignment + PointerAssignment # EndFunction, EndProgram, EndSubroutine - part of the corresponding blocks executable_construct = [ Associate, Do, ForallConstruct, IfThen, SelectCase, SelectType, WhereConstruct, ] + action_stmt execution_part_construct = executable_construct + [Format, Entry, Data] execution_part = execution_part_construct[:] # C201, R208 for cls in [EndFunction, EndProgram, EndSubroutine]: try: execution_part.remove(cls) except ValueError: pass internal_subprogram = [Function, Subroutine] internal_subprogram_part = [Contains] + internal_subprogram # In Fortran2003 we can have a Procedure declaration. We therefore # include SpecificBinding as a valid declaration construct. declaration_construct = ( [ TypeDecl, Entry, Enum, Format, Interface, Parameter, ModuleProcedure, SpecificBinding, ] + specification_stmt + type_declaration_stmt ) # stmt-function-stmt implicit_part = [Implicit, Parameter, Format, Entry] specification_part = [Use, Import] + implicit_part + declaration_construct external_subprogram = [Function, Subroutine] main_program = ( [Program] + specification_part + execution_part + internal_subprogram_part ) program_unit = main_program + external_subprogram + [Module, BlockData]