diff --git a/include/sys/util.h b/include/sys/util.h index 8024cf691e5..a53265e8008 100644 --- a/include/sys/util.h +++ b/include/sys/util.h @@ -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 */ diff --git a/tests/unit/util/main.c b/tests/unit/util/main.c index 8c971ed01f8..b32f24db7c5 100644 --- a/tests/unit/util/main.c +++ b/tests/unit/util/main.c @@ -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);