Skip to content

Commit

Permalink
ENH: add support for operator() in crackfortran.
Browse files Browse the repository at this point in the history
Some interface name may contains parenthesis when used
with operator, like:
 interface operator(==)
   module procedure my_type_equals
 end interface operator(==)

Make the end part properly detected, and store also
the operator ('==' in that case) in the name.

Also implement support to list the implemented by in
any interface declaration.
  • Loading branch information
dcaliste committed Sep 20, 2021
1 parent df0b1bd commit f29d1bd
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 4 deletions.
7 changes: 7 additions & 0 deletions doc/release/upcoming_changes/15006.new_feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
crackfortran has support for operator and assignement overloading
-----------------------------------------------------------------
``crackfortran`` parser now understands operator and assignement
defintions in a module. They are added in the ``body`` list of the
module which contains a new key ``implementedby`` listing the names
of the subroutines or functions implementing the operator or
assignment.
30 changes: 26 additions & 4 deletions numpy/f2py/crackfortran.py
Original file line number Diff line number Diff line change
Expand Up @@ -575,11 +575,16 @@ def readfortrancode(ffile, dowithline=show, istop=1):
groupends = (r'end|endprogram|endblockdata|endmodule|endpythonmodule|'
r'endinterface|endsubroutine|endfunction')
endpattern = re.compile(
beforethisafter % ('', groupends, groupends, r'[\w\s]*'), re.I), 'end'
endifs = r'(end\s*(if|do|where|select|while|forall|associate|block|critical|enum|team))|(module\s*procedure)'
beforethisafter % ('', groupends, groupends, r'.*'), re.I), 'end'
endifs = r'end\s*(if|do|where|select|while|forall|associate|block|' + \
r'critical|enum|team)'
endifpattern = re.compile(
beforethisafter % (r'[\w]*?', endifs, endifs, r'[\w\s]*'), re.I), 'endif'
#
moduleprocedures = r'module\s*procedure'
moduleprocedurepattern = re.compile(
beforethisafter % ('', moduleprocedures, moduleprocedures, r'.*'), re.I), \
'moduleprocedure'
implicitpattern = re.compile(
beforethisafter % ('', 'implicit', 'implicit', '.*'), re.I), 'implicit'
dimensionpattern = re.compile(beforethisafter % (
Expand Down Expand Up @@ -728,7 +733,8 @@ def crackline(line, reset=0):
callpattern, usepattern, containspattern,
entrypattern,
f2pyenhancementspattern,
multilinepattern
multilinepattern,
moduleprocedurepattern
]:
m = pat[0].match(line)
if m:
Expand Down Expand Up @@ -798,6 +804,8 @@ def crackline(line, reset=0):
expectbegin = 0
elif pat[1] == 'endif':
pass
elif pat[1] == 'moduleprocedure':
analyzeline(m, pat[1], line)
elif pat[1] == 'contains':
if ignorecontains:
return
Expand Down Expand Up @@ -886,6 +894,9 @@ def appenddecl(decl, decl2, force=1):
r'\s*(?P<this>(@\(@.*?@\)@|\*[\d*]+|\*\s*@\(@.*?@\)@|))(?P<after>.*)\Z', re.I)
nameargspattern = re.compile(
r'\s*(?P<name>\b[\w$]+\b)\s*(@\(@\s*(?P<args>[\w\s,]*)\s*@\)@|)\s*((result(\s*@\(@\s*(?P<result>\b[\w$]+\b)\s*@\)@|))|(bind\s*@\(@\s*(?P<bind>.*)\s*@\)@))*\s*\Z', re.I)
operatorpattern = re.compile(
r'\s*(?P<scheme>(operator|assignment))' + \
r'@\(@\s*(?P<name>[^)]+)\s*@\)@\s*\Z', re.I)
callnameargspattern = re.compile(
r'\s*(?P<name>\b[\w$]+\b)\s*@\(@\s*(?P<args>.*)\s*@\)@\s*\Z', re.I)
real16pattern = re.compile(
Expand All @@ -908,6 +919,10 @@ def _resolvenameargspattern(line):
m1 = nameargspattern.match(line)
if m1:
return m1.group('name'), m1.group('args'), m1.group('result'), m1.group('bind')
m1 = operatorpattern.match(line)
if m1:
name = m1.group('scheme') + '(' + m1.group('name') + ')'
return name, [], None, None
m1 = callnameargspattern.match(line)
if m1:
return m1.group('name'), m1.group('args'), None, None
Expand Down Expand Up @@ -1151,6 +1166,9 @@ def analyzeline(m, case, line):
continue
else:
k = rmbadname1(m1.group('name'))
if case in ['public', 'private'] and \
(k == 'operator' or k == 'assignment'):
k += m1.group('after')
if k not in edecl:
edecl[k] = {}
if case == 'dimension':
Expand Down Expand Up @@ -1193,6 +1211,9 @@ def analyzeline(m, case, line):
groupcache[groupcounter]['vars'] = edecl
if last_name is not None:
previous_context = ('variable', last_name, groupcounter)
elif case == 'moduleprocedure':
groupcache[groupcounter]['implementedby'] = \
[x.strip() for x in m.group('after').split(',')]
elif case == 'parameter':
edecl = groupcache[groupcounter]['vars']
ll = m.group('after').strip()[1:-1]
Expand Down Expand Up @@ -2105,7 +2126,8 @@ def analyzebody(block, args, tab=''):
else:
as_ = args
b = postcrack(b, as_, tab=tab + '\t')
if b['block'] in ['interface', 'abstract interface'] and not b['body']:
if b['block'] in ['interface', 'abstract interface'] and \
not b['body'] and not b['implementedby']:
if 'f2pyenhancements' not in b:
continue
if b['block'].replace(' ', '') == 'pythonmodule':
Expand Down
35 changes: 35 additions & 0 deletions numpy/f2py/tests/test_crackfortran.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,41 @@ def test_defaultPublic(self, tmp_path):
assert 'public' in mod['vars']['seta']['attrspec']


class TestModuleProcedure():
def test_defaultPrivate(self, tmp_path):
f_path = tmp_path / "mod.f90"
with f_path.open('w') as ff:
ff.write(textwrap.dedent("""\
module foo
interface operator(.item.)
module procedure item_char, item_dict, item_dbl
end interface
interface operator(==)
module procedure dicts_are_equal
end interface
interface assignment(=)
module procedure get_value, get_integer, get_real
end interface
contains
end module foo
"""))
mod = crackfortran.crackfortran([str(f_path)])
assert len(mod) == 1
mod = mod[0]
assert "body" in mod and len(mod["body"]) == 3
assert mod["body"][0]["name"] == "operator(.item.)"
assert "implementedby" in mod["body"][0]
assert mod["body"][0]["implementedby"] == \
["item_char", "item_dict", "item_dbl"]
assert mod["body"][1]["name"] == "operator(==)"
assert "implementedby" in mod["body"][1]
assert mod["body"][1]["implementedby"] == ["dicts_are_equal"]
assert mod["body"][2]["name"] == "assignment(=)"
assert "implementedby" in mod["body"][2]
assert mod["body"][2]["implementedby"] == \
["get_value", "get_integer", "get_real"]

class TestExternal(util.F2PyTest):
# issue gh-17859: add external attribute support
code = """
Expand Down

0 comments on commit f29d1bd

Please sign in to comment.