From 05b71b9dc22823722bee6713ee28bb4c3f502112 Mon Sep 17 00:00:00 2001 From: Abtin Keshavarzian Date: Tue, 13 Oct 2020 00:18:05 -0700 Subject: [PATCH] [common] macros to get first/second/rest of variadic arguments (#5650) This commit adds three macros in a new header `arg_macros.hpp` `OT_GET_FIRST()`, `OT_GET_RESET(...)`, and `OT_GET_SECOND()`. These macros help with parsing variadic arguments passed to a function/macro and can handle the case where variadic macro arguments is empty without requiring toolchain specific behavior/support. This commit also adds a unit test `test_macros` for the newly added macros. --- src/core/BUILD.gn | 1 + src/core/Makefile.am | 1 + src/core/common/arg_macros.hpp | 103 +++++++++++++++++++++++++++ tests/unit/CMakeLists.txt | 22 ++++++ tests/unit/Makefile.am | 4 ++ tests/unit/test_macros.cpp | 124 +++++++++++++++++++++++++++++++++ 6 files changed, 255 insertions(+) create mode 100644 src/core/common/arg_macros.hpp create mode 100644 tests/unit/test_macros.cpp diff --git a/src/core/BUILD.gn b/src/core/BUILD.gn index 18790dfeb..eabb75613 100644 --- a/src/core/BUILD.gn +++ b/src/core/BUILD.gn @@ -342,6 +342,7 @@ openthread_core_files = [ "coap/coap_message.hpp", "coap/coap_secure.cpp", "coap/coap_secure.hpp", + "common/arg_macros.hpp", "common/bit_vector.hpp", "common/clearable.hpp", "common/code_utils.hpp", diff --git a/src/core/Makefile.am b/src/core/Makefile.am index 3897a1607..5e2a8ec13 100644 --- a/src/core/Makefile.am +++ b/src/core/Makefile.am @@ -339,6 +339,7 @@ HEADERS_COMMON = \ coap/coap.hpp \ coap/coap_message.hpp \ coap/coap_secure.hpp \ + common/arg_macros.hpp \ common/bit_vector.hpp \ common/clearable.hpp \ common/code_utils.hpp \ diff --git a/src/core/common/arg_macros.hpp b/src/core/common/arg_macros.hpp new file mode 100644 index 000000000..e832f2f30 --- /dev/null +++ b/src/core/common/arg_macros.hpp @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2020, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file + * This file includes macros for parsing variadic arguments. + */ + +#ifndef OT_ARG_MACROS_HPP_ +#define OT_ARG_MACROS_HPP_ + +/** + * This macro returns the first argument in a list of input arguments. + * + * @param[in] ... A list of arguments (MUST contain at least one). + * + * @returns The first argument in the given list of input arguments. + * + */ +#define OT_FIRST_ARG(...) _OT_GET_FIRST_ARG(__VA_ARGS__, JUNK) + +/** + * This macro returns the second argument in a list of input arguments if any. + * + * @note: This macro works when the list contains either one or two arguments. + * + * @param[in] ... A list of arguments (MUST contain either one or two arguments). + * + * @returns The second argument if any. + * + */ +#define OT_SECOND_ARG(...) _OT_GET_SECOND_ARG(_OT_HAS_ONE_ARG_OR_TWO_ARGS(__VA_ARGS__), __VA_ARGS__) + +/** + * This macro expands to comma separated list of arguments excluding the first one. + * + * If there is only one argument, it expands to empty. If there is more than one argument, it expands to an initial + * comma followed by all the rest of arguments excluding the first one. + * + * @note: This macro supports up to 20 arguments. + * + * @param[in] ... A list of arguments (MUST contain at least one). + * + * @returns A comma separated list of arguments excluding the first one. + * + */ +#define OT_REST_ARGS(...) _OT_GET_REST_ARGS(_OT_HAS_ONE_ARG_VS_TWO_OR_MORE(__VA_ARGS__), __VA_ARGS__) + +//--------------------------------------------------------------------------------------------------------------------- +// Private/local macros - for use in this header only. + +#define _OT_GET_FIRST_ARG(aFirst, ...) aFirst + +#define _OT_HAS_ONE_ARG_OR_TWO_ARGS(...) _OT_SELECT_3(__VA_ARGS__, TWO_ARGS, ONE_ARG, JUNK) +#define _OT_SELECT_3(a1, a2, a3, ...) a3 + +#define _OT_GET_SECOND_ARG(aNum, ...) _OT_GET_SECOND_APPEND_NUM_ARGS(aNum, __VA_ARGS__) +#define _OT_GET_SECOND_APPEND_NUM_ARGS(aNum, ...) _OT_GET_SECOND_WITH_##aNum(__VA_ARGS__) + +#define _OT_GET_SECOND_WITH_ONE_ARG(aFirst) +#define _OT_GET_SECOND_WITH_TWO_ARGS(aFirst, aSecond) aSecond + +#define _OT_GET_REST_ARGS(aNum, ...) _OT_GET_REST_APPEND_NUM_ARGS(aNum, __VA_ARGS__) +#define _OT_GET_REST_APPEND_NUM_ARGS(aNum, ...) _OT_GET_REST_WITH_##aNum(__VA_ARGS__) + +#define _OT_GET_REST_WITH_ONE_ARG(aFirst) +#define _OT_GET_REST_WITH_TWO_OR_MORE_ARGS(aFirst, ...) , __VA_ARGS__ + +#define _OT_HAS_ONE_ARG_VS_TWO_OR_MORE(...) \ + _OT_SELECT_20(__VA_ARGS__, TWO_OR_MORE_ARGS, TWO_OR_MORE_ARGS, TWO_OR_MORE_ARGS, TWO_OR_MORE_ARGS, \ + TWO_OR_MORE_ARGS, TWO_OR_MORE_ARGS, TWO_OR_MORE_ARGS, TWO_OR_MORE_ARGS, TWO_OR_MORE_ARGS, \ + TWO_OR_MORE_ARGS, TWO_OR_MORE_ARGS, TWO_OR_MORE_ARGS, TWO_OR_MORE_ARGS, TWO_OR_MORE_ARGS, \ + TWO_OR_MORE_ARGS, TWO_OR_MORE_ARGS, TWO_OR_MORE_ARGS, TWO_OR_MORE_ARGS, ONE_ARG, JUNK) + +#define _OT_SELECT_20(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, ...) \ + a20 + +#endif // OT_ARG_MACROS_HPP_ diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index 410714a61..e411ddcff 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -362,6 +362,27 @@ target_link_libraries(test-mac-frame add_test(NAME test-mac-frame COMMAND test-mac-frame) +add_executable(test-macros + test_macros.cpp +) + +target_include_directories(test-macros + PRIVATE + ${COMMON_INCLUDES} +) + +target_compile_options(test-macros + PRIVATE + ${COMMON_COMPILE_OPTIONS} +) + +target_link_libraries(test-macros + PRIVATE + ${COMMON_LIBS} +) + +add_test(NAME test-macros COMMAND test-macros) + add_executable(test-message test_message.cpp ) @@ -632,6 +653,7 @@ set_target_properties( test-lookup-table test-lowpan test-mac-frame + test-macros test-message test-message-queue test-multicast-listeners-table diff --git a/tests/unit/Makefile.am b/tests/unit/Makefile.am index 1e4a26872..cc4e76ef9 100644 --- a/tests/unit/Makefile.am +++ b/tests/unit/Makefile.am @@ -120,6 +120,7 @@ check_PROGRAMS += \ test-lookup-table \ test-lowpan \ test-mac-frame \ + test-macros \ test-message \ test-message-queue \ test-multicast-listeners-table \ @@ -217,6 +218,9 @@ test_lowpan_SOURCES = $(COMMON_SOURCES) test_lowpan.cpp test_mac_frame_LDADD = $(COMMON_LDADD) test_mac_frame_SOURCES = $(COMMON_SOURCES) test_mac_frame.cpp +test_macros_LDADD = $(COMMON_LDADD) +test_macros_SOURCES = $(COMMON_SOURCES) test_macros.cpp + test_message_LDADD = $(COMMON_LDADD) test_message_SOURCES = $(COMMON_SOURCES) test_message.cpp diff --git a/tests/unit/test_macros.cpp b/tests/unit/test_macros.cpp new file mode 100644 index 000000000..b4068538e --- /dev/null +++ b/tests/unit/test_macros.cpp @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2020, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "test_platform.h" + +#include "common/arg_macros.hpp" + +static constexpr uint8_t NumberOfArgs(void) +{ + return 0; +} + +static constexpr uint8_t NumberOfArgs(uint8_t) +{ + return 1; +} + +static constexpr uint8_t NumberOfArgs(uint8_t, uint8_t) +{ + return 2; +} + +static constexpr uint8_t NumberOfArgs(uint8_t, uint8_t, uint8_t) +{ + return 3; +} + +static constexpr uint8_t NumberOfArgs(uint8_t, uint8_t, uint8_t, uint8_t) +{ + return 4; +} + +static constexpr uint8_t NumberOfArgs(uint8_t, uint8_t, uint8_t, uint8_t, uint8_t) +{ + return 5; +} + +static constexpr uint8_t NumberOfArgs(uint8_t, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t) +{ + return 6; +} + +static constexpr uint8_t NumberOfArgs(uint8_t, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t) +{ + return 7; +} + +int Sum(int aFirst) +{ + return aFirst; +} + +template int Sum(int aFirst, Args... aArgs) +{ + return aFirst + Sum(aArgs...); +} + +void TestMacros(void) +{ + // Verify `OT_FIRST_ARG()` macro. + + VerifyOrQuit(OT_FIRST_ARG(1) == 1, "OT_FIRST_ARG() failed"); + VerifyOrQuit(OT_FIRST_ARG(1, 2, 3) == 1, "OT_FIRST_ARG() failed"); + VerifyOrQuit(OT_FIRST_ARG(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12) == 1, "OT_FIRST_ARG() failed"); + + // Verify `OT_REST_ARGS()` macro. + + VerifyOrQuit(NumberOfArgs(OT_REST_ARGS(1)) == 0, "OT_REST_ARGS() failed for empty"); + + VerifyOrQuit(NumberOfArgs(0 OT_REST_ARGS(1)) == 1, "OT_REST_ARGS() failed"); + VerifyOrQuit(NumberOfArgs(0 OT_REST_ARGS(1, 2)) == 2, "OT_REST_ARGS() failed"); + VerifyOrQuit(NumberOfArgs(0 OT_REST_ARGS(1, 2, 3)) == 3, "OT_REST_ARGS() failed"); + VerifyOrQuit(NumberOfArgs(0 OT_REST_ARGS(1, 2, 3, 4)) == 4, "OT_REST_ARGS() failed"); + VerifyOrQuit(NumberOfArgs(0 OT_REST_ARGS(1, 2, 3, 4, 5)) == 5, "OT_REST_ARGS() failed"); + VerifyOrQuit(NumberOfArgs(0 OT_REST_ARGS(1, 2, 3, 4, 5, 6)) == 6, "OT_REST_ARGS() failed"); + VerifyOrQuit(NumberOfArgs(0 OT_REST_ARGS(1, 2, 3, 4, 5, 6, 7)) == 7, "OT_REST_ARGS() failed"); + + VerifyOrQuit(Sum(100 OT_REST_ARGS(1)) == 100, "OT_REST_ARGS() failed"); + VerifyOrQuit(Sum(100 OT_REST_ARGS(1, 2)) == 102, "OT_REST_ARGS() failed"); + VerifyOrQuit(Sum(100 OT_REST_ARGS(1, 2, 3)) == 105, "OT_REST_ARGS() failed"); + VerifyOrQuit(Sum(100 OT_REST_ARGS(1, 2, 3, 4)) == 109, "OT_REST_ARGS() failed"); + VerifyOrQuit(Sum(100 OT_REST_ARGS(1, 2, 3, 4, 5)) == 114, "OT_REST_ARGS() failed"); + VerifyOrQuit(Sum(100 OT_REST_ARGS(1, 2, 3, 4, 5, 6)) == 120, "OT_REST_ARGS() failed"); + VerifyOrQuit(Sum(100 OT_REST_ARGS(1, 2, 3, 4, 5, 6, 7)) == 127, "OT_REST_ARGS() failed"); + + // Verify `OT_SECOND_ARG()` macro. + + VerifyOrQuit(NumberOfArgs(OT_SECOND_ARG(1)) == 0, "OT_SECOND_ARG() failed"); + VerifyOrQuit(NumberOfArgs(OT_SECOND_ARG(1, 2)) == 1, "OT_SECOND_ARG() failed"); + + VerifyOrQuit(OT_SECOND_ARG(1, 2) == 2, "OT_SECOND_ARG() failed"); +} + +int main(void) +{ + TestMacros(); + printf("All tests passed\n"); + return 0; +}