summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--libmisc/include/libmisc/macro.h20
-rw-r--r--libmisc/tests/test_macro.c6
-rwxr-xr-xlibmisc/wrap-cc192
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)