diff --git a/docs/options.rst b/docs/options.rst index c5327418..faf9c37a 100644 --- a/docs/options.rst +++ b/docs/options.rst @@ -72,6 +72,7 @@ All the ``fortls`` settings with their default arguments can be found below "pp_suffixes": [], "include_dirs": [], "pp_defs": {}, + "pp_parse_intel": false, "symbol_skip_mem": false, @@ -198,6 +199,13 @@ Additional **preprocessor definitions** from what are specified in files found i .. note:: Definitions in ``pp_defs`` will override definitions from ``include_dirs`` +pp_parse_intel +************** + +Parses Intel compiler directive defines and conditionals of the form +``!DEC$``, ``!DIR$``, ``CDIR$``, ``CDEC$``, ``*DIR$``, ``*DEC$``, or ``!MS$``. +Only defines, undefines, and if defined statements are evaluated. + Limitations *********** diff --git a/fortls/__init__.py b/fortls/__init__.py index faebde88..ddedcd19 100644 --- a/fortls/__init__.py +++ b/fortls/__init__.py @@ -472,6 +472,7 @@ def locate_config(root: str) -> str | None: pp_suffixes = None pp_defs = {} include_dirs = set() + pp_parse_intel = False if args.debug_rootpath: # Check for config files config_path = locate_config(args.debug_rootpath) @@ -482,6 +483,7 @@ def locate_config(root: str) -> str | None: config_dict = json.load(fhandle) pp_suffixes = config_dict.get("pp_suffixes", None) pp_defs = config_dict.get("pp_defs", {}) + pp_parse_intel = config_dict.get("pp_parse_intel", False) include_dirs = set() for path in config_dict.get("include_dirs", set()): include_dirs.update( @@ -501,7 +503,12 @@ def locate_config(root: str) -> str | None: error_exit(f"Reading file failed: {err_str}") print(f" Detected format: {'fixed' if file_obj.fixed else 'free'}") print("\n=========\nParser Output\n=========\n") - file_ast = file_obj.parse(debug=True, pp_defs=pp_defs, include_dirs=include_dirs) + file_ast = file_obj.parse( + debug=True, + pp_defs=pp_defs, + include_dirs=include_dirs, + pp_parse_intel=pp_parse_intel, + ) print("\n=========\nObject Tree\n=========\n") for obj in file_ast.get_scopes(): print("{}: {}".format(obj.get_type(), obj.FQSN)) diff --git a/fortls/fortls.schema.json b/fortls/fortls.schema.json index f6a8c09a..ba78802c 100644 --- a/fortls/fortls.schema.json +++ b/fortls/fortls.schema.json @@ -171,6 +171,12 @@ "default": {}, "type": "object" }, + "pp_parse_intel": { + "title": "Pp Parse Intel", + "description": "Parses Intel compiler directive defines and conditionals.", + "default": false, + "type": "boolean" + }, "symbol_skip_mem": { "title": "Symbol Skip Mem", "description": "Do not include type members in document symbol results", diff --git a/fortls/interface.py b/fortls/interface.py index 05cc8c18..6113cfa6 100644 --- a/fortls/interface.py +++ b/fortls/interface.py @@ -256,6 +256,11 @@ def cli(name: str = "fortls") -> argparse.ArgumentParser: "Preprocessor definitions are normally included via INCLUDE_DIRS" ), ) + group.add_argument( + "--pp_parse_intel", + action="store_true", + help="Parses Intel compiler directive defines and conditionals.", + ) # Symbols options ---------------------------------------------------------- group = parser.add_argument_group("Symbols options") diff --git a/fortls/langserver.py b/fortls/langserver.py index 422061d9..d41b4017 100644 --- a/fortls/langserver.py +++ b/fortls/langserver.py @@ -751,11 +751,18 @@ def get_definition( return None # Search in Preprocessor defined variables if def_name in def_file.pp_defs: + def_value = def_file.pp_defs.get(def_name) + def_arg_str = "" + if isinstance(def_value, tuple): + def_arg_str, def_value = def_value + def_arg_str = ", ".join([x.strip() for x in def_arg_str.split(",")]) + def_arg_str = f"({def_arg_str})" + var = Variable( def_file.ast, def_line + 1, def_name, - f"#define {def_name} {def_file.pp_defs.get(def_name)}", + f"#define {def_name}{def_arg_str} {def_value}", [], ) return var @@ -1316,7 +1323,9 @@ def serve_onChange(self, request: dict): # Update inheritance (currently file only) # tmp_file.ast.resolve_links(self.obj_tree, self.link_version) elif file_obj.preproc: - file_obj.preprocess(pp_defs=self.pp_defs) + file_obj.preprocess( + pp_defs=self.pp_defs, pp_parse_intel=self.pp_parse_intel + ) self.pp_defs = {**self.pp_defs, **file_obj.pp_defs} def serve_onOpen(self, request: dict): @@ -1389,7 +1398,9 @@ def update_workspace_file( if not file_changed: return False, None ast_new = file_obj.parse( - pp_defs=self.pp_defs, include_dirs=self.include_dirs + pp_defs=self.pp_defs, + include_dirs=self.include_dirs, + pp_parse_intel=self.pp_parse_intel, ) # Add the included read in pp_defs from to the ones specified in the # configuration file @@ -1422,6 +1433,7 @@ def file_init( pp_suffixes: list[str], include_dirs: set[str], sort: bool, + pp_parse_intel: bool, ): """Initialise a Fortran file @@ -1437,6 +1449,8 @@ def file_init( Preprocessor only include directories, not used by normal parser sort : bool Whether or not keywords should be sorted + pp_parse_intel : bool + Parse Intel FPP directives Returns ------- @@ -1453,7 +1467,11 @@ def file_init( # This is a bypass. # For more see on SO: shorturl.at/hwAG1 set_keyword_ordering(sort) - file_ast = file_obj.parse(pp_defs=pp_defs, include_dirs=include_dirs) + file_ast = file_obj.parse( + pp_defs=pp_defs, + include_dirs=include_dirs, + pp_parse_intel=pp_parse_intel, + ) except: log.error("Error while parsing file %s", filepath, exc_info=True) return "Error during parsing" @@ -1476,6 +1494,7 @@ def workspace_init(self): self.pp_suffixes, self.include_dirs, self.sort_keywords, + self.pp_parse_intel, ), ) pool.close() @@ -1632,6 +1651,7 @@ def _load_config_file_general(self, config_dict: dict) -> None: def _load_config_file_preproc(self, config_dict: dict) -> None: self.pp_suffixes = config_dict.get("pp_suffixes", None) self.pp_defs = config_dict.get("pp_defs", {}) + self.pp_parse_intel = config_dict.get("pp_parse_intel", False) if isinstance(self.pp_defs, list): self.pp_defs = {key: "" for key in self.pp_defs} diff --git a/fortls/parsers/internal/parser.py b/fortls/parsers/internal/parser.py index ae5cfa1e..0889bad7 100644 --- a/fortls/parsers/internal/parser.py +++ b/fortls/parsers/internal/parser.py @@ -1176,7 +1176,11 @@ def find_word_in_code_line( return line_no, word_range def preprocess( - self, pp_defs: dict = None, include_dirs: set = None, debug: bool = False + self, + pp_defs: dict = None, + include_dirs: set = None, + debug: bool = False, + pp_parse_intel: bool = False, ) -> tuple[list, list]: if pp_defs is None: pp_defs = {} @@ -1189,6 +1193,7 @@ def preprocess( pp_defs=pp_defs, include_dirs=include_dirs, debug=debug, + pp_parse_intel=pp_parse_intel, ) return pp_skips, pp_defines @@ -1231,6 +1236,7 @@ def parse( debug: bool = False, pp_defs: dict = None, include_dirs: set = None, + pp_parse_intel: bool = False, ) -> FortranAST: """Parse Fortran file contents of a fortran_file object and build an Abstract Syntax Tree (AST) @@ -1243,6 +1249,8 @@ def parse( Preprocessor definitions and their values, by default None include_dirs : set, optional Preprocessor include directories, by default None + pp_parse_intel : bool, optional + Parse Intel FPP directives, by default False Returns ------- @@ -1265,7 +1273,10 @@ def parse( if self.preproc: log.debug("=== PreProc Pass ===\n") pp_skips, pp_defines = self.preprocess( - pp_defs=pp_defs, include_dirs=include_dirs, debug=debug + pp_defs=pp_defs, + include_dirs=include_dirs, + debug=debug, + pp_parse_intel=pp_parse_intel, ) for pp_reg in pp_skips: file_ast.start_ppif(pp_reg[0]) @@ -2026,18 +2037,49 @@ def preprocess_file( pp_defs: dict = None, include_dirs: set = None, debug: bool = False, + pp_parse_intel: bool = False, ): # Look for and mark excluded preprocessor paths in file # Initial implementation only looks for "if" and "ifndef" statements. # For "if" statements all blocks are excluded except the "else" block if present # For "ifndef" statements all blocks excluding the first block are excluded - def eval_pp_if(text, defs: dict = None): + def eval_pp_if(text, defs: dict = None, pp_parse_intel: bool = False): def replace_ops(expr: str): expr = expr.replace("&&", " and ") expr = expr.replace("||", " or ") expr = expr.replace("!=", " <> ") expr = expr.replace("!", " not ") expr = expr.replace(" <> ", " != ") + + return expr + + def replace_intel_ops(expr: str): + expr = expr.replace("/=", " != ") + expr = expr.replace(".AND.", " && ") + expr = expr.replace(".LT.", " < ") + expr = expr.replace(".GT.", " > ") + expr = expr.replace(".EQ.", " == ") + expr = expr.replace(".LE.", " <= ") + expr = expr.replace(".GE.", " >= ") + expr = expr.replace(".NE.", " != ") + expr = expr.replace(".EQV.", " == ") + expr = expr.replace(".NEQV.", " != ") + expr = expr.replace(".NOT.", "!") + expr = expr.replace(".OR.", " || ") + expr = expr.replace(".XOR.", " != ") # admittedly a hack... + expr = expr.replace(".and.", " && ") + expr = expr.replace(".lt.", " < ") + expr = expr.replace(".gt.", " > ") + expr = expr.replace(".eq.", " == ") + expr = expr.replace(".le.", " <= ") + expr = expr.replace(".ge.", " >= ") + expr = expr.replace(".ne.", " != ") + expr = expr.replace(".eqv.", " == ") + expr = expr.replace(".neqv.", " != ") + expr = expr.replace(".not.", "!") + expr = expr.replace(".or.", " || ") + expr = expr.replace(".xor.", " != ") # admittedly a hack... + return expr def replace_defined(line: str): @@ -2070,7 +2112,12 @@ def replace_vars(line: str): if defs is None: defs = {} - out_line = replace_defined(text) + + out_line = text + if pp_parse_intel: + out_line = replace_intel_ops(out_line) + + out_line = replace_defined(out_line) out_line = replace_vars(out_line) try: line_res = eval(replace_ops(out_line)) @@ -2098,26 +2145,27 @@ def replace_vars(line: str): if def_cont_name is not None: output_file.append("") if line.rstrip()[-1] != "\\": - defs_tmp[def_cont_name] += line.strip() + append_multiline_macro(defs_tmp, def_cont_name, line.strip()) def_cont_name = None else: - defs_tmp[def_cont_name] += line[0:-1].strip() + append_multiline_macro(defs_tmp, def_cont_name, line[0:-1].strip()) + continue # Handle conditional statements match = FRegex.PP_REGEX.match(line) - if match: + if match and check_pp_prefix(match.group(1), pp_parse_intel): output_file.append(line) def_name = None if_start = False # Opening conditional statements - if match.group(1) == "if ": - is_path = eval_pp_if(line[match.end(1) :], defs_tmp) + if match.group(2).lower() == "if ": + is_path = eval_pp_if(line[match.end(2) :], defs_tmp, pp_parse_intel) if_start = True - elif match.group(1) == "ifdef": + elif match.group(2).lower() == "ifdef": if_start = True def_name = line[match.end(0) :].strip() is_path = def_name in defs_tmp - elif match.group(1) == "ifndef": + elif match.group(2).lower() == "ifndef": if_start = True def_name = line[match.end(0) :].strip() is_path = not (def_name in defs_tmp) @@ -2135,7 +2183,7 @@ def replace_vars(line: str): inc_start = False exc_start = False exc_continue = False - if match.group(1) == "elif": + if match.group(2).lower() == "elif": 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: @@ -2147,7 +2195,7 @@ def replace_vars(line: str): 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): + elif eval_pp_if(line[match.end(2) :], defs_tmp, pp_parse_intel): pp_stack[-1][1] = i + 1 pp_skips.append(pp_stack.pop()) pp_stack_group[-1][1] = True @@ -2155,7 +2203,7 @@ def replace_vars(line: str): inc_start = True else: exc_start = True - elif match.group(1) == "else": + elif match.group(2).lower() == "else": if pp_stack[-1][0] < 0: pp_stack[-1][0] = i + 1 exc_start = True @@ -2171,7 +2219,7 @@ def replace_vars(line: str): pp_skips.append(pp_stack.pop()) pp_stack.append([-1, -1]) inc_start = True - elif match.group(1) == "endif": + elif match.group(2).lower() == "endif": if pp_stack_group and (pp_stack_group[-1][0] == len(pp_stack)): pp_stack_group.pop() if pp_stack[-1][0] < 0: @@ -2192,10 +2240,12 @@ def replace_vars(line: str): continue # Handle variable/macro definitions files match = FRegex.PP_DEF.match(line) - if (match is not None) and ((len(pp_stack) == 0) or (pp_stack[-1][0] < 0)): + if (match is not None and check_pp_prefix(match.group(1), pp_parse_intel)) and ( + (len(pp_stack) == 0) or (pp_stack[-1][0] < 0) + ): output_file.append(line) pp_defines.append(i + 1) - def_name = match.group(2) + def_name = match.group(3) # If this is an argument list of a function add them to the name # get_definition will only return the function name upon hover # hence if the argument list is appended in the def_name then @@ -2204,18 +2254,30 @@ def replace_vars(line: str): # This also does not allow for multiline argument list definitions. # if match.group(3): # def_name += match.group(3) - if (match.group(1) == "define") and (def_name not in defs_tmp): + if (match.group(2) == "define") and (def_name not in defs_tmp): eq_ind = line[match.end(0) :].find(" ") + if eq_ind < 0: + eq_ind = line[match.end(0) :].find("\t") + if eq_ind >= 0: # Handle multiline macros if line.rstrip()[-1] == "\\": - defs_tmp[def_name] = line[match.end(0) + eq_ind : -1].strip() + def_value = line[match.end(0) + eq_ind : -1].strip() def_cont_name = def_name else: - defs_tmp[def_name] = line[match.end(0) + eq_ind :].strip() + def_value = line[match.end(0) + eq_ind :].strip() else: - defs_tmp[def_name] = "True" - elif (match.group(1) == "undef") and (def_name in defs_tmp): + def_value = "True" + + # are there arguments to parse? + if match.group(4): + def_value = (match.group(5), def_value) + + defs_tmp[def_name] = def_value + elif ( + match.group(2) == "undef" + or (pp_parse_intel and match.group(2) == "undefine") + ) and (def_name in defs_tmp): defs_tmp.pop(def_name, None) log.debug(f"{line.strip()} !!! Define statement({i + 1})") continue @@ -2245,6 +2307,7 @@ def replace_vars(line: str): pp_defs=defs_tmp, include_dirs=include_dirs, debug=debug, + pp_parse_intel=pp_parse_intel, ) log.debug("!!! Completed parsing include file\n") @@ -2265,8 +2328,16 @@ def replace_vars(line: str): continue def_regex = def_regexes.get(def_tmp) if def_regex is None: - def_regex = re.compile(rf"\b{def_tmp}\b") + if isinstance(value, tuple): + def_regex = expand_def_func_macro(def_tmp, value) + else: + def_regex = re.compile(rf"\b{def_tmp}\b") + def_regexes[def_tmp] = def_regex + + if isinstance(def_regex, tuple): + def_regex, value = def_regex + line_new, nsubs = def_regex.subn(value, line) if nsubs > 0: log.debug( @@ -2275,3 +2346,33 @@ def replace_vars(line: str): line = line_new output_file.append(line) return output_file, pp_skips, pp_defines, defs_tmp + + +def expand_def_func_macro(def_name: str, def_value: tuple[str, str]): + def_args, sub = def_value + def_args = def_args.split(",") + regex = re.compile(rf"\b{def_name}\s*\({','.join(['(.*)']*len(def_args))}\)") + + for i, arg in enumerate(def_args): + arg = arg.strip() + sub = re.sub(rf"\b({arg})\b", rf"\\{i + 1}", sub) + + return regex, sub + + +def append_multiline_macro(pp_defs: dict, def_name: str, line: str): + def_value = pp_defs[def_name] + def_args = None + if isinstance(def_value, tuple): + def_args, def_value = def_value + + def_value += line + + if def_args is not None: + def_value = (def_args, def_value) + + pp_defs[def_name] = def_value + + +def check_pp_prefix(prefix: str, pp_parse_intel: bool): + return prefix == "#" or (pp_parse_intel and FRegex.INTEL_FPP_PRE.match(prefix)) diff --git a/fortls/regex_patterns.py b/fortls/regex_patterns.py index 6f590402..64b4f269 100644 --- a/fortls/regex_patterns.py +++ b/fortls/regex_patterns.py @@ -124,11 +124,20 @@ class FortranRegularExpressions: FREE_FORMAT_TEST: Pattern = compile(r"[ ]{1,4}[a-z]", I) # Preprocessor matching rules DEFINED: Pattern = compile(r"defined[ ]*\(?[ ]*([a-z_]\w*)[ ]*\)?", I) - PP_REGEX: Pattern = compile(r"#(if |ifdef|ifndef|else|elif|endif)") - PP_DEF: Pattern = compile(r"#(define|undef)[ ]*([\w]+)(\((\w+(,[ ]*)?)+\))?", I) + INTEL_FPP_PRE_STR = r"[c!*]DEC\$|[c!*]DIR\$|!MS\$" + INTEL_FPP_PRE: Pattern = compile(f"{INTEL_FPP_PRE_STR}", I) + PP_REGEX: Pattern = compile( + rf"[ ]*(#|{INTEL_FPP_PRE_STR})[ ]*(if |ifdef|ifndef|else|elif|endif)", + I, + ) + PP_DEF: Pattern = compile( + rf"[ ]*(#|{INTEL_FPP_PRE_STR})[ ]*(define|undef|undefined)" + r"[ ]+(\w+)(\([ ]*([ \w,]*?)[ ]*\))?", + I, + ) PP_DEF_TEST: Pattern = compile(r"(![ ]*)?defined[ ]*\([ ]*(\w*)[ ]*\)$", I) - PP_INCLUDE: Pattern = compile(r"#include[ ]*([\"\w\.]*)", I) - PP_ANY: Pattern = compile(r"(^#:?\w+)") + PP_INCLUDE: Pattern = compile(r"[ ]*#[ ]*include[ ]+([\"\w\.]*)", I) + PP_ANY: Pattern = compile(rf"(^[ ]*(?:#|{INTEL_FPP_PRE_STR})[ ]*\w*:?\w+)") # Context matching rules CALL: Pattern = compile(r"[ ]*CALL[ ]+[\w%]*$", I) INT_STMNT: Pattern = compile(r"^[ ]*[a-z]*$", I) diff --git a/test/test_interface.py b/test/test_interface.py index 1b46f3f4..3befc308 100644 --- a/test/test_interface.py +++ b/test/test_interface.py @@ -65,12 +65,14 @@ def test_command_line_diagnostic_options(): def test_command_line_preprocessor_options(): args = parser.parse_args( - "--pp_suffixes .h .fh --include_dirs /usr/include/** ./local/incl --pp_defs" + "--pp_suffixes .h .fh --include_dirs /usr/include/** ./local/incl" + " --pp_parse_intel --pp_defs" ' {"HAVE_PETSC":"","HAVE_ZOLTAN":"","Mat":"type(tMat)"}'.split() ) assert args.pp_suffixes == [".h", ".fh"] assert args.include_dirs == {"/usr/include/**", "./local/incl"} assert args.pp_defs == {"HAVE_PETSC": "", "HAVE_ZOLTAN": "", "Mat": "type(tMat)"} + assert args.pp_parse_intel def test_command_line_symbol_options(): @@ -150,6 +152,7 @@ def test_config_file_preprocessor_options(): "HAVE_ZOLTAN": "", "Mat": "type(tMat)", } + assert server.pp_parse_intel def test_config_file_symbols_options(): diff --git a/test/test_preproc.py b/test/test_preproc.py index 50f50607..c8fc3214 100644 --- a/test/test_preproc.py +++ b/test/test_preproc.py @@ -42,6 +42,11 @@ def check_return(result_array, checks): string += hover_req(file_path, 30, 23) file_path = root_dir / "preproc_if_elif_skip.F90" string += hover_req(file_path, 30, 23) + file_path = root_dir / "preproc_spacing_arg_defs.F90" + string += hover_req(file_path, 11, 20) + string += hover_req(file_path, 24, 17) + string += hover_req(file_path, 26, 13) + string += hover_req(file_path, 26, 42) config = str(root_dir / ".pp_conf.json") errcode, results = run_request(string, ["--config", config]) assert errcode == 0 @@ -52,12 +57,12 @@ def check_return(result_array, checks): "```fortran90\n#define PETSC_ERR_INT_OVERFLOW 84\n```", "```fortran90\n#define varVar 55\n```", ( - "```fortran90\n#define ewrite if (priority <= 3) write((priority)," - " format)\n```" + "```fortran90\n#define ewrite(priority, format)" + " if (priority <= 3) write((priority), format)\n```" ), ( - "```fortran90\n#define ewrite2 if (priority <= 3) write((priority)," - " format)\n```" + "```fortran90\n#define ewrite2(priority, format)" + " if (priority <= 3) write((priority), format)\n```" ), "```fortran90\n#define SUCCESS .true.\n```", "```fortran90\nREAL, CONTIGUOUS, POINTER, DIMENSION(:) :: var1\n```", @@ -68,6 +73,23 @@ def check_return(result_array, checks): "```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```", + "```fortran90\n#define MAYBEWRAP(PROCEDURE) PROCEDURE\n```", + "```fortran90\nSUBROUTINE test_type_set_test()\n```", + "```fortran90\n#define MACROARGS(x, y) x + y\n```", + "```fortran90\nINTEGER(KIND=4), PARAMETER :: C_LONG = 4\n```", ) assert len(ref_results) == len(results) - 1 check_return(results[1:], ref_results) + + +# def test_FortranFile_pp(): +# from fortls import FortranFile + +# root_dir = test_dir / "pp" +# # file_path = root_dir / "preproc.F90" +# file_path = root_dir / "preproc_spacing_arg_defs.F90" +# ff = FortranFile(file_path) +# ff.load_from_disk() +# include_dirs = {root_dir / "include"} +# file_ast = ff.parse(include_dirs=include_dirs, debug=True, pp_parse_intel=True) +# assert file_ast diff --git a/test/test_server.py b/test/test_server.py index 639ef427..fb63377d 100644 --- a/test/test_server.py +++ b/test/test_server.py @@ -175,6 +175,7 @@ def test_workspace_symbols(): def check_return(result_array): # Expected objects objs = ( + ["argtest", 13, 16], ["test", 6, 7], ["test_abstract", 2, 0], ["test_associate_block", 2, 0], @@ -196,7 +197,11 @@ def check_return(result_array): ["test_str1", 13, 5], ["test_str2", 13, 5], ["test_sub", 6, 8], + ["test_type", 5, 5], + ["test_type_set_test", 6, 29], ["test_vis_mod", 2, 0], + ["the_test", 13, 15], + ["wrap_test_type_set_test", 6, 37], ) assert len(result_array) == len(objs) for i, obj in enumerate(objs): diff --git a/test/test_source/f90_config.json b/test/test_source/f90_config.json index 2d4779b7..2466d3ae 100644 --- a/test/test_source/f90_config.json +++ b/test/test_source/f90_config.json @@ -31,6 +31,7 @@ "HAVE_ZOLTAN": "", "Mat": "type(tMat)" }, + "pp_parse_intel": true, "symbol_skip_mem": true, diff --git a/test/test_source/pp/.pp_conf.json b/test/test_source/pp/.pp_conf.json index 0cf75a8a..ac1bd691 100644 --- a/test/test_source/pp/.pp_conf.json +++ b/test/test_source/pp/.pp_conf.json @@ -7,5 +7,6 @@ "pp_suffixes": [".h", ".F90"], "incl_suffixes": [".h"], "include_dirs": ["include"], - "pp_defs": { "HAVE_CONTIGUOUS": "" } + "pp_defs": { "HAVE_CONTIGUOUS": "" }, + "pp_parse_intel": true } diff --git a/test/test_source/pp/include/indent.h b/test/test_source/pp/include/indent.h new file mode 100644 index 00000000..f477f249 --- /dev/null +++ b/test/test_source/pp/include/indent.h @@ -0,0 +1,22 @@ +!! sample code adapted from json-fortran/json_macros.inc + + # define SPACING_TEST + # define FILE_ENCODING ,encoding='UTF-8' + +# ifdef __GFORTRAN__ +! gfortran uses cpp in old-school compatibility mode so +! the # stringify and ## concatenate operators don't work +! but we can use C/C++ style comment to ensure PROCEDURE is +! correctly tokenized and prepended with 'wrap_' when the +! macro is expanded +# define MAYBEWRAP(PROCEDURE) PROCEDURE , wrap_/**/PROCEDURE +# else +! Intel's fpp does support the more contemporary ## concatenation +! operator, but doesn't treat the C/C++ comments the same way. +! If you use the gfortran approach and pass the -noB switch to +! fpp, the macro will expand, but with a space between wrap_ and +! whatever PROCEDURE expands to +# define MAYBEWRAP(PROCEDURE) PROCEDURE +# endif + +# define MACROARGS( x , y ) x + y diff --git a/test/test_source/pp/preproc_spacing_arg_defs.F90 b/test/test_source/pp/preproc_spacing_arg_defs.F90 new file mode 100644 index 00000000..ced397ab --- /dev/null +++ b/test/test_source/pp/preproc_spacing_arg_defs.F90 @@ -0,0 +1,46 @@ +program preprocessor_spacing_arg_defs + implicit none + + #include "indent.h" + + type :: test_type + private + integer, public :: test_int + + contains + generic, public :: set_test => MAYBEWRAP(test_type_set_test) + procedure :: MAYBEWRAP(test_type_set_test) + + end type test_type + + type(test_type) :: the_test + integer :: argtest + + !DEC$ IF DEFINED(SPACING_TEST).AND.DEFINED(MACROARGS) + INTEGER (KIND=4), PARAMETER :: C_LONG = 4 + !DEC$ ELSE + INTEGER (KIND=4), PARAMETER :: C_LONG = 8 + !DEC$ ENDIF + + call the_test%set_test() + + argtest = MACROARGS(the_test%test_int, C_LONG) + +contains + subroutine test_type_set_test(me) + implicit none + + class(test_type), intent(inout) :: me + + me%test_int = 3 + end subroutine test_type_set_test + + subroutine wrap_test_type_set_test(me) + implicit none + + class(test_type), intent(inout) :: me + + me%test_int = 5 + end subroutine wrap_test_type_set_test + +end program preprocessor_spacing_arg_defs