Compare commits

...

4 Commits

Author SHA1 Message Date
Dan Streetman 4fcba22deb
Merge 413f6e0f56 into d99198819c 2024-11-21 20:18:51 -08:00
Dan Streetman 413f6e0f56 test/test-variadic: add unit tests for variadic helper macros 2023-05-01 14:14:18 -04:00
Dan Streetman f3ab293474 fundamental/variadic-fundamental: add variadic helper macros
Adds VA_MACRO_HELPER() which allows calling another macro with temporary
variables, unique tokens, and/or existing variables protected from side
effects.

Also adds various other helper macros for dealing with variadic args, such as
VA_GROUP(), VA_IF(), VA_FIRST(), etc.
2023-05-01 14:14:18 -04:00
Dan Streetman b595c40c72 fundamental/macro-fundamental: allow stringifying variadic args, instead of just a single arg 2023-05-01 12:58:34 -04:00
4 changed files with 1284 additions and 2 deletions

View File

@ -90,8 +90,8 @@
# define _fallthrough_
#endif
#define XSTRINGIFY(x) #x
#define STRINGIFY(x) XSTRINGIFY(x)
#define XSTRINGIFY(...) #__VA_ARGS__
#define STRINGIFY(...) XSTRINGIFY(__VA_ARGS__)
#ifndef __COVERITY__
# define VOID_0 ((void)0)

View File

@ -0,0 +1,555 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
/* This contains macros that all help simplify the use of macros with variadic args. Also provided is a macro
* 'helper' that helps provide some commonly used things, such as a unique variable name or temporary
* variable.
*
* Since the C preprocessor does not allow recursive macros, none of these macros may be used to call
* themselves, even indirectly (except by using a "trick"; e.g. see __VA_WRAP_RECURSE() below). If you get a
* compiler error complaining about "implicit declaration of function" for any of the macros here, it is most
* likely due to an attempt to use the macro recursively.
*
* Some macros operate based on if there are 'any variadic args' or 'no variadic args'; this distinction is
* based on the use of __VA_OPT__(). The description 'any variadic args' means __VA_OPT__() evaluates to its
* content, and 'no variadic args' means __VA_OPT__() evaluates to nothing. Note that whitespace is not a
* preprocessor token, so a single whitespace-only arg is the same as no args. For example these calls all
* evaluate to 2:
* VA_IF_ELSE(1,2)
* VA_IF_ELSE(1,2,)
* VA_IF_ELSE(1,2, )
* #define NONE
* VA_IF_ELSE(1,2,NONE)
* VA_IF_ELSE(1,2, NONE)
* However, this call evaluates to 1:
* VA_IF_ELSE(1,2,,)
*/
/* Wraps variadic args in a single group. This can be passed to macros that will then expand the group into
* all its variadic args. */
#define VA_GROUP(...) __VA_ARGS__
/* Evaluates to 'x' if any variadic args, otherwise 'y'. */
#define VA_IF_ELSE(x, y, ...) _VA_IF_ELSE_MACRO(__VA_ARGS__)(_VA_IF_ELSE_GROUP(x), _VA_IF_ELSE_GROUP(y))
#define _VA_IF_ELSE_GROUP(...) __VA_ARGS__
#define _VA_IF_ELSE_MACRO(...) __VA_IF_ELSE_MACRO(__VA_OPT__(1))
#define __VA_IF_ELSE_MACRO(o) _VA_IF_ELSE ## o
#define _VA_IF_ELSE1(x, y) x
#define _VA_IF_ELSE(x, y) y
/* Evaluates to nothing if no variadic args, otherwise 'x'. */
#define VA_IF(x, ...) VA_IF_ELSE(_VA_IF_GROUP(x), /*false*/, __VA_ARGS__)
#define _VA_IF_GROUP(...) __VA_ARGS__
/* Same as VA_IF() but negates the condition. */
#define VA_IF_NOT(x, ...) VA_IF_ELSE(/*true*/, _VA_IF_NOT_GROUP(x), __VA_ARGS__)
#define _VA_IF_NOT_GROUP(...) __VA_ARGS__
/* Evaluates to token 1 if no variadic args, otherwise nothing. */
#define VA_NOT(...) VA_IF_NOT(1, __VA_ARGS__)
/* Evaluates to the first variadic arg, otherwise nothing. */
#define VA_FIRST(...) VA_IF(_VA_FIRST(__VA_ARGS__), __VA_ARGS__)
#define _VA_FIRST(x, ...) x
/* Evaluates to the rest of the variadic args, after the first, otherwise nothing. */
#define VA_REST(...) VA_IF(_VA_REST(__VA_ARGS__), __VA_ARGS__)
#define _VA_REST(x, ...) __VA_ARGS__
/* Evaluates to token , if any variadic args, otherwise nothing. */
#define VA_COMMA(...) __VA_OPT__(,)
/* Evaluates to token 1 if both args are non-empty (ignoring whitespace), otherwise evaluates to nothing. */
#define VA_AND(x, y) VA_NOT(VA_NOT(x) VA_NOT(y))
/* Evaluates to token 1 if either arg is non-empty (ignoring whitespace), otherwise evaluates to nothing. */
#define VA_OR(x, y) VA_IF(1, x y)
/* Evaluates to nothing. */
#define VA_NOOP(...)
/* Similar to VA_GROUP(), but encloses the variadic args in (), so they are not expanded when passed to other
* macros. Unlike VA_GROUP(), this requires the final macro that actually uses the group contents to ungroup it
* using VA_UNPGROUP(), or to handle the () directly. */
#define VA_PGROUP(...) (__VA_ARGS__)
/* Converts a group of args protected with () into a normal VA_GROUP(). 'x' must be a pgroup, i.e. (...). */
#define VA_UNPGROUP(x) VA_GROUP x
/* Similar to VA_FIRST(), but x is a pgroup. Evaluates to the first arg if present, otherwise nothing. */
#define VA_PGROUP_FIRST(x) VA_FIRST(VA_UNPGROUP(x))
/* Similar to VA_REST(), but x is a pgroup. Evaluates to a pgroup of the args after the first. If there are
* no more args after the first, evaluates to an empty pgroup. */
#define VA_PGROUP_REST(x) VA_PGROUP(VA_REST(VA_UNPGROUP(x)))
/* Evaluates to token 1 if pgroup is empty, otherwise nothing. */
#define VA_PGROUP_EMPTY(x) VA_IF_NOT(1, VA_UNPGROUP(x))
/* Similar to VA_PGROUP_EMPTY() but negates the condition. */
#define VA_PGROUP_NOT_EMPTY(x) VA_NOT(VA_PGROUP_EMPTY(x))
/* Evaluates to 'macro' called with the expanded variadic args. */
#define VA_MACRO(macro, ...) macro(__VA_ARGS__)
/* Evaluates to x(__VA_ARGS__) if t is non-empty, otherwise y(__VA_ARGS__). */
#define VA_MACRO_IF_ELSE(x, y, t, ...) VA_IF_ELSE(x, y, t)(__VA_ARGS__)
/* Evaluates to m(__VA_ARGS__) if t is non-empty, otherwise nothing. */
#define VA_MACRO_IF(m, t, ...) VA_MACRO_IF_ELSE(m, VA_NOOP, t, __VA_ARGS__)
/* Evaluates to m(__VA_ARGS__) if t is empty, otherwise nothing. */
#define VA_MACRO_IF_NOT(m, t, ...) VA_MACRO_IF_ELSE(VA_NOOP, m, t, __VA_ARGS__)
/* Same as VA_MACRO() but takes a pgroup, which is unpgrouped before passing to the macro. */
#define VA_MACRO_PGROUP(macro, pgroup) VA_MACRO(macro, VA_UNPGROUP(pgroup))
/* Expands to 'macro' for each variadic arg, which will be called with:
* 1) the provided 'context'
* 2) a hex iteration number (starting at 0x0001)
* 3) the variadic arg
* 4...) the rest of the variadic args
*
* Each expansion of 'macro', except for the last, will be followed by 'separator' called with the same
* parameters as 'macro'.
*
* If there are no variadic args, this evaluates to 'zero' called with the single arg 'context'.
*
* If there are too many variadic args, this evaluates to 'toomany' called with the single arg 'context'.
*
* The 'macro', 'separator', 'zero', and 'toomany' parameters must be callable macros. The VA_WRAP_*() macros
* below may be used. The 'context' parameter may be anything and is not directly called (except by the
* VA_WRAP_*_CONTEXT() below). */
#define VA_WRAP(macro, separator, context, zero, toomany, ...) \
__VA_WRAP_RECURSE(macro, separator, context, zero, toomany, __VA_ARGS__)
/* These can be used for the VA_WRAP() 'macro' parameter. */
#define VA_WRAP_MACRO_CONTEXT(c, i, v, ...) c
#define VA_WRAP_MACRO_INDEX(c, i, v, ...) i
#define VA_WRAP_MACRO_LAST(c, i, v, ...) VA_IF_NOT(v, __VA_ARGS__)
#define VA_WRAP_MACRO_LAST_INDEX(c, i, v, ...) VA_IF_NOT(i, __VA_ARGS__)
#define VA_WRAP_MACRO_NONE(c, i, v, ...)
#define VA_WRAP_MACRO_VALUE(c, i, v, ...) v
/* These can be used for the VA_WRAP() 'separator' parameter. */
#define VA_WRAP_SEPARATOR_AND(c, i, v, ...) &&
#define VA_WRAP_SEPARATOR_COMMA(c, i, v, ...) ,
#define VA_WRAP_SEPARATOR_COMMA_IF_PREV(c, i, v, ...) VA_COMMA(v)
#define VA_WRAP_SEPARATOR_CONTEXT(c, i, v, ...) c
#define VA_WRAP_SEPARATOR_INDEX(c, i, v, ...) i
#define VA_WRAP_SEPARATOR_NONE(c, i, v, ...)
#define VA_WRAP_SEPARATOR_SEMICOLON(c, i, v, ...) ;
/* This can be used for the VA_WRAP() 'context' parameter. It is strictly to help with code readability, and
* is not required. */
#define VA_WRAP_CONTEXT_NONE
/* These can be used for the VA_WRAP() 'zero' parameter. */
#define VA_WRAP_ZERO_0(c) 0
#define VA_WRAP_ZERO_0x0000(c) 0x0000
#define VA_WRAP_ZERO_CONTEXT(c) c
#define VA_WRAP_ZERO_ERROR(c) _Pragma("GCC error \"Zero variadic args.\"")
#define VA_WRAP_ZERO_FALSE(c) false
#define VA_WRAP_ZERO_NONE(c)
#define VA_WRAP_ZERO_TRUE(c) true
#define VA_WRAP_ZERO_VOID_0(c) VOID_0
/* These can be used for the VA_WRAP() 'toomany' parameter. */
#define VA_WRAP_TOOMANY_CONTEXT(c) c
#define VA_WRAP_TOOMANY_ERROR(c) _Pragma("GCC error \"Too many variadic args.\"")
#define VA_WRAP_TOOMANY_FALSE(c) false
#define VA_WRAP_TOOMANY_NONE(c)
#define VA_WRAP_TOOMANY_TRUE(c) true
/* Simple version of VA_WRAP(); each variadic arg is wrapped by the provided macro, separated by commas. No
* context is used. Zero args evaluates to nothing. Toomany args results in error. */
#define VA_MACRO_FOREACH(macro, ...) \
VA_WRAP(macro, \
VA_WRAP_SEPARATOR_COMMA, \
VA_WRAP_CONTEXT_NONE, \
VA_WRAP_ZERO_NONE, \
VA_WRAP_TOOMANY_ERROR, \
__VA_ARGS__)
/* Expands to list of variadic args, with any "empty" (whitespace only) args removed. This processes the list
* twice, to remove a trailing comma if needed. */
#define VA_FILTER(...) \
VA_MACRO(VA_WRAP, \
VA_WRAP_MACRO_VALUE, \
VA_WRAP_SEPARATOR_COMMA_IF_PREV, \
VA_WRAP_CONTEXT_NONE, \
VA_WRAP_ZERO_NONE, \
VA_WRAP_TOOMANY_ERROR, \
VA_WRAP(VA_WRAP_MACRO_VALUE, \
VA_WRAP_SEPARATOR_COMMA_IF_PREV, \
VA_WRAP_CONTEXT_NONE, \
VA_WRAP_ZERO_NONE, \
VA_WRAP_TOOMANY_ERROR, \
__VA_ARGS__))
/* Evaluates to the number of variadic args. */
#define VA_NARGS(...) \
VA_WRAP(VA_WRAP_MACRO_LAST_INDEX, \
VA_WRAP_SEPARATOR_NONE, \
VA_WRAP_CONTEXT_NONE, \
VA_WRAP_ZERO_0x0000, \
VA_WRAP_TOOMANY_ERROR, \
__VA_ARGS__)
/* Evaluates to the last variadic arg. If there are no variadic args, evaluates to nothing. */
#define VA_LAST(...) \
VA_WRAP(VA_WRAP_MACRO_LAST, \
VA_WRAP_SEPARATOR_NONE, \
VA_WRAP_CONTEXT_NONE, \
VA_WRAP_ZERO_NONE, \
VA_WRAP_TOOMANY_ERROR, \
__VA_ARGS__)
#define _VA_DECLARATIONS(macro, name, ...) \
VA_WRAP(macro, \
VA_WRAP_SEPARATOR_SEMICOLON, \
name, \
VA_WRAP_ZERO_NONE, \
VA_WRAP_TOOMANY_ERROR, \
__VA_ARGS__)
#define _VA_DECLARATION_TOKEN(x, y) __VA_DECLARATION_TOKEN(x, y)
#define __VA_DECLARATION_TOKEN(x, y) x ## _ ## y
/* Evaluates to a variable declaration for each variadic arg. Each variadic arg must be a type. Each variable
* name is the concatenation of 'name', '_', and the variadic arg index (as a hex number). */
#define VA_DECLARATIONS(name, ...) \
_VA_DECLARATIONS(_VA_DECLARATION, name, __VA_ARGS__)
#define _VA_DECLARATION(c, i, v, ...) \
v _VA_DECLARATION_TOKEN(c, i)
/* Same as VA_DECLARATIONS(), but the variadic args must be variables (or constants). Each declaration
* uses __auto_type and is initialized to its corresponding variadic arg. */
#define VA_INITIALIZED_DECLARATIONS(name, ...) \
_VA_DECLARATIONS(_VA_INITIALIZED_DECLARATION, name, __VA_ARGS__)
#define _VA_INITIALIZED_DECLARATION(c, i, v, ...) \
_VA_DECLARATION(c, i, __auto_type, __VA_ARGS__) = (v)
/* Same as VA_INITIALIZED_DECLARATIONS(), but the temp variable is declared with const. */
#define VA_CONST_INITIALIZED_DECLARATIONS(name, ...) \
_VA_DECLARATIONS(_VA_CONST_INITIALIZED_DECLARATION, name, __VA_ARGS__)
#define _VA_CONST_INITIALIZED_DECLARATION(c, i, v, ...) \
const _VA_INITIALIZED_DECLARATION(c, i, v, __VA_ARGS__)
/* Evaluates to a comma-separated list of tokens by concatenating 'name' and a literal '_' with each variadic
* arg index. This will produce the same tokens as the variable names generated by VA_DECLARATIONS(). Note
* this does not actually evaluate any of the variadic args. */
#define VA_TOKENS(name, ...) \
VA_WRAP(_VA_TOKEN, \
VA_WRAP_SEPARATOR_COMMA, \
name, \
VA_WRAP_ZERO_NONE, \
VA_WRAP_TOOMANY_ERROR, \
__VA_ARGS__)
#define _VA_TOKEN(c, i, v, ...) _VA_DECLARATION_TOKEN(c, i)
/* Evaluates to a comma-separated list of unique tokens using UNIQ_T() for each variadic arg. This is similar
* to VA_TOKENS() but uses UNIQ_T() to generate the tokens. */
#define VA_UNIQ(...) \
VA_WRAP(_VA_UNIQ, \
VA_WRAP_SEPARATOR_COMMA, \
UNIQ, \
VA_WRAP_ZERO_NONE, \
VA_WRAP_TOOMANY_ERROR, \
__VA_ARGS__)
#define _VA_UNIQ(c, i, v, ...) UNIQ_T(v, c)
/* This is similar to VA_FILTER(), but we can't use VA_FILTER() because macros can't be used recursively, and
* this is called from inside a VA_WRAP() (which VA_FILTER() relies on). */
#define __VMH_GROUPS(g1, g2, g3, g4, g5) \
g1 VA_IF(VA_COMMA(g1), g2 g3 g4 g5) \
g2 VA_IF(VA_COMMA(g2), g3 g4 g5) \
g3 VA_IF(VA_COMMA(g3), g4 g5) \
g4 VA_IF(VA_COMMA(g4), g5) \
g5
#define __VMH_TOKEN(x, u) __va_macro_helper ## x ## u
#define __VMH_STATEMENT_EXPRESSION(macro, u, uniq, var, varinit, varconst, direct) \
({ \
VA_DECLARATIONS( __VMH_TOKEN(_var_, u), var); \
VA_INITIALIZED_DECLARATIONS( __VMH_TOKEN(_varinit_, u), varinit); \
VA_CONST_INITIALIZED_DECLARATIONS(__VMH_TOKEN(_varconst_, u), varconst); \
VA_MACRO(macro, \
__VMH_GROUPS(VA_UNIQ(uniq), \
VA_TOKENS(__VMH_TOKEN(_var_, u), var), \
VA_TOKENS(__VMH_TOKEN(_varinit_, u), varinit), \
VA_TOKENS(__VMH_TOKEN(_varconst_, u), varconst), \
VA_GROUP(direct))); \
})
#define __VMH_EXPRESSION(macro, u, uniq, var, varinit, varconst, direct) \
VA_MACRO(macro, \
__VMH_GROUPS(VA_UNIQ(uniq), VA_GROUP(direct),,,))
/* Calls 'macro' with a set of args based on the provided arg groups, in the order shown. Multiple args may
* be provided to each group by using VA_GROUP().
*
* Each arg in the 'uniq' group provides a unique token, named based on the arg token, to the macro in
* place of the arg. This is equivalent to UNIQ_T() for each arg.
*
* Each arg in the 'var' group provides a temporary variable of the specified type to the macro in place of
* the arg. All args in this group must be types.
*
* The 'varinit' group is similar to the 'var' group, but each arg must be a variable or constant, and each
* temporary variable is initialized to the value of the provided arg. The macro may use these args without
* any concern for side effects.
*
* The 'varconst' group is similar to the 'varinit' group, but the temporary variables are also marked as
* const. The macro should not modify args in this group.
*
* Each arg in the 'direct' group is provided directly to the macro. */
#define VA_MACRO_HELPER(macro, uniq, var, varinit, varconst, direct) \
VA_IF_ELSE(__VMH_STATEMENT_EXPRESSION, \
__VMH_EXPRESSION, \
var varinit varconst)(macro, \
UNIQ, \
VA_GROUP(uniq), \
VA_GROUP(var), \
VA_GROUP(varinit), \
VA_GROUP(varconst), \
VA_GROUP(direct))
/* Same as VA_MACRO_HELPER() but only with 'uniq' group; all variadic args are put in 'direct' group. */
#define VA_MACRO_UNIQ(macro, uniq, ...) \
VA_MACRO_HELPER(macro, \
VA_GROUP(uniq), \
/* var= */, \
/* varinit= */, \
/* varconst= */, \
VA_GROUP(__VA_ARGS__))
/* Same as VA_MACRO_HELPER() but only with 'var' group; all variadic args are put in 'direct' group. */
#define VA_MACRO_VAR(macro, var, ...) \
VA_MACRO_HELPER(macro, \
/* uniq= */, \
VA_GROUP(var), \
/* varinit= */, \
/* varconst= */, \
VA_GROUP(__VA_ARGS__))
/* Same as VA_MACRO_HELPER() but only with 'varinit' group; all variadic args are put in 'direct' group. */
#define VA_MACRO_VARINIT(macro, varinit, ...) \
VA_MACRO_HELPER(macro, \
/* uniq= */, \
/* var= */, \
VA_GROUP(varinit), \
/* varconst= */, \
VA_GROUP(__VA_ARGS__))
/* Same as VA_MACRO_HELPER() but only with 'varconst' group; all variadic args are put in 'direct' group. */
#define VA_MACRO_VARCONST(macro, varconst, ...) \
VA_MACRO_HELPER(macro, \
/* uniq= */, \
/* var= */, \
/* varinit= */, \
VA_GROUP(varconst), \
VA_GROUP(__VA_ARGS__))
/* Macros below are complex, internal-use-only macros and should not be used directly. They are used by the
* macros above. */
/* Integer increment at the preprocessor stage; each macro evaluates to the next integer. Overflow is not
* handled; f wraps to 0. */
#define __VAI0 1
#define __VAI1 2
#define __VAI2 3
#define __VAI3 4
#define __VAI4 5
#define __VAI5 6
#define __VAI6 7
#define __VAI7 8
#define __VAI8 9
#define __VAI9 a
#define __VAIa b
#define __VAIb c
#define __VAIc d
#define __VAId e
#define __VAIe f
#define __VAIf 0
/* Integer increment carryover; all macros evaluate to 0 except f, which evaluates to 1. */
#define __VAC0 0
#define __VAC1 0
#define __VAC2 0
#define __VAC3 0
#define __VAC4 0
#define __VAC5 0
#define __VAC6 0
#define __VAC7 0
#define __VAC8 0
#define __VAC9 0
#define __VACa 0
#define __VACb 0
#define __VACc 0
#define __VACd 0
#define __VACe 0
#define __VACf 1
/* Increment x based on carryover c. Requires x to be single hex digit (0-f) and carryover to be 0-1.
* Evaluates to 0 if x == f and c == 1, otherwise x+1 if c == 1, otherwise x. */
#define ___VAI(x, c) ____VAI(x, c)
#define ____VAI(x, c) ____VAI ## c(x)
#define ____VAI0(x) x
#define ____VAI1(x) __VAI ## x
/* Carryover of x based on carryover c. Requires x to be single hex digit (0-f) and carryover to be
* 0-1. Evaluates to 1 if x is f and c is 1, otherwise 0. */
#define ___VAC(x, c) ____VAC(x, c)
#define ____VAC(x, c) ____VAC ## c(x)
#define ____VAC0(x) 0
#define ____VAC1(x) __VAC ## x
/* Carryover of multiple digits. Each calculates the carryover of its digit, with 1 being the least
* significant digit, and 4 being the most significant digit. */
#define ___VAC1(x1) ___VAC(x1, 1)
#define ___VAC2(x2, x1) ___VAC(x2, ___VAC1(x1))
#define ___VAC3(x3, x2, x1) ___VAC(x3, ___VAC2(x2, x1))
#define ___VAC4(x4, x3, x2, x1) ___VAC(x4, ___VAC3(x3, x2, x1))
/* Increment with carryover across all digits. Each evaluate to their digit incremented if there is carryover
* from previous digits. */
#define ___VAI1(x1) ___VAI(x1, 1)
#define ___VAI2(x2, x1) ___VAI(x2, ___VAC1(x1))
#define ___VAI3(x3, x2, x1) ___VAI(x3, ___VAC2(x2, x1))
#define ___VAI4(x4, x3, x2, x1) ___VAI(x4, ___VAC3(x3, x2, x1))
/* Detect overflow. If all digits are f, this causes preprocessor error, otherwise this evaluates to
* nothing. */
#define ___VAIO(x4, x3, x2, x1) ____VAIO(___VAC4(x4, x3, x2, x1))
#define ____VAIO(c) _____VAIO(c)
#define _____VAIO(c) ______VAIO ## c()
#define ______VAIO0()
#define ______VAIO1() _Pragma("GCC error \"VA increment overflow\"")
/* Increment a 4-digit hex number. Requires pgroup to be a 4-digit hex number pgroup, e.g. (0,1,2,3)
* represents 0x0123. Evaluates to a 4-digit hex number pgroup that has been incremented by 1. On overflow, a
* preprocessor error is generated. */
#define __VAINC4(pgroup) ___VAINC4 pgroup
#define ___VAINC4(x4, x3, x2, x1) \
___VAIO(x4, x3, x2, x1) \
(___VAI4(x4, x3, x2, x1), \
___VAI3(x3, x2, x1), \
___VAI2(x2, x1), \
___VAI1(x1))
/* Convert a 4-digit hex number pgroup to a standard hex number. Requires pgroup to be a 4-digit hex number
* pgroup. Evaluates to a standard hex number for the pgroup, e.g. (a,b,c,d) evalutes to 0xabcd. */
#define __VANUM4(pgroup) ___VANUM4 pgroup
#define ___VANUM4(x4, x3, x2, x1) 0x ## x4 ## x3 ## x2 ## x1
/* Nested repeated evaluations. This is what controls when the 'toomany' VA_WRAP() parameter is evaluated. */
#define __VA_EVAL_0x0002(...) __VA_ARGS__
#define __VA_EVAL_0x0004(...) __VA_EVAL_0x0002(__VA_EVAL_0x0002(__VA_ARGS__))
#define __VA_EVAL_0x0008(...) __VA_EVAL_0x0004(__VA_EVAL_0x0004(__VA_ARGS__))
#define __VA_EVAL_0x0010(...) __VA_EVAL_0x0008(__VA_EVAL_0x0008(__VA_ARGS__))
#define __VA_EVAL_0x0020(...) __VA_EVAL_0x0010(__VA_EVAL_0x0010(__VA_ARGS__))
#define __VA_EVAL_0x0040(...) __VA_EVAL_0x0020(__VA_EVAL_0x0020(__VA_ARGS__))
#define __VA_EVAL_0x0080(...) __VA_EVAL_0x0040(__VA_EVAL_0x0040(__VA_ARGS__))
#define __VA_EVAL_0x0100(...) __VA_EVAL_0x0080(__VA_EVAL_0x0080(__VA_ARGS__))
#define __VA_EVAL_0x0200(...) __VA_EVAL_0x0100(__VA_EVAL_0x0100(__VA_ARGS__))
/* This should match the list of macros above. */
#define __VA_EVAL_STEPS (0x0002, 0x0004, 0x0008, 0x0010, 0x0020, 0x0040, 0x0080, 0x0100, 0x0200)
/* Determine which __VA_EVAL_0x* macro to use for the given variadic args. This is a quick evaluation for the
* preprocessor and avoids unnecessary reevaluations for complex macro expansions. Evaluates to the smallest
* (least evaluations) __VA_EVAL_0x* macro token that can handle the number of provided variadic args. */
#define __VA_EVAL_MACRO(...) __VA_EVAL_MACRO_CHECK_EACH(__VA_EVAL_STEPS, __VA_ARGS__)
/* Re-evaluates for each step. If __VA_EVAL_STEPS is increased this may need to be increased. */
#define __VA_EVAL_MACRO_CHECK_EACH(steps, ...) __VA_EVAL_MACRO_EVAL16(__VA_EVAL_MACRO_CHECK(steps, __VA_ARGS__))
#define __VA_EVAL_MACRO_EVAL02(...) __VA_ARGS__
#define __VA_EVAL_MACRO_EVAL04(...) __VA_EVAL_MACRO_EVAL02(__VA_EVAL_MACRO_EVAL02(__VA_ARGS__))
#define __VA_EVAL_MACRO_EVAL08(...) __VA_EVAL_MACRO_EVAL04(__VA_EVAL_MACRO_EVAL04(__VA_ARGS__))
#define __VA_EVAL_MACRO_EVAL16(...) __VA_EVAL_MACRO_EVAL08(__VA_EVAL_MACRO_EVAL08(__VA_ARGS__))
/* Evaluates to the first __VA_EVAL_0x* macro name that can handle all the variadic args. If there are too
* many variadic args for the largest macro to handle, evaluates to nothing. Note this uses the same
* preprocessor recursion "trick" as __VA_WRAP_RECURSE() below. */
#define __VA_EVAL_MACRO_CHECK(steps, ...) \
___VA_EVAL_MACRO_CHECK \
VA_PGROUP(__VA_EVAL_MACRO_MORE(VA_PGROUP_FIRST(steps), __VA_ARGS__))(steps, __VA_ARGS__)
/* 'x' is the evaluation of __VA_EVAL_MACRO_MORE(); if it is empty, this evaluates to __VA_EVAL_MACRO_OK,
* otherwise the tested __VA_EVAL_0x* macro cannot handle all the variadic args, and this evaluates to
* __VA_EVAL_MACRO_NEXT. */
#define ___VA_EVAL_MACRO_CHECK(x) VA_IF_ELSE(__VA_EVAL_MACRO_NEXT, __VA_EVAL_MACRO_OK, x)
/* Move on to testing the next step (i.e. next 0x* value). */
#define __VA_EVAL_MACRO_NEXT(steps, ...) ___VA_EVAL_MACRO_NEXT(VA_PGROUP_REST(steps), __VA_ARGS__)
/* Test the next step value. If there are no more steps, evaluate to nothing. */
#define ___VA_EVAL_MACRO_NEXT(steps, ...) \
VA_MACRO_IF(__VA_EVAL_MACRO_CHECK, VA_PGROUP_NOT_EMPTY(steps), steps, __VA_ARGS__)
/* The first value of 'steps' is acceptable, so evaluate to the corresponding __VA_EVAL_* macro name. */
#define __VA_EVAL_MACRO_OK(steps, ...) ___VA_EVAL_MACRO_OK(VA_PGROUP_FIRST(steps))
#define ___VA_EVAL_MACRO_OK(n) ____VA_EVAL_MACRO_OK(n)
#define ____VA_EVAL_MACRO_OK(n) __VA_EVAL_ ## n
/* Bug in Centos Stream 8 gcc preprocessor doesn't correctly handle __VA_OPT__(); work around it. Once Centos
* Stream 8 is no longer supported, this can be dropped. */
#define __CENTOS_STREAM_8_NONE
#define __CENTOS_STREAM_8_BUG_CHECK() ___CENTOS_STREAM_8_BUG_CHECK(__CENTOS_STREAM_8_NONE)
#define ___CENTOS_STREAM_8_BUG_CHECK(...) __VA_OPT__(1)
#define __VA_EVAL_MACRO_MORE_IF_ONCE(...) __VA_OPT__(1)
#define __VA_EVAL_MACRO_MORE_IF_TWICE(...) __VA_EVAL_MACRO_MORE_IF_ONCE(__VA_ARGS__)
#define __VA_EVAL_MACRO_MORE_IF_MACRO() \
VA_IF_ELSE(__VA_EVAL_MACRO_MORE_IF_TWICE, \
__VA_EVAL_MACRO_MORE_IF_ONCE, \
__CENTOS_STREAM_8_BUG_CHECK())
#define __VA_EVAL_MACRO_MORE_IF() __VA_EVAL_MACRO_MORE_IF_MACRO()
/* Test if the __VA_EVAL_0x* macro for hex number 'n' can handle all the variadic args. Evaluates to 1 if
* there are remaining (unhandled) variadic args after all evaluations, otherwise nothing. */
#define __VA_EVAL_MACRO_MORE(n, ...) \
__VA_EVAL_MACRO_MORE_IF()(__VA_EVAL_MACRO_MORE_N(n)(__VA_OPT__(___VA_EVAL_MACRO_MORE(__VA_ARGS__))))
#define __VA_EVAL_MACRO_MORE_N(n) __VA_EVAL_ ## n
#define ___VA_EVAL_MACRO_MORE(v, ...) __VA_OPT__(___VA_EVAL_MACRO_MORE_NEXT VA_PGROUP()(__VA_ARGS__))
#define ___VA_EVAL_MACRO_MORE_NEXT() ___VA_EVAL_MACRO_MORE
/* Recursive macro evaluation. This is intended for use by VA_WRAP() above. This performs the actions
* described by VA_WRAP() for each variadic arg.
*
* This "trick" inspired by:
* https://www.scs.stanford.edu/~dm/blog/va-opt.html
* https://github.com/pfultz2/Cloak/wiki/C-Preprocessor-tricks,-tips,-and-idioms
*
* This determines the number of re-evaluations required for the provided number of variadic args, then calls
* the appropriate __VA_EVAL_0x*() macro with ___VA_WRAP_RECURSE(), providing the initial index of 0x0001. */
#define __VA_WRAP_RECURSE(macro, separator, context, zero, toomany, ...) \
VA_IF_ELSE(__VA_WRAP_RECURSE_NONZERO, \
__VA_WRAP_RECURSE_ZERO, \
__VA_ARGS__)(macro, separator, context, zero, toomany, __VA_ARGS__)
#define __VA_WRAP_RECURSE_ZERO(macro, separator, context, zero, toomany, ...) zero(context)
#define __VA_WRAP_RECURSE_NONZERO(macro, separator, context, zero, toomany, ...) \
__VA_WRAP_RECURSE_CHECK_TOOMANY(__VA_EVAL_MACRO(__VA_ARGS__), \
macro, separator, context, toomany, __VA_ARGS__)
#define __VA_WRAP_RECURSE_CHECK_TOOMANY(evalmacro, macro, separator, context, toomany, ...) \
VA_IF_ELSE(__VA_WRAP_RECURSE_EVAL, \
__VA_WRAP_RECURSE_TOOMANY, \
evalmacro)(evalmacro, macro, separator, context, toomany, __VA_ARGS__)
#define __VA_WRAP_RECURSE_TOOMANY(evalmacro, macro, separator, context, toomany, ...) toomany(context)
#define __VA_WRAP_RECURSE_EVAL(evalmacro, macro, separator, context, toomany, ...) \
evalmacro(___VA_WRAP_RECURSE(macro, \
separator, \
context, \
(0,0,0,1), \
__VA_ARGS__))
/* This is the "trick" macro, which evaluates to the current variadic arg 'value' wrapped by 'macro', and
* then (if there are remaining variadic args) followed by 'separator' followed by the "trick"; which is
* ___VA_WRAP_NEXT token and VA_PGROUP(). On the next re-evaluation, this (indirectly) evaluates recursively
* to ___VA_WRAP_RECURSE(). */
#define ___VA_WRAP_RECURSE(macro, separator, context, index, value, ...) \
___VA_WRAP_RECURSE_CALL(macro, \
VA_IF_ELSE(separator, VA_NOOP, __VA_ARGS__), \
VA_GROUP(context, __VANUM4(index), value, __VA_ARGS__)) \
__VA_OPT__(___VA_WRAP_NEXT VA_PGROUP()(macro, separator, context, __VAINC4(index), __VA_ARGS__))
#define ___VA_WRAP_RECURSE_CALL(macro, separator, args) macro(args)separator(args)
#define ___VA_WRAP_NEXT() ___VA_WRAP_RECURSE

View File

@ -188,6 +188,7 @@ simple_tests += files(
'test-user-record.c',
'test-user-util.c',
'test-utf8.c',
'test-variadic.c',
'test-verbs.c',
'test-vpick.c',
'test-web-util.c',

726
src/test/test-variadic.c Normal file
View File

@ -0,0 +1,726 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <stddef.h>
#include "log.h"
#include "string-util.h"
#include "tests.h"
#include "variadic-fundamental.h"
#define _MACRO_LOG(...) ({ log_info("%s", #__VA_ARGS__); 0; })
#define MACRO_LOG(...) _MACRO_LOG(__VA_ARGS__)
#define NONE
#define MACRO_NONE()
#define MACRO_IGNORE(...)
#define MACRO1(x, ...) (x)
#define MACRO2(x1, x2, ...) (x2)
#define MACRO_SUM12(x1, x2, ...) (x1 + x2)
#define MACRO_3ARG_SUM(x1, x2, x3) x1 + x2 + x3
#define MACRO_4ARG_SUM(x1, x2, x3, x4) x1 + x2 + x3 + x4
#define MACRO_VARG_1(x1, ...) x1
#define MACRO_VARG_2(x1, ...) MACRO_VARG_1(__VA_ARGS__)
#define MACRO_VARG_3(x1, ...) MACRO_VARG_2(__VA_ARGS__)
#define MACRO_VARG_4(x1, ...) MACRO_VARG_3(__VA_ARGS__)
#define MACRO_GROUP_VARG_1(x) MACRO_VARG_1(x)
#define MACRO_GROUP_VARG_2(x) MACRO_VARG_2(x)
#define MACRO_GROUP_VARG_3(x) MACRO_VARG_3(x)
#define MACRO_GROUP_VARG_4(x) MACRO_VARG_4(x)
#define MACRO_GROUP_3ARG_SUM(x) MACRO_3ARG_SUM(x)
#define MACRO_GROUP_4ARG_SUM(x) MACRO_4ARG_SUM(x)
#define MACRO_2GROUP_4ARG_3ARG_SUM(g1, g2) MACRO_4ARG_SUM(g1) + MACRO_3ARG_SUM(g2)
#define MACRO_2GROUP_VARG_3ARG_G2A2(g1, g2) MACRO_VARG_2(g2)
#define MACRO_2GROUP_4ARG_VARG_SUM_G1A4_G2A3(g1, g2) MACRO_VARG_4(g1) + MACRO_VARG_3(g2)
TEST(va_group) {
assert_se(MACRO_GROUP_VARG_4(VA_GROUP(1,2,3,4)) == 4);
assert_se(MACRO_GROUP_VARG_1(VA_GROUP(5,10,20)) == 5);
assert_se(MACRO_GROUP_3ARG_SUM(VA_GROUP(1, 1000, -2)) == 999);
assert_se(MACRO_GROUP_4ARG_SUM(VA_GROUP(1, 1, 1, 2)) == 5);
assert_se(MACRO_2GROUP_4ARG_3ARG_SUM(VA_GROUP(5,6,7,8), VA_GROUP(1,1,1)) == 29);
assert_se(MACRO_2GROUP_VARG_3ARG_G2A2(VA_GROUP(1,2,3,4,5,6,7,8,9), VA_GROUP(3,2,1)) == 2);
assert_se(MACRO_2GROUP_4ARG_VARG_SUM_G1A4_G2A3(VA_GROUP(4,3,2,1), VA_GROUP(9,8,7,6,5,4)) == 8);
}
#define V1() 1
#define V2() 2
#define VI6E7(x) VA_IF_ELSE(6, 7, x)
#define VI8E9(x) VA_IF_ELSE(8, 9, x)
TEST(va_if_else) {
assert_se(VA_IF_ELSE(1,2) == 2);
assert_se(VA_IF_ELSE(1,2,) == 2);
assert_se(VA_IF_ELSE(1,2, ) == 2);
assert_se(VA_IF_ELSE(1,2,NONE) == 2);
assert_se(VA_IF_ELSE(1,2, NONE) == 2);
assert_se(VA_IF_ELSE(1,2,,) == 1);
assert_se(VA_IF_ELSE(1, 2, ) == 2);
assert_se(VA_IF_ELSE(1, 2,NONE ) == 2);
assert_se(VA_IF_ELSE(1, 2, 1) == 1);
assert_se(VA_IF_ELSE(1, 2, "no") == 1);
assert_se(VA_IF_ELSE(1, 2, VA_IF(1, )) == 2);
assert_se(VA_IF_ELSE(1, 2, VA_IF(1, 1) ) == 1);
assert_se(VA_IF_ELSE(1, 2, VA_IF_NOT(1, )) == 1);
assert_se(VA_IF_ELSE(1, 2, VA_IF_NOT(1, 2)) == 2);
assert_se(VA_IF_ELSE(1, 2, VA_NOT()) == 1);
assert_se(VA_IF_ELSE(1, 2, VA_NOT(1)) == 2);
assert_se(VA_IF_ELSE(1, 2, VA_IF_ELSE(100, 200, )) == 1);
assert_se(VA_IF_ELSE(1, 2, VA_IF_ELSE(100, 200, 1)) == 1);
assert_se(VA_IF_ELSE(1, 2, VA_IF_ELSE(100, , )) == 2);
assert_se(VA_IF_ELSE(1, 2, VA_IF_ELSE(, 4 , )) == 1);
assert_se(VA_IF_ELSE(V1, V2, 1)() == 1);
assert_se(VA_IF_ELSE(V1, V2, )() == 2);
assert_se(VA_IF_ELSE(VI6E7, VI8E9, )(1) == 8);
assert_se(VA_IF_ELSE(VI6E7, VI8E9, 0)(1) == 6);
assert_se(VA_IF_ELSE(VI6E7, VI8E9, )() == 9);
assert_se(VA_IF_ELSE(VI6E7, VI8E9, 55)() == 7);
assert_se(VA_IF_ELSE(VA_IF_ELSE(3, 4, ), VA_IF_ELSE(5, 6, ), ) == 6);
assert_se(VA_IF_ELSE(VA_IF_ELSE(3, 4, ), VA_IF_ELSE(5, 6, ), 1) == 4);
assert_se(VA_IF_ELSE(VA_IF_ELSE(3, 4, ), VA_IF_ELSE(5, 6, 1), ) == 5);
assert_se(VA_IF_ELSE(VA_IF_ELSE(3, 4, ), VA_IF_ELSE(5, 6, 1), 1) == 4);
assert_se(VA_IF_ELSE(VA_IF_ELSE(3, 4, 1), VA_IF_ELSE(5, 6, ), ) == 6);
assert_se(VA_IF_ELSE(VA_IF_ELSE(3, 4, 1), VA_IF_ELSE(5, 6, ), 1) == 3);
assert_se(VA_IF_ELSE(VA_IF_ELSE(3, 4, 1), VA_IF_ELSE(5, 6, 1), ) == 5);
assert_se(VA_IF_ELSE(VA_IF_ELSE(3, 4, 1), VA_IF_ELSE(5, 6, 1), 1) == 3);
}
TEST(va_if) {
assert_se(VA_IF(123,1) == 123);
assert_se(VA_IF(1+,1) 0 == 1);
assert_se(VA_IF(1+,0) 0 == 1);
assert_se(VA_IF(1+,) 0 == 0);
assert_se(VA_IF(1+, )0 == 0);
assert_se(VA_IF(1+, VA_IF(2+, VA_IF(3+, 4))) 0 == 1);
assert_se(VA_IF(1+, VA_IF(2+, VA_IF(3+, ))) 0 == 0);
assert_se(VA_IF(1+, VA_IF(, VA_IF(3+, 4))) 0 == 0);
assert_se(streq(VA_IF("hi", VA_IF(x,1)) "", "hi"));
assert_se(!streq(VA_IF("hi", VA_IF(x,NONE)) "", "hi"));
}
TEST(va_if_not) {
assert_se(VA_IF_NOT(123,) == 123);
assert_se(VA_IF_NOT(1+,1) 0 == 0);
assert_se(VA_IF_NOT(1+,0) 0 == 0);
assert_se(VA_IF_NOT(1+,) 0 == 1);
assert_se(VA_IF_NOT(1+, )0 == 1);
assert_se(VA_IF_NOT(1+, VA_IF_NOT(2+, VA_IF_NOT(3+, 4))) 0 == 0);
assert_se(VA_IF_NOT(1+, VA_IF_NOT(2+, VA_IF_NOT(3+, ))) 0 == 1);
assert_se(VA_IF_NOT(1+, VA_IF_NOT(, VA_IF_NOT(3+, 4))) 0 == 1);
assert_se(!streq(VA_IF_NOT("hi", 1) "", "hi"));
assert_se(streq(VA_IF_NOT("hi", NONE) "", "hi"));
}
TEST(va_not) {
assert_se(streq(STRINGIFY(VA_NOT()), "1"));
assert_se(streq(STRINGIFY(VA_NOT( )), "1"));
assert_se(streq(STRINGIFY(VA_NOT(1)), ""));
assert_se(streq(STRINGIFY(VA_NOT(0)), ""));
assert_se(streq(STRINGIFY(VA_NOT(1,2,3)), ""));
assert_se(streq(STRINGIFY(VA_NOT(,1,)), ""));
assert_se(streq(STRINGIFY(VA_NOT(,1)), ""));
assert_se(streq(STRINGIFY(VA_NOT("")), ""));
assert_se(streq(STRINGIFY(VA_NOT("hi")), ""));
assert_se(streq(STRINGIFY(VA_NOT(VA_NOT())), ""));
assert_se(streq(STRINGIFY(VA_NOT(VA_NOT(2))), "1"));
assert_se(streq(STRINGIFY(VA_NOT(VA_NOT("hi"))), "1"));
assert_se(streq(STRINGIFY(VA_NOT(VA_NOT(VA_NOT(2)))), ""));
assert_se(streq(STRINGIFY(VA_NOT(VA_NOT(2),VA_NOT(3))), ""));
assert_se(streq(STRINGIFY(VA_NOT(VA_NOT(),VA_NOT(3))), ""));
assert_se(streq(STRINGIFY(VA_NOT(VA_NOT(2),VA_NOT())), ""));
}
TEST(va_first) {
assert_se(VA_FIRST(1,2,3) == 1);
assert_se(VA_FIRST(1+,2+) 0 == 1);
assert_se(VA_FIRST(1+) 0 == 1);
assert_se(VA_FIRST() 0 == 0);
assert_se(streq(STRINGIFY(VA_FIRST()), ""));
assert_se(streq(STRINGIFY(VA_FIRST( )), ""));
assert_se(streq(STRINGIFY(VA_FIRST(,)), ""));
assert_se(streq(STRINGIFY(VA_FIRST(NONE)), ""));
assert_se(streq(STRINGIFY(VA_FIRST( NONE )), ""));
assert_se(streq(STRINGIFY(VA_FIRST( NONE, )), ""));
assert_se(streq(STRINGIFY(VA_FIRST( NONE,1,3 )), ""));
}
TEST(va_rest) {
assert_se(VA_REST(1,3) == 3);
assert_se(VA_REST(1+,2+) 0 == 2);
assert_se(VA_REST(1+) 0 == 0);
assert_se(VA_REST() 0 == 0);
assert_se(streq(STRINGIFY(VA_REST(NONE,1)), "1"));
assert_se(streq(STRINGIFY(VA_REST(1,NONE,1)), ",1"));
assert_se(streq(STRINGIFY(VA_REST(1,NONE)), ""));
assert_se(VA_FIRST(VA_REST(1,2,3,4,5)) == 2);
int ia[] = { VA_REST(1,2,3,4,5) };
assert_se(ELEMENTSOF(ia) == 4);
assert_se(ia[0] == 2);
assert_se(ia[1] == 3);
assert_se(ia[2] == 4);
assert_se(ia[3] == 5);
}
TEST(va_comma) {
assert_se(streq("0 , 1, 2", STRINGIFY(0 VA_COMMA(0) 1, 2)));
assert_se(streq("0 , 1, 2", STRINGIFY(0 VA_COMMA(1) 1, 2)));
assert_se(streq("0 1, 2", STRINGIFY(0 VA_COMMA() 1, 2)));
}
TEST(va_and) {
assert_se(streq(STRINGIFY(VA_AND(1,2)), "1"));
assert_se(streq(STRINGIFY(VA_AND(,2)), ""));
assert_se(streq(STRINGIFY(VA_AND(1,)), ""));
assert_se(streq(STRINGIFY(VA_AND(,)), ""));
assert_se(streq(STRINGIFY(VA_AND( , )), ""));
assert_se(streq(STRINGIFY(VA_AND(1 , )), ""));
assert_se(streq(STRINGIFY(VA_AND( , 2 )), ""));
assert_se(streq(STRINGIFY(VA_AND( 1 , 2 )), "1"));
assert_se(streq(STRINGIFY(VA_AND("hi",2)), "1"));
assert_se(streq(STRINGIFY(VA_AND(1,"hi")), "1"));
assert_se(streq(STRINGIFY(VA_AND("hi","hi")), "1"));
assert_se(streq(STRINGIFY(VA_AND(VA_AND(1,2),2)), "1"));
assert_se(streq(STRINGIFY(VA_AND(VA_AND(1,),2)), ""));
assert_se(streq(STRINGIFY(VA_AND(VA_AND(1,2),)), ""));
assert_se(streq(STRINGIFY(VA_AND( VA_AND( , 1 ) , VA_AND( , ) )), ""));
assert_se(streq(STRINGIFY(VA_AND( VA_AND( , ) , VA_AND( , ) )), ""));
}
TEST(va_or) {
assert_se(streq(STRINGIFY(VA_OR(1,2)), "1"));
assert_se(streq(STRINGIFY(VA_OR(,2)), "1"));
assert_se(streq(STRINGIFY(VA_OR(1,)), "1"));
assert_se(streq(STRINGIFY(VA_OR(,)), ""));
assert_se(streq(STRINGIFY(VA_OR("hi",2)), "1"));
assert_se(streq(STRINGIFY(VA_OR(1,"hi")), "1"));
assert_se(streq(STRINGIFY(VA_OR("hi","hi")), "1"));
assert_se(streq(STRINGIFY(VA_OR("hi",)), "1"));
assert_se(streq(STRINGIFY(VA_OR(,"hi")), "1"));
assert_se(streq(STRINGIFY(VA_OR( , )), ""));
assert_se(streq(STRINGIFY(VA_OR(VA_OR(1,),)), "1"));
assert_se(streq(STRINGIFY(VA_OR(VA_OR(,),)), ""));
assert_se(streq(STRINGIFY(VA_OR(VA_OR(,),2)), "1"));
assert_se(streq(STRINGIFY(VA_OR( VA_OR(1,) , )), "1"));
assert_se(streq(STRINGIFY(VA_OR( VA_OR( , 1 ) , VA_OR( , ) )), "1"));
assert_se(streq(STRINGIFY(VA_OR( VA_OR( , ) , VA_OR( , ) )), ""));
}
TEST(va_macro) {
assert_se(VA_MACRO(MACRO1, 3,2,1) == 3);
assert_se(VA_MACRO(MACRO1, 4) == 4);
assert_se(VA_MACRO(MACRO2, 4,5) == 5);
assert_se(streq(VA_MACRO(MACRO2, 4,"hi"), "hi"));
}
#define VA_NARGS_MAX_LESS_1 \
1,2,3,4,5,6,7,8,9,a,b,c,d,e,f,010, \
1,2,3,4,5,6,7,8,9,a,b,c,d,e,f,020, \
1,2,3,4,5,6,7,8,9,a,b,c,d,e,f,030, \
1,2,3,4,5,6,7,8,9,a,b,c,d,e,f,040, \
1,2,3,4,5,6,7,8,9,a,b,c,d,e,f,050, \
1,2,3,4,5,6,7,8,9,a,b,c,d,e,f,060, \
1,2,3,4,5,6,7,8,9,a,b,c,d,e,f,070, \
1,2,3,4,5,6,7,8,9,a,b,c,d,e,f,080, \
1,2,3,4,5,6,7,8,9,a,b,c,d,e,f,090, \
1,2,3,4,5,6,7,8,9,a,b,c,d,e,f,0a0, \
1,2,3,4,5,6,7,8,9,a,b,c,d,e,f,0b0, \
1,2,3,4,5,6,7,8,9,a,b,c,d,e,f,0c0, \
1,2,3,4,5,6,7,8,9,a,b,c,d,e,f,0d0, \
1,2,3,4,5,6,7,8,9,a,b,c,d,e,f,0e0, \
1,2,3,4,5,6,7,8,9,a,b,c,d,e,f,0f0, \
1,2,3,4,5,6,7,8,9,a,b,c,d,e,f,100, \
1,2,3,4,5,6,7,8,9,a,b,c,d,e,f,110, \
1,2,3,4,5,6,7,8,9,a,b,c,d,e,f,120, \
1,2,3,4,5,6,7,8,9,a,b,c,d,e,f,130, \
1,2,3,4,5,6,7,8,9,a,b,c,d,e,f,140, \
1,2,3,4,5,6,7,8,9,a,b,c,d,e,f,150, \
1,2,3,4,5,6,7,8,9,a,b,c,d,e,f,160, \
1,2,3,4,5,6,7,8,9,a,b,c,d,e,f,170, \
1,2,3,4,5,6,7,8,9,a,b,c,d,e,f,180, \
1,2,3,4,5,6,7,8,9,a,b,c,d,e,f,190, \
1,2,3,4,5,6,7,8,9,a,b,c,d,e,f,1a0, \
1,2,3,4,5,6,7,8,9,a,b,c,d,e,f,1b0, \
1,2,3,4,5,6,7,8,9,a,b,c,d,e,f,1c0, \
1,2,3,4,5,6,7,8,9,a,b,c,d,e,f,1d0, \
1,2,3,4,5,6,7,8,9,a,b,c,d,e,f,1e0, \
1,2,3,4,5,6,7,8,9,a,b,c,d,e,f,1f0, \
1,2,3,4,5,6,7,8,9,a,b,c,d,e, 1ff
#define TEST_EQ_STR(expected, result) assert_se(streq(expected, STRINGIFY(result)))
#define XvX(c, i, v, ...) X v X
TEST(va_macro_foreach) {
TEST_EQ_STR("", VA_MACRO_FOREACH(XvX));
TEST_EQ_STR("", VA_MACRO_FOREACH(XvX,));
TEST_EQ_STR("", VA_MACRO_FOREACH(XvX, ));
TEST_EQ_STR("X 1 X", VA_MACRO_FOREACH(XvX, 1));
TEST_EQ_STR("X 1 X, X 2 X", VA_MACRO_FOREACH(XvX, 1, 2));
TEST_EQ_STR("X hi X", VA_MACRO_FOREACH(XvX, hi));
TEST_EQ_STR("X one X, X two X, X three X", VA_MACRO_FOREACH(XvX, one, two, three));
TEST_EQ_STR("X 1 X, X 2 X, X X, X 4 X, X 5 X", VA_MACRO_FOREACH(XvX, 1, 2, , 4, 5));
TEST_EQ_STR("X X, X 2 X, X 3 X, X 4 X, X 5 X", VA_MACRO_FOREACH(XvX, , 2, 3, 4, 5));
/* Note that if the final arg is empty (or only whitespace), it is not included. */
TEST_EQ_STR("X 1 X", VA_MACRO_FOREACH(XvX, 1,));
TEST_EQ_STR("X X, X 1 X", VA_MACRO_FOREACH(XvX, ,1));
TEST_EQ_STR("X 1 X", VA_MACRO_FOREACH(XvX, 1, ));
TEST_EQ_STR("X X, X 1 X", VA_MACRO_FOREACH(XvX,,1));
TEST_EQ_STR("X X, X 1 X", VA_MACRO_FOREACH(XvX,,1,));
TEST_EQ_STR("X X, X 1 X", VA_MACRO_FOREACH(XvX,,1, ));
TEST_EQ_STR("X X, X X, X X, X X", VA_MACRO_FOREACH(XvX, , , , , ));
TEST_EQ_STR("X X, X X, X X, X X, X 1 X", VA_MACRO_FOREACH(XvX, , , , , 1));
TEST_EQ_STR("X X, X X, X X, X X, X 1 X", VA_MACRO_FOREACH(XvX, , , , ,1));
TEST_EQ_STR("X X, X X, X X, X X", VA_MACRO_FOREACH(XvX,,,,,));
TEST_EQ_STR("X X, X X, X X, X X, X 1 X", VA_MACRO_FOREACH(XvX,,,,,1));
TEST_EQ_STR("X X, X X, X X, X X, X 1 X", VA_MACRO_FOREACH(XvX,,,,, 1));
}
TEST(va_filter) {
TEST_EQ_STR("0, 1, 2, 3, hi, later", VA_FILTER(0, 1, 2, 3, , , , hi, later, ));
TEST_EQ_STR("", VA_FILTER(, , , , ,));
TEST_EQ_STR("5", VA_FILTER(, , , , ,5));
TEST_EQ_STR("4, 5", VA_FILTER(4, , , , ,5));
TEST_EQ_STR("6, 7", VA_FILTER(, 6, 7, , ,));
TEST_EQ_STR("\"one\", \"two\"", VA_FILTER(, "one", ,"two" , ,));
}
#define TEST_NARGS(expect, expect_token, ...) \
({ \
assert_se(VA_NARGS(__VA_ARGS__) == expect); \
assert_se(streq(STRINGIFY(expect_token), STRINGIFY(VA_NARGS(__VA_ARGS__)))); \
assert_se(__builtin_constant_p(VA_NARGS(__VA_ARGS__))); \
})
TEST(va_nargs) {
_unused_ int i = 0;
_unused_ const char *hi = "hello";
TEST_NARGS(0, 0x0000);
TEST_NARGS(0, 0x0000,);
TEST_NARGS(0, 0x0000, );
TEST_NARGS(1, 0x0001, 1);
TEST_NARGS(1, 0x0001, "hello");
TEST_NARGS(1, 0x0001, "hello");
TEST_NARGS(1, 0x0001, i);
TEST_NARGS(1, 0x0001, i++);
TEST_NARGS(2, 0x0002, i, hi);
TEST_NARGS(16, 0x0010, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15);
}
TEST(va_last) {
_unused_ int i = 0;
_unused_ const char *hi = "hello";
assert_se(streq(STRINGIFY(VA_LAST()), ""));
assert_se(VA_LAST(1,2,10) == 10);
assert_se(streq(VA_LAST("hi", "there"), "there"));
assert_se(VA_LAST(1,2,i++) == 0);
assert_se(i == 1);
assert_se(VA_LAST(1,2,++i) == 2);
assert_se(i == 2);
assert_se(VA_LAST(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15) == 15);
assert_se(VA_LAST(VA_NARGS_MAX_LESS_1,123) == 123);
}
TEST(va_declarations) {
int i = 999;
VA_DECLARATIONS(test_decl, int, char*, uint64_t, typeof(i));
test_decl_0x0001 = 10;
test_decl_0x0002 = (char*)"hello";
test_decl_0x0003 = 0xffff000000000001;
test_decl_0x0004 = 8;
assert_se(test_decl_0x0001 == 10);
assert_se(__builtin_types_compatible_p(typeof(test_decl_0x0001), int));
assert_se(streq(test_decl_0x0002, "hello"));
assert_se(__builtin_types_compatible_p(typeof(test_decl_0x0002), char*));
assert_se(test_decl_0x0003 == 0xffff000000000001);
assert_se(__builtin_types_compatible_p(typeof(test_decl_0x0003), uint64_t));
assert_se(test_decl_0x0004 == 8);
assert_se(__builtin_types_compatible_p(typeof(test_decl_0x0004), int));
VA_DECLARATIONS();
VA_INITIALIZED_DECLARATIONS(test_i, test_decl_0x0003, test_decl_0x0004, i, test_decl_0x0002, test_decl_0x0001, i);
assert_se(__builtin_types_compatible_p(typeof(test_i_0x0001), uint64_t));
assert_se(test_i_0x0001 == 0xffff000000000001);
test_i_0x0001--;
assert_se(test_i_0x0001 == 0xffff000000000000);
assert_se(test_decl_0x0003 == 0xffff000000000001);
test_decl_0x0003 = 0xffff;
assert_se(test_i_0x0001 == 0xffff000000000000);
assert_se(__builtin_types_compatible_p(typeof(test_i_0x0002), int));
assert_se(test_i_0x0002 == 8);
test_i_0x0002--;
assert_se(test_i_0x0002 == 7);
assert_se(test_decl_0x0004 == 8);
test_decl_0x0004 = 50;
assert_se(test_i_0x0002 == 7);
assert_se(__builtin_types_compatible_p(typeof(test_i_0x0003), int));
assert_se(test_i_0x0003 == 999);
test_i_0x0003--;
assert_se(test_i_0x0003 == 998);
assert_se(i == 999);
i = 333;
assert_se(test_i_0x0003 == 998);
assert_se(__builtin_types_compatible_p(typeof(test_i_0x0004), char*));
assert_se(streq(test_i_0x0004, "hello"));
assert_se(streq(test_i_0x0004, test_decl_0x0002));
test_i_0x0004 = NULL;
assert_se(test_i_0x0004 == NULL);
assert_se(streq(test_decl_0x0002, "hello"));
assert_se(__builtin_types_compatible_p(typeof(test_i_0x0005), int));
assert_se(test_i_0x0005 == 10);
test_i_0x0005--;
assert_se(test_i_0x0005 == 9);
assert_se(test_decl_0x0001 == 10);
test_decl_0x0001 = 44;
assert_se(test_i_0x0005 == 9);
assert_se(__builtin_types_compatible_p(typeof(test_i_0x0006), int));
assert_se(test_i_0x0006 == 999);
test_i_0x0006--;
assert_se(test_i_0x0006 == 998);
assert_se(i == 333);
i = 222;
assert_se(test_i_0x0006 == 998);
VA_INITIALIZED_DECLARATIONS();
}
#define TEST_TOKENS(equal1, equal2, equal3, equal4, \
expect1, expect2, expect3, expect4, \
v1, v2, v3, v4) \
({ \
assert_se((expect1 == v1) == equal1); \
assert_se((expect2 == v2) == equal2); \
assert_se((expect3 == v3) == equal3); \
assert_se((expect4 == v4) == equal4); \
})
TEST(va_tokens) {
int i1 = 10, i2 = 100, i3 = 50, i4 = 99;
VA_INITIALIZED_DECLARATIONS(test_i_, i1, i2, i3, i4);
VA_MACRO(TEST_TOKENS, true, true, true, true, i1, i2, i3, i4, VA_TOKENS(test_i_, i1, i2, i3, i4));
VA_MACRO(TEST_TOKENS, true, true, true, true, 10, 100, i3, 99, VA_TOKENS(test_i_, i1, i2, i3, i4));
/* VA_TOKENS() doesn't actually use the variadic args, the tokens are based on index */
VA_MACRO(TEST_TOKENS, true, true, true, true, i1, i2, i3, i4, VA_TOKENS(test_i_, x, x, x, x));
VA_MACRO(TEST_TOKENS, true, false, true, false, i1, i4, i3, 1234, VA_TOKENS(test_i_, i1, i2, i3, i4));
}
#define TEST_UNIQ(x, y, z) \
_unused_ int x = 10; \
_unused_ const char *y = "hi"; \
_unused_ uint64_t z = 0xffff;
TEST(va_uniq) {
int x = 20;
const char *y = "still me";
uint64_t z = 0xf;
VA_MACRO(TEST_UNIQ, VA_UNIQ(first, second, third));
assert_se(x == 20);
assert_se(streq(y, "still me"));
assert_se(z == 0xf);
}
#define TEST_MACRO_SWAP(tmp, x, y) \
({ \
tmp = x; \
x = y; \
y = tmp; \
})
#define TEST_MACRO_ALL(u1, u2, v1, v2, vi1, vi2, vc1, vc2, d1, d2) \
({ \
int u1 = 100; \
char *u2 = (char*)"u2"; \
assert_se(u1 == 100); \
assert_se(streq(u2, "u2")); \
\
v1 = d1; \
v2 = d2; \
assert_se(v1 == 30); \
assert_se(streq(v2, "d2")); \
v1++; \
v2++; \
assert_se(v1 == 31); \
assert_se(streq(v2, "2")); \
\
assert_se(vi1 == 10); \
assert_se(streq(vi2, "vi2")); \
vi1++; \
vi2++; \
assert_se(vi1 == 11); \
assert_se(streq(vi2, "i2")); \
\
assert_se(vc1 == 20); \
assert_se(streq(vc2, "vc2")); \
\
assert_se(d1 == 30); \
assert_se(streq(d2, "d2")); \
\
d1 = u1; \
d2 = u2; \
assert_se(d1 == 100); \
assert_se(streq(d2, "u2")); \
\
d1 + 1000; \
})
TEST(va_macro_helper) {
int i1, i2;
i1 = 10;
i2 = 20;
VA_MACRO_HELPER(TEST_MACRO_SWAP,
/*uniq*/,
int,
/*varinit*/,
/*varconst*/,
VA_GROUP(i1, i2));
assert_se(i1 == 20);
assert_se(i2 == 10);
int vi1 = 10, vc1 = 20, d1 = 30;
char *vi2 = (char*)"vi2", *vc2 = (char*)"vc2", *d2 = (char*)"d2";
int all = VA_MACRO_HELPER(TEST_MACRO_ALL,
VA_GROUP(u1, u2),
VA_GROUP(int, char*),
VA_GROUP(vi1, vi2),
VA_GROUP(vc1, vc2),
VA_GROUP(d1, d2));
assert_se(all == 1100);
assert_se(vi1 == 10);
assert_se(streq(vi2, "vi2"));
assert_se(vc1 == 20);
assert_se(streq(vc2, "vc2"));
assert_se(d1 == 100);
assert_se(streq(d2, "u2"));
}
#define TEST_UNIQ_INT_X(_x) \
({ \
int _x = 5; \
_x++; \
})
#define TEST_UNIQ_INT_X_Y_Z(x, y, z, v, ...) \
({ \
int x = v; \
int y = VA_IF_ELSE(VA_FIRST(__VA_ARGS__), 100, __VA_ARGS__); \
int z = VA_IF_ELSE(VA_FIRST(VA_REST(__VA_ARGS__)), 2000, VA_REST(__VA_ARGS__)); \
x + y + z; \
})
TEST(va_macro_uniq) {
int x = 1, _x = 2;
int y = VA_MACRO_UNIQ(TEST_UNIQ_INT_X, _x);
assert_se(x == 1);
assert_se(_x == 2);
assert_se(y == 5);
int z = VA_MACRO_UNIQ(TEST_UNIQ_INT_X_Y_Z, VA_GROUP(x, y, z), x);
assert_se(x == 1);
assert_se(_x == 2);
assert_se(y == 5);
assert_se(z == 2101);
_x = VA_MACRO_UNIQ(TEST_UNIQ_INT_X_Y_Z, VA_GROUP(1, 2, z), 99);
assert_se(x == 1);
assert_se(_x == 2199);
assert_se(y == 5);
assert_se(z == 2101);
z = VA_MACRO_UNIQ(TEST_UNIQ_INT_X_Y_Z, VA_GROUP(_X, _Y, _Z), 5, 20);
assert_se(x == 1);
assert_se(_x == 2199);
assert_se(y == 5);
assert_se(z == 2025);
z = VA_MACRO_UNIQ(TEST_UNIQ_INT_X_Y_Z, VA_GROUP(_X, _Y, _Z), 7, 70, 5000);
assert_se(x == 1);
assert_se(_x == 2199);
assert_se(y == 5);
assert_se(z == 5077);
}
#define TEST_MACRO_INT_CHARP(x1, x2) \
({ \
assert_se(__builtin_types_compatible_p(typeof(x1), int)); \
assert_se(__builtin_types_compatible_p(typeof(x2), char*)); \
})
typedef struct { int a; } structabc;
#define TEST_MACRO_INTP_STRUCTABC_INT(x1, x2, x3) \
({ \
assert_se(__builtin_types_compatible_p(typeof(x1), int*)); \
assert_se(__builtin_types_compatible_p(typeof(x2), structabc)); \
assert_se(__builtin_types_compatible_p(typeof(x3), int)); \
})
#define TEST_MACRO_INT_TMP1(x) \
({ \
x = 7; \
x++; \
})
TEST(va_macro_var) {
int j = VA_MACRO_VAR(TEST_MACRO_INT_TMP1, int);
assert_se(j == 7);
assert_se(VA_MACRO_VAR(TEST_MACRO_INT_TMP1, int) == 7);
VA_MACRO_VAR(TEST_MACRO_INT_CHARP, VA_GROUP(int, char*));
VA_MACRO_VAR(TEST_MACRO_INTP_STRUCTABC_INT, VA_GROUP(int*, structabc, int));
}
#define MACRO_USE_TWICE_1L2_OR_B0(x1, x2) \
({ \
(x1 < x2) || (x1 == 0 && x2 == 0); \
})
#define MACRO_INT_USE_ARGS1_EVAL1(x1) \
({ \
_unused_ int x = x1; \
x1; \
})
#define MACRO_INT_USE_ARGS2_EVAL1(x1,x2) \
({ \
_unused_ int x = x1 + x2; \
x1; \
})
#define MACRO_INT_USE_ARGS2_EVAL2(x1,x2) \
({ \
_unused_ int x = x1 + x2; \
x2; \
})
#define MACRO_INT_USE_ARGS6_EVAL1(x1,x2,x3,x4,x5,x6) \
({ \
_unused_ int x = x1 + x2 + x3 + x4 + x5 + x6; \
x1; \
})
#define MACRO_INT_USE_ARGS6_EVAL4(x1,x2,x3,x4,x5,x6) \
({ \
_unused_ int x = x1 + x2 + x3 + x4 + x5 + x6; \
x4; \
})
TEST(va_macro_varinit) {
_unused_ int i = 1, j = 0, k = 5678;
assert_se(VA_MACRO_VARINIT(MACRO_INT_USE_ARGS1_EVAL1, 1) == 1);
assert_se(VA_MACRO_VARINIT(MACRO_INT_USE_ARGS2_EVAL1, VA_GROUP(1, 10)) == 1);
assert_se(VA_MACRO_VARINIT(MACRO_INT_USE_ARGS2_EVAL2, VA_GROUP(1, 10)) == 10);
assert_se(VA_MACRO_VARINIT(MACRO_INT_USE_ARGS6_EVAL1, VA_GROUP(100, 1000, 1, 0, 20, -80)) == 100);
assert_se(VA_MACRO_VARINIT(MACRO_INT_USE_ARGS6_EVAL4, VA_GROUP(-9,i,1,k,3,4)) == 5678);
assert_se(VA_MACRO_VARINIT(MACRO_SUM12, VA_GROUP(1,10)) == 11);
assert_se(VA_MACRO_VARINIT(MACRO_SUM12, VA_GROUP(10,k)) == 5688);
i = 1234;
assert_se(VA_MACRO_VARINIT(MACRO1, i) == 1234);
assert_se(VA_MACRO_VARINIT(MACRO1, 1234) == i);
i = 10;
j = 20;
assert_se(VA_MACRO_VARINIT(MACRO_USE_TWICE_1L2_OR_B0, VA_GROUP(i++, j--)) == 1);
assert_se(i == 11);
assert_se(j == 19);
assert_se(VA_MACRO_VARINIT(MACRO_USE_TWICE_1L2_OR_B0, VA_GROUP(j + 5, j + 10)) == 1);
assert_se(i == 11);
assert_se(j == 19);
i = 10;
j = 0;
assert_se(VA_MACRO_VARINIT(MACRO_USE_TWICE_1L2_OR_B0, VA_GROUP(i - 10, j)) == 1);
assert_se(i == 10);
assert_se(j == 0);
assert_se(VA_MACRO_VARINIT(MACRO_USE_TWICE_1L2_OR_B0, VA_GROUP(i, j--)) == 0);
assert_se(i == 10);
assert_se(j == -1);
}
TEST(va_macro_varconst) {
_unused_ int i = 1, j = 0, k = 4444;
assert_se(VA_MACRO_VARCONST(MACRO_INT_USE_ARGS1_EVAL1, 1) == 1);
assert_se(VA_MACRO_VARCONST(MACRO_INT_USE_ARGS2_EVAL2, VA_GROUP(1, 10)) == 10);
assert_se(VA_MACRO_VARCONST(MACRO_INT_USE_ARGS6_EVAL4, VA_GROUP(0,i,1,k,3,4)) == 4444);
assert_se(VA_MACRO_VARCONST(MACRO_INT_USE_ARGS6_EVAL1, VA_GROUP(i,2,2,3,4,k)) == 1);
assert_se(VA_MACRO_VARCONST(MACRO_INT_USE_ARGS6_EVAL1, VA_GROUP(1000,2,3,4,k,0)) == 1000);
assert_se(VA_MACRO_VARCONST(MACRO_SUM12, VA_GROUP(1,10)) == 11);
assert_se(VA_MACRO_VARCONST(MACRO_SUM12, VA_GROUP(k,1)) == 4445);
i = 1234;
assert_se(VA_MACRO_VARCONST(MACRO_INT_USE_ARGS1_EVAL1, i) == 1234);
assert_se(VA_MACRO_VARCONST(MACRO_INT_USE_ARGS1_EVAL1, 1234) == i);
}
TEST(va_toomany) {
/* Test assumes largest __VA_EVAL_0x*() macro is 0x0200. */
assert_se(VA_NARGS(VA_NARGS_MAX_LESS_1) == 0x1ff);
assert_se(VA_NARGS(VA_NARGS_MAX_LESS_1,1) == 0x200);
assert_se(VA_WRAP(VA_WRAP_MACRO_LAST,
VA_WRAP_SEPARATOR_NONE,
-1,
VA_WRAP_ZERO_NONE,
VA_WRAP_TOOMANY_CONTEXT,
VA_NARGS_MAX_LESS_1, -2) == -2);
assert_se(VA_WRAP(VA_WRAP_MACRO_LAST,
VA_WRAP_SEPARATOR_NONE,
-1,
VA_WRAP_ZERO_NONE,
VA_WRAP_TOOMANY_CONTEXT,
VA_NARGS_MAX_LESS_1, -2, -3) == -1);
}
TEST(va_number) {
assert_se(___VANUM4(4,3,2,1) == 0x4321);
assert_se(___VANUM4(f,f,f,f) == 0xffff);
assert_se(___VANUM4(0,0,0,0) == 0);
assert_se(___VANUM4(0,0,0,1) == 1);
assert_se(___VANUM4(0,1,0,0) == 0x100);
assert_se(___VANUM4(1,0,0,1) == 0x1001);
assert_se(__VANUM4((1,0,0,1)) == 0x1001);
}
TEST(va_inc) {
assert_se(__VANUM4(__VAINC4((1,2,3,4))) == 0x1235);
assert_se(__VANUM4(__VAINC4((0,0,0,0))) == 1);
assert_se(__VANUM4(__VAINC4((0,0,0,1))) == 2);
assert_se(__VANUM4(__VAINC4((1,0,0,0))) == 0x1001);
assert_se(__VANUM4(__VAINC4((f,f,f,e))) == 0xffff);
assert_se(__VANUM4(__VAINC4((e,f,f,e))) == 0xefff);
assert_se(__VANUM4(__VAINC4((e,f,e,f))) == 0xeff0);
assert_se(__VANUM4(__VAINC4((d,f,f,f))) == 0xe000);
}
DEFINE_TEST_MAIN(LOG_INFO);