diff options
author | Luke T. Shumaker <lukeshu@lukeshu.com> | 2025-05-13 13:33:12 -0600 |
---|---|---|
committer | Luke T. Shumaker <lukeshu@lukeshu.com> | 2025-05-15 14:42:57 -0600 |
commit | a8c1de352ec10694b9b971bf111257ac5c9f0b71 (patch) | |
tree | fc9ce4234242ca4bcee2a0ca55be68da398cb43e | |
parent | 377e3b7e358a0912260860e095e390264b377e0c (diff) |
libmisc: macro.h: Tidy and test LM_FOREACH_TUPLE
-rw-r--r-- | libmisc/include/libmisc/macro.h | 61 | ||||
-rw-r--r-- | libmisc/include/libmisc/obj.h | 27 | ||||
-rw-r--r-- | libmisc/tests/test_macro.c | 80 |
3 files changed, 109 insertions, 59 deletions
diff --git a/libmisc/include/libmisc/macro.h b/libmisc/include/libmisc/macro.h index 9ac29b9..d7f6243 100644 --- a/libmisc/include/libmisc/macro.h +++ b/libmisc/include/libmisc/macro.h @@ -63,6 +63,7 @@ #define LM_FIRST(a, ...) a #define LM_SECOND(a, b, ...) b + #define LM_EAT(...) #define LM_EXPAND(...) __VA_ARGS__ @@ -80,57 +81,45 @@ /* tuples */ -#define LM_IS_TUPLE(x) LM_IS_SENTINEL(_LM_IS_TUPLE x) -#define _LM_IS_TUPLE(...) LM_SENTINEL() +#define LM_IS_TUPLE(x) LM_IS_SENTINEL(_LM_IS_TUPLE x) +#define _LM_IS_TUPLE(...) LM_SENTINEL() /* `tuples` is a sequence of `(tuple1)(tuple2)(tuple3)` */ -#define _LM_TUPLES_COMMA(tuple...) (tuple), -#define LM_TUPLES_NONEMPTY(tuples) LM_IS_TUPLE(_LM_TUPLES_COMMA tuples) +#define _LM_TUPLES_COMMA(...) (__VA_ARGS__), +#define LM_TUPLES_IS_NONEMPTY(tuples) LM_IS_TUPLE(_LM_TUPLES_COMMA tuples) #define LM_TUPLES_HEAD(tuples) LM_EXPAND(LM_FIRST LM_EAT() (_LM_TUPLES_COMMA tuples)) #define LM_TUPLES_TAIL(tuples) LM_EAT tuples /* iteration */ -/* BUG: LM_FOREACH_TUPLE maxes out at 1024 tuples. */ -#define LM_FOREACH_TUPLE(tuples, func, ...) \ - _LM_EVAL(_LM_FOREACH_TUPLE(tuples, func, __VA_ARGS__)) -#define _LM_FOREACH_TUPLE(tuples, func, ...) \ - LM_IF(LM_TUPLES_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 - -#define _LM_DEFER2(macro) macro LM_EAT LM_EAT()() - -#define _LM_EVAL(...) _LM_EVAL__1024(__VA_ARGS__) /* 1024 iterations aught to be enough for anybody */ -#define _LM_EVAL__1024(...) _LM_EVAL__512(_LM_EVAL__512(__VA_ARGS__)) -#define _LM_EVAL__512(...) _LM_EVAL__256(_LM_EVAL__256(__VA_ARGS__)) -#define _LM_EVAL__256(...) _LM_EVAL__128(_LM_EVAL__128(__VA_ARGS__)) -#define _LM_EVAL__128(...) _LM_EVAL__64(_LM_EVAL__64(__VA_ARGS__)) -#define _LM_EVAL__64(...) _LM_EVAL__32(_LM_EVAL__32(__VA_ARGS__)) -#define _LM_EVAL__32(...) _LM_EVAL__16(_LM_EVAL__16(__VA_ARGS__)) +/* 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. */ +#define _LM_EVAL _LM_EVAL__16 #define _LM_EVAL__16(...) _LM_EVAL__8(_LM_EVAL__8(__VA_ARGS__)) #define _LM_EVAL__8(...) _LM_EVAL__4(_LM_EVAL__4(__VA_ARGS__)) #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__ -/** The same as LM_FOREACH_TUPLE(), but callable from inside of LM_FOREACH_TUPLE(). */ -#define LM_FOREACH_TUPLE2(tuples, func, ...) \ - LM_IF(LM_TUPLES_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_TUPLE3(tuples, func, ...) \ - LM_IF(LM_TUPLES_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_TUPLE4(tuples, func, ...) \ - LM_IF(LM_TUPLES_NONEMPTY(tuples))( \ +#define _LM_DEFER2(macro) macro LM_EAT LM_EAT()() + +/** + * LM_FOREACH_TUPLE( (tup1) (tup2) (tup3), func, args...) calls + * func(args..., tup...) for each tuple. + * + * BUG: LM_FOREACH_TUPLE is limited to (16*2)-1=31 tuples. + */ +#define LM_FOREACH_TUPLE(tuples, func, ...) \ + _LM_EVAL(_LM_FOREACH_TUPLE(tuples, func, __VA_ARGS__)) +#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 + +/** 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__) #endif /* _LIBMISC_MACRO_H_ */ diff --git a/libmisc/include/libmisc/obj.h b/libmisc/include/libmisc/obj.h index d30a6f2..6645db7 100644 --- a/libmisc/include/libmisc/obj.h +++ b/libmisc/include/libmisc/obj.h @@ -56,17 +56,8 @@ #define _LO_IFACE_VTABLE_lo_nest(_ARG_child_iface_name) const struct _lo_##_ARG_child_iface_name##_vtable *_lo_##_ARG_child_iface_name##_vtable; LM_FOREACH_TUPLE2(_ARG_child_iface_name##_LO_IFACE, _LO_IFACE_VTABLE2) #define _LO_IFACE_VTABLE_lo_func(_ARG_ret_type, _ARG_func_name, ...) _ARG_ret_type (*_ARG_func_name)(void * __VA_OPT__(,) __VA_ARGS__); -#define _LO_IFACE_VTABLE2(_tuple_typ, ...) _LO_IFACE_VTABLE2_##_tuple_typ(__VA_ARGS__) -#define _LO_IFACE_VTABLE2_lo_nest(_ARG_child_iface_name) const struct _lo_##_ARG_child_iface_name##_vtable *_lo_##_ARG_child_iface_name##_vtable; LM_FOREACH_TUPLE3(_ARG_child_iface_name##_LO_IFACE, _LO_IFACE_VTABLE3) -#define _LO_IFACE_VTABLE2_lo_func(_ARG_ret_type, _ARG_func_name, ...) _ARG_ret_type (*_ARG_func_name)(void * __VA_OPT__(,) __VA_ARGS__); - -#define _LO_IFACE_VTABLE3(_tuple_typ, ...) _LO_IFACE_VTABLE3_##_tuple_typ(__VA_ARGS__) -#define _LO_IFACE_VTABLE3_lo_nest(_ARG_child_iface_name) const struct _lo_##_ARG_child_iface_name##_vtable *_lo_##_ARG_child_iface_name##_vtable; LM_FOREACH_TUPLE4(_ARG_child_iface_name##_LO_IFACE, _LO_IFACE_VTABLE4) -#define _LO_IFACE_VTABLE3_lo_func(_ARG_ret_type, _ARG_func_name, ...) _ARG_ret_type (*_ARG_func_name)(void * __VA_OPT__(,) __VA_ARGS__); - -#define _LO_IFACE_VTABLE4(_tuple_typ, ...) _LO_IFACE_VTABLE4_##_tuple_typ(__VA_ARGS__) -#define _LO_IFACE_VTABLE4_lo_nest(_ARG_child_iface_name) static_assert(0, "BUG: libmisc/obj.h cannot nest interfaces more than 4 deep"); -#define _LO_IFACE_VTABLE4_lo_func(_ARG_ret_type, _ARG_func_name, ...) _ARG_ret_type (*_ARG_func_name)(void * __VA_OPT__(,) __VA_ARGS__); +#define _LO_IFACE_VTABLE_indirect() _LO_IFACE_VTABLE +#define _LO_IFACE_VTABLE2(...) _LM_DEFER2(_LO_IFACE_VTABLE_indirect)()(__VA_ARGS__) #define _LO_IFACE_PROTO(_ARG_iface_name, _tuple_typ, ...) _LO_IFACE_PROTO_##_tuple_typ(_ARG_iface_name, __VA_ARGS__) #define _LO_IFACE_PROTO_lo_nest(_ARG_iface_name, _ARG_child_iface_name) \ @@ -157,17 +148,7 @@ #define _LO_IMPL_VTABLE_lo_nest(_ARG_impl_name, _ARG_child_iface_name) ._lo_##_ARG_child_iface_name##_vtable = &_lo_##_ARG_impl_name##_##_ARG_child_iface_name##_vtable, LM_FOREACH_TUPLE2(_ARG_child_iface_name##_LO_IFACE, _LO_IMPL_VTABLE2, _ARG_impl_name) #define _LO_IMPL_VTABLE_lo_func(_ARG_impl_name, _ARG_ret_type, _ARG_func_name, ...) ._ARG_func_name = (void*)_ARG_impl_name##_##_ARG_func_name, -#define _LO_IMPL_VTABLE2(_ARG_impl_name, _tuple_typ, ...) _LO_IMPL_VTABLE2_##_tuple_typ(_ARG_impl_name, __VA_ARGS__) -#define _LO_IMPL_VTABLE2_lo_nest(_ARG_impl_name, _ARG_child_iface_name) ._lo_##_ARG_child_iface_name##_vtable = &_lo_##_ARG_impl_name##_##_ARG_child_iface_name##_vtable, LM_FOREACH_TUPLE3(_ARG_child_iface_name##_LO_IFACE, _LO_IMPL_VTABLE3, _ARG_impl_name) -#define _LO_IMPL_VTABLE2_lo_func(_ARG_impl_name, _ARG_ret_type, _ARG_func_name, ...) ._ARG_func_name = (void*)_ARG_impl_name##_##_ARG_func_name, - -#define _LO_IMPL_VTABLE3(_ARG_impl_name, _tuple_typ, ...) _LO_IMPL_VTABLE3_##_tuple_typ(_ARG_impl_name, __VA_ARGS__) -#define _LO_IMPL_VTABLE3_lo_nest(_ARG_impl_name, _ARG_child_iface_name) ._lo_##_ARG_child_iface_name##_vtable = &_lo_##_ARG_impl_name##_##_ARG_child_iface_name##_vtable, LM_FOREACH_TUPLE4(_ARG_child_iface_name##_LO_IFACE, _LO_IMPL_VTABLE4, _ARG_impl_name) -#define _LO_IMPL_VTABLE3_lo_func(_ARG_impl_name, _ARG_ret_type, _ARG_func_name, ...) ._ARG_func_name = (void*)_ARG_impl_name##_##_ARG_func_name, - -#define _LO_IMPL_VTABLE4(_ARG_impl_name, _tuple_typ, ...) _LO_IMPL_VTABLE4_##_tuple_typ(_ARG_impl_name, __VA_ARGS__) -#define _LO_IMPL_VTABLE4_lo_nest(_ARG_impl_name, _ARG_child_iface_name) static_assert(0, "BUG: libmisc/obj.h cannot nest interfaces more than 4 deep"); -#define _LO_IMPL_VTABLE4_lo_func(_ARG_impl_name, _ARG_ret_type, _ARG_func_name, ...) ._ARG_func_name = (void*)_ARG_impl_name##_##_ARG_func_name, - +#define _LO_IMPL_VTABLE_indirect() _LO_IMPL_VTABLE +#define _LO_IMPL_VTABLE2(...) _LM_DEFER2(_LO_IMPL_VTABLE_indirect)()(__VA_ARGS__) #endif /* _LIBMISC_OBJ_H_ */ diff --git a/libmisc/tests/test_macro.c b/libmisc/tests/test_macro.c index 1320eb3..39a8b40 100644 --- a/libmisc/tests/test_macro.c +++ b/libmisc/tests/test_macro.c @@ -4,10 +4,34 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ +#include <stdlib.h> /* for free() */ +#include <string.h> /* for strcmp(), strlen(), memcmp(), strdup() */ + #include <libmisc/macro.h> #include "test.h" +/** Given `N` from `#define _LM_EVAL _LM_EVAL__{N}`, UNDER is `(N*2)-2`. (16*2)-2=30. */ +#define UNDER 30 +/** Given `N` from `#define _LM_EVAL _LM_EVAL__{N}`, OVER is `(N*2)-1`. (16*2)-1=31. */ +#define OVER 31 + +/** XUNDER is 0 through `UNDER` inclusive. */ +#define XUNDER \ + X(0) X(1) X(2) X(3) X(4) X(5) X(6) X(7) X(8) X(9) X(10) X(11) X(12) X(13) X(14) X(15) \ + X(16) X(17) X(18) X(19) X(20) X(21) X(22) X(23) X(24) X(25) X(26) X(27) X(28) X(29) X(30) +/** XUNDER is 0 through `OVER` inclusive. */ +#define XOVER XUNDER X(OVER) + +static char *without_spaces(const char *in) { + char *out = strdup(in); + for (size_t i = 0; out[i]; i++) + while (out[i] == ' ') + for (size_t j = i; out[j]; j++) + out[j] = out[j+1]; + return out; +} + int main() { printf("== LM_NEXT_POWER_OF_2 =====================================\n"); /* valid down to 0. */ @@ -50,5 +74,61 @@ int main() { /* ... */ test_assert(LM_FLOORLOG2(0xFFFFFFFFFFFFFFFF) == 63); + printf("== LM_TUPLE ===============================================\n"); + test_assert(LM_IF(LM_IS_TUPLE( 9 ))(0)(1)); + test_assert(LM_IF(LM_IS_TUPLE( a ))(0)(1)); + test_assert(LM_IF(LM_IS_TUPLE( () ))(1)(0)); + test_assert(LM_IF(LM_IS_TUPLE( (9) ))(1)(0)); + test_assert(LM_IF(LM_IS_TUPLE( (a) ))(1)(0)); + test_assert(LM_IF(LM_IS_TUPLE( (a, b) ))(1)(0)); + + printf("== LM_TUPLES ==============================================\n"); + test_assert(LM_IF(LM_TUPLES_IS_NONEMPTY( ))(0)(1)); + test_assert(LM_IF(LM_TUPLES_IS_NONEMPTY( () ))(1)(0)); + test_assert(LM_IF(LM_TUPLES_IS_NONEMPTY( (a) ))(1)(0)); + 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_TUPLE =======================================\n"); + /* Basic test. */ + { + #define FN(a, b) a "-" b + const char *str = LM_FOREACH_TUPLE( ("a") ("b") ("c"), FN, " "); + #undef FN + test_assert(strcmp(str, " -a -b -c") == 0); + } + + /* Test that it works with the documented limit of tuples. */ + { + #define X(n) (n) + #define FN(n) #n "\n" + const char *str = LM_FOREACH_TUPLE(XUNDER, FN); + #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_TUPLE(XOVER, FN)); + #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_TUPLE_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); + } + return 0; } |