Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ENH: add support for operator() in crackfortran. #15006

Merged
merged 1 commit into from
Jan 4, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 assignment 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 @@ -574,11 +574,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 @@ -727,7 +732,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 @@ -797,6 +803,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
49 changes: 49 additions & 0 deletions numpy/f2py/tests/src/crackfortran/operators.f90
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
module foo
type bar
character(len = 32) :: item
end type bar
interface operator(.item.)
module procedure item_int, item_real
end interface operator(.item.)
interface operator(==)
module procedure items_are_equal
end interface operator(==)
interface assignment(=)
module procedure get_int, get_real
end interface assignment(=)
contains
function item_int(val) result(elem)
integer, intent(in) :: val
type(bar) :: elem

write(elem%item, "(I32)") val
end function item_int

function item_real(val) result(elem)
real, intent(in) :: val
type(bar) :: elem

write(elem%item, "(1PE32.12)") val
end function item_real

function items_are_equal(val1, val2) result(equal)
type(bar), intent(in) :: val1, val2
logical :: equal

equal = (val1%item == val2%item)
end function items_are_equal

subroutine get_real(rval, item)
real, intent(out) :: rval
type(bar), intent(in) :: item

read(item%item, *) rval
end subroutine get_real

subroutine get_int(rval, item)
integer, intent(out) :: rval
type(bar), intent(in) :: item

read(item%item, *) rval
end subroutine get_int
end module foo
20 changes: 20 additions & 0 deletions numpy/f2py/tests/test_crackfortran.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,26 @@ def test_defaultPublic(self, tmp_path):
assert "public" in mod["vars"]["seta"]["attrspec"]


class TestModuleProcedure():
def test_moduleOperators(self, tmp_path):
fpath = util.getpath("tests", "src", "crackfortran", "operators.f90")
mod = crackfortran.crackfortran([str(fpath)])
assert len(mod) == 1
mod = mod[0]
assert "body" in mod and len(mod["body"]) == 9
assert mod["body"][1]["name"] == "operator(.item.)"
assert "implementedby" in mod["body"][1]
assert mod["body"][1]["implementedby"] == \
["item_int", "item_real"]
assert mod["body"][2]["name"] == "operator(==)"
assert "implementedby" in mod["body"][2]
assert mod["body"][2]["implementedby"] == ["items_are_equal"]
assert mod["body"][3]["name"] == "assignment(=)"
assert "implementedby" in mod["body"][3]
assert mod["body"][3]["implementedby"] == \
["get_int", "get_real"]


class TestExternal(util.F2PyTest):
# issue gh-17859: add external attribute support
sources = [util.getpath("tests", "src", "crackfortran", "gh17859.f")]
Expand Down