# # 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()