2013-11-25

How to apply a macro to all arguments of a C preprocessor macro

This blog post explains how to iterate over the variable number of arguments of a C preprocessor macro, and call another macro for each argument.

Let's assume we want automate generating the following strings:

const char import0[] = "";
const char import1[] = "import os\n";
const char import2[] = "import os\nimport os.path\n";
const char import3[] = "import os\nimport os.path\nimport sys\n";
const char import4[] = "import os\nimport os.path\nimport sys\nimport re\n";
const char import5[] = "import os\nimport os.path\nimport sys\nimport re\n"
                       "import select\n";
const char import6[] = "import os\nimport os.path\nimport sys\nimport re\n"
                       "import select\nimport struct\n";

A simple option is to create a macro which uses stringification:

#define IMPORT(n) "import " #n "\n"

const char import0[] = "";
const char import1[] = IMPORT(os);
const char import2[] = IMPORT(os) IMPORT(os.path);
const char import3[] = IMPORT(os) IMPORT(os.path) IMPORT(sys);
const char import4[] = IMPORT(os) IMPORT(os.path) IMPORT(sys) IMPORT(re);
const char import5[] = IMPORT(os) IMPORT(os.path) IMPORT(sys) IMPORT(re)
                       IMPORT(select);
const char import6[] = IMPORT(os) IMPORT(os.path) IMPORT(sys) IMPORT(re)
                       IMPORT(select) IMPORT(struct);

Would it be possible to create a variadic macro which would call IMPORT for all its arguments? We want the following to work:

const char import0[] = IMPORT_ALL();
const char import1[] = IMPORT_ALL(os);
const char import2[] = IMPORT_ALL(os, os.path);
const char import3[] = IMPORT_ALL(os, os.path, sys);
const char import4[] = IMPORT_ALL(os, os.path, sys, re);
const char import5[] = IMPORT_ALL(os, os.path, sys, re, select);
const char import6[] = IMPORT_ALL(os, os.path, sys, re, select, struct);

Indeed, it's possible, define the IMPORT_ALL macro like this:

#define IMPORT_ALL(...) "" APPLY_ALL(IMPORT, __VA_ARGS__)

The tricky part is defining the APPLY_ALL macro. Here is a possible implementation which works with gcc and clang, and it supports up to 6 arguments. (It's easy to increase the number of supported arguments.)

#define APPLY0(t, dummy)
#define APPLY1(t, a) t(a)
#define APPLY2(t, a, b) t(a) t(b)
#define APPLY3(t, a, b, c) t(a) t(b) t(c)
#define APPLY4(t, a, b, c, d) t(a) t(b) t(c) t(d)
#define APPLY5(t, a, b, c, d, e) t(a) t(b) t(c) t(d) t(e)
#define APPLY6(t, a, b, c, d, e, f) t(a) t(b) t(c) t(d) t(e) t(f)

#define NUM_ARGS_H1(dummy, x6, x5, x4, x3, x2, x1, x0, ...) x0
#define NUM_ARGS(...) NUM_ARGS_H1(dummy, ##__VA_ARGS__, 6, 5, 4, 3, 2, 1, 0)
#define APPLY_ALL_H3(t, n, ...) APPLY##n(t, __VA_ARGS__)
#define APPLY_ALL_H2(t, n, ...) APPLY_ALL_H3(t, n, __VA_ARGS__)
#define APPLY_ALL(t, ...) APPLY_ALL_H2(t, NUM_ARGS(__VA_ARGS__), __VA_ARGS__)

#define IMPORT_ALL(...) "" APPLY_ALL(IMPORT, __VA_ARGS__)
#define IMPORT(n) "import " #n "\n"

const char import0[] = IMPORT_ALL();
const char import1[] = IMPORT_ALL(os);
const char import2[] = IMPORT_ALL(os, os.path);
const char import3[] = IMPORT_ALL(os, os.path, sys);
const char import4[] = IMPORT_ALL(os, os.path, sys, re);
const char import5[] = IMPORT_ALL(os, os.path, sys, re, select);
const char import6[] = IMPORT_ALL(os, os.path, sys, re, select, struct);

See http://stackoverflow.com/questions/2632300/looping-through-macro-varargs-values and http://stackoverflow.com/questions/824639/variadic-recursive-preprocessor-macros-is-it-possible for inspiration and a more detailed explanation on NUM_ARGS and C macros with a variable number of arguments.

2013-11-11

How to create Git commits dated based on last-modification time of involved files

This blog post explains how to create Git commits dated based on last-modification time of involved files.

git commit creates the commit with the date taken from the current system date. Sometimes, especially if old versions of files are added to git, it's preferable to create a commit whose date is based on last-modification time of involved (i.e. non-deleted) files. There is no built-in Git command for that. As a workaround, it's possible to specify git commit --date=... ... manually, but it would be nicer if the time was autodetected as the maximum of the last-modification times of the files involved.

The git-dcommit tool tool can be used to automate that. Download the script from here, make it executable, add it to your $PATH, and use git dcommit ... instead of git commit ... . The tool is written in Python, and it needs a Unix system such as Linux. It shouldn't be hard do port it to Windows.