diff options
-rw-r--r-- | libmisc/include/libmisc/macro.h | 20 | ||||
-rw-r--r-- | libmisc/tests/test_macro.c | 6 | ||||
-rwxr-xr-x | libmisc/wrap-cc | 192 |
3 files changed, 218 insertions, 0 deletions
diff --git a/libmisc/include/libmisc/macro.h b/libmisc/include/libmisc/macro.h index 48f52e5..011d61f 100644 --- a/libmisc/include/libmisc/macro.h +++ b/libmisc/include/libmisc/macro.h @@ -116,6 +116,9 @@ /* CPP: iteration *************************************************************/ +#ifdef __LIBMISC_ENHANCED_CPP__ +#define _LM_EVAL(...) __xx_LM_EVAL_xx__(__VA_ARGS__) +#else /* The desire to support a high number of iterations is in competition * with the desire for short compile times. 16 is the lowest * power-of-2 for which the current code compiles. */ @@ -125,6 +128,7 @@ #define _LM_EVAL__4(...) _LM_EVAL__2(_LM_EVAL__2(__VA_ARGS__)) #define _LM_EVAL__2(...) _LM_EVAL__1(_LM_EVAL__1(__VA_ARGS__)) #define _LM_EVAL__1(...) __VA_ARGS__ +#endif #define _LM_DEFER2(macro) macro LM_EAT LM_EAT()() @@ -135,6 +139,9 @@ * BUG: LM_FOREACH_PARAM is limited to (16*2)-1=31 params. */ #define LM_FOREACH_PARAM(func, fixedparams, ...) _LM_EVAL(_LM_FOREACH_PARAM(func, fixedparams, __VA_ARGS__)) +#ifdef __LIBMISC_ENHANCED_CPP__ +#define _LM_FOREACH_PARAM(func, fixedparams, ...) __xx_LM_FOREACH_PARAM_xx__(func, fixedparams __VA_OPT__(,) __VA_ARGS__) +#else #define _LM_FOREACH_PARAM(func, fixedparams, ...) _LM_FOREACH_PARAM_ITEM(func, fixedparams, __VA_ARGS__, ()) #define _LM_FOREACH_PARAM_FIXEDPARAMS(fixedparams) _LM_FOREACH_PARAM_FIXEDPARAMS_inner fixedparams #define _LM_FOREACH_PARAM_FIXEDPARAMS_inner(...) __VA_ARGS__ __VA_OPT__(,) @@ -144,9 +151,14 @@ _LM_DEFER2(_LM_FOREACH_PARAM_ITEM_indirect)()(func, fixedparams, __VA_ARGS__) \ ) #define _LM_FOREACH_PARAM_ITEM_indirect() _LM_FOREACH_PARAM_ITEM +#endif /** The same as LM_FOREACH_PARAM(), but callable from inside of LM_FOREACH_PARAM(). */ +#ifdef __LIBMISC_ENHANCED_CPP__ +#define LM_FOREACH_PARAM2 _LM_FOREACH_PARAM +#else #define LM_FOREACH_PARAM2(...) _LM_DEFER2(_LM_FOREACH_PARAM_ITEM_indirect)()(__VA_ARGS__, ()) +#endif /** The same as above, but evaluates the arguments first. */ #define LM_FOREACH_PARAM_(...) LM_FOREACH_PARAM(__VA_ARGS__) @@ -160,15 +172,23 @@ */ #define LM_FOREACH_TUPLE(tuples, func, ...) \ _LM_EVAL(_LM_FOREACH_TUPLE(tuples, func, __VA_ARGS__)) +#ifdef __LIBMISC_ENHANCED_CPP__ +#define _LM_FOREACH_TUPLE(tuples, func, ...) __xx_LM_FOREACH_TUPLE_xx__(tuples, func __VA_OPT__(,) __VA_ARGS__) +#else #define _LM_FOREACH_TUPLE(tuples, func, ...) \ LM_IF(LM_TUPLES_IS_NONEMPTY(tuples))( \ _LM_DEFER2(func)(__VA_ARGS__ __VA_OPT__(,) LM_EXPAND LM_TUPLES_HEAD(tuples)) \ _LM_DEFER2(_LM_FOREACH_TUPLE_indirect)()(LM_TUPLES_TAIL(tuples), func, __VA_ARGS__) \ )() #define _LM_FOREACH_TUPLE_indirect() _LM_FOREACH_TUPLE +#endif /** The same as LM_FOREACH_TUPLE(), but callable from inside of LM_FOREACH_TUPLE(). */ +#ifdef __LIBMISC_ENHANCED_CPP__ +#define LM_FOREACH_TUPLE2 _LM_FOREACH_TUPLE +#else #define LM_FOREACH_TUPLE2(...) _LM_DEFER2(_LM_FOREACH_TUPLE_indirect)()(__VA_ARGS__) +#endif /* CPP: wrap-cc extensions ****************************************************/ diff --git a/libmisc/tests/test_macro.c b/libmisc/tests/test_macro.c index 6810005..95a9f32 100644 --- a/libmisc/tests/test_macro.c +++ b/libmisc/tests/test_macro.c @@ -23,6 +23,7 @@ /** XUNDER is 0 through `OVER` inclusive. */ #define XOVER XUNDER X(OVER) +#ifndef __LIBMISC_ENHANCED_CPP__ static char *without_spaces(const char *in) { char *out = strdup(in); for (size_t i = 0; out[i]; i++) @@ -31,6 +32,7 @@ static char *without_spaces(const char *in) { out[j] = out[j+1]; return out; } +#endif int main() { printf("== LM_NEXT_POWER_OF_2 =====================================\n"); @@ -118,6 +120,7 @@ int main() { } /* Test that it breaks at documented_limit+1 tuples. */ +#ifndef __LIBMISC_ENHANCED_CPP__ { #define X(n) , n #define FN(n) n @@ -136,6 +139,7 @@ int main() { test_assert(strcmp(act_suffix, exp_suffix) == 0); free(act_suffix); } +#endif printf("== LM_FOREACH_TUPLE =======================================\n"); /* Basic test. */ @@ -159,6 +163,7 @@ int main() { } /* Test that it breaks at documented_limit+1 tuples. */ +#ifndef __LIBMISC_ENHANCED_CPP__ { #define X(n) (n) #define FN(n) n @@ -177,6 +182,7 @@ int main() { test_assert(strcmp(act_suffix, exp_suffix) == 0); free(act_suffix); } +#endif printf("== LM_DEFAPPEND ===========================================\n"); LM_DEFAPPEND(mylist, a); 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) |