プリプロセッサ代わりに Ruby を使う

後輩からこんな質問を受けました。
「こういうマクロの展開結果を 16 進数で出したいんですけど、簡単な方法ありませんか?」

(EVENT_DEF.h)

#define ID_MODULE1 8
#define PRIORITY   2

#define EVENT_A_CHANGE_STATE       ((2<<24)+( 0<<16)+(ID_MODULE1<<8)+PRIORITY)
#define EVENT_A_CHANGE_RESULT      ((2<<24)+( 1<<16)+(ID_MODULE1<<8)+PRIORITY)
// 以下続く…

一番単純な方法はこんなプログラムを書くことです。

#include "EVENT_DEF.h"
void main() {
    printf( "EVENT_A_CHANGE_STATE  - %08x\n", EVENT_A_CHANGE_STATE );
    printf( "EVENT_A_CHANGE_RESULT - %08x\n", EVENT_A_CHANGE_RESULT );
    // 以下延々続く…
}

しかし、 define が数百もある場合はそうもいきません。全ての定義の printf() を書かなければならないからです。ぞっとしませんね。
次に考えたのは、プリプロセッシング後のソースを見ることです。 VC++ 6.0 でやる場合、 cl.exe の引数に /P (/EP)を指定しろと MSDN に書かれています。というわけで、上の単純なプログラムの設定を書き換えてみました。[プロジェクトの設定] -> [C/C++]タブのプロジェクトオプションで

/nologo /MLd /W3 /Gm

/nologo /P /EP /MLd /W3 /Gm

に変更。これでソースコードと同じ場所に *.i が出力されます。結果、こんなファイルが出てきました。

void main()
{
    printf( "EVENT_A_CHANGE_STATE  - 0x%08x\n", ((2<<24)+( 0<<16)+(8<<8)+2) );
    printf( "EVENT_A_CHANGE_RESULT - 0x%08x\n", ((2<<24)+( 1<<16)+(8<<8)+2) );
}

えー。
…というわけで、結局 Rubyプリプロセッサライクなスクリプトを書くことに。

(pp.rb)

File.open( ARGV.shift ) { |f|
  f.each_line {|l|
    if l =~ /^\#define\s(\w+)\s+(.+)$/

      name, s = $1, $2

      # 定義を置換
      s.gsub!( /ID_MODULE1/, 8.to_s )
      s.gsub!( /PRIORITY/,   2.to_s )

      printf "%s\t0x%08x\n", name, eval(s).to_i
    end
  }
}

これで

ruby pp.rb EVENT_DEF.h

として、ようやく次のような出力結果が得られました。やれやれ。

EVENT_A_CHANGE_STATE    0x02000802
EVENT_A_CHANGE_RESULT   0x02010802