You are not logged in.

#1 2021-12-16 18:45:18

porcelain1
Member
Registered: 2020-01-18
Posts: 92

X macro: most epic C trick or worst abuse of preprocessor?

Hey

I want to let you know about the existence of this X macro technique, show you a toy example, show some of my discoveries and discuss a little bit about it.

I discovered this thing X macro a few months ago, I've been studying it extensively and made several experiments, but today I finished a working toy program which source code is located here at GitHub.

I don't remember how I came across it, but this Wikipedia entry introduced me to the concept, but I'd say that the linked articles at the References section were the most valuable sources to understand all, and they explain it all better than I could here.

I have this huge list vehicle building games, so far I stored it on JSON, almost 4000 lines of data, but I translated it to over 1000 lines of X macros in a file vehicle-building-games.xs (I made up the file extension tongue) and made a C program (vehicle-building-games.c) that expands those X macros and spits a HTML file. The code itself is not spectacular, I recognize it could improve but I wanted to finish it fast although I hope it is minimally readable.

Now let's see... other capabilities of X macros, some tricks I learned and some limitations.

At first X macros are introduced as the solution to maintain interrelated declarations of arrays, but I think the true power of X macros is also generate data at compile time and make simple computations. Consider this example:

#include <stdio.h>

#define AS_COMMA_FIRST( A, ...)    A,
#define AS_COMMA_SECOND(A, B, ...) B,
#define AS_COUNT(...) + 1

#define ENEMY_XS(X)           \
X(AGENT    , "Agent"    , NIL) \
X(CLOWN    , "Clown"    , NIL) \
X(GRUNT    , "Grunt"    , NIL) \
X(JEBUS    , "Jebus"    , NIL) \
X(MAG_AGENT, "Mag agent", NIL) \
X(SHERIFF  , "Sheriff"  , NIL)

enum enemy                         { ENEMY_XS(AS_COMMA_FIRST)  };
static char const *enemy_names[] = { ENEMY_XS(AS_COMMA_SECOND) };

int main(void)
{
	printf("Enemies:\n");

	for (int enemy_i = 0; enemy_i < ENEMY_XS(AS_COUNT); enemy_i++)
	{
		printf("- %s\n", enemy_names[enemy_i]);
	}

	return 0;
}

We are printing the names of the enemies, but how many enemies are there? Initially you could create one final enum constant like N_ENEMY or use sizeof, but instead we can define AS_COUNT(...) + 1 that will expand anything onto the amount of X macro declarations, at compile time. We could do other forms of counting using ternary operators, like counting how many NULL or non NULL etc.

Notice I used ellipsis notation on the X macro expansions... this is so we can reuse them in different X macro declarations, no matter how many fields each have. The only catch is that you need to keep an extra column I always fill with NIL since the ellipsis notation requires at least one argument, but you don't need to define NIL anywhere since you are not expanding it onto something.

There's many other things you could do. Take a look at this snippet:

static char const ***game_location_resource_urls[] = {
	#define  GAME_X(NAME, NOTE, FOOTNOTE, LOCATION_XS) (char const **[]){LOCATION_XS},
	#define  LOCATION_X(LOCATION, RESOURCE_XS) (char const *[]){RESOURCE_XS},
	#define  RESOURCE_X(ATTRIBUTE, INDEX, URL) URL,
	#define  NIL NULL
	#include "vehicle-building-games.xs"
	#undef   GAME_X
	#undef   LOCATION_X
	#undef   RESOURCE_X
	#undef   NIL
};

Using nested X macros as seen in the file vehicle-building-games.xs of my toy example, you can create at compile time arrays of arrays of arrays of strings, but each nested array is actually a compound literal. What's the advantage? It is super easy to iterate if you remember our glorious AS_COUNT macro and create an array of sizes or an array of array of sizes. No struct is used, no matrices are used, and if you try to expand the urls as single very long array of urls like below, you will have to maintain lots of annoying offset variables (my first, naive attempt at iterating over nested X macros).

static char const *urls[] = {
	#define  GAME_X(NAME, NOTE, FOOTNOTE, LOCATION_XS) LOCATION_XS
	#define  LOCATION_X(LOCATION, RESOURCE_XS) RESOURCE_XS
	#define  RESOURCE_X(ATTRIBUTE, INDEX, URL) URL,
	#define  NIL NULL,
	#include "vehicle-building-games.xs"
	#undef   GAME_X
	#undef   LOCATION_X
	#undef   RESOURCE_X
	#undef   NIL
};

Now, I have to confess I used to believe C was the most powerful programming language in the world. Used to. Exploring those X macros and looking at the declarations above, it is obvious that using 11 lines of code to declare a variable is kinda inelegant. Also, there are some computations I believe are impossible to be done at compilation time, like computing the longest string or the biggest number inside X macros, in order to declare somewhere e.g. a buffer that will hold some text. Certainly there must be a language with equal or better capabilities of doing compile time computation and data generation in a more elegant, expressive and powerful way. Rust? Nim? C++? Something older like Ada, Eiffel or Pascal? Unfortunately I wasted too much time studying X macros and now I lack time to further research similar capabilities in other languages.

What do you think? Would you use X macros in your programs? You see, I only generated data, but some people generate code too... would you? Do you know if similar feature is available in other languages? Do you think this might be the worst sin that can be committed using the preprocessor? Have you ever seen this technique in the wild, while inspecting other's people code? Can you think of other usages for X macros? I have so much more to tell, but that's it for now because I'm running out of time.


Behemoth, wake up!

Offline

#2 2021-12-21 18:34:20

porcelain1
Member
Registered: 2020-01-18
Posts: 92

Re: X macro: most epic C trick or worst abuse of preprocessor?

Just uploaded to Neocities another page generated using C and X macros. I have a HTML file with dozens of links, I put them all into a X macros file (also, .xs extension seems to be used for Perl FFI with C files sad) and wrote a diminute C program that iterated over the generated data and spits the HTML.

Now I'm thinking I could go full blown and try to make a Web server in C and put my "website" to run on AWS or something. There seems to be a couple of libraries for C I could experiment with. Hmmmmm.


Behemoth, wake up!

Offline

Board footer

Powered by FluxBB