Skip to content

Commit

Permalink
Include support for CPP condition blocks
Browse files Browse the repository at this point in the history
This patch adds support for handling most #ifdef-like condition blocks.
The following preprocessing commands are handled:

* #define / #undef
* #ifdef / #else / #endif

A new flag is added to provide defined macros (-D), and is chosen to
match the `cpp` flag, so that flags can be shared across programs.

Macros are tracked in a new internal variable and are used for #ifdef
testing.  Nested condition blocks are supported by using an internal
stack of the exclusion state.

Certain cases are still not handled.

* #if blocks containing logical expressions are not parsed.
* CPP content inside of #include is ignored.

No doubt many other cases are still unconsidered, such as exotic macro
names.

The autoconf builds use this feature by passing the generated $(DEFS)
argument to makedep.  This is suitable for now, but we may need to
consider two cases in the future:

* Macros defined in $CPPFLAGS are currently ignored, and perhaps they
  should be included here.  The risk is that it may contain non-macro
  flags.

* At some point, DEFS could be moved to a config.h file, and DEFS would
  no longer contain these macros.  (Note that this is very unlikely at
  the moment, since this feature only works with C.)
  • Loading branch information
marshallward committed Dec 13, 2023
1 parent 11759d6 commit a2087bf
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 7 deletions.
3 changes: 1 addition & 2 deletions ac/Makefile.in
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ SRC_DIRS = @SRC_DIRS@

-include Makefile.dep


# Generate Makefile from template
Makefile: @srcdir@/ac/Makefile.in config.status
./config.status
Expand All @@ -33,7 +32,7 @@ rwildcard=$(foreach d,$(wildcard $(1:=/*)),$(call rwildcard,$d,$2) $(filter $(su
.PHONY: depend
depend: Makefile.dep
Makefile.dep: $(MAKEDEP) $(call rwildcard,$(SRC_DIRS),*.h *.c *.inc *.F90)
$(PYTHON) $(MAKEDEP) -o Makefile.dep -e $(SRC_DIRS)
$(PYTHON) $(MAKEDEP) $(DEFS) -o Makefile.dep -e $(SRC_DIRS)


# Delete any files associated with configuration (including the Makefile).
Expand Down
93 changes: 88 additions & 5 deletions ac/makedep
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,16 @@ import sys
# Pre-compile re searches
re_module = re.compile(r"^ *module +([a-z_0-9]+)")
re_use = re.compile(r"^ *use +([a-z_0-9]+)")
re_cpp_define = re.compile(r"^ *# *define +[_a-zA-Z][_a-zA-Z0-9]")
re_cpp_undef = re.compile(r"^ *# *undef +[_a-zA-Z][_a-zA-Z0-9]")
re_cpp_ifdef = re.compile(r"^ *# *ifdef +[_a-zA-Z][_a-zA-Z0-9]")
re_cpp_ifndef = re.compile(r"^ *# *ifndef +[_a-zA-Z][_a-zA-Z0-9]")
re_cpp_if = re.compile(r"^ *# *if +")
re_cpp_else = re.compile(r"^ *# *else")
re_cpp_endif = re.compile(r"^ *# *endif")
re_cpp_include = re.compile(r"^ *# *include *[<\"']([a-zA-Z_0-9\.]+)[>\"']")
re_f90_include = re.compile(r"^ *include +[\"']([a-zA-Z_0-9\.]+)[\"']")
re_program = re.compile(r"^ *[pP][rR][oO][gG][rR][aA][mM] +([a-zA-Z_0-9]+)")
re_program = re.compile(r"^ *program +([a-z_0-9]+)", re.IGNORECASE)
re_end = re.compile(r"^ *end *(module|procedure) ", re.IGNORECASE)
# NOTE: This excludes comments and tokens with substrings containing `function`
# or `subroutine`, but will fail if the keywords appear in other contexts.
Expand All @@ -26,7 +33,7 @@ re_procedure = re.compile(


def create_deps(src_dirs, skip_dirs, makefile, debug, exec_target, fc_rule,
link_externals, script_path):
link_externals, defines):
"""Create "makefile" after scanning "src_dis"."""

# Scan everything Fortran related
Expand Down Expand Up @@ -66,7 +73,7 @@ def create_deps(src_dirs, skip_dirs, makefile, debug, exec_target, fc_rule,
o2mods, o2uses, o2h, o2inc, o2prg, prg2o, mod2o = {}, {}, {}, {}, {}, {}, {}
externals, all_modules = [], []
for f in F90_files:
mods, used, cpp, inc, prg, has_externals = scan_fortran_file(f)
mods, used, cpp, inc, prg, has_externals = scan_fortran_file(f, defines)
# maps object file to modules produced
o2mods[object_file(f)] = mods
# maps module produced to object file
Expand Down Expand Up @@ -272,10 +279,16 @@ def nested_inc(inc_files, f2F):
return inc_files + sorted(set(hlst)), used_mods


def scan_fortran_file(src_file):
def scan_fortran_file(src_file, defines=None):
"""Scan the Fortran file "src_file" and return lists of module defined,
module used, and files included."""
module_decl, used_modules, cpp_includes, f90_includes, programs = [], [], [], [], []

cpp_defines = defines if defines is not None else []

cpp_macros = [define.split('=')[0] for define in cpp_defines]
cpp_group_stack = []

with io.open(src_file, 'r', errors='replace') as file:
lines = file.readlines()

Expand All @@ -285,7 +298,72 @@ def scan_fortran_file(src_file):
file_has_externals = False
# True if the file contains any external objects

cpp_exclude = False
# True if the parser excludes the subsequent lines

cpp_group_stack = []
# Stack of condition group exclusion states

for line in lines:
# Start of #ifdef condition group
match = re_cpp_ifdef.match(line)
if match:
cpp_group_stack.append(cpp_exclude)

# If outer group is excluding or macro is missing, then exclude
macro = line.lstrip()[1:].split()[1]
cpp_exclude = cpp_exclude or macro not in cpp_macros

# Start of #ifndef condition group
match = re_cpp_ifndef.match(line)
if match:
cpp_group_stack.append(cpp_exclude)

# If outer group is excluding or macro is present, then exclude
macro = line.lstrip()[1:].split()[1]
cpp_exclude = cpp_exclude or macro in cpp_macros

# Start of #if condition group
match = re_cpp_if.match(line)
if match:
cpp_group_stack.append(cpp_exclude)

# XXX: Don't attempt to parse #if statements, but store the state.
# if/endif stack. For now, assume that these always fail.
cpp_exclude = False

# Complement #else condition group
match = re_cpp_else.match(line)
if match:
# Reverse the exclude state, if there is no outer exclude state
outer_grp_exclude = cpp_group_stack and cpp_group_stack[-1]
cpp_exclude = not cpp_exclude or outer_grp_exclude

# Restore exclude state when exiting conditional block
match = re_cpp_endif.match(line)
if match:
cpp_exclude = cpp_group_stack.pop()

# Skip lines inside of false condition blocks
if cpp_exclude:
continue

# Activate a new macro (ignoring the value)
match = re_cpp_define.match(line)
if match:
new_macro = line.lstrip()[1:].split()[1]
cpp_macros.append(new_macro)

# Deactivate a macro
match = re_cpp_undef.match(line)
if match:
new_macro = line.lstrip()[1:].split()[1]
try:
cpp_macros.remove(new_macro)
except:
# Ignore missing macros (for now?)
continue

match = re_module.match(line.lower())
if match:
if match.group(1) not in 'procedure': # avoid "module procedure" statements
Expand Down Expand Up @@ -404,8 +482,13 @@ parser.add_argument(
action='append',
help="Skip directory in source code search."
)
parser.add_argument(
'-D', '--define',
action='append',
help="Apply preprocessor define macros (of the form -DMACRO[=value])",
)
args = parser.parse_args()

# Do the thing
create_deps(args.path, args.skip, args.makefile, args.debug, args.exec_target,
args.fc_rule, args.link_externals, sys.argv[0])
args.fc_rule, args.link_externals, args.define)

0 comments on commit a2087bf

Please sign in to comment.