#
# include_preprocessor.py
#
# Short pyparsing script to perform #include inclusions similar to the C preprocessor
#
# Copyright 2019, Paul McGuire
#
import pyparsing as pp
from pathlib import Path

# parser elements to be used to assemble into #include parser
SEMI = pp.Suppress(";")
INCLUDE = pp.Keyword("#include")
quoted_string = pp.quotedString.addParseAction(pp.removeQuotes)
file_ref = quoted_string | pp.Word(pp.printables, excludeChars=";")

# parser for parsing "#include xyz.dat;" directives
include_directive = INCLUDE + file_ref("include_file_name") + SEMI

# add parse action that will recursively pull in included files - when
# using transformString, the value returned from the parse action will replace
# the text matched by the attached expression
seen = set()


def read_include_contents(s, l, t):
    include_file_ref = t.include_file_name
    include_echo = "/* {} */".format(pp.line(l, s).strip())

    # guard against recursive includes
    if include_file_ref not in seen:
        seen.add(include_file_ref)
        included_file_contents = Path(include_file_ref).read_text()
        return (
            include_echo
            + "\n"
            + include_directive.transformString(included_file_contents)
        )
    else:
        lead = " " * (pp.col(l, s) - 1)
        return "/* recursive include! */\n{}{}".format(lead, include_echo)


# attach include processing method as parse action (parse-time callback)
# to include_directive expression
include_directive.addParseAction(read_include_contents)


if __name__ == "__main__":

    # demo

    # create test files:
    # - a.txt includes b.txt
    # - b.txt includes c.txt
    # - c.txt includes b.txt (must catch infinite recursion)
    Path("a.txt").write_text(
        """\
        /* a.txt */
        int i;

        /* sometimes included files aren't in quotes */
        #include b.txt;
        """
    )

    Path("b.txt").write_text(
        """\
        i = 100;
        #include 'c.txt';
        """
    )

    Path("c.txt").write_text(
        """\
        i += 1;

        /* watch out! this might be recursive if this file included by b.txt */
        #include b.txt;
        """
    )

    # use include_directive.transformString to perform includes

    # read contents of original file
    initial_file = Path("a.txt").read_text()

    # print original file
    print(initial_file)
    print("-----------------")

    # expand includes in source file (and any included files) and print the result
    expanded_source = include_directive.transformString(initial_file)
    print(expanded_source)

    # clean up
    for fname in "a.txt b.txt c.txt".split():
        Path(fname).unlink()
