1

I'm trying to create a macro that expands into font effect escape codes for the terminal. Here's two and three argument examples:

#define FONT_FX2(arg1, arg2) "\x1b[" #arg1 ";" #arg2 "m"
#define FONT_FX3(arg1, arg2, arg3) "\x1b[" #arg1 ";" #arg2 ";" #arg3 "m"

Each argument is a font effect code stored either as macro constants or in an enum.

Is it possible to do this using variadic macro arguments?

Stringizing __VA_ARGS__ directly places commas between each argument, and I couldn't find any way to place semicolons between each argument. It would be nice if there was a to replace the commas with semicolons directly, like s/, /;/g using sed.

ealker
  • 68
  • 7
  • Do you want something that will paste an *arbitrary* number of arguments together with semicolons, or would it be sufficient to cap the number of arguments supported at some relatively small number (3? 4? 5?)? – John Bollinger Jul 14 '23 at 23:35
  • And how much are you willing to pay for this, in terms of macro-stack complexity? Because although there may be ways to get what you want, there are no *easy* ways. – John Bollinger Jul 14 '23 at 23:37
  • @JohnBollinger An arbitrary number of arguments would be nice but I don't see myself using more than 3 or 4 if the cost in complexity is high. I could also probably just solve this without macros, or not do this at all since I can use separate escape codes for each font effect. – ealker Jul 14 '23 at 23:51
  • This is a typical [“When you have a hammer, everything looks like a nail”](https://en.wikipedia.org/wiki/Law_of_the_instrument) problem. Just because you have a standard C preprocessor does not mean you need to use it for all preprocessing. It is fairly simple to write a script or program that scans C code for some pattern you designate, like `FONT_FX(argument, arugment, argument)` and rewrites it into the desired C code, and it is also simple to write a `make` or IDE rule that runs that script or program on source files you designate to produce C files ready for compilation. – Eric Postpischil Jul 15 '23 at 00:16

1 Answers1

1

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.

John Bollinger
  • 160,171
  • 8
  • 81
  • 157