前回の記事でC言語のプリプロセッサでFizzBuzzする方法を書いたのですが、GCCとかでしか動かなくてちょっとアレだなぁ、と思ったので がんばって MSVCとかTCCとかでも動くようにしてみました。
がんばって ってところがポイントです。がんばったんです!
##で、どんなコードになったの?
こんなコードになりました。
#include "counter.h"
#ifndef FIZZBUZZ_MAX
# define FIZZBUZZ_MAX 100
#endif
#if COUNTER % 15 == 0
FizzBuzz
#elif COUNTER % 3 == 0
Fizz
#elif COUNTER % 5 == 0
Buzz
#else
COUNTER
#endif
#if COUNTER < FIZZBUZZ_MAX
# include __FILE__
#endif
このコード自体は前回の__INCLUDE_LEVEL__
を使ったものとそう変わらないような気がします。
しかし、このコードでは__INCLUDE_LEVEL__
の代わりにCOUNTER
というマクロが使われています。
このCOUNTER
というマクロはもちろん、コンパイラによって組み込まれたものではなく、僕が定義したものです。そして、その定義はcounter.h
の中にあります。
このcounter.h
に秘密がありそうですね。
##counter.h
。あるいはCPPで状態を扱うということ
前回の記事で「CPPでは変数のようにマクロの更新ができない」と書きました。
それはつまり、
#define COUNTER 0
というマクロがあったとして、
#define COUNTER (COUNTER + 1)
のようにもう一度定義しなおしても、展開結果は‘(COUNTER + 1)‘のようになってしまう、ということです。
しかも、仮に(0 + 1)
と展開されたとしても、CPPではそこからさらに計算を行なって1
という数値にしてくれるわけではないので、とてもFizzBuzzの出力には使えそうにありません。
前回の記事ではタウンページよろしく「そんなときに__INCLUDE_LEVEL__
が便利なんですよ」と言ったわけなのですが、今回は__INCLUDE_LEVEL__
の無い環境でも動くようにしなければいけないので、使うことはできません。
そこでどのようにするのかと言うと‥‥。
###「御託はいいからさっさとコードを見せろ!」
はい。
いやまあ、言う程すごいことはしてないんですよ。
強いて言えばちょっとがんばっただけです。
それではcounter.h
のコード。
#ifndef COUNTER_H_
#define COUNTER_H_
/* 1の位の値 */
#define COUNTER_VALUE_1 0
/* 10の位の値 */
#define COUNTER_VALUE_2 0
/* 100の位の値 */
#define COUNTER_VALUE_3 0
/* 1000の位の値 */
#define COUNTER_VALUE_4 0
#define COUNTER 0
/* 引数として与えられたものを、展開してから結合するマクロ */
#define JOIN1(x) x
#define JOIN2(x, y) JOIN2_(x, y)
#define JOIN2_(x, y) x ## y
#define JOIN3(x, y, z) JOIN3_(x, y, z)
#define JOIN3_(x, y, z) x ## y ## z
#define JOIN4(x, y, z, w) JOIN4_(x, y, z, w)
#define JOIN4_(x, y, z, w) x ## y ## z ## w
#endif /* COUNTER_H_ */
/* 1の位から処理 */
#if COUNTER_VALUE_1 == 0
# undef COUNTER_VALUE_1
# define COUNTER_VALUE_1 1
#elif COUNTER_VALUE_1 == 1
# undef COUNTER_VALUE_1
# define COUNTER_VALUE_1 2
#elif COUNTER_VALUE_1 == 2
# undef COUNTER_VALUE_1
# define COUNTER_VALUE_1 3
#elif COUNTER_VALUE_1 == 3
# undef COUNTER_VALUE_1
# define COUNTER_VALUE_1 4
#elif COUNTER_VALUE_1 == 4
# undef COUNTER_VALUE_1
# define COUNTER_VALUE_1 5
#elif COUNTER_VALUE_1 == 5
# undef COUNTER_VALUE_1
# define COUNTER_VALUE_1 6
#elif COUNTER_VALUE_1 == 6
# undef COUNTER_VALUE_1
# define COUNTER_VALUE_1 7
#elif COUNTER_VALUE_1 == 7
# undef COUNTER_VALUE_1
# define COUNTER_VALUE_1 8
#elif COUNTER_VALUE_1 == 8
# undef COUNTER_VALUE_1
# define COUNTER_VALUE_1 9
#elif COUNTER_VALUE_1 == 9
# undef COUNTER_VALUE_1
/* 10位に繰り上がる場合 */
# define COUNTER_VALUE_1 0
# if COUNTER_VALUE_2 == 0
# undef COUNTER_VALUE_2
# define COUNTER_VALUE_2 1
# elif COUNTER_VALUE_2 == 1
# undef COUNTER_VALUE_2
# define COUNTER_VALUE_2 2
# elif COUNTER_VALUE_2 == 2
# undef COUNTER_VALUE_2
# define COUNTER_VALUE_2 3
# elif COUNTER_VALUE_2 == 3
# undef COUNTER_VALUE_2
# define COUNTER_VALUE_2 4
# elif COUNTER_VALUE_2 == 4
# undef COUNTER_VALUE_2
# define COUNTER_VALUE_2 5
# elif COUNTER_VALUE_2 == 5
# undef COUNTER_VALUE_2
# define COUNTER_VALUE_2 6
# elif COUNTER_VALUE_2 == 6
# undef COUNTER_VALUE_2
# define COUNTER_VALUE_2 7
# elif COUNTER_VALUE_2 == 7
# undef COUNTER_VALUE_2
# define COUNTER_VALUE_2 8
# elif COUNTER_VALUE_2 == 8
# undef COUNTER_VALUE_2
# define COUNTER_VALUE_2 9
# elif COUNTER_VALUE_2 == 9
# undef COUNTER_VALUE_2
# define COUNTER_VALUE_2 0
/* 100の位に繰り上がる場合 */
# if COUNTER_VALUE_3 == 0
# undef COUNTER_VALUE_3
# define COUNTER_VALUE_3 1
# elif COUNTER_VALUE_3 == 1
# undef COUNTER_VALUE_3
# define COUNTER_VALUE_3 2
# elif COUNTER_VALUE_3 == 2
# undef COUNTER_VALUE_3
# define COUNTER_VALUE_3 3
# elif COUNTER_VALUE_3 == 3
# undef COUNTER_VALUE_3
# define COUNTER_VALUE_3 4
# elif COUNTER_VALUE_3 == 4
# undef COUNTER_VALUE_3
# define COUNTER_VALUE_3 5
# elif COUNTER_VALUE_3 == 5
# undef COUNTER_VALUE_3
# define COUNTER_VALUE_3 6
# elif COUNTER_VALUE_3 == 6
# undef COUNTER_VALUE_3
# define COUNTER_VALUE_3 7
# elif COUNTER_VALUE_3 == 7
# undef COUNTER_VALUE_3
# define COUNTER_VALUE_3 8
# elif COUNTER_VALUE_3 == 8
# undef COUNTER_VALUE_3
# define COUNTER_VALUE_3 9
# elif COUNTER_VALUE_3 == 9
# undef COUNTER_VALUE_3
# define COUNTER_VALUE_3 0
/* さすがに1000の位まで対応しなくていいよね? */
# define COUNTER_VALUE_4 1
# endif
# endif
#endif
/* 桁の数に合わせて結合するようにする */
#if COUNTER_VALUE_2 == 0 && COUNTER_VALUE_3 == 0 && COUNTER_VALUE_4 == 0
# undef COUNTER
# define COUNTER JOIN1(COUNTER_VALUE_1)
#elif COUNTER_VALUE_3 == 0 && COUNTER_VALUE_4 == 0
# undef COUNTER
# define COUNTER JOIN2(COUNTER_VALUE_2, COUNTER_VALUE_1)
#elif COUNTER_VALUE_4 == 0
# undef COUNTER
# define COUNTER JOIN3(COUNTER_VALUE_3, COUNTER_VALUE_2, COUNTER_VALUE_1)
#else
# undef COUNTER
# define COUNTER JOIN4(COUNTER_VALUE_4, COUNTER_VALUE_3, COUNTER_VALUE_2, COUNTER_VALUE_1)
#endif
長いです。
しかも、長いわりに中身がないという、プログラマの三大美徳にひっかかりそうものになっています。
読めば分かりますが、展開結果にCOUNTER
が出てこないようにするためと計算の無いCPPのために、10進数に数値を分解してインクリメントを自力で実装しています。
状態を表現するのが難しいCPPとはいえ、何だかとても残念なコードなのでした。
##MSVCでの実行
Visual Studio立ち上げるの面倒だったので、cl
コマンドの使えるプロンプトを起動して、fizzbuzz.c
とcounter.h
の保存されたディレクトリに移動して、
$ cl /EP fizzbuzz.c | grep -v -e "#" -v -e "^$"
でFizzBuzzが見れます。ちなみにMSVCでは#include
の深さが1024までいけるっぽいので、
$ cl /EP /DFIZZBUZZ_MAX=1000 fizzbuzz.c | grep -v -e "#" -v -e "^$"
のようにして、1000までFizzBuzzできます。
MSVCたんいじめるの楽しい! ✌(’ω’✌ )三✌(’ω’)✌三( ✌’ω’)✌
また、TCCでは再帰のレベルが32までしかサポートされてないっぽいので、
$ tcc -E -w -DFIZZBUZZ_MAX=30 fizzbuzz.c | grep -v -e "#" -v -e "^ $" -v -e "^$"
とするといいかと思います。
##最後に
コンパイラいじめダメゼッタイ