summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--libmisc/include/libmisc/macro.h14
-rw-r--r--libmisc/tests/test_macro.c9
-rwxr-xr-xlibmisc/wrap-cc141
3 files changed, 150 insertions, 14 deletions
diff --git a/libmisc/include/libmisc/macro.h b/libmisc/include/libmisc/macro.h
index 2531587..48f52e5 100644
--- a/libmisc/include/libmisc/macro.h
+++ b/libmisc/include/libmisc/macro.h
@@ -170,4 +170,18 @@
/** The same as LM_FOREACH_TUPLE(), but callable from inside of LM_FOREACH_TUPLE(). */
#define LM_FOREACH_TUPLE2(...) _LM_DEFER2(_LM_FOREACH_TUPLE_indirect)()(__VA_ARGS__)
+/* CPP: wrap-cc extensions ****************************************************/
+
+#ifdef __LIBMISC_ENHANCED_CPP__
+/**
+ * `LM_DEFAPPEND(macro, val)` is like `#define macro val`, but can (1)
+ * be used from inside of a macro, and (2) appends to a value if it is
+ * already defined with LM_DEFAPPEND. There are lots of edge-cases,
+ * don't get cute.
+ */
+#define LM_DEFAPPEND(macro, ...) __xx__LM_DEFAPPEND__xx__(#macro, #__VA_ARGS__) LM_FORCE_SEMICOLON
+#define LM_DEFAPPEND_(macro, ...) _LM_DEFAPPEND_(#macro, __VA_ARGS__)
+#define _LM_DEFAPPEND_(macrostr, ...) __xx__LM_DEFAPPEND__xx__(macrostr, #__VA_ARGS__) LM_FORCE_SEMICOLON
+#endif
+
#endif /* _LIBMISC_MACRO_H_ */
diff --git a/libmisc/tests/test_macro.c b/libmisc/tests/test_macro.c
index 5157820..6810005 100644
--- a/libmisc/tests/test_macro.c
+++ b/libmisc/tests/test_macro.c
@@ -178,5 +178,14 @@ int main() {
free(act_suffix);
}
+ printf("== LM_DEFAPPEND ===========================================\n");
+ LM_DEFAPPEND(mylist, a);
+ LM_DEFAPPEND(mylist,
+ b);
+ {
+ const char *str = LM_STR_(mylist);
+ test_assert(strcmp(str, "a b") == 0);
+ }
+
return 0;
}
diff --git a/libmisc/wrap-cc b/libmisc/wrap-cc
index 5124689..e7a0b91 100755
--- a/libmisc/wrap-cc
+++ b/libmisc/wrap-cc
@@ -5,11 +5,70 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
import os
+import subprocess
import sys
import typing
+def scan_tuple(
+ text: str, beg: int, on_part: typing.Callable[[str], None] | None = None
+) -> int:
+ assert text[beg] == "("
+ pos = beg + 1
+ arg_start = pos
+ parens = 1
+ instring = False
+ while parens:
+ c = text[pos]
+ if instring:
+ match c:
+ case "\\":
+ pos += 1
+ case '"':
+ instring = False
+ else:
+ match c:
+ case "(":
+ parens += 1
+ case ")":
+ parens -= 1
+ if on_part and parens == 0 and text[beg + 1 : pos].strip():
+ on_part(text[arg_start:pos])
+ case ",":
+ if on_part and parens == 1:
+ on_part(text[arg_start:pos])
+ arg_start = pos + 1
+ case '"':
+ instring = True
+ pos += 1
+ assert text[pos - 1] == ")"
+ return pos - 1
+
+
+def unquote(cstr: str) -> str:
+ assert len(cstr) >= 2 and cstr[0] == '"' and cstr[-1] == '"'
+ cstr = cstr[1:-1]
+ out = ""
+ while cstr:
+ if cstr[0] == "\\":
+ match cstr[1]:
+ case "n":
+ out += "\n"
+ cstr = cstr[2:]
+ case "\\":
+ out += "\\"
+ cstr = cstr[2:]
+ case '"':
+ out += '"'
+ cstr = cstr[2:]
+ else:
+ out += cstr[0]
+ cstr = cstr[1:]
+ return out
+
+
def preprocess(all_args: list[str]) -> typing.NoReturn:
+ # argparse #################################################################
_args = all_args
def shift(n: int) -> list[str]:
@@ -19,47 +78,101 @@ def preprocess(all_args: list[str]) -> typing.NoReturn:
return ret
arg0 = shift(1)[0]
- flags: list[str] = []
+ common_flags: list[str] = []
+ output_flags: list[str] = []
positional: list[str] = []
while _args:
if len(_args[0]) > 2 and _args[0][0] == "-" and _args[0][1] in "IDU":
_args = [_args[0][:2], _args[0][2:], *_args[1:]]
match _args[0]:
# Mode
- case "-E" | "-quiet" | "-lang-asm":
- flags += shift(1)
+ case "-E" | "-quiet":
+ common_flags += shift(1)
+ case "-lang-asm":
+ os.execvp(all_args[0], all_args)
# Search path
case "-I" | "-imultilib" | "-isystem":
- flags += shift(2)
+ common_flags += shift(2)
# Define/Undefine
case "-D" | "-U":
- flags += shift(2)
+ common_flags += shift(2)
# Optimization
case "-O0" | "-O1" | "-O2" | "-O3" | "-Os" | "-Ofast" | "-Og" | "-Oz":
- flags += shift(1)
+ common_flags += shift(1)
case "-g":
- flags += shift(1)
+ common_flags += shift(1)
# Output files
case "-MD" | "-MF" | "-MT" | "-dumpbase" | "-dumpbase-ext":
- flags += shift(2)
+ output_flags += shift(2)
case "-o":
- flags += shift(2)
+ output_flags += shift(2)
# Other
case _:
if _args[0].startswith("-"):
if _args[0].startswith("-std="):
- flags += shift(1)
+ common_flags += shift(1)
elif _args[0].startswith("-m"):
- flags += shift(1)
+ common_flags += shift(1)
elif _args[0].startswith("-f"):
- flags += shift(1)
+ common_flags += shift(1)
elif _args[0].startswith("-W"):
- flags += shift(1)
+ common_flags += shift(1)
else:
raise ValueError(f"unknown flag: {_args!r}")
else:
positional += shift(1)
- os.execvp(arg0, [arg0, *flags, *positional])
+ if len(positional) != 1:
+ raise ValueError("expected 1 input file")
+ infile = positional[0]
+
+ # enhance ##################################################################
+
+ common_flags += ["-D", "__LIBMISC_ENHANCED_CPP__"]
+
+ text = subprocess.run(
+ [arg0, *common_flags, infile],
+ stdin=subprocess.DEVNULL,
+ stdout=subprocess.PIPE,
+ stderr=sys.stderr,
+ check=True,
+ text=True,
+ ).stdout
+
+ macros: dict[str, str] = {}
+
+ marker = "__xx__LM_DEFAPPEND__xx__"
+ pos = 0
+ while (marker_beg := text.find(marker, pos)) >= 0:
+ args: list[str] = []
+
+ def add_arg(arg: str) -> None:
+ nonlocal args
+ args.append(arg)
+
+ beg_paren = marker_beg + len(marker)
+ end_paren = scan_tuple(text, beg_paren, add_arg)
+
+ before = text[:marker_beg]
+ # old = text[marker_beg : end_paren + 1]
+ after = text[end_paren + 1 :]
+
+ assert len(args) == 2
+ k = unquote(args[0].strip())
+ v = unquote(args[1].strip())
+ if k not in macros:
+ macros[k] = v
+ else:
+ macros[k] += " " + v
+
+ text = before + after
+ pos = len(before)
+
+ common_flags += ["-D", marker + "=LM_EAT"]
+ for k, v in macros.items():
+ common_flags += ["-D", k + "=" + v]
+
+ # Run, for-real ############################################################
+ os.execvp(arg0, [arg0, *common_flags, *output_flags, infile])
def main(all_args: list[str]) -> typing.NoReturn: