6

Can I write a single-parameter macro which takes a sequence of words/tokens separated by whitespace, and produces the same sequence but with underscores between each word/token?

e.g.

MAGIC_MACRO(brave new  world)

would evaluate to

brave_new_world

Notes:

  • I don't mind whether each whitespace character becomes an underscore, just that at least one be used.
  • If I can't do this generally, I would at least like to know if this is possible with exactly two words.
einpoklum
  • 118,144
  • 57
  • 340
  • 684
  • This is very specific (and most likely impossible), perhaps try posting the broader problem you are trying to solve then we can help you better? – Sabrina Jewson May 29 '19 at 16:35
  • @KaiJ: I've already solved the problem I was trying to solve (which involved generating identifiers using concatenation) another way. – einpoklum May 29 '19 at 16:38
  • By the time preprocessor substitutions are done, the input has been tokenised and whitespace sequences (including comments) are no longer taken into account. So if it were possible -- and I'm pretty sure it isn't -- it wouldn't make any difference if there were one, seven, or zero spaces between the tokens. (For the later case, `brave/**/new/**/world`.) – rici May 29 '19 at 21:46
  • @rici: Let me rephrase the question then. – einpoklum May 29 '19 at 21:47

1 Answers1

1

Can I write a single-parameter macro which takes a sequence of words/tokens separated by whitespace, and produces the same sequence but with underscores between each word/token?

Of course, it's not possible. There are no string manipulation utilities in preprocessor.

Och, who am I kidding. First, you have to build a dictionary with all possible words combinations. For the purpose of this, we will have a small dictionary with few words:

#define WORD_world  world,
#define WORD_new    new,
// etc.

You might get the pattern. Then let's implement the macro that will do the following:

brave new  world                 // our starting argument
WORD_##brave new world           // add WORD_ to all arguments and join arguments with spaces
WORD_brave new world
brave, new world                 // expand WORD_brave macro
WORD_brave WORD_new world        // add WORD_ to all arguments and join arguments with spaces
brave, new, world                // expand WORD_* macros
WORD_brave WORD_new WORD_world   // add WORD_ to all arguments and join arguments with spaces
brave, new, world,               // expand WORD_* macros
     /* --- repeat above steps up to maximum words you need to handle --- */
brave_new_world                  // join arguments with `_` ignoring last empty one

The following code:

// our dictionary
#define WORD_world  world,
#define WORD_new    new,
#define WORD_brave  brave,
#define WORD_hello  hello,
#define WORD_Hello  Hello,

// the classics
#define COMMA(...)  ,
#define FIRST(a, ...)  a

// apply function f for each argument recursively with tail
#define FOREACHTAIL_1(f,a)      f(a,)
#define FOREACHTAIL_2(f,a,...)  f(a,FOREACHTAIL_1(f,__VA_ARGS__)) 
#define FOREACHTAIL_3(f,a,...)  f(a,FOREACHTAIL_2(f,__VA_ARGS__)) 
#define FOREACHTAIL_4(f,a,...)  f(a,FOREACHTAIL_3(f,__VA_ARGS__)) 
#define FOREACHTAIL_N(_4,_3,_2,_1,N,...)  \
        FOREACHTAIL_##N
#define FOREACHTAIL(f,...) \
        FOREACHTAIL_N(__VA_ARGS__,4,3,2,1)(f,__VA_ARGS__)

// if there are two arguments, expand to true. Otherwise false.
#define IFTWO_N(_0,_1,N,...)     N
#define IFTWO(true, false, ...)  IFTWO_N(__VA_ARGS__, true, false)

// If empty, expand to true, otherwise false.
// https://gustedt.wordpress.com/2010/06/08/detect-empty-macro-arguments/
#define IFEMPTY(true, false, ...)  IFTWO(true, false, COMMA __VA_ARGS__ ())

// Join arguments with `_`.
#define JOIN_U(a, b)      a##_##b
#define JOIN_TWO_IN(a,b)  IFEMPTY(FIRST, JOIN_U, b)(a, b)
#define JOIN_TWO(a,b)     JOIN_TWO_IN(a,b)
#define JOIN(...)         FOREACHTAIL(JOIN_TWO, __VA_ARGS__)

// Append WORD_ to each argument and join arguments with spaces.
#define WORD_             /* the last one expands to empty */
#define WORDS_TWO(a, b)   WORD_##a b
#define WORDS(...)        FOREACHTAIL(WORDS_TWO, __VA_ARGS__)

#define MAGIC_MACRO(a)  JOIN(WORDS(WORDS(WORDS(WORDS(WORDS(a))))))

MAGIC_MACRO(brave new  world)
MAGIC_MACRO(Hello world)

Produces:

brave_new_world
Hello_world
KamilCuk
  • 120,984
  • 8
  • 59
  • 111
  • "First, you have to build a dictionary with all possible words combinations. " <- You mean all infinitely-many combinations of words in the English language plus gibberish? :-( – einpoklum Aug 05 '22 at 09:50