Skip to content

Commit 477d095

Browse files
committed
Update end statement identification to support multi-word end clauses, fixes #172
1 parent 22ceea4 commit 477d095

File tree

4 files changed

+66
-53
lines changed

4 files changed

+66
-53
lines changed

Diff for: fortls/objects.py

+7-7
Original file line numberDiff line numberDiff line change
@@ -1743,7 +1743,7 @@ def __init__(self, file_obj=None):
17431743
self.none_scope = None
17441744
self.inc_scope = None
17451745
self.current_scope = None
1746-
self.END_SCOPE_WORD = None
1746+
self.END_SCOPE_REGEX = None
17471747
self.enc_scope_name = None
17481748
self.last_obj = None
17491749
self.pending_doc = None
@@ -1761,7 +1761,7 @@ def get_enc_scope_name(self):
17611761
return None
17621762
return self.current_scope.FQSN
17631763

1764-
def add_scope(self, new_scope, END_SCOPE_WORD, exportable=True, req_container=False):
1764+
def add_scope(self, new_scope, END_SCOPE_REGEX, exportable=True, req_container=False):
17651765
self.scope_list.append(new_scope)
17661766
if new_scope.require_inherit():
17671767
self.inherit_objs.append(new_scope)
@@ -1779,10 +1779,10 @@ def add_scope(self, new_scope, END_SCOPE_WORD, exportable=True, req_container=Fa
17791779
else:
17801780
self.current_scope.add_child(new_scope)
17811781
self.scope_stack.append(self.current_scope)
1782-
if self.END_SCOPE_WORD is not None:
1783-
self.end_stack.append(self.END_SCOPE_WORD)
1782+
if self.END_SCOPE_REGEX is not None:
1783+
self.end_stack.append(self.END_SCOPE_REGEX)
17841784
self.current_scope = new_scope
1785-
self.END_SCOPE_WORD = END_SCOPE_WORD
1785+
self.END_SCOPE_REGEX = END_SCOPE_REGEX
17861786
self.enc_scope_name = self.get_enc_scope_name()
17871787
self.last_obj = new_scope
17881788
if self.pending_doc is not None:
@@ -1799,9 +1799,9 @@ def end_scope(self, line_number, check=True):
17991799
else:
18001800
self.current_scope = None
18011801
if len(self.end_stack) > 0:
1802-
self.END_SCOPE_WORD = self.end_stack.pop()
1802+
self.END_SCOPE_REGEX = self.end_stack.pop()
18031803
else:
1804-
self.END_SCOPE_WORD = None
1804+
self.END_SCOPE_REGEX = None
18051805
self.enc_scope_name = self.get_enc_scope_name()
18061806

18071807
def add_variable(self, new_var):

Diff for: fortls/parse_fortran.py

+43-40
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
fortran_subroutine, fortran_function, fortran_block, fortran_select, \
1010
fortran_type, fortran_enum, fortran_int, fortran_var, fortran_meth, \
1111
fortran_associate, fortran_do, fortran_where, fortran_if, \
12-
INTERFACE_TYPE_ID, SELECT_TYPE_ID, SUBMODULE_TYPE_ID
12+
INTERFACE_TYPE_ID, SELECT_TYPE_ID, SUBMODULE_TYPE_ID, DO_TYPE_ID
1313
PY3K = sys.version_info >= (3, 0)
1414
if not PY3K:
1515
import io
@@ -21,46 +21,46 @@
2121
IMPLICIT_REGEX = re.compile(r'[ ]*IMPLICIT[ ]+([a-z]*)', re.I)
2222
SUB_MOD_REGEX = re.compile(r'[ ]*(PURE|IMPURE|ELEMENTAL|RECURSIVE)+', re.I)
2323
SUB_REGEX = re.compile(r'[ ]*SUBROUTINE[ ]+([a-z0-9_]+)', re.I)
24-
END_SUB_WORD = r'SUBROUTINE'
24+
END_SUB_REGEX = re.compile(r'SUBROUTINE', re.I)
2525
FUN_REGEX = re.compile(r'[ ]*FUNCTION[ ]+([a-z0-9_]+)', re.I)
2626
RESULT_REGEX = re.compile(r'RESULT[ ]*\(([a-z0-9_]*)\)', re.I)
27-
END_FUN_WORD = r'FUNCTION'
27+
END_FUN_REGEX = re.compile(r'FUNCTION', re.I)
2828
MOD_REGEX = re.compile(r'[ ]*MODULE[ ]+([a-z0-9_]+)', re.I)
29-
END_MOD_WORD = r'MODULE'
29+
END_MOD_REGEX = re.compile(r'MODULE', re.I)
3030
SUBMOD_REGEX = re.compile(r'[ ]*SUBMODULE[ ]*\(', re.I)
31-
END_SMOD_WORD = r'SUBMODULE'
32-
END_PRO_WORD = r'PROCEDURE'
31+
END_SMOD_REGEX = re.compile(r'SUBMODULE', re.I)
32+
END_PRO_REGEX = re.compile(r'(MODULE)?[ ]*PROCEDURE', re.I)
3333
BLOCK_REGEX = re.compile(r'[ ]*([a-z_][a-z0-9_]*[ ]*:[ ]*)?BLOCK(?![a-z0-9_])', re.I)
34-
END_BLOCK_WORD = r'BLOCK'
34+
END_BLOCK_REGEX = re.compile(r'BLOCK', re.I)
3535
DO_REGEX = re.compile(r'[ ]*(?:[a-z_][a-z0-9_]*[ ]*:[ ]*)?DO([ ]+[0-9]*|$)', re.I)
36-
END_DO_WORD = r'DO'
36+
END_DO_REGEX = re.compile(r'DO', re.I)
3737
WHERE_REGEX = re.compile(r'[ ]*WHERE[ ]*\(', re.I)
38-
END_WHERE_WORD = r'WHERE'
38+
END_WHERE_REGEX = re.compile(r'WHERE', re.I)
3939
IF_REGEX = re.compile(r'[ ]*(?:[a-z_][a-z0-9_]*[ ]*:[ ]*)?IF[ ]*\(', re.I)
4040
THEN_REGEX = re.compile(r'\)[ ]*THEN$', re.I)
41-
END_IF_WORD = r'IF'
41+
END_IF_REGEX = re.compile(r'IF', re.I)
4242
ASSOCIATE_REGEX = re.compile(r'[ ]*ASSOCIATE[ ]*\(', re.I)
43-
END_ASSOCIATE_WORD = r'ASSOCIATE'
43+
END_ASSOCIATE_REGEX = re.compile(r'ASSOCIATE', re.I)
4444
END_FIXED_REGEX = re.compile(r'[ ]*([0-9]*)[ ]*CONTINUE', re.I)
4545
SELECT_REGEX = re.compile(r'[ ]*(?:[a-z_][a-z0-9_]*[ ]*:[ ]*)?SELECT[ ]*'
4646
r'(CASE|TYPE)[ ]*\(([a-z0-9_=> ]*)', re.I)
4747
SELECT_TYPE_REGEX = re.compile(r'[ ]*(TYPE|CLASS)[ ]+IS[ ]*\(([a-z0-9_ ]*)', re.I)
4848
SELECT_DEFAULT_REGEX = re.compile(r'[ ]*CLASS[ ]+DEFAULT', re.I)
49-
END_SELECT_WORD = r'SELECT'
49+
END_SELECT_REGEX = re.compile(r'SELECT', re.I)
5050
PROG_REGEX = re.compile(r'[ ]*PROGRAM[ ]+([a-z0-9_]+)', re.I)
51-
END_PROG_WORD = r'PROGRAM'
51+
END_PROG_REGEX = re.compile(r'PROGRAM', re.I)
5252
INT_REGEX = re.compile(r'[ ]*(ABSTRACT)?[ ]*INTERFACE[ ]*([a-z0-9_]*)', re.I)
53-
END_INT_WORD = r'INTERFACE'
53+
END_INT_REGEX = re.compile(r'INTERFACE', re.I)
5454
END_WORD_REGEX = re.compile(r'[ ]*END[ ]*(DO|WHERE|IF|BLOCK|ASSOCIATE|SELECT'
5555
r'|TYPE|ENUM|MODULE|SUBMODULE|PROGRAM|INTERFACE'
5656
r'|SUBROUTINE|FUNCTION|PROCEDURE)?([ ]+|$)', re.I)
5757
TYPE_DEF_REGEX = re.compile(r'[ ]*(TYPE)[, :]+', re.I)
5858
EXTENDS_REGEX = re.compile(r'EXTENDS[ ]*\(([a-z0-9_]*)\)', re.I)
5959
GENERIC_PRO_REGEX = re.compile(r'[ ]*(GENERIC)[ ]*::[ ]*[a-z]', re.I)
6060
GEN_ASSIGN_REGEX = re.compile(r'(ASSIGNMENT|OPERATOR)\(', re.I)
61-
END_TYPED_WORD = r'TYPE'
61+
END_TYPED_REGEX = re.compile(r'TYPE', re.I)
6262
ENUM_DEF_REGEX = re.compile(r'[ ]*ENUM[, ]+', re.I)
63-
END_ENUMD_WORD = r'ENUM'
63+
END_ENUMD_REGEX = re.compile(r'ENUM', re.I)
6464
NAT_VAR_REGEX = re.compile(r'[ ]*(INTEGER|REAL|DOUBLE[ ]*PRECISION|COMPLEX'
6565
r'|DOUBLE[ ]*COMPLEX|CHARACTER|LOGICAL|PROCEDURE'
6666
r'|EXTERNAL|CLASS|TYPE)', re.I)
@@ -1387,18 +1387,21 @@ def process_file(file_obj, close_open_scopes, debug=False, pp_defs={}, include_d
13871387
line_no_comment = line
13881388
line_post_comment = None
13891389
# Test for scope end
1390-
if file_ast.END_SCOPE_WORD is not None:
1390+
if file_ast.END_SCOPE_REGEX is not None:
13911391
match = END_WORD_REGEX.match(line_no_comment)
13921392
# Handle end statement
13931393
if match is not None:
1394-
end_scope_word = match.group(1)
1395-
if (end_scope_word is not None) or (match.group(2) == ""):
1396-
if end_scope_word is not None:
1397-
end_scope_word = end_scope_word.strip().upper()
1398-
if ((end_scope_word != file_ast.END_SCOPE_WORD)
1399-
and (file_ast.current_scope.req_named_end() or (end_scope_word is not None))
1394+
end_scope_word = None
1395+
if match.group(1) is None:
1396+
end_scope_word = ""
1397+
if (file_ast.current_scope.req_named_end()
14001398
and (file_ast.current_scope is not file_ast.none_scope)):
14011399
file_ast.end_errors.append([line_number, file_ast.current_scope.sline])
1400+
else:
1401+
scope_match = file_ast.END_SCOPE_REGEX.match(line_no_comment[match.start(1):])
1402+
if (scope_match is not None):
1403+
end_scope_word = scope_match.group(0)
1404+
if end_scope_word is not None:
14021405
if (file_ast.current_scope.get_type() == SELECT_TYPE_ID) \
14031406
and (file_ast.current_scope.is_type_region()):
14041407
file_ast.end_scope(line_number)
@@ -1407,7 +1410,7 @@ def process_file(file_obj, close_open_scopes, debug=False, pp_defs={}, include_d
14071410
print('{1} !!! END "{2}" scope({0})'.format(line_number, line.strip(), end_scope_word))
14081411
continue
14091412
# Look for old-style end of DO loops with line labels
1410-
if (file_ast.END_SCOPE_WORD == 'DO') and (line_label is not None):
1413+
if (file_ast.current_scope.get_type() == DO_TYPE_ID) and (line_label is not None):
14111414
did_close = False
14121415
while (len(block_id_stack) > 0) and (line_label == block_id_stack[-1]):
14131416
file_ast.end_scope(line_number)
@@ -1531,32 +1534,32 @@ def process_file(file_obj, close_open_scopes, debug=False, pp_defs={}, include_d
15311534
print('{1} !!! VARIABLE statement({0})'.format(line_number, line.strip()))
15321535
elif obj_type == 'mod':
15331536
new_mod = fortran_module(file_ast, line_number, obj_info)
1534-
file_ast.add_scope(new_mod, END_MOD_WORD)
1537+
file_ast.add_scope(new_mod, END_MOD_REGEX)
15351538
if(debug):
15361539
print('{1} !!! MODULE statement({0})'.format(line_number, line.strip()))
15371540
elif obj_type == 'smod':
15381541
new_smod = fortran_submodule(file_ast, line_number, obj_info.name, ancestor_name=obj_info.parent)
1539-
file_ast.add_scope(new_smod, END_SMOD_WORD)
1542+
file_ast.add_scope(new_smod, END_SMOD_REGEX)
15401543
if(debug):
15411544
print('{1} !!! SUBMODULE statement({0})'.format(line_number, line.strip()))
15421545
elif obj_type == 'prog':
15431546
new_prog = fortran_program(file_ast, line_number, obj_info)
1544-
file_ast.add_scope(new_prog, END_PROG_WORD)
1547+
file_ast.add_scope(new_prog, END_PROG_REGEX)
15451548
if(debug):
15461549
print('{1} !!! PROGRAM statement({0})'.format(line_number, line.strip()))
15471550
elif obj_type == 'sub':
15481551
keywords, _ = map_keywords(obj_info.keywords)
15491552
new_sub = fortran_subroutine(file_ast, line_number, obj_info.name, args=obj_info.args,
15501553
mod_flag=obj_info.mod_flag, keywords=keywords)
1551-
file_ast.add_scope(new_sub, END_SUB_WORD)
1554+
file_ast.add_scope(new_sub, END_SUB_REGEX)
15521555
if(debug):
15531556
print('{1} !!! SUBROUTINE statement({0})'.format(line_number, line.strip()))
15541557
elif obj_type == 'fun':
15551558
keywords, _ = map_keywords(obj_info.keywords)
15561559
new_fun = fortran_function(file_ast, line_number, obj_info.name, args=obj_info.args,
15571560
mod_flag=obj_info.mod_flag, keywords=keywords,
15581561
return_type=obj_info.return_type, result_var=obj_info.return_var)
1559-
file_ast.add_scope(new_fun, END_FUN_WORD)
1562+
file_ast.add_scope(new_fun, END_FUN_REGEX)
15601563
if obj_info.return_type is not None:
15611564
keywords, keyword_info = map_keywords(obj_info.return_type[1])
15621565
new_obj = fortran_var(file_ast, line_number, obj_info.name,
@@ -1570,7 +1573,7 @@ def process_file(file_obj, close_open_scopes, debug=False, pp_defs={}, include_d
15701573
block_counter += 1
15711574
name = '#BLOCK{0}'.format(block_counter)
15721575
new_block = fortran_block(file_ast, line_number, name)
1573-
file_ast.add_scope(new_block, END_BLOCK_WORD, req_container=True)
1576+
file_ast.add_scope(new_block, END_BLOCK_REGEX, req_container=True)
15741577
if(debug):
15751578
print('{1} !!! BLOCK statement({0})'.format(line_number, line.strip()))
15761579
elif obj_type == 'do':
@@ -1579,7 +1582,7 @@ def process_file(file_obj, close_open_scopes, debug=False, pp_defs={}, include_d
15791582
if obj_info != '':
15801583
block_id_stack.append(obj_info)
15811584
new_do = fortran_do(file_ast, line_number, name)
1582-
file_ast.add_scope(new_do, END_DO_WORD, req_container=True)
1585+
file_ast.add_scope(new_do, END_DO_REGEX, req_container=True)
15831586
if(debug):
15841587
print('{1} !!! DO statement({0})'.format(line_number, line.strip()))
15851588
elif obj_type == 'where':
@@ -1588,14 +1591,14 @@ def process_file(file_obj, close_open_scopes, debug=False, pp_defs={}, include_d
15881591
do_counter += 1
15891592
name = '#WHERE{0}'.format(do_counter)
15901593
new_do = fortran_where(file_ast, line_number, name)
1591-
file_ast.add_scope(new_do, END_WHERE_WORD, req_container=True)
1594+
file_ast.add_scope(new_do, END_WHERE_REGEX, req_container=True)
15921595
if(debug):
15931596
print('{1} !!! WHERE statement({0})'.format(line_number, line.strip()))
15941597
elif obj_type == 'assoc':
15951598
block_counter += 1
15961599
name = '#ASSOC{0}'.format(block_counter)
15971600
new_assoc = fortran_associate(file_ast, line_number, name)
1598-
file_ast.add_scope(new_assoc, END_ASSOCIATE_WORD, req_container=True)
1601+
file_ast.add_scope(new_assoc, END_ASSOCIATE_REGEX, req_container=True)
15991602
for bound_var in obj_info:
16001603
binding_split = bound_var.split('=>')
16011604
if len(binding_split) == 2:
@@ -1610,14 +1613,14 @@ def process_file(file_obj, close_open_scopes, debug=False, pp_defs={}, include_d
16101613
if_counter += 1
16111614
name = '#IF{0}'.format(if_counter)
16121615
new_if = fortran_if(file_ast, line_number, name)
1613-
file_ast.add_scope(new_if, END_IF_WORD, req_container=True)
1616+
file_ast.add_scope(new_if, END_IF_REGEX, req_container=True)
16141617
if(debug):
16151618
print('{1} !!! IF statement({0})'.format(line_number, line.strip()))
16161619
elif obj_type == 'select':
16171620
select_counter += 1
16181621
name = '#SELECT{0}'.format(select_counter)
16191622
new_select = fortran_select(file_ast, line_number, name, obj_info)
1620-
file_ast.add_scope(new_select, END_SELECT_WORD, req_container=True)
1623+
file_ast.add_scope(new_select, END_SELECT_REGEX, req_container=True)
16211624
new_var = new_select.create_binding_variable(
16221625
file_ast, line_number, '{0}({1})'.format(obj_info.desc, obj_info.binding), obj_info.type
16231626
)
@@ -1630,14 +1633,14 @@ def process_file(file_obj, close_open_scopes, debug=False, pp_defs={}, include_d
16301633
new_type = fortran_type(file_ast, line_number, obj_info.name, keywords)
16311634
if obj_info.parent is not None:
16321635
new_type.set_inherit(obj_info.parent)
1633-
file_ast.add_scope(new_type, END_TYPED_WORD, req_container=True)
1636+
file_ast.add_scope(new_type, END_TYPED_REGEX, req_container=True)
16341637
if(debug):
16351638
print('{1} !!! TYPE statement({0})'.format(line_number, line.strip()))
16361639
elif obj_type == 'enum':
16371640
block_counter += 1
16381641
name = '#ENUM{0}'.format(block_counter)
16391642
new_enum = fortran_enum(file_ast, line_number, name)
1640-
file_ast.add_scope(new_enum, END_ENUMD_WORD, req_container=True)
1643+
file_ast.add_scope(new_enum, END_ENUMD_REGEX, req_container=True)
16411644
if(debug):
16421645
print('{1} !!! ENUM statement({0})'.format(line_number, line.strip()))
16431646
elif obj_type == 'int':
@@ -1646,12 +1649,12 @@ def process_file(file_obj, close_open_scopes, debug=False, pp_defs={}, include_d
16461649
int_counter += 1
16471650
name = '#GEN_INT{0}'.format(int_counter)
16481651
new_int = fortran_int(file_ast, line_number, name, abstract=obj_info.abstract)
1649-
file_ast.add_scope(new_int, END_INT_WORD, req_container=True)
1652+
file_ast.add_scope(new_int, END_INT_REGEX, req_container=True)
16501653
if(debug):
16511654
print('{1} !!! INTERFACE statement({0})'.format(line_number, line.strip()))
16521655
elif obj_type == 'gen':
16531656
new_int = fortran_int(file_ast, line_number, obj_info.bound_name, abstract=False)
1654-
file_ast.add_scope(new_int, END_INT_WORD, req_container=True)
1657+
file_ast.add_scope(new_int, END_INT_REGEX, req_container=True)
16551658
for pro_link in obj_info.pro_links:
16561659
file_ast.add_int_member(pro_link)
16571660
file_ast.end_scope(line_number)
@@ -1666,7 +1669,7 @@ def process_file(file_obj, close_open_scopes, debug=False, pp_defs={}, include_d
16661669
print('{1} !!! INTERFACE-PRO statement({0})'.format(line_number, line.strip()))
16671670
elif file_ast.current_scope.get_type() == SUBMODULE_TYPE_ID:
16681671
new_impl = fortran_scope(file_ast, line_number, obj_info[0])
1669-
file_ast.add_scope(new_impl, END_PRO_WORD)
1672+
file_ast.add_scope(new_impl, END_PRO_REGEX)
16701673
if(debug):
16711674
print('{1} !!! PROCEDURE-IMPL statement({0})'.format(line_number, line.strip()))
16721675
elif obj_type == 'use':

Diff for: test/test_server.py

+5-5
Original file line numberDiff line numberDiff line change
@@ -233,11 +233,11 @@ def comp_request(file_path, line, char):
233233
string += comp_request(file_path, 21, 42)
234234
string += comp_request(file_path, 23, 26)
235235
file_path = os.path.join(test_dir, "subdir", "test_submod.F90")
236-
string += comp_request(file_path, 25, 12)
237-
string += comp_request(file_path, 26, 8)
238-
string += comp_request(file_path, 26, 23)
239236
string += comp_request(file_path, 30, 12)
240-
string += comp_request(file_path, 31, 46)
237+
string += comp_request(file_path, 31, 8)
238+
string += comp_request(file_path, 31, 23)
239+
string += comp_request(file_path, 35, 12)
240+
string += comp_request(file_path, 36, 48)
241241
file_path = os.path.join(test_dir, "test_inc.f90")
242242
string += comp_request(file_path, 10, 2)
243243
file_path = os.path.join(test_dir, "subdir", "test_inc2.f90")
@@ -380,8 +380,8 @@ def def_request(file_path, line, char):
380380
string += def_request(file_path, 21, 42)
381381
string += def_request(file_path, 23, 26)
382382
file_path = os.path.join(test_dir, "subdir", "test_submod.F90")
383-
string += def_request(file_path, 25, 12)
384383
string += def_request(file_path, 30, 12)
384+
string += def_request(file_path, 35, 12)
385385
file_path = os.path.join(test_dir, "test_inc.f90")
386386
string += def_request(file_path, 10, 2)
387387
file_path = os.path.join(test_dir, "subdir", "test_inc2.f90")

Diff for: test/test_source/subdir/test_submod.F90

+11-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ end function point_dist
1212
module logical function is_point_equal_a(a, b)
1313
type(point), intent(in) :: a, b
1414
end function is_point_equal_a
15+
16+
module subroutine is_point_equal_sub(a, b, test)
17+
type(point), intent(in) :: a, b
18+
logical, intent(out) :: test
19+
end subroutine is_point_equal_sub
1520
end interface
1621
contains
1722
logical function is_point_equal(a, b)
@@ -29,6 +34,11 @@ end function point_dist
2934

3035
module procedure is_point_equal_a
3136
type(point) :: c
32-
is_point_equal = merge(.true., .false., a%x == b%x .and. a%y == b%y)
37+
is_point_equal_a = merge(.true., .false., a%x == b%x .and. a%y == b%y)
3338
end procedure is_point_equal_a
39+
40+
module procedure is_point_equal_sub
41+
type(point) :: c
42+
test = is_point_equal(a,b)
43+
end module procedure is_point_equal_sub
3444
end submodule points_a

0 commit comments

Comments
 (0)