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 <npitre@baylibre.com>
This commit is contained in:
Nicolas Pitre 2021-10-27 15:10:29 -04:00 committed by Anas Nashif
commit e3144ca68a
3 changed files with 23 additions and 14 deletions

View file

@ -67,9 +67,23 @@
#define __DEBRACKET(...) __VA_ARGS__ #define __DEBRACKET(...) __VA_ARGS__
/* Used by IS_EMPTY() */ /* Used by IS_EMPTY() */
#define Z_IS_EMPTY_(...) Z_IS_EMPTY__(__VA_ARGS__) /* reference: https://gustedt.wordpress.com/2010/06/08/detect-empty-macro-arguments/ */
#define Z_IS_EMPTY__(a, ...) Z_IS_EMPTY___(_ZZ##a##ZZ0, __VA_ARGS__) #define Z_HAS_COMMA(...) \
#define Z_IS_EMPTY___(...) GET_ARG_N(3, __VA_ARGS__) 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() */ /* Used by LIST_DROP_EMPTY() */
/* Adding ',' after each element would add empty element at the end of /* Adding ',' after each element would add empty element at the end of

View file

@ -210,15 +210,6 @@ extern "C" {
* This macro may be used with COND_CODE_1() and COND_CODE_0() while * This macro may be used with COND_CODE_1() and COND_CODE_0() while
* processing <tt>__VA_ARGS__</tt> to avoid processing empty arguments. * processing <tt>__VA_ARGS__</tt> 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: * Example:
* *
* #define EMPTY * #define EMPTY
@ -234,9 +225,9 @@ extern "C" {
* In above examples, the invocations of IS_EMPTY(...) return @p true, * In above examples, the invocations of IS_EMPTY(...) return @p true,
* @p false, and @p true; @p some_conditional_code is included. * @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 <tt>__VA_ARGS__</tt>)
*/ */
#define IS_EMPTY(a) Z_IS_EMPTY_(a, 1, 0,) #define IS_EMPTY(...) Z_IS_EMPTY_(__VA_ARGS__)
/** /**
* @brief Remove empty arguments from list. * @brief Remove empty arguments from list.

View file

@ -354,6 +354,10 @@ static void test_IS_EMPTY(void)
"Expected to be empty"); "Expected to be empty");
zassert_false(IS_EMPTY(test_IS_EMPTY_NOT_EMPTY), zassert_false(IS_EMPTY(test_IS_EMPTY_NOT_EMPTY),
"Expected to be non-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) static void test_LIST_DROP_EMPTY(void)