From e3144ca68a626314e4d4bbd285b0467a31b93b9c Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Wed, 27 Oct 2021 15:10:29 -0400 Subject: [PATCH] sys: util: improve IS_EMPTY() implementation The current implementation relies on preprocessor concatenation to work. This makes it incompatible with any content which expansion is not a valid preprocessor token such as strings, pointers, etc. and therefore limits its usefulness. Replace it with an implementation that can cope with all cases. Signed-off-by: Nicolas Pitre --- include/sys/util_internal.h | 20 +++++++++++++++++--- include/sys/util_macro.h | 13 ++----------- tests/unit/util/test.inc | 4 ++++ 3 files changed, 23 insertions(+), 14 deletions(-) diff --git a/include/sys/util_internal.h b/include/sys/util_internal.h index 3b70ec3deb7..45fd7af5766 100644 --- a/include/sys/util_internal.h +++ b/include/sys/util_internal.h @@ -67,9 +67,23 @@ #define __DEBRACKET(...) __VA_ARGS__ /* Used by IS_EMPTY() */ -#define Z_IS_EMPTY_(...) Z_IS_EMPTY__(__VA_ARGS__) -#define Z_IS_EMPTY__(a, ...) Z_IS_EMPTY___(_ZZ##a##ZZ0, __VA_ARGS__) -#define Z_IS_EMPTY___(...) GET_ARG_N(3, __VA_ARGS__) +/* reference: https://gustedt.wordpress.com/2010/06/08/detect-empty-macro-arguments/ */ +#define Z_HAS_COMMA(...) \ + NUM_VA_ARGS_LESS_1_IMPL(__VA_ARGS__, 1, 1, 1, 1, 1, 1, 1, 1, \ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, \ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, \ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0) +#define Z_TRIGGER_PARENTHESIS_(...) , +#define Z_IS_EMPTY_(...) \ + Z_IS_EMPTY__( \ + Z_HAS_COMMA(__VA_ARGS__), \ + Z_HAS_COMMA(Z_TRIGGER_PARENTHESIS_ __VA_ARGS__), \ + Z_HAS_COMMA(__VA_ARGS__ (/*empty*/)), \ + Z_HAS_COMMA(Z_TRIGGER_PARENTHESIS_ __VA_ARGS__ (/*empty*/))) +#define Z_CAT5(_0, _1, _2, _3, _4) _0 ## _1 ## _2 ## _3 ## _4 +#define Z_IS_EMPTY__(_0, _1, _2, _3) \ + Z_HAS_COMMA(Z_CAT5(Z_IS_EMPTY_CASE_, _0, _1, _2, _3)) +#define Z_IS_EMPTY_CASE_0001 , /* Used by LIST_DROP_EMPTY() */ /* Adding ',' after each element would add empty element at the end of diff --git a/include/sys/util_macro.h b/include/sys/util_macro.h index 8c3791762d4..fb212919502 100644 --- a/include/sys/util_macro.h +++ b/include/sys/util_macro.h @@ -210,15 +210,6 @@ extern "C" { * This macro may be used with COND_CODE_1() and COND_CODE_0() while * processing __VA_ARGS__ to avoid processing empty arguments. * - * Note that this macro is intended to check macro names that evaluate - * to replacement lists being empty or containing numbers or macro name - * like tokens. - * - * @note Not all arguments are accepted by this macro and compilation will fail - * if argument cannot be concatenated with literal constant. That will - * happen if argument does not start with letter or number. Example - * arguments that will fail during compilation: .arg, (arg), "arg", {arg}. - * * Example: * * #define EMPTY @@ -234,9 +225,9 @@ extern "C" { * In above examples, the invocations of IS_EMPTY(...) return @p true, * @p false, and @p true; @p some_conditional_code is included. * - * @param a macro to check for emptiness + * @param ... macro to check for emptiness (may be __VA_ARGS__) */ -#define IS_EMPTY(a) Z_IS_EMPTY_(a, 1, 0,) +#define IS_EMPTY(...) Z_IS_EMPTY_(__VA_ARGS__) /** * @brief Remove empty arguments from list. diff --git a/tests/unit/util/test.inc b/tests/unit/util/test.inc index b39f76f9eee..4489a29ddf7 100644 --- a/tests/unit/util/test.inc +++ b/tests/unit/util/test.inc @@ -354,6 +354,10 @@ static void test_IS_EMPTY(void) "Expected to be empty"); zassert_false(IS_EMPTY(test_IS_EMPTY_NOT_EMPTY), "Expected to be non-empty"); + zassert_false(IS_EMPTY("string"), + "Expected to be non-empty"); + zassert_false(IS_EMPTY(&test_IS_EMPTY), + "Expected to be non-empty"); } static void test_LIST_DROP_EMPTY(void)