The C preprocessor is not a text manipulation system, but rather a token manipulation system. It cannot split, introspect, or modify tokens, but it can replace some token sequences with others, and it can form new tokens by pasting tokens together or by forming a string literal token representing the text of another token. The macro expansion facility has no recursion and no iteration (though limited iteration can be simulated). Although there is conditional compilation, there is no conditional evaluation in the context of macro expansion. There is certainly no sed
-like character substitution anywhere in the preprocessor's arsenal.
One can accomplish some surprising things despite those limitations. This gives a small taste, but does not push anywhere near the limits:
// Helper macros:
#define JOIN_4(a1, a2, a3, a4, ...) #a1 ";" #a2 ";" #a3 ";" #a4
#define JOIN_3(a1, a2, a3, ...) #a1 ";" #a2 ";" #a3
#define JOIN_2(a1, a2, ...) #a1 ";" #a2
#define JOIN_1(a1, ...) #a1
#define SEMI_LIST(a1, a2, a3, a4, a5, ...) JOIN ## a5 (a1, a2, a3, a4)
// Main macro
// Use this with at least one and no more than four arguments:
#define FONT_FX(...) "\x1b[" SEMI_LIST(__VA_ARGS__, _4, _3, _2, _1) "m"
That determines the number of variable arguments, between one and four, and expands a different macro for each case, where the name of the argument-count-specific macro is formed via token pasting. That technique can be extended in the obvious way to a larger maximum number of arguments, but it cannot support an arbitrary number of arguments.