sys/util.h: Add IS_EMPTY and LIST_DROP_EMPTY macros

IS_EMPTY macro allows to check if defined name is empty, i.e.
does not contain replacement list.
LIST_DROP_EMPTY macro may be used to process __VA_ARGS__ type lists,
e.g. a,b,,c , and remove empty elements.

Signed-off-by: Dominik Ermel <dominik.ermel@nordicsemi.no>
This commit is contained in:
Dominik Ermel 2020-04-30 07:35:21 +00:00 committed by Carles Cufí
commit 6ba69f19d4
2 changed files with 128 additions and 1 deletions

View file

@ -345,6 +345,81 @@ u8_t u8_to_dec(char *buf, u8_t buflen, u8_t value);
*/
#define IF_ENABLED(_flag, _code) \
COND_CODE_1(_flag, _code, ())
/**
* @brief Check if defined name does have replacement string.
*
* If defined macro has value this will return true, otherwise
* it will return false. It only works with defined macros, so additional
* test (if defined) may be needed in some cases.
*
* This macro may be used, with COND_CODE_* macros, while processing
* __VA_ARG__ to avoid processing empty arguments.
*
* Note that this macro is indented to check macro names that evaluate
* to replacement lists being empty or containing numbers or macro name
* like tokens.
*
* Example:
*
* #define EMPTY
* #define NON_EMPTY 1
* #undef UNDEFINED
* IS_EMPTY(EMPTY)
* IS_EMPTY(NON_EMPTY)
* IS_EMPTY(UNDEFINED)
* #if defined(EMPTY) && IS_EMPTY(EMPTY) == true
* ...
* #endif
*
* In above examples, the invocations of IS_EMPTY(...) will return: true,
* false, true and conditional code will be included.
*
* @param a Makro to check
*/
#define IS_EMPTY(a) Z_IS_EMPTY_(a, true, false,)
#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___(...) Z_IS_EMPTY____(GET_ARGS_LESS_1(__VA_ARGS__))
#define Z_IS_EMPTY____(...) GET_ARG2(__VA_ARGS__)
/**
* @brief Remove empty arguments from list.
*
* Due to evaluation, __VA_ARGS__ and other preprocessor generated lists
* may contain empty elements, e.g.:
*
* #define LIST ,a,b,,d,
*
* In above example the first, the element between b and d, and the last
* are empty.
* When processing such lists, by for-each type loops, all empty elements
* will be processed, and may require filtering out within a loop.
* To make that process easier, it is enough to invoke LIST_DROP_EMPTY
* which will remove all empty elements from list.
*
* Example:
* LIST_DROP_EMPTY(list)
* will return:
* a,b,d
* Notice that ',' are preceded by space.
*
* @param ... list to be processed
*/
#define LIST_DROP_EMPTY(...) \
Z_LIST_DROP_FIRST(FOR_EACH(Z_LIST_NO_EMPTIES, __VA_ARGS__))
/* Adding ',' after each element would add empty element at the end of
* list, which is hard to remove, so instead precede each element with ',',
* this way first element is empty, and this one is easy to drop.
*/
#define Z_LIST_ADD_ELEM(e) EMPTY, e
#define Z_LIST_DROP_FIRST(...) GET_ARGS_LESS_1(__VA_ARGS__)
#define Z_LIST_NO_EMPTIES(e) \
COND_CODE_1(IS_EMPTY(e), (), (Z_LIST_ADD_ELEM(e)))
/**
* @brief Insert code depending on result of flag evaluation.
*
@ -376,6 +451,25 @@ u8_t u8_to_dec(char *buf, u8_t buflen, u8_t value);
*/
#define __GET_ARG2_DEBRACKET(ignore_this, val, ...) __DEBRACKET val
/**
* @brief Macro with empty replacement list
*
* This trivial definition is provided to use where macro is expected
* to evaluate to empty replacement string or when it is needed to
* cheat checkpatch.
*
* Examples
*
* #define LIST_ITEM(n) , item##n
*
* would cause error with checkpatch, but:
*
* #define LIST_TIEM(n) EMPTY, item##m
*
* would not.
*/
#define EMPTY
/**
* @brief Get first argument from variable list of arguments
*/

View file

@ -288,6 +288,37 @@ static void test_FOR_EACH_IDX_FIXED_ARG(void)
zassert_equal(a2, 3, "Unexpected value %d", a2);
}
static void test_IS_EMPTY(void)
{
#define test_IS_EMPTY_REAL_EMPTY
#define test_IS_EMPTY_NOT_EMPTY XXX_DO_NOT_REPLACE_XXX
zassert_true(IS_EMPTY(test_IS_EMPTY_REAL_EMPTY),
"Expected to be empty");
zassert_false(IS_EMPTY(test_IS_EMPTY_NOT_EMPTY),
"Expected to be non-empty");
}
static void test_LIST_DROP_EMPTY(void)
{
/*
* The real definition should be:
* #define TEST_BROKEN_LIST ,Henry,,Dorsett,Case,
* but checkpatch complains, so below equivalent is defined.
*/
#define TEST_BROKEN_LIST EMPTY, Henry, EMPTY, Dorsett, Case,
#define TEST_FIXED_LIST LIST_DROP_EMPTY(TEST_BROKEN_LIST)
#define TEST_MKSTR(a) #a,
static const char *const arr[] = {
FOR_EACH(TEST_MKSTR, TEST_FIXED_LIST)
};
zassert_equal(sizeof(arr) / sizeof(char *), 3,
"Failed to cleanup list");
zassert_equal(strcmp(arr[0], "Henry"), 0, "Failed at 0");
zassert_equal(strcmp(arr[1], "Dorsett"), 0, "Failed at 1");
zassert_equal(strcmp(arr[2], "Case"), 0, "Failed at 0");
}
void test_main(void)
{
ztest_test_suite(test_lib_sys_util_tests,
@ -303,7 +334,9 @@ void test_main(void)
ztest_unit_test(test_FOR_EACH),
ztest_unit_test(test_FOR_EACH_FIXED_ARG),
ztest_unit_test(test_FOR_EACH_IDX),
ztest_unit_test(test_FOR_EACH_IDX_FIXED_ARG)
ztest_unit_test(test_FOR_EACH_IDX_FIXED_ARG),
ztest_unit_test(test_IS_EMPTY),
ztest_unit_test(test_LIST_DROP_EMPTY)
);
ztest_run_test_suite(test_lib_sys_util_tests);