diff options
author | Luke T. Shumaker <lukeshu@lukeshu.com> | 2025-05-31 11:41:42 -0600 |
---|---|---|
committer | Luke T. Shumaker <lukeshu@lukeshu.com> | 2025-06-02 17:03:08 -0600 |
commit | cc8b1b019d2b1d9ff2f1b9fd0aab0cae6d7cf96e (patch) | |
tree | ca8a875b172f01afc97164c8462ab7549aae97e9 /libmisc/wrap-cc | |
parent | ce8ae41d677875adb45d99c351bcba108fb82a44 (diff) |
wip: libmisc: Move FOREACH into wrap-cclukeshu/wrap-foreach
Diffstat (limited to 'libmisc/wrap-cc')
-rwxr-xr-x | libmisc/wrap-cc | 192 |
1 files changed, 192 insertions, 0 deletions
diff --git a/libmisc/wrap-cc b/libmisc/wrap-cc index e7a0b91..f8d58c5 100755 --- a/libmisc/wrap-cc +++ b/libmisc/wrap-cc @@ -5,6 +5,7 @@ # SPDX-License-Identifier: AGPL-3.0-or-later import os +import re import subprocess import sys import typing @@ -175,6 +176,197 @@ def preprocess(all_args: list[str]) -> typing.NoReturn: os.execvp(arg0, [arg0, *common_flags, *output_flags, infile]) +def cpp_squash_linemarkers(text: str) -> str: + out = "" + buf_marker = "" + buf_body = "" + for line in text.splitlines(keepends=True): + if line.startswith("# "): + if buf_marker != line or buf_body.strip(): + out += buf_marker + if buf_body.strip(): + out += buf_body + buf_marker = line + buf_body = "" + else: + buf_body += line + if buf_body.strip(): + out += buf_marker + out += buf_body + return out + + +class Preprocessor: + _cpp: list[str] + _builtin_defines: list[str] | None = None + + def __init__(self, cpp: list[str]) -> None: + self._cpp = cpp + self._builtin_defines = None + + @property + def builtin_defines(self) -> list[str]: + if self._builtin_defines is None: + self._builtin_defines = subprocess.run( + [*self._cpp, "-quiet", "-undef", "-nostdinc", "-dM"], + stdin=subprocess.DEVNULL, + stdout=subprocess.PIPE, + stderr=sys.stderr, + check=True, + text=True, + ).stdout.splitlines(keepends=True) + return self._builtin_defines + + def process_file(self, infile: str, flags: list[str]) -> str: + # First/main preprocessor pass. + text = subprocess.run( + [*self._cpp, "-dD", *flags, infile], + stdin=subprocess.DEVNULL, + stdout=subprocess.PIPE, + stderr=sys.stderr, + check=True, + text=True, + ).stdout + + # Extra (subclass) processing. + text = self.extra(text) + + # Split the combined "-dD" output into "-dM" output and normal + # output. + macro_text = "" + normal_text = "" + for line in text.splitlines(keepends=True): + if line.startswith("#define ") or line.startswith("#undef "): + macro_text += line + normal_text += "\n" + else: + normal_text += line + return normal_text + + def process_fragment(self, before: str, text: str) -> str: + before_lines = before.splitlines(keepends=True) + builtin_defines = self.builtin_defines + prefix = "".join( + [ + line + for line in before_lines + if line.startswith("#") and line not in builtin_defines + ] + ) + prefix = cpp_squash_linemarkers(prefix) + + text = subprocess.run( + [ + *self._cpp, + "-quiet", + "-undef", + "-nostdinc", + "-dD", + ], + input=prefix + text, + stdout=subprocess.PIPE, + stderr=sys.stderr, + check=True, + text=True, + ).stdout + + text = cpp_squash_linemarkers(text) + text = text[text.index(prefix) + len(prefix) :] + + if text.startswith("# "): + text = "\n" + text + + text = self.extra(text) + + return text + + def extra(self, text: str) -> str: + return text + + +################################################################################ + + +class EnhancedPreprocessor(Preprocessor): + def process_file(self, infile: str, flags: list[str]) -> str: + return super().process_file(infile, ["-D__LIBMISC_ENHANCED_CPP__", *flags]) + + special_macros = [ + "LM_EVAL", + "LM_FOREACH_PARAM", + "LM_FOREACH_TUPLE", + ] + re_special = re.compile( + r"__xx_(?P<macro>" + + "|".join([re.escape(m) for m in special_macros]) + + r")_xx__\(" + ) + + def extra(self, text: str) -> str: + pos = 0 + while intro := self.re_special.search(text, pos): + nl = text.rfind("\n", 0, intro.start()) + if text[nl + 1] == "#": + pos = intro.end() + continue + + macro = intro.group("macro") + args: list[str] = [] + + def add_arg(arg: str) -> None: + args.append(arg) + + beg_paren = intro.end() - 1 + end_paren = cpp_scan_tuple(text, beg_paren, add_arg) + + before = text[: intro.start()] + # old = text[intro.start() : end_paren + 1] + after = text[end_paren + 1 :] + + new = self._eval_macro(before, macro, args) + + text = before + new + after + pos = len(before) + len(new) + return text + + def _eval_macro(self, before: str, macro: str, args: list[str]) -> str: + match macro: + case "LM_EVAL": # LM_EVAL(...) + ret = ",".join(args) + while True: + ret2 = self.process_fragment(before, ret) + if ret2 == ret: + break + ret = ret2 + return ret + case "LM_FOREACH_PARAM": # LM_FOREACH_PARAM(func, (fixedparams), params...) + assert len(args) >= 2 + func = args[0].strip() + fixedparams = args[1].strip()[1:-1].strip() + if fixedparams: + fixedparams += ", " + ret = "" + for param in args[2:]: + ret += f"{func}({fixedparams}{param})" + return ret + case "LM_FOREACH_TUPLE": # LM_FOREACH_TUPLE(tuples, func, fixedparams...) + tuples_str = args[0].lstrip() + func = args[1].strip() + fixedparams = "".join([a.strip() + ", " for a in args[2:]]) + ret = "" + while tuples_str: + end_paren = cpp_scan_tuple(tuples_str, 0) + tup = tuples_str[1:end_paren] + ret += f"{func}({fixedparams}{tup})" + tuples_str = tuples_str[end_paren + 1 :].lstrip() + return ret + case _: + raise ValueError(f"unknown macro: {macro}") + + +################################################################################ + + def main(all_args: list[str]) -> typing.NoReturn: if len(all_args) >= 2 and all_args[0].endswith("cc1") and all_args[1] == "-E": preprocess(all_args) |