Skip to content

Commit

Permalink
Merge pull request #331 from AljenU/fix_else_and_elif
Browse files Browse the repository at this point in the history
Fix preprocessor else and elif
  • Loading branch information
gnikit authored Oct 29, 2023
2 parents 27b9d49 + dc3449e commit bfc4f04
Show file tree
Hide file tree
Showing 8 changed files with 202 additions and 7 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@

### Fixed

- Fixed preprocessor bug with `if` and `elif` conditionals
([#322](https://github.com/fortran-lang/fortls/issues/322))
- Fixed bug where type fields or methods were not detected if spaces were
used around `%`
([#286](https://github.com/fortran-lang/fortls/issues/286))
Expand Down
41 changes: 34 additions & 7 deletions fortls/parse_fortran.py
Original file line number Diff line number Diff line change
Expand Up @@ -2090,6 +2090,7 @@ def replace_vars(line: str):
pp_skips = []
pp_defines = []
pp_stack = []
pp_stack_group = []
defs_tmp = pp_defs.copy()
def_regexes = {}
output_file = []
Expand Down Expand Up @@ -2135,25 +2136,49 @@ def replace_vars(line: str):
# Closing/middle conditional statements
inc_start = False
exc_start = False
exc_continue = False
if match.group(1) == "elif":
if pp_stack[-1][0] < 0:
pp_stack[-1][0] = i + 1
exc_start = True
if (not pp_stack_group) or (pp_stack_group[-1][0] != len(pp_stack)):
# First elif statement for this elif group
if pp_stack[-1][0] < 0:
pp_stack_group.append([len(pp_stack), True])
else:
pp_stack_group.append([len(pp_stack), False])
if pp_stack_group[-1][1]:
# An earlier if or elif in this group has been true
exc_continue = True
if pp_stack[-1][0] < 0:
pp_stack[-1][0] = i + 1
elif eval_pp_if(line[match.end(1) :], defs_tmp):
pp_stack[-1][1] = i + 1
pp_skips.append(pp_stack.pop())
pp_stack_group[-1][1] = True
pp_stack.append([-1, -1])
inc_start = True
else:
if eval_pp_if(line[match.end(1) :], defs_tmp):
pp_stack[-1][1] = i - 1
pp_stack.append([-1, -1])
inc_start = True
exc_start = True
elif match.group(1) == "else":
if pp_stack[-1][0] < 0:
pp_stack[-1][0] = i + 1
exc_start = True
elif (
pp_stack_group
and (pp_stack_group[-1][0] == len(pp_stack))
and (pp_stack_group[-1][1])
):
# An earlier if or elif in this group has been true
exc_continue = True
else:
pp_stack[-1][1] = i + 1
pp_skips.append(pp_stack.pop())
pp_stack.append([-1, -1])
inc_start = True
elif match.group(1) == "endif":
if pp_stack_group and (pp_stack_group[-1][0] == len(pp_stack)):
pp_stack_group.pop()
if pp_stack[-1][0] < 0:
pp_stack.pop()
log.debug(f"{line.strip()} !!! Conditional TRUE/END({i + 1})")
continue
if pp_stack[-1][1] < 0:
pp_stack[-1][1] = i + 1
Expand All @@ -2164,6 +2189,8 @@ def replace_vars(line: str):
log.debug(f"{line.strip()} !!! Conditional TRUE({i + 1})")
elif exc_start:
log.debug(f"{line.strip()} !!! Conditional FALSE({i + 1})")
elif exc_continue:
log.debug(f"{line.strip()} !!! Conditional EXCLUDED({i + 1})")
continue
# Handle variable/macro definitions files
match = FRegex.PP_DEF.match(line)
Expand Down
19 changes: 19 additions & 0 deletions test/test_preproc.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,18 @@ def check_return(result_array, checks):
string += hover_req(file_path, 10, 15) # defined without ()
file_path = root_dir / "preproc_keywords.F90"
string += hover_req(file_path, 6, 2) # ignores PP across Fortran line continuations
file_path = root_dir / "preproc_else.F90"
string += hover_req(file_path, 8, 12)
string += hover_req(file_path, 18, 12)
file_path = root_dir / "preproc_elif.F90"
string += hover_req(file_path, 22, 15)
string += hover_req(file_path, 24, 10)
file_path = root_dir / "preproc_elif_elif_skip.F90"
string += hover_req(file_path, 30, 23)
file_path = root_dir / "preproc_if_elif_else.F90"
string += hover_req(file_path, 30, 23)
file_path = root_dir / "preproc_if_elif_skip.F90"
string += hover_req(file_path, 30, 23)
config = str(root_dir / ".pp_conf.json")
errcode, results = run_request(string, ["--config", config])
assert errcode == 0
Expand All @@ -49,6 +61,13 @@ def check_return(result_array, checks):
),
"```fortran90\n#define SUCCESS .true.\n```",
"```fortran90\nREAL, CONTIGUOUS, POINTER, DIMENSION(:) :: var1\n```",
"```fortran90\nINTEGER :: var0\n```",
"```fortran90\nREAL :: var1\n```",
"```fortran90\nINTEGER :: var2\n```",
"```fortran90\nINTEGER, INTENT(INOUT) :: var\n```",
"```fortran90\nINTEGER, PARAMETER :: res = 0+1+0+0\n```",
"```fortran90\nINTEGER, PARAMETER :: res = 0+0+0+1\n```",
"```fortran90\nINTEGER, PARAMETER :: res = 1+0+0+0\n```",
)
assert len(ref_results) == len(results) - 1
check_return(results[1:], ref_results)
27 changes: 27 additions & 0 deletions test/test_source/pp/preproc_elif.F90
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
subroutine preprocessor_elif(var, var3, var4, var5, var6)

! This file, as used in test_preproc, checks that
! 1. the steps after the preprocessor parsing has fully finished, are only
! using content from the parts within the preprocessor if-elif-else that
! should be used. To do this, it has some regular fortran code within the
! #if and #elif.
! 2. the #endif correctly concludes the if-elif, so any new #define statements
! that come after the #endif, are picked up during the preprocessor parsing.

#if 0
integer, intent(in) :: var
#elif 1
integer, intent(inout) :: var
var = 3
#else
integer, intent(out) :: var
var = 5
#endif

#define OTHERTYPE integer

OTHERTYPE :: var2

PRINT*, var

endsubroutine preprocessor_elif
33 changes: 33 additions & 0 deletions test/test_source/pp/preproc_elif_elif_skip.F90
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
subroutine preprocessor_elif_elif_skip()

! This file, as used in test_preproc, and together with the two similar files,
! tests that when there is an if-elif-elif-else, only the first branch that
! evaluates to true is used, and the others ignored. Also when multiple
! conditions evaluate to true.

#if 0
#define PART1 0
#elif 1
#define PART2 1
#elif 1
#define PART3 0
#else
#define PART4 0
#endif

#ifndef PART1
#define PART1 0
#endif
#ifndef PART2
#define PART2 0
#endif
#ifndef PART3
#define PART3 0
#endif
#ifndef PART4
#define PART4 0
#endif

integer, parameter :: res = PART1+PART2+PART3+PART4

end subroutine preprocessor_elif_elif_skip
21 changes: 21 additions & 0 deletions test/test_source/pp/preproc_else.F90
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
subroutine preprocessor_else(var)

#if 0
#define MYTYPE logical
#else
#define MYTYPE integer
#endif

MYTYPE :: var0

#undef MYTYPE

#if 1
#define MYTYPE real
#else
#define MYTYPE character
#endif

MYTYPE :: var1

endsubroutine preprocessor_else
33 changes: 33 additions & 0 deletions test/test_source/pp/preproc_if_elif_else.F90
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
subroutine preprocessor_if_elif_else()

! This file, as used in test_preproc, and together with the two similar files,
! tests that when there is an if-elif-elif-else, only the first branch that
! evaluates to true is used, and the others ignored. Also when multiple
! conditions evaluate to true.

#if 0
#define PART1 0
#elif 0
#define PART2 0
#elif 0
#define PART3 0
#else
#define PART4 1
#endif

#ifndef PART1
#define PART1 0
#endif
#ifndef PART2
#define PART2 0
#endif
#ifndef PART3
#define PART3 0
#endif
#ifndef PART4
#define PART4 0
#endif

integer, parameter :: res = PART1+PART2+PART3+PART4

endsubroutine preprocessor_if_elif_else
33 changes: 33 additions & 0 deletions test/test_source/pp/preproc_if_elif_skip.F90
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
subroutine preprocessor_if_elif_skip()

! This file, as used in test_preproc, and together with the two similar files,
! tests that when there is an if-elif-elif-else, only the first branch that
! evaluates to true is used, and the others ignored. Also when multiple
! conditions evaluate to true.

#if 1
#define PART1 1
#elif 0
#define PART2 0
#elif 1
#define PART3 0
#else
#define PART4 0
#endif

#ifndef PART1
#define PART1 0
#endif
#ifndef PART2
#define PART2 0
#endif
#ifndef PART3
#define PART3 0
#endif
#ifndef PART4
#define PART4 0
#endif

integer, parameter :: res = PART1+PART2+PART3+PART4

end subroutine preprocessor_if_elif_skip

0 comments on commit bfc4f04

Please sign in to comment.