summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--libmisc/include/libmisc/macro.h46
-rw-r--r--libmisc/tests/test_macro.c48
2 files changed, 94 insertions, 0 deletions
diff --git a/libmisc/include/libmisc/macro.h b/libmisc/include/libmisc/macro.h
index d7f6243..a2d4264 100644
--- a/libmisc/include/libmisc/macro.h
+++ b/libmisc/include/libmisc/macro.h
@@ -64,6 +64,9 @@
#define LM_FIRST(a, ...) a
#define LM_SECOND(a, b, ...) b
+#define LM_FIRST_(...) LM_FIRST(__VA_ARGS__)
+#define LM_SECOND_(...) LM_SECOND(__VA_ARGS__)
+
#define LM_EAT(...)
#define LM_EXPAND(...) __VA_ARGS__
@@ -83,6 +86,25 @@
#define LM_IS_TUPLE(x) LM_IS_SENTINEL(_LM_IS_TUPLE x)
#define _LM_IS_TUPLE(...) LM_SENTINEL()
+/* For LM_IS_EMPTY_TUPLE:
+ *
+ * Given
+ *
+ * #define HELPER(...) B, __VA_OPT__(C,) D
+ *
+ * then evaluating the sequence of tokens `HELPER x , A` will give us a
+ * new sequence of tokens according to the following table:
+ *
+ * not a tuple : HELPER x , A
+ * tuple, nonempty: B , C , D , A
+ * tuple, empty : B , D , A
+ *
+ * Looking at this table, it is clear that we must look at the 2nd
+ * resulting comma-separated-value (argument), and set A=false,
+ * C=false, D=true (and B doesn't matter).
+ */
+#define LM_IS_EMPTY_TUPLE(x) LM_SECOND_(_LM_IS_EMPTY_TUPLE x, LM_F)
+#define _LM_IS_EMPTY_TUPLE(...) bogus, __VA_OPT__(LM_F,) LM_T
/* `tuples` is a sequence of `(tuple1)(tuple2)(tuple3)` */
#define _LM_TUPLES_COMMA(...) (__VA_ARGS__),
@@ -105,6 +127,30 @@
#define _LM_DEFER2(macro) macro LM_EAT LM_EAT()()
/**
+ * LM_FOREACH_PARAM(func, (fixedparams), params...) calls
+ * func(fixedparams..., param) for each param.
+ *
+ * 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__))
+#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__(,)
+#define _LM_FOREACH_PARAM_ITEM(func, fixedparams, param, ...) \
+ LM_IF(LM_IS_EMPTY_TUPLE(param))()( \
+ _LM_DEFER2(func)(_LM_FOREACH_PARAM_FIXEDPARAMS(fixedparams) param) \
+ _LM_DEFER2(_LM_FOREACH_PARAM_ITEM_indirect)()(func, fixedparams, __VA_ARGS__) \
+ )
+#define _LM_FOREACH_PARAM_ITEM_indirect() _LM_FOREACH_PARAM_ITEM
+
+/** The same as LM_FOREACH_PARAM(), but callable from inside of LM_FOREACH_PARAM(). */
+#define LM_FOREACH_PARAM2(...) _LM_DEFER2(_LM_FOREACH_PARAM_ITEM_indirect)()(__VA_ARGS__, ())
+
+/** The same as above, but evaluates the arguments first. */
+#define LM_FOREACH_PARAM_(...) LM_FOREACH_PARAM(__VA_ARGS__)
+#define LM_FOREACH_PARAM2_(...) LM_FOREACH_PARAM2(__VA_ARGS__)
+
+/**
* LM_FOREACH_TUPLE( (tup1) (tup2) (tup3), func, args...) calls
* func(args..., tup...) for each tuple.
*
diff --git a/libmisc/tests/test_macro.c b/libmisc/tests/test_macro.c
index 39a8b40..5157820 100644
--- a/libmisc/tests/test_macro.c
+++ b/libmisc/tests/test_macro.c
@@ -82,6 +82,13 @@ int main() {
test_assert(LM_IF(LM_IS_TUPLE( (a) ))(1)(0));
test_assert(LM_IF(LM_IS_TUPLE( (a, b) ))(1)(0));
+ test_assert(LM_IF(LM_IS_EMPTY_TUPLE( () ))(1)(0));
+ test_assert(LM_IF(LM_IS_EMPTY_TUPLE( 9 ))(0)(1));
+ test_assert(LM_IF(LM_IS_EMPTY_TUPLE( a ))(0)(1));
+ test_assert(LM_IF(LM_IS_EMPTY_TUPLE( (9) ))(0)(1));
+ test_assert(LM_IF(LM_IS_EMPTY_TUPLE( (a) ))(0)(1));
+ test_assert(LM_IF(LM_IS_EMPTY_TUPLE( (a, b) ))(0)(1));
+
printf("== LM_TUPLES ==============================================\n");
test_assert(LM_IF(LM_TUPLES_IS_NONEMPTY( ))(0)(1));
test_assert(LM_IF(LM_TUPLES_IS_NONEMPTY( () ))(1)(0));
@@ -89,6 +96,47 @@ int main() {
test_assert(LM_IF(LM_TUPLES_IS_NONEMPTY( (a)(b) ))(1)(0));
test_assert(LM_IF(LM_TUPLES_IS_NONEMPTY( (a)(b)(c) ))(1)(0));
+ printf("== LM_FOREACH_PARAM =======================================\n");
+ /* Basic test. */
+ {
+ #define FN(A, B) A "-" #B
+ const char *str = LM_FOREACH_PARAM(FN, (" "), a, (b), c);
+ #undef FN
+ test_assert(strcmp(str, " -a -(b) -c") == 0);
+ }
+
+ /* Test that it works with the documented limit of params. */
+ {
+ #define X(n) , n
+ #define FN(n) #n "\n"
+ const char *str = LM_FOREACH_PARAM_(FN, () XUNDER);
+ #undef FN
+ #undef X
+ #define X(n) #n "\n"
+ test_assert(strcmp(str, XUNDER) == 0);
+ #undef X
+ }
+
+ /* Test that it breaks at documented_limit+1 tuples. */
+ {
+ #define X(n) , n
+ #define FN(n) n
+ const char *str = LM_STR_(LM_FOREACH_PARAM_(FN, () XOVER));
+ #undef FN
+ #undef X
+ /* This comparison is a little extra complicated in
+ * order to not be sensitive to whitespace in the
+ * suffix. */
+ #define X(n) #n " "
+ const char *exp_prefix = XUNDER;
+ #undef X
+ const char *exp_suffix = "FN(" LM_STR_(OVER) ")_LM_FOREACH_PARAM_ITEM_indirect()(FN,(),())";
+ test_assert(strlen(exp_prefix) < strlen(str) && memcmp(exp_prefix, str, strlen(exp_prefix)) == 0);
+ char *act_suffix = without_spaces(&str[strlen(exp_prefix)]);
+ test_assert(strcmp(act_suffix, exp_suffix) == 0);
+ free(act_suffix);
+ }
+
printf("== LM_FOREACH_TUPLE =======================================\n");
/* Basic test. */
{