From f86801df598904e72597a924e1f8e142d8726b28 Mon Sep 17 00:00:00 2001 From: gnikit Date: Mon, 17 Jan 2022 22:49:34 +0000 Subject: [PATCH] Fixes bug with fortan line cont and preproc Fixes Preprocessor statements in declarations break parsing #203 Fixes Preprocessor statements in declarations break parsing gnikit/fortls#4 --- CHANGELOG.md | 3 +++ fortls/parse_fortran.py | 19 ++++++++++++------- fortls/regex_patterns.py | 1 + test/test_preproc.py | 3 +++ test/test_server.py | 1 + test/test_source/pp/.pp_conf.json | 3 ++- test/test_source/pp/preproc_keywords.F90 | 10 ++++++++++ 7 files changed, 32 insertions(+), 8 deletions(-) create mode 100644 test/test_source/pp/preproc_keywords.F90 diff --git a/CHANGELOG.md b/CHANGELOG.md index bd5d8e2d..3600a9e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,9 @@ ([gnikit/fortls#16](https://github.com/gnikit/fortls/issues/16)) - Fixes `END FORALL` end of scope error ([gnikit/fortls#18](https://github.com/gnikit/fortls/issues/18)) +- Fixes Fortran line continuation definitions intermingled with preprocessor directives + ([#203](https://github.com/hansec/fortran-language-server/issues/203)) + ([gnikit/fortls#4](https://github.com/gnikit/fortls/issues/4)) ## 1.16.0 diff --git a/fortls/parse_fortran.py b/fortls/parse_fortran.py index a2518e53..eac250f6 100644 --- a/fortls/parse_fortran.py +++ b/fortls/parse_fortran.py @@ -104,6 +104,7 @@ NAT_VAR_REGEX, NON_DEF_REGEX, PARAMETER_VAL_REGEX, + PP_ANY_REGEX, PP_DEF_REGEX, PP_INCLUDE_REGEX, PP_REGEX, @@ -978,7 +979,7 @@ def get_code_line( line_ind -= 1 else: # Free format file opt_cont_match = FREE_CONT_REGEX.match(curr_line) - if opt_cont_match is not None: + if opt_cont_match: curr_line = ( " " * opt_cont_match.end(0) + curr_line[opt_cont_match.end(0) :] ) @@ -989,7 +990,7 @@ def get_code_line( tmp_no_comm = tmp_line.split("!")[0] cont_ind = tmp_no_comm.rfind("&") opt_cont_match = FREE_CONT_REGEX.match(tmp_no_comm) - if opt_cont_match is not None: + if opt_cont_match: if cont_ind == opt_cont_match.end(0) - 1: break tmp_no_comm = ( @@ -1022,6 +1023,7 @@ def get_code_line( if iComm < 0: iComm = iAmper + 1 next_line = "" + # Read the next line if needed while (iAmper >= 0) and (iAmper < iComm): if line_ind == line_number + 1: curr_line = curr_line[:iAmper] @@ -1029,14 +1031,19 @@ def get_code_line( post_lines[-1] = next_line[:iAmper] next_line = self.get_line(line_ind, pp_content) line_ind += 1 + # Skip any preprocessor statements when seeking the next line + if PP_ANY_REGEX.match(next_line): + next_line = "" + post_lines.append("") + continue # Skip empty or comment lines match = FREE_COMMENT_LINE_MATCH.match(next_line) - if (next_line.rstrip() == "") or (match is not None): + if next_line.rstrip() == "" or match: next_line = "" post_lines.append("") continue opt_cont_match = FREE_CONT_REGEX.match(next_line) - if opt_cont_match is not None: + if opt_cont_match: next_line = ( " " * opt_cont_match.end(0) + next_line[opt_cont_match.end(0) :] @@ -1056,9 +1063,7 @@ def get_code_line( def strip_comment(self, line: str) -> str: """Strip comment from line""" if self.fixed: - if (FIXED_COMMENT_LINE_MATCH.match(line) is not None) and ( - FIXED_OPENMP_MATCH.match(line) is not None - ): + if FIXED_COMMENT_LINE_MATCH.match(line) and FIXED_OPENMP_MATCH.match(line): return "" else: if FREE_OPENMP_MATCH.match(line) is None: diff --git a/fortls/regex_patterns.py b/fortls/regex_patterns.py index 972d163e..6766b4a3 100644 --- a/fortls/regex_patterns.py +++ b/fortls/regex_patterns.py @@ -111,6 +111,7 @@ PP_DEF_REGEX = re.compile(r"#(define|undef)[ ]*([\w]+)(\((\w+(,[ ]*)?)+\))?", re.I) PP_DEF_TEST_REGEX = re.compile(r"(![ ]*)?defined[ ]*\([ ]*([a-z0-9_]*)[ ]*\)$", re.I) PP_INCLUDE_REGEX = re.compile(r"#include[ ]*([\"a-z0-9_\.]*)", re.I) +PP_ANY_REGEX = re.compile(r"(^#:?\w+)") # Context matching rules CALL_REGEX = re.compile(r"[ ]*CALL[ ]+[a-z0-9_%]*$", re.I) INT_STMNT_REGEX = re.compile(r"^[ ]*[a-z]*$", re.I) diff --git a/test/test_preproc.py b/test/test_preproc.py index 8037a07f..04809cf4 100644 --- a/test/test_preproc.py +++ b/test/test_preproc.py @@ -34,6 +34,8 @@ def check_return(result_array, checks): string += hover_req(file_path, 7, 40) # multi-lin variable string += hover_req(file_path, 8, 7) # function with if conditional string += hover_req(file_path, 9, 7) # multiline function with if conditional + file_path = os.path.join(test_dir, "pp", "preproc_keywords.F90") + string += hover_req(file_path, 6, 2) # ignores PP across Fortran line continuations errcode, results = run_request(string, f" --config={root_dir}/.pp_conf.json") assert errcode == 0 @@ -44,6 +46,7 @@ def check_return(result_array, checks): "#define varVar 55", "#define ewrite if (priority <= 3) write((priority), format)", "#define ewrite2 if (priority <= 3) write((priority), format)", + "REAL, CONTIGUOUS, POINTER, DIMENSION(:)", ) assert len(ref_results) == len(results) - 1 check_return(results[1:], ref_results) diff --git a/test/test_server.py b/test/test_server.py index c83bbe95..cd326911 100644 --- a/test/test_server.py +++ b/test/test_server.py @@ -186,6 +186,7 @@ def check_return(result_array): ["test_int", 2, 0], ["test_mod", 2, 0], ["test_nonint_mod", 2, 0], + ["test_preproc_keywords", 2, 0], ["test_program", 2, 0], ["test_rename_sub", 6, 9], ["test_select", 2, 0], diff --git a/test/test_source/pp/.pp_conf.json b/test/test_source/pp/.pp_conf.json index 90bda382..0cf75a8a 100644 --- a/test/test_source/pp/.pp_conf.json +++ b/test/test_source/pp/.pp_conf.json @@ -6,5 +6,6 @@ "enable_code_actions": true, "pp_suffixes": [".h", ".F90"], "incl_suffixes": [".h"], - "include_dirs": ["include"] + "include_dirs": ["include"], + "pp_defs": { "HAVE_CONTIGUOUS": "" } } diff --git a/test/test_source/pp/preproc_keywords.F90 b/test/test_source/pp/preproc_keywords.F90 new file mode 100644 index 00000000..c216b51e --- /dev/null +++ b/test/test_source/pp/preproc_keywords.F90 @@ -0,0 +1,10 @@ +program test_preproc_keywords +REAL & +#ifdef HAVE_CONTIGUOUS +, CONTIGUOUS & +#endif +, POINTER :: & +var1(:), & +var2(:) + +end program test_preproc_keywords \ No newline at end of file