5

We have inherited a very convolved project (500kloc) with a lot of preprocessor conditional logic, most of which is no longer relevant, and I want to clean it up.

Can I use the preprocessor¹ to expand only some of the conditional logic, and leave all other preprocessor macros, defines, and includes alone in the output?

¹ Here, by "the preprocessor", I really mean "any tool", either a standard C preprocessor, something I can install, or even a hacked-together Perl or Python script.


For instance, suppose we have this set of code:

#include <foo>
#define baz
#define bar(a) do{(a)+1} \
               while(0)
#ifdef X
  #if Y > 20
    #if Z > 5
      so_far_so_good = true;
    #endif
    #ifdef baz
    something();
    #endif
  #else
    otherthing();
  #endif
#else
  #if Z > 10
    wow().this.is.bad;
  #endif
#endif

The tool I want (and might need to write if it doesn't exist) would be a version of CPP that accepts not only the list of definitions for a particular invocation, but also a list of defines to respect during expansion. Any preprocessor symbol not in the second list is completely left alone. An example is in order:

cpptreadlight -DY=22 --only=Y

would produce:

#include <foo>
#define baz
#define bar(a) do{(a)+1} \
               while(0)
#ifdef X
    #if Z > 5
      so_far_so_good = true;
    #endif
    #ifdef baz
    something();
    #endif
#else
  #if Z > 10
    wow().this.is.bad;
  #endif
#endif

and:

cpptreadlight -DY=22 -DZ=8 -DX --only=Y,baz,Z

would give me:

#include <foo>
#define bar(a) do{(a)+1} \
               while(0)
#ifdef X
      so_far_so_good = true;
    something();
#else
#endif

Notice that even though X is defined, it was left behind, because it didn't appear in the --only list. Also note that baz was in the --only list, and so was expanded once it was defined in the source.


I tried a hack-solution: escaping uninteresting stuff using a pipeline like the following (my own gsub tool is used, but it does what you might expect):

function escape_tr() { 
   gsub "#(define|include)" '@@@\1' < $1 | 
     (echo '#include "simple.h"' && gsub '\\$' "%%%") | 
       cpp -C -P -DY=301 -DZ > $1.new 
}

Now I can filter out a lot of stuff, and then put the things I want the preprocessor to expand in simple.h. Comments are left alone, and #line directives are left out.

This almost does the trick, in that includes are not pulled in, #define blocks are not defined, and so not expanded into the body. But of course it doesn't let me specify the set of conditional logic that I'd like to keep in the output. That's bad. Some of it is important to keep conditional.

#if nests, and the #else and #endif tokens don't lexically match, putting the problem beyond a regex pipeline. I need a full-blown parser, something practically identical to cpp itself, but with finer grained control of what is expanded.

Hence, before digging into a preprocessor to implement this, I thought I'd ask if anyone has solved this problem before. I can't be the only one to have inherited a preprocessor spaghetti nest full of dead branches.

Toby Speight
  • 27,591
  • 48
  • 66
  • 103
clord
  • 338
  • 1
  • 6

2 Answers2

9

There is a tool called "unifdef" that will do what you want.

Paul Tomblin
  • 179,021
  • 58
  • 319
  • 408
  • Confirmed that this in combination with my `escape_tr` shell script in the original question can perform all of the actions I'd like done on this source-base. Thanks Paul. Something like `cpptreadlight` should exist though. It generalizes both `unifdef` and `cpp`. – clord Nov 10 '10 at 19:47
  • I'm not sure why you need `escape_tr`, but possibly I'm missing something. – Paul Tomblin Nov 10 '10 at 20:38
  • `unifdef` only deals with ifdefs. I also want to elide some of the redundant defines. Granted this wasn't part of what I was asking in the original question. – clord Nov 11 '10 at 13:07
  • I don't think that's true - if you do "unifdef -DY=22" it will take care of all the "#if Y..." as well. – Paul Tomblin Nov 11 '10 at 13:25
0

You should definitely take a look at boost.wave

from the boost.wave preface:

So the main goals for the Wave project are:

  • full conformance with the C++ standard (ISO/IEC 14882:1998) 1 and with the C99 standard (INCITS/ISO/IEC 9899:1999)
  • usage of Spirit[4] for the parsing parts of the game (certainly :-)
  • maximal usage of STL and/or Boost libraries (for compactness and maintainability)
  • straightforward extendability for the implementation of additional features building a flexible library for different C++ lexing and preprocessing needs
David
  • 9,635
  • 5
  • 62
  • 68