Skip to content

Commit 609d0ed

Browse files
committed
ENH: add support for operator() in crackfortran.
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.
1 parent d64df45 commit 609d0ed

File tree

4 files changed

+102
-4
lines changed

4 files changed

+102
-4
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
crackfortran has support for operator and assignment overloading
2+
----------------------------------------------------------------
3+
``crackfortran`` parser now understands operator and assignement
4+
defintions in a module. They are added in the ``body`` list of the
5+
module which contains a new key ``implementedby`` listing the names
6+
of the subroutines or functions implementing the operator or
7+
assignment.

numpy/f2py/crackfortran.py

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -574,11 +574,16 @@ def readfortrancode(ffile, dowithline=show, istop=1):
574574
groupends = (r'end|endprogram|endblockdata|endmodule|endpythonmodule|'
575575
r'endinterface|endsubroutine|endfunction')
576576
endpattern = re.compile(
577-
beforethisafter % ('', groupends, groupends, r'[\w\s]*'), re.I), 'end'
578-
endifs = r'(end\s*(if|do|where|select|while|forall|associate|block|critical|enum|team))|(module\s*procedure)'
577+
beforethisafter % ('', groupends, groupends, r'.*'), re.I), 'end'
578+
endifs = r'end\s*(if|do|where|select|while|forall|associate|block|' + \
579+
r'critical|enum|team)'
579580
endifpattern = re.compile(
580581
beforethisafter % (r'[\w]*?', endifs, endifs, r'[\w\s]*'), re.I), 'endif'
581582
#
583+
moduleprocedures = r'module\s*procedure'
584+
moduleprocedurepattern = re.compile(
585+
beforethisafter % ('', moduleprocedures, moduleprocedures, r'.*'), re.I), \
586+
'moduleprocedure'
582587
implicitpattern = re.compile(
583588
beforethisafter % ('', 'implicit', 'implicit', '.*'), re.I), 'implicit'
584589
dimensionpattern = re.compile(beforethisafter % (
@@ -727,7 +732,8 @@ def crackline(line, reset=0):
727732
callpattern, usepattern, containspattern,
728733
entrypattern,
729734
f2pyenhancementspattern,
730-
multilinepattern
735+
multilinepattern,
736+
moduleprocedurepattern
731737
]:
732738
m = pat[0].match(line)
733739
if m:
@@ -797,6 +803,8 @@ def crackline(line, reset=0):
797803
expectbegin = 0
798804
elif pat[1] == 'endif':
799805
pass
806+
elif pat[1] == 'moduleprocedure':
807+
analyzeline(m, pat[1], line)
800808
elif pat[1] == 'contains':
801809
if ignorecontains:
802810
return
@@ -886,6 +894,9 @@ def appenddecl(decl, decl2, force=1):
886894
r'\s*(?P<this>(@\(@.*?@\)@|\*[\d*]+|\*\s*@\(@.*?@\)@|))(?P<after>.*)\Z', re.I)
887895
nameargspattern = re.compile(
888896
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)
897+
operatorpattern = re.compile(
898+
r'\s*(?P<scheme>(operator|assignment))'
899+
r'@\(@\s*(?P<name>[^)]+)\s*@\)@\s*\Z', re.I)
889900
callnameargspattern = re.compile(
890901
r'\s*(?P<name>\b[\w$]+\b)\s*@\(@\s*(?P<args>.*)\s*@\)@\s*\Z', re.I)
891902
real16pattern = re.compile(
@@ -908,6 +919,10 @@ def _resolvenameargspattern(line):
908919
m1 = nameargspattern.match(line)
909920
if m1:
910921
return m1.group('name'), m1.group('args'), m1.group('result'), m1.group('bind')
922+
m1 = operatorpattern.match(line)
923+
if m1:
924+
name = m1.group('scheme') + '(' + m1.group('name') + ')'
925+
return name, [], None, None
911926
m1 = callnameargspattern.match(line)
912927
if m1:
913928
return m1.group('name'), m1.group('args'), None, None
@@ -1151,6 +1166,9 @@ def analyzeline(m, case, line):
11511166
continue
11521167
else:
11531168
k = rmbadname1(m1.group('name'))
1169+
if case in ['public', 'private'] and \
1170+
(k == 'operator' or k == 'assignment'):
1171+
k += m1.group('after')
11541172
if k not in edecl:
11551173
edecl[k] = {}
11561174
if case == 'dimension':
@@ -1193,6 +1211,9 @@ def analyzeline(m, case, line):
11931211
groupcache[groupcounter]['vars'] = edecl
11941212
if last_name is not None:
11951213
previous_context = ('variable', last_name, groupcounter)
1214+
elif case == 'moduleprocedure':
1215+
groupcache[groupcounter]['implementedby'] = \
1216+
[x.strip() for x in m.group('after').split(',')]
11961217
elif case == 'parameter':
11971218
edecl = groupcache[groupcounter]['vars']
11981219
ll = m.group('after').strip()[1:-1]
@@ -2105,7 +2126,8 @@ def analyzebody(block, args, tab=''):
21052126
else:
21062127
as_ = args
21072128
b = postcrack(b, as_, tab=tab + '\t')
2108-
if b['block'] in ['interface', 'abstract interface'] and not b['body']:
2129+
if b['block'] in ['interface', 'abstract interface'] and \
2130+
not b['body'] and not b['implementedby']:
21092131
if 'f2pyenhancements' not in b:
21102132
continue
21112133
if b['block'].replace(' ', '') == 'pythonmodule':
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
module foo
2+
type bar
3+
character(len = 32) :: item
4+
end type bar
5+
interface operator(.item.)
6+
module procedure item_int, item_real
7+
end interface operator(.item.)
8+
interface operator(==)
9+
module procedure items_are_equal
10+
end interface operator(==)
11+
interface assignment(=)
12+
module procedure get_int, get_real
13+
end interface assignment(=)
14+
contains
15+
function item_int(val) result(elem)
16+
integer, intent(in) :: val
17+
type(bar) :: elem
18+
19+
write(elem%item, "(I32)") val
20+
end function item_int
21+
22+
function item_real(val) result(elem)
23+
real, intent(in) :: val
24+
type(bar) :: elem
25+
26+
write(elem%item, "(1PE32.12)") val
27+
end function item_real
28+
29+
function items_are_equal(val1, val2) result(equal)
30+
type(bar), intent(in) :: val1, val2
31+
logical :: equal
32+
33+
equal = (val1%item == val2%item)
34+
end function items_are_equal
35+
36+
subroutine get_real(rval, item)
37+
real, intent(out) :: rval
38+
type(bar), intent(in) :: item
39+
40+
read(item%item, *) rval
41+
end subroutine get_real
42+
43+
subroutine get_int(rval, item)
44+
integer, intent(out) :: rval
45+
type(bar), intent(in) :: item
46+
47+
read(item%item, *) rval
48+
end subroutine get_int
49+
end module foo

numpy/f2py/tests/test_crackfortran.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,26 @@ def test_defaultPublic(self, tmp_path):
4545
assert "public" in mod["vars"]["seta"]["attrspec"]
4646

4747

48+
class TestModuleProcedure():
49+
def test_moduleOperators(self, tmp_path):
50+
fpath = util.getpath("tests", "src", "crackfortran", "operators.f90")
51+
mod = crackfortran.crackfortran([str(fpath)])
52+
assert len(mod) == 1
53+
mod = mod[0]
54+
assert "body" in mod and len(mod["body"]) == 9
55+
assert mod["body"][1]["name"] == "operator(.item.)"
56+
assert "implementedby" in mod["body"][1]
57+
assert mod["body"][1]["implementedby"] == \
58+
["item_int", "item_real"]
59+
assert mod["body"][2]["name"] == "operator(==)"
60+
assert "implementedby" in mod["body"][2]
61+
assert mod["body"][2]["implementedby"] == ["items_are_equal"]
62+
assert mod["body"][3]["name"] == "assignment(=)"
63+
assert "implementedby" in mod["body"][3]
64+
assert mod["body"][3]["implementedby"] == \
65+
["get_int", "get_real"]
66+
67+
4868
class TestExternal(util.F2PyTest):
4969
# issue gh-17859: add external attribute support
5070
sources = [util.getpath("tests", "src", "crackfortran", "gh17859.f")]

0 commit comments

Comments
 (0)