Skip to content

Commit bfc4f04

Browse files
authored
Merge pull request #331 from AljenU/fix_else_and_elif
Fix preprocessor else and elif
2 parents 27b9d49 + dc3449e commit bfc4f04

File tree

8 files changed

+202
-7
lines changed

8 files changed

+202
-7
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929

3030
### Fixed
3131

32+
- Fixed preprocessor bug with `if` and `elif` conditionals
33+
([#322](https://github.com/fortran-lang/fortls/issues/322))
3234
- Fixed bug where type fields or methods were not detected if spaces were
3335
used around `%`
3436
([#286](https://github.com/fortran-lang/fortls/issues/286))

fortls/parse_fortran.py

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2090,6 +2090,7 @@ def replace_vars(line: str):
20902090
pp_skips = []
20912091
pp_defines = []
20922092
pp_stack = []
2093+
pp_stack_group = []
20932094
defs_tmp = pp_defs.copy()
20942095
def_regexes = {}
20952096
output_file = []
@@ -2135,25 +2136,49 @@ def replace_vars(line: str):
21352136
# Closing/middle conditional statements
21362137
inc_start = False
21372138
exc_start = False
2139+
exc_continue = False
21382140
if match.group(1) == "elif":
2139-
if pp_stack[-1][0] < 0:
2140-
pp_stack[-1][0] = i + 1
2141-
exc_start = True
2141+
if (not pp_stack_group) or (pp_stack_group[-1][0] != len(pp_stack)):
2142+
# First elif statement for this elif group
2143+
if pp_stack[-1][0] < 0:
2144+
pp_stack_group.append([len(pp_stack), True])
2145+
else:
2146+
pp_stack_group.append([len(pp_stack), False])
2147+
if pp_stack_group[-1][1]:
2148+
# An earlier if or elif in this group has been true
2149+
exc_continue = True
2150+
if pp_stack[-1][0] < 0:
2151+
pp_stack[-1][0] = i + 1
2152+
elif eval_pp_if(line[match.end(1) :], defs_tmp):
2153+
pp_stack[-1][1] = i + 1
2154+
pp_skips.append(pp_stack.pop())
2155+
pp_stack_group[-1][1] = True
2156+
pp_stack.append([-1, -1])
2157+
inc_start = True
21422158
else:
2143-
if eval_pp_if(line[match.end(1) :], defs_tmp):
2144-
pp_stack[-1][1] = i - 1
2145-
pp_stack.append([-1, -1])
2146-
inc_start = True
2159+
exc_start = True
21472160
elif match.group(1) == "else":
21482161
if pp_stack[-1][0] < 0:
21492162
pp_stack[-1][0] = i + 1
21502163
exc_start = True
2164+
elif (
2165+
pp_stack_group
2166+
and (pp_stack_group[-1][0] == len(pp_stack))
2167+
and (pp_stack_group[-1][1])
2168+
):
2169+
# An earlier if or elif in this group has been true
2170+
exc_continue = True
21512171
else:
21522172
pp_stack[-1][1] = i + 1
2173+
pp_skips.append(pp_stack.pop())
2174+
pp_stack.append([-1, -1])
21532175
inc_start = True
21542176
elif match.group(1) == "endif":
2177+
if pp_stack_group and (pp_stack_group[-1][0] == len(pp_stack)):
2178+
pp_stack_group.pop()
21552179
if pp_stack[-1][0] < 0:
21562180
pp_stack.pop()
2181+
log.debug(f"{line.strip()} !!! Conditional TRUE/END({i + 1})")
21572182
continue
21582183
if pp_stack[-1][1] < 0:
21592184
pp_stack[-1][1] = i + 1
@@ -2164,6 +2189,8 @@ def replace_vars(line: str):
21642189
log.debug(f"{line.strip()} !!! Conditional TRUE({i + 1})")
21652190
elif exc_start:
21662191
log.debug(f"{line.strip()} !!! Conditional FALSE({i + 1})")
2192+
elif exc_continue:
2193+
log.debug(f"{line.strip()} !!! Conditional EXCLUDED({i + 1})")
21672194
continue
21682195
# Handle variable/macro definitions files
21692196
match = FRegex.PP_DEF.match(line)

test/test_preproc.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,18 @@ def check_return(result_array, checks):
3030
string += hover_req(file_path, 10, 15) # defined without ()
3131
file_path = root_dir / "preproc_keywords.F90"
3232
string += hover_req(file_path, 6, 2) # ignores PP across Fortran line continuations
33+
file_path = root_dir / "preproc_else.F90"
34+
string += hover_req(file_path, 8, 12)
35+
string += hover_req(file_path, 18, 12)
36+
file_path = root_dir / "preproc_elif.F90"
37+
string += hover_req(file_path, 22, 15)
38+
string += hover_req(file_path, 24, 10)
39+
file_path = root_dir / "preproc_elif_elif_skip.F90"
40+
string += hover_req(file_path, 30, 23)
41+
file_path = root_dir / "preproc_if_elif_else.F90"
42+
string += hover_req(file_path, 30, 23)
43+
file_path = root_dir / "preproc_if_elif_skip.F90"
44+
string += hover_req(file_path, 30, 23)
3345
config = str(root_dir / ".pp_conf.json")
3446
errcode, results = run_request(string, ["--config", config])
3547
assert errcode == 0
@@ -49,6 +61,13 @@ def check_return(result_array, checks):
4961
),
5062
"```fortran90\n#define SUCCESS .true.\n```",
5163
"```fortran90\nREAL, CONTIGUOUS, POINTER, DIMENSION(:) :: var1\n```",
64+
"```fortran90\nINTEGER :: var0\n```",
65+
"```fortran90\nREAL :: var1\n```",
66+
"```fortran90\nINTEGER :: var2\n```",
67+
"```fortran90\nINTEGER, INTENT(INOUT) :: var\n```",
68+
"```fortran90\nINTEGER, PARAMETER :: res = 0+1+0+0\n```",
69+
"```fortran90\nINTEGER, PARAMETER :: res = 0+0+0+1\n```",
70+
"```fortran90\nINTEGER, PARAMETER :: res = 1+0+0+0\n```",
5271
)
5372
assert len(ref_results) == len(results) - 1
5473
check_return(results[1:], ref_results)

test/test_source/pp/preproc_elif.F90

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
subroutine preprocessor_elif(var, var3, var4, var5, var6)
2+
3+
! This file, as used in test_preproc, checks that
4+
! 1. the steps after the preprocessor parsing has fully finished, are only
5+
! using content from the parts within the preprocessor if-elif-else that
6+
! should be used. To do this, it has some regular fortran code within the
7+
! #if and #elif.
8+
! 2. the #endif correctly concludes the if-elif, so any new #define statements
9+
! that come after the #endif, are picked up during the preprocessor parsing.
10+
11+
#if 0
12+
integer, intent(in) :: var
13+
#elif 1
14+
integer, intent(inout) :: var
15+
var = 3
16+
#else
17+
integer, intent(out) :: var
18+
var = 5
19+
#endif
20+
21+
#define OTHERTYPE integer
22+
23+
OTHERTYPE :: var2
24+
25+
PRINT*, var
26+
27+
endsubroutine preprocessor_elif
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
subroutine preprocessor_elif_elif_skip()
2+
3+
! This file, as used in test_preproc, and together with the two similar files,
4+
! tests that when there is an if-elif-elif-else, only the first branch that
5+
! evaluates to true is used, and the others ignored. Also when multiple
6+
! conditions evaluate to true.
7+
8+
#if 0
9+
#define PART1 0
10+
#elif 1
11+
#define PART2 1
12+
#elif 1
13+
#define PART3 0
14+
#else
15+
#define PART4 0
16+
#endif
17+
18+
#ifndef PART1
19+
#define PART1 0
20+
#endif
21+
#ifndef PART2
22+
#define PART2 0
23+
#endif
24+
#ifndef PART3
25+
#define PART3 0
26+
#endif
27+
#ifndef PART4
28+
#define PART4 0
29+
#endif
30+
31+
integer, parameter :: res = PART1+PART2+PART3+PART4
32+
33+
end subroutine preprocessor_elif_elif_skip

test/test_source/pp/preproc_else.F90

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
subroutine preprocessor_else(var)
2+
3+
#if 0
4+
#define MYTYPE logical
5+
#else
6+
#define MYTYPE integer
7+
#endif
8+
9+
MYTYPE :: var0
10+
11+
#undef MYTYPE
12+
13+
#if 1
14+
#define MYTYPE real
15+
#else
16+
#define MYTYPE character
17+
#endif
18+
19+
MYTYPE :: var1
20+
21+
endsubroutine preprocessor_else
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
subroutine preprocessor_if_elif_else()
2+
3+
! This file, as used in test_preproc, and together with the two similar files,
4+
! tests that when there is an if-elif-elif-else, only the first branch that
5+
! evaluates to true is used, and the others ignored. Also when multiple
6+
! conditions evaluate to true.
7+
8+
#if 0
9+
#define PART1 0
10+
#elif 0
11+
#define PART2 0
12+
#elif 0
13+
#define PART3 0
14+
#else
15+
#define PART4 1
16+
#endif
17+
18+
#ifndef PART1
19+
#define PART1 0
20+
#endif
21+
#ifndef PART2
22+
#define PART2 0
23+
#endif
24+
#ifndef PART3
25+
#define PART3 0
26+
#endif
27+
#ifndef PART4
28+
#define PART4 0
29+
#endif
30+
31+
integer, parameter :: res = PART1+PART2+PART3+PART4
32+
33+
endsubroutine preprocessor_if_elif_else
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
subroutine preprocessor_if_elif_skip()
2+
3+
! This file, as used in test_preproc, and together with the two similar files,
4+
! tests that when there is an if-elif-elif-else, only the first branch that
5+
! evaluates to true is used, and the others ignored. Also when multiple
6+
! conditions evaluate to true.
7+
8+
#if 1
9+
#define PART1 1
10+
#elif 0
11+
#define PART2 0
12+
#elif 1
13+
#define PART3 0
14+
#else
15+
#define PART4 0
16+
#endif
17+
18+
#ifndef PART1
19+
#define PART1 0
20+
#endif
21+
#ifndef PART2
22+
#define PART2 0
23+
#endif
24+
#ifndef PART3
25+
#define PART3 0
26+
#endif
27+
#ifndef PART4
28+
#define PART4 0
29+
#endif
30+
31+
integer, parameter :: res = PART1+PART2+PART3+PART4
32+
33+
end subroutine preprocessor_if_elif_skip

0 commit comments

Comments
 (0)