Skip to content

Commit

Permalink
Merge pull request #396 from fortran-lang/feat/debug-preproc
Browse files Browse the repository at this point in the history
feat/debug preproc
  • Loading branch information
gnikit authored May 12, 2024
2 parents e82ce61 + c964f7c commit a5fc5ed
Show file tree
Hide file tree
Showing 4 changed files with 115 additions and 46 deletions.
1 change: 1 addition & 0 deletions docs/options.rst
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,7 @@ Options for debugging language server
- ``--debug_filepath DEBUG_FILEPATH`` File path for language server tests
- ``--debug_rootpath DEBUG_ROOTPATH`` Root path for language server tests
- ``--debug_parser`` Test source code parser on specified file
- ``--debug_preproc`` Test preprocessor on specified file
- ``--debug_hover`` Test `textDocument/hover` request for specified file and position
- ``--debug_rename RENAME_STRING`` Test `textDocument/rename` request for specified file and position
- ``--debug_actions`` Test `textDocument/codeAction` request for specified file and position
Expand Down
11 changes: 10 additions & 1 deletion fortls/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,13 @@
import sys
from multiprocessing import freeze_support

from .debug import DebugError, debug_lsp, debug_parser, is_debug_mode
from .debug import (
DebugError,
debug_lsp,
debug_parser,
debug_preprocessor,
is_debug_mode,
)
from .interface import cli
from .jsonrpc import JSONRPC2Connection, ReadWriter
from .langserver import LangServer
Expand All @@ -20,6 +26,9 @@ def main():
if args.debug_parser:
debug_parser(args)

elif args.debug_preproc:
debug_preprocessor(args)

elif is_debug_mode(args):
debug_lsp(args, vars(args))

Expand Down
144 changes: 99 additions & 45 deletions fortls/debug.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
from __future__ import annotations

import logging
import os
import pprint
import sys

import json5

from .helper_functions import only_dirs, resolve_globs
from .jsonrpc import JSONRPC2Connection, ReadWriter, path_from_uri
from .langserver import LangServer
from .parsers.internal.parser import FortranFile
from .parsers.internal.parser import FortranFile, preprocess_file


class DebugError(Exception):
Expand Down Expand Up @@ -415,54 +417,11 @@ def debug_parser(args):
The arguments parsed from the `ArgumentParser`
"""

def locate_config(root: str) -> str | None:
default_conf_files = [args.config, ".fortlsrc", ".fortls.json5", ".fortls"]
present_conf_files = [
os.path.isfile(os.path.join(root, f)) for f in default_conf_files
]
if not any(present_conf_files):
return None

# Load the first config file found
for f, present in zip(default_conf_files, present_conf_files):
if not present:
continue
config_path = os.path.join(root, f)
return config_path

def read_config(root: str | None):
pp_suffixes = None
pp_defs = {}
include_dirs = set()
if root is None:
return pp_suffixes, pp_defs, include_dirs

# Check for config files
config_path = locate_config(root)
print(f" Config file = {config_path}")
if config_path is None or not os.path.isfile(config_path):
return pp_suffixes, pp_defs, include_dirs

try:
with open(config_path, encoding="utf-8") as fhandle:
config_dict = json5.load(fhandle)
pp_suffixes = config_dict.get("pp_suffixes", None)
pp_defs = config_dict.get("pp_defs", {})
for path in config_dict.get("include_dirs", set()):
include_dirs.update(only_dirs(resolve_globs(path, root)))

if isinstance(pp_defs, list):
pp_defs = {key: "" for key in pp_defs}
except ValueError as e:
print(f"Error {e} while parsing '{config_path}' settings file")

return pp_suffixes, pp_defs, include_dirs

print("\nTesting parser")
separator()

ensure_file_accessible(args.debug_filepath)
pp_suffixes, pp_defs, include_dirs = read_config(args.debug_rootpath)
pp_suffixes, pp_defs, include_dirs = read_config(args.debug_rootpath, args.config)

print(f' File = "{args.debug_filepath}"')
file_obj = FortranFile(args.debug_filepath, pp_suffixes)
Expand All @@ -482,6 +441,56 @@ def read_config(root: str | None):
separator()


def debug_preprocessor(args):
"""Debug the preprocessor of the Language Server
Triggered by `--debug_preprocessor` option.
Parameters
----------
args : Namespace
The arguments parsed from the `ArgumentParser`
"""

def sep_lvl2(heading: str):
print("\n" + "=" * 75 + f"\n{heading}\n" + "=" * 75)

print("\nTesting preprocessor")
separator()

logging.basicConfig(level=logging.DEBUG, stream=sys.stdout, format="%(message)s")

file = args.debug_filepath
ensure_file_accessible(file)
with open(file, encoding="utf-8") as f:
lines = f.readlines()

root = args.debug_rootpath if args.debug_rootpath else os.path.dirname(file)
_, pp_defs, include_dirs = read_config(root, args.config)

sep_lvl2("Preprocessor Pass:")
output, skips, defines, defs = preprocess_file(
lines, file, pp_defs, include_dirs, debug=True
)

sep_lvl2("Preprocessor Skipped Lines:")
for line in skips:
print(f" {line}")

sep_lvl2("Preprocessor Macros:")
for key, value in defs.items():
print(f" {key} = {value}")

sep_lvl2("Preprocessor Defines (#define):")
for line in defines:
print(f" {line}")

sep_lvl2("Preprocessor Final Output:")
for line in output:
print(rf" {line.rstrip()}")

separator()


def ensure_file_accessible(filepath: str):
"""Ensure the file exists and is accessible, raising an error if not."""
if not os.path.isfile(filepath):
Expand All @@ -500,6 +509,51 @@ def check_request_params(args, loc_needed=True):
print(f" Char = {args.debug_char}\n")


def locate_config(root: str, input_config: str) -> str | None:
default_conf_files = [input_config, ".fortlsrc", ".fortls.json5", ".fortls"]
present_conf_files = [
os.path.isfile(os.path.join(root, f)) for f in default_conf_files
]
if not any(present_conf_files):
return None

# Load the first config file found
for f, present in zip(default_conf_files, present_conf_files):
if not present:
continue
config_path = os.path.join(root, f)
return config_path


def read_config(root: str | None, input_config: str):
pp_suffixes = None
pp_defs = {}
include_dirs = set()
if root is None:
return pp_suffixes, pp_defs, include_dirs

# Check for config files
config_path = locate_config(root, input_config)
print(f" Config file = {config_path}")
if config_path is None or not os.path.isfile(config_path):
return pp_suffixes, pp_defs, include_dirs

try:
with open(config_path, encoding="utf-8") as fhandle:
config_dict = json5.load(fhandle)
pp_suffixes = config_dict.get("pp_suffixes", None)
pp_defs = config_dict.get("pp_defs", {})
for path in config_dict.get("include_dirs", set()):
include_dirs.update(only_dirs(resolve_globs(path, root)))

if isinstance(pp_defs, list):
pp_defs = {key: "" for key in pp_defs}
except ValueError as e:
print(f"Error {e} while parsing '{config_path}' settings file")

return pp_suffixes, pp_defs, include_dirs


def debug_generic(args, test_label, lsp_request, format_results, loc_needed=True):
print(f'\nTesting "{test_label}" request:')
check_request_params(args, loc_needed)
Expand Down
5 changes: 5 additions & 0 deletions fortls/interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,11 @@ def hide_opt(help: str) -> str:
action="store_true",
help=hide_opt("Test source code parser on specified file"),
)
group.add_argument(
"--debug_preproc",
action="store_true",
help=hide_opt("Test source code preprocessor parser on specified file"),
)
group.add_argument(
"--debug_hover",
action="store_true",
Expand Down

0 comments on commit a5fc5ed

Please sign in to comment.