diff options
Diffstat (limited to 'python/tsexpand.py')
-rw-r--r-- | python/tsexpand.py | 131 |
1 files changed, 131 insertions, 0 deletions
diff --git a/python/tsexpand.py b/python/tsexpand.py new file mode 100644 index 0000000..5d60997 --- /dev/null +++ b/python/tsexpand.py @@ -0,0 +1,131 @@ +#!/usr/bin/python3 +# SPDX-License-Identifier: MIT +# +# 2024 by David Lamparter +# +# this tool edits an FRR source .c file to expand the typesafe DECLARE_DLIST +# et al. definitions. This can be helpful to get better warnings/errors from +# GCC when something re. a typesafe container is involved. You can also use +# it on .h files. +# The actual expansions created by this tool are written to separate files +# called something like "lib/cspf__visited_tsexpand.h" (for a container named +# "visited") +# +# THIS TOOL EDITS THE FILE IN PLACE. MAKE A BACKUP IF YOU HAVE UNSAVED WORK +# IN PROGRESS (which is likely because you're debugging a typesafe container +# problem!) +# +# The PREDECL_XYZ is irrelevant for this tool, it needs to be run on the file +# that has the DECLARE_XYZ (can be .c or .h) +# +# the lines added by this tool all have /* $ts_expand: remove$ */ at the end +# you can undo the effects of this tool by calling sed: +# +# sed -e '/\$ts_expand: remove\$/ d' -i.orig filename.c + +import os +import sys +import re +import subprocess +import shlex + +decl_re = re.compile( + r"""(?<=\n)[ \t]*DECLARE_(LIST|ATOMLIST|DLIST|HEAP|HASH|(SORTLIST|SKIPLIST|RBTREE|ATOMSORT)_(NON)?UNIQ)\(\s*(?P<name>[^, \t\n]+)\s*,[^)]+\)\s*;[ \t]*\n""" +) +kill_re = re.compile(r"""(?<=\n)[^\n]*/\* \$ts_expand: remove\$ \*/\n""") + +src_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + +# some files may be compiled with different CPPFLAGS, that's not supported +# here... +cpp = subprocess.check_output( + ["make", "var-CPP", "var-AM_CPPFLAGS", "var-DEFS"], cwd=src_root +) +cpp = shlex.split(cpp.decode("UTF-8")) + + +def process_file(filename): + with open(filename, "r") as ifd: + data = ifd.read() + + data = kill_re.sub("", data) + + before = 0 + + dirname = os.path.dirname(filename) + basename = os.path.basename(filename).removesuffix(".c").removesuffix(".h") + + xname = filename + ".exp" + with open(filename + ".exp", "w") as ofd: + for m in decl_re.finditer(data): + s = m.start() + e = m.end() + ofd.write(data[before:s]) + + # start gcc/clang with some "magic" options to make it expand the + # typesafe macros, but nothing else. + # -P removes the "#line" markers (which are useless because + # everything ends up on one line anyway) + # -D_TYPESAFE_EXPAND_MACROS prevents the system header files + # (stddef.h, stdint.h, etc.) from being included and expanded + # -imacros loads the macro definitions from typesafe.h, but + # doesn't include any of the "plain text" (i.e. prototypes + # and outside-macro struct definitions) from it + # atomlist.h is sufficient because it includes typesafe.h which + # includes typerb.h, that's all of them + p_expand = subprocess.Popen( + cpp + + [ + "-P", + "-D_TYPESAFE_EXPAND_MACROS", + "-imacros", + "lib/atomlist.h", + "-", + ], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + cwd=src_root, + ) + # the output will look like shit, all on one line. format it. + p_format = subprocess.Popen( + ["clang-format", "-"], + stdin=p_expand.stdout, + stdout=subprocess.PIPE, + cwd=src_root, + ) + # pipe between cpp & clang-format needs to be closed + p_expand.stdout.close() + + # ... and finally, write the DECLARE_XYZ statement, and ONLY that + # statements. No headers, no other definitions. + p_expand.stdin.write(data[s:e].encode("UTF-8")) + p_expand.stdin.close() + + odata = b"" + while rd := p_format.stdout.read(): + odata = odata + rd + + p_expand.wait() + p_format.wait() + + # and now that we have the expanded text, write it out, put an + # #include in the .c file, and put "#if 0" around the original + # DECLARE_XYZ statement (otherwise it'll be duplicate...) + newname = os.path.join(dirname, f"{basename}__{m.group('name')}_tsexpand.h") + with open(newname, "wb") as nfd: + nfd.write(odata) + + ofd.write(f'#include "{newname}" /* $ts_expand: remove$ */\n') + ofd.write("#if 0 /* $ts_expand: remove$ */\n") + ofd.write(data[s:e]) + ofd.write("#endif /* $ts_expand: remove$ */\n") + before = e + + ofd.write(data[before:]) + + os.rename(xname, filename) + + +if __name__ == "__main__": + for filename in sys.argv[1:]: + process_file(filename) |