# Modified work Copyright (c) 2017-2022 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.
"""
Various utility functions.
Permission to use, modify, and distribute this software is given under the
terms of the NumPy License. See http://scipy.org.
NO WARRANTY IS EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
Author: Pearu Peterson <pearu@cens.ioc.ee>
Created: May 2006
"""
__all__ = [
"split_comma",
"specs_split_comma",
"ParseError",
"AnalyzeError",
"get_module_file",
"parse_bind",
"parse_result",
"is_name",
"parse_array_spec",
"CHAR_BIT",
"str2stmt",
"classes",
]
import logging
import glob
import io
import os
import re
import traceback
[docs]
class ParseError(Exception):
pass
[docs]
class AnalyzeError(Exception):
pass
[docs]
is_name = re.compile(r"^[a-z_]\w*$", re.I).match
name_re = re.compile(r"[a-z_]\w*", re.I).match
is_entity_decl = re.compile(r"^[a-z_]\w*", re.I).match
is_int_literal_constant = re.compile(r"^\d+(_\w+|)$").match
module_file_extensions = [".f", ".f90", ".f95", ".f03", ".f08"]
[docs]
def split_comma(line, item=None, comma=",", keep_empty=False, brackets=None):
"""Split (an optionally bracketed) comma-separated list into
items and return a list containing them. If supplied then
brackets must be a list of containing two strings, the first
being the opening bracket and the second the closing bracket."""
# we may have blank space so strip the line
line = line.strip()
if not line:
return []
if brackets:
if not isinstance(brackets, tuple):
raise ParseError("split_comma: brackets must be a tuple")
if len(brackets) != 2:
raise ParseError(
"split_comma: brackets tuple must contain "
"just two items but got: {0}",
brackets,
)
open = brackets[0]
close = brackets[1]
if not line.startswith(open) or not line.endswith(close):
return []
line = line.strip(brackets[0])
line = line.strip(brackets[1])
items = []
if item is None:
for s in line.split(comma):
s = s.strip()
if not s and not keep_empty:
continue
items.append(s)
return items
newitem = item.copy(line, True)
apply_map = newitem.apply_map
for s in newitem.get_line().split(comma):
s = apply_map(s).strip()
if not s and not keep_empty:
continue
items.append(s)
return items
def extract_bracketed_list_items(line, item=None):
"""Takes any line that contains "xxx (a,b,...) yyy" and returns
a list of items corresponding to a, b, ... Anything outside of
the parentheses is ignored. Only works for strings containing
a single set of parentheses."""
if line.count("(") > 1 or line.count(")") > 1:
raise ParseError(
"parse_bracketed_list: more than one opening/closing parenthesis "
"found in string '{0}'; this is not supported".format(line)
)
idx1 = line.find("(")
idx2 = line.rfind(")")
if idx1 < 0 or idx2 < 0 or idx2 < idx1:
raise ParseError(
"parse_bracketed_list: failed to find expression within "
"parentheses in '{0}'".format(line)
)
items = split_comma(line[idx1 : idx2 + 1], item, brackets=("(", ")"))
if item:
for idx in range(len(items)):
itm = item.copy(items[idx])
rlst = []
for rpart in itm.get_line().split(":"):
rlst.append(itm.apply_map(rpart.strip()))
items[idx] = rlst
return items
[docs]
def parse_array_spec(line, item=None):
items = []
for spec in split_comma(line, item):
items.append(tuple(split_comma(spec, item, comma=":", keep_empty=True)))
return items
[docs]
def specs_split_comma(line, item=None, upper=False):
specs0 = split_comma(line, item)
specs = []
for spec in specs0:
i = spec.find("=")
if i != -1:
kw = spec[:i].strip().upper()
v = spec[i + 1 :].strip()
specs.append("%s = %s" % (kw, v))
else:
if upper:
spec = spec.upper()
specs.append(spec)
return specs
[docs]
def parse_bind(line, item=None):
if not line.lower().startswith("bind"):
return None, line
if item is not None:
newitem = item.copy(line, apply_map=True)
newline = newitem.get_line()
else:
newitem = None
newline = newline[4:].lstrip()
i = newline.find(")")
assert i != -1, repr(newline)
args = []
for a in specs_split_comma(newline[1:i].strip(), newitem, upper=True):
args.append(a)
rest = newline[i + 1 :].lstrip()
if item is not None:
rest = newitem.apply_map(rest)
return args, rest
[docs]
def parse_result(line, item=None):
if not line.lower().startswith("result"):
return None, line
line = line[6:].lstrip()
i = line.find(")")
assert i != -1, repr(line)
name = line[1:i].strip()
assert is_name(name), repr(name)
return name, line[i + 1 :].lstrip()
def filter_stmts(content, classes):
"""Pop and return classes instances from content."""
stmts = []
indices = []
for i in range(len(content)):
stmt = content[i]
if isinstance(stmt, classes):
stmts.append(stmt)
indices.append(i)
indices.reverse()
for i in indices:
del content[i]
return stmts
def get_module_files(directory, _cache={}):
if directory in _cache:
return _cache[directory]
module_line = re.compile(r"(\A|^)module\s+(?P<name>\w+)\s*(!.*|)$", re.I | re.M)
d = {}
files = []
for ext in module_file_extensions:
files += glob.glob(os.path.join(directory, "*" + ext))
for fn in files:
f = open(fn, "r")
for name in module_line.findall(f.read()):
name = name[1]
if name in d:
print(d[name], "already defines", name)
continue
d[name] = fn
_cache[directory] = d
return d
[docs]
def get_module_file(name, directory, _cache={}):
fn = _cache.get(name, None)
if fn is not None:
return fn
if name.endswith("_module"):
for ext in module_file_extensions:
f1 = os.path.join(directory, name[:-7] + ext)
if os.path.isfile(f1):
_cache[name] = fn
return f1
files = []
for ext in module_file_extensions:
files += glob.glob(os.path.join(directory, "*" + ext))
for fn in files:
if module_in_file(name, fn):
_cache[name] = fn
return fn
return None
def module_in_file(name, filename):
name = name.lower()
pattern = re.compile(r"\s*module\s+(?P<name>[a-z]\w*)", re.I).match
encoding = {"encoding": "UTF-8"}
f = io.open(filename, "r", **encoding)
for line in f:
m = pattern(line)
if m and m.group("name").lower() == name:
f.close()
return filename
f.close()
[docs]
def str2stmt(string, isfree=True, isstrict=False):
"""Convert Fortran code to Statement tree."""
from .readfortran import Line, FortranStringReader
from .parsefortran import FortranParser
reader = FortranStringReader(string, isfree, isstrict)
parser = FortranParser(reader)
parser.parse()
parser.analyze()
block = parser.block
while len(block.content) == 1:
block = block.content[0]
return block
def show_item_on_failure(func, _exception_depth=[0]):
"""
Decorator for analyze methods.
"""
def new_func(self):
try:
func(self)
except AnalyzeError as msg:
clsname = self.__class__.__name__
self.error("%s.analyze error: %s" % (clsname, msg))
traceback.print_exc()
except ParseError as msg:
self.error("parse error: %s" % (msg))
except Exception as msg:
_exception_depth[0] += 1
if _exception_depth[0] == 1:
self.error("exception triggered here: %s %s" % (Exception, msg))
raise
_exception_depth[0] = 0
return new_func
_classes_cache = {}
class meta_classes(type):
"""Meta class for ``classes``."""
__abstractmethods__ = False
def __getattr__(self, name):
# Expose created classes only as attributes to ``classes`` type.
cls = _classes_cache.get(name)
if cls is None:
raise AttributeError("instance does not have attribute %r" % (name))
return cls
[docs]
class classes(type, metaclass=meta_classes):
"""Make classes available as attributes of this class.
To add a class to the attributes list, one must use::
class Name(metaclass=classes):
in the definition of the class.
In addition, apply the following tasks:
* decorate analyze methods with show_item_on_failure
"""
def __new__(metacls, name, bases, dict):
if "analyze" in dict:
dict["analyze"] = show_item_on_failure(dict["analyze"])
cls = type.__new__(metacls, name, bases, dict)
_classes_cache[name] = cls
return cls