/*
 *	app.cpp
 *
 *	StripComments.exe - Delete Comments from C/C++ Source Code
 *	Copyright (C) 2016 Naoyuki Sawa
 *
 *	* Mon Aug 15 21:37:25 JST 2016 Naoyuki Sawa
 *	- 1st リリース。
 *	- 下記の資料を参考にさせて頂きました。
 *	  「Dream.In.Code - Delete Comments from Source Code」(http://www.dreamincode.net/forums/topic/57048-delete-comments-from-source-code/)
 *	* Tue Aug 16 21:54:11 JST 2016 Naoyuki Sawa
 *	- '-i'(インプレースモード)オプションを追加しました。
 *	  尚、一般的なコメント削除ツールには、インプレースモードに加えて、より強力な、フォルダを再帰的に辿って全てのファイルに対してファイルの上書き更新を行うようなオプションも有るのですが、当ツールにはそのようなオプションは追加しない事にしました。
 *	  万一、誤って、ルートフォルダ等を指定した場合に、危険が大きすぎると思ったからです。
 *	  上記のような処理を行いたい場合は、予め「DIR /B /S *.c *.h > List.txt」でファイル一覧を作成しておき、「TYPE List.txt | xargs.exe -n1 StripComments.exe -i」のようにして実行して下さい。
 *	- '-s'(行末の空白文字,及び,空行を削除する)オプションを追加しました。
 *	  別途sed等を使って行う事も可能なのですが、手間を省くために、当ツールの機能に含める事にしました。
 *	* Wed Aug 17 21:19:23 JST 2016 Naoyuki Sawa
 *	- 行コメントを改行せずに終端に到達した場合もエラーにせず許容する事にしました。
 *	- '-z'(「#if 0」～「#endif」の区間を削除する)オプションを追加しました。
 *	* Thu Aug 18 21:53:46 JST 2016 Naoyuki Sawa
 *	- '-d','-u'オプションを追加しました。
 *	- '-z'オプション指定時の削除ルールを追加しました。
 *	  これまでは、「#if 0 ～ #endif」を削除する事しか行っていませんでしたが、それに加えて、「#if 1 ～ #endif」「#if 0 ① #else ② #endif」「#if 1 ① #else ② #endif」の削除ルールを追加しました。
 *	  詳細は、StripIfZero()の実装を参照して下さい。
 *	* Thu Aug 18 22:33:28 JST 2016 Naoyuki Sawa
 *	- 「#elif」を含むブロックに対する削除ルールを追加しました。
 *	  削除ルールの詳細は、DeleteBlock()関数の実装を参照して下さい。
 *	* Sat Oct 15 21:50:56 JST 2016 Naoyuki Sawa
 *	- 「#if」「#elif」の式を(限定的ではありますが)評価出来るようにしました。
 *	  評価出来る式については、「「#if」及び「#elif」の式を評価する処理」のコメントを参照して下さい。
 *	  主な目的は、「#if defined(～)」や「#if (defined(～)&&defined(～))」のような疑似命令も解釈して、削除出来るようにするためです。
 */
#include "app.h"
#define VERSION		"20161015"	//最終更新日
/****************************************************************************
 *	ローカル関数宣言
 ****************************************************************************/
static char* LoadFile(FILE* fp);
static void SaveFile(FILE* fp, const char* s);
static int GetChar(const char* s, int* p_index);
static void PutChar(GString* s, int c);
static char* StripComments(const char* src);
static char* TrimSpace(const char* src);
static char* StripIfZero(const char* src);
static int DeleteBlock(GPtrArray* array, const struct _ST_Block* pBlock);	//{{2016/08/18追加:「#elif」を含むブロックに対する削除ルールを追加しました。}}
static int IfExprEval(const char* expr);					//{{2016/10/15追加:「#if」「#elif」の式を(限定的ではありますが)評価出来るようにしました。}}
/****************************************************************************
 *	ローカル変数
 ****************************************************************************/
//'-d'オプションと'-u'オプションのためのハッシュテーブル
static ght_hash_table_t* ht_symbol;	//'-d'オプションで指定されたシンボルならば、シンボル名⇒'d'を登録する。'-u'オプションで指定されたシンボルならば、シンボル名⇒'u'を登録する。
/****************************************************************************
 *	main
 ****************************************************************************/
int main(int argc, char* argv[]) {
	int exitCode = EXIT_SUCCESS;
#ifdef  _DEBUG
	remove("syslog.out");			//毎回ログを削除する事にした。
    #ifndef USE_JOB_DEBUG
	openlog(NULL, LOG_PERROR, LOG_USER);	//デバッグビルドでは、システムログを標準エラーにも出力する。ただし、デバッグジョブを使用する場合は、デバッグジョブのcurses表示と衝突しないよう、標準エラーには出力しない。
    #endif//USE_JOB_DEBUG
#else //_DEBUG
	setlogmask(LOG_UPTO(LOG_INFO));		//リリースビルドでは、LOG_DEBUGを抑制する。
#endif//_DEBUG
#if     (defined(GC_H) && !defined(USE_BISON_FLEX))
//	putenv("GC_LOG_FILE=CON");//ログ出力先を指定する。
//	GC_set_find_leak(1);//メモリリーク検出を開始する。
#endif//(defined(GC_H) && !defined(USE_BISON_FLEX))
#ifdef  __CLIP_SEH_H__
	SEH_init();
#endif//__CLIP_SEH_H__
	app_init(argc, argv);
	exitCode = app_main(argc, argv);
	app_exit();
#if     (defined(GC_H) && !defined(USE_BISON_FLEX))
//	CHECK_LEAKS();//メモリリークを検出する。
#endif//(defined(GC_H) && !defined(USE_BISON_FLEX))
	return exitCode;
}
/****************************************************************************
 *	usage
 ****************************************************************************/
__declspec(noreturn) static void usage() {
	fprintf(stderr, "StripComments.exe - Delete Comments from C/C++ Source Code (%s)\n", VERSION);
	fprintf(stderr, "Copyright (C) 2016 Naoyuki Sawa\n");
	fprintf(stderr, "\n");
	fprintf(stderr, "USAGE:\n");
	fprintf(stderr, "  StripComments.exe [option ...] [filename ...]\n");
	fprintf(stderr, "  ・filenameは、ワイルドカードによる複数指定も可能です。\n");
	fprintf(stderr, "  ・filenameを指定しなければ、標準入力から読み込みます。\n");
	fprintf(stderr, "    ただし、-iオプションを指定した場合は、少なくとも一つ以上、filenameを指定する必要が有ります。\n");
	fprintf(stderr, "\n");
	fprintf(stderr, "OPTIONS:\n");
	fprintf(stderr, "  -i            ファイルの上書き更新を行います。\n");
	fprintf(stderr, "                このオプションを指定しなければ、結果を標準出力へ出力します。\n");
	fprintf(stderr, "  -s            行末の空白文字,及び,空行を削除します。\n");
	fprintf(stderr, "                このオプションを指定しなければ、行末の空白文字,及び,空行を削除しません。\n");
	fprintf(stderr, "  -z            「#if ～ #endif」のブロックを、削除ルールに基づいて削除します。\n");
//{{2016/08/18削除:「#elif」を含むブロックに対する削除ルールを追加しました。
//	fprintf(stderr, "                □現時点での削除ルール(※今後、追加・変更する可能性が有ります。)\n");
//	fprintf(stderr, "                │「#if 0 ～ #endif」の単純なブロックならば、このブロック全体を削除する。\n");
//	fprintf(stderr, "                │「#if 1 ～ #endif」の単純なブロックならば、「#if 1」の行と「#endif」の行を削除する。\n");
//	fprintf(stderr, "                │「#if 0 ① #else ② #endif」のブロックならば、②の部分以外を削除する。\n");
//	fprintf(stderr, "                │「#if 1 ① #else ② #endif」のブロックならば、①の部分以外を削除する。\n");
//	fprintf(stderr, "                ※現時点の実装では、「#elif」を含むブロックに対する削除ルールがまだ無い事に注意して下さい。\n");
//}}2016/08/18削除:「#elif」を含むブロックに対する削除ルールを追加しました。
	fprintf(stderr, "  -d シンボル   「#ifdef  シンボル」を「#if 1」と見なして、削除ルールを適用します。\n");
	fprintf(stderr, "                「#ifndef シンボル」を「#if 0」と見なして、削除ルールを適用します。\n");
	fprintf(stderr, "  -u シンボル   「#ifdef  シンボル」を「#if 0」と見なして、削除ルールを適用します。\n");
	fprintf(stderr, "                「#ifndef シンボル」を「#if 1」と見なして、削除ルールを適用します。\n");
	fprintf(stderr, "                □'-d'と'-u'に関する注意点\n");
	fprintf(stderr, "                │'-d'及び'-u'は、'-z'と併用しなければ効果が有りません。\n");
	fprintf(stderr, "                │'-d'及び'-u'は、複数指定可能です。ただし、同じシンボルを重複して指定する事は出来ません。\n");
	fprintf(stderr, "                │'-d'でも'-u'でも指定されていないシンボルの、「#ifdef」や「#ifndef」に対しては、削除ルールが適用されません。\n");
	fprintf(stderr, "  -h            使い方を表示します。\n");
	fprintf(stderr, "\n");
	fprintf(stderr, "EXAMPLE:\n");
	fprintf(stderr, "  ①Sample1.h,Sample2.cのコメント,行末の空白文字,空行,「#if ～ #endif」の区間を削除ルールに基づいて削除し、結果を標準出力へ出力します。\n");
	fprintf(stderr, "  │StripComments.exe -sz -dWIN32 -uPIECE Sample1.h Sample2.c\n");
	fprintf(stderr, "  ②Sample1.h,Sample2.cのコメント,行末の空白文字,空行,「#if ～ #endif」の区間を削除ルールに基づいて削除し、ファイルの上書き更新を行います。\n");
	fprintf(stderr, "  │StripComments.exe -isz  -dWIN32 -uPIECE Sample1.h Sample2.c\n");
	fprintf(stderr, "  ③カレントフォルダ以下の全てのCソースファイルの、コメント,行末の空白文字,空行,「#if ～ #endif」の区間を削除ルールに基づいて削除します。\n");
	fprintf(stderr, "  │DIR /B /S *.c *.h > List.txt\n");
	fprintf(stderr, "  │TYPE List.txt | xargs.exe -n1 StripComments.exe -isz -dWIN32 -uPIECE \n");
	exit(EXIT_FAILURE);
}
/****************************************************************************
 *	
 ****************************************************************************/
void app_init(int argc, char* argv[]) {
//{{アプリケーション特有の処理
	{
		/** no job **/
	}
//}}アプリケーション特有の処理
}
/*--------------------------------------------------------------------------*/
void app_exit() {
//{{アプリケーション特有の処理
	{
		/** no job **/
	}
//}}アプリケーション特有の処理
}
/*--------------------------------------------------------------------------*/
int app_main(int argc, char* argv[]) {
	int exitCode = EXIT_SUCCESS;
	int opt_i = 0;	//edit files in place
	int opt_s = 0;	//行末の空白文字,及び,空行を削除する。
	int opt_z = 0;	//「#if 0」～「#endif」の区間を削除する。
	//'-d'オプションと'-u'オプションのためのハッシュテーブルを作成する。
	ght_set_rehash((ht_symbol = ght_create(0)), 1);
	{
		int opt;
		opterr = 0;	//オプションエラーは当プログラム内で処理するので、getopt()内でのエラー表示は不要。
		while((opt = getopt(argc, argv, "iszd:u:h")) != -1) {	//コマンドラインオプションを処理する。
			switch(opt) {
			case 'i':
				opt_i = 1;	//edit files in place
				break;
			case 's':
				opt_s = 1;	//行末の空白文字,及び,空行を削除する。
				break;
			case 'z':
				opt_z = 1;	//「#if 0」～「#endif」の区間を削除する。
				break;
			case 'd':
			case 'u':
				if(ght_insert(ht_symbol,
					(void*)opt,
					strlen(optarg),		//終端のnulはキーに含めません。GetDirectiveKind()にて、行の途中の文字列を使ってルックアップするので、終端のnulはキーに含まれていない方が扱い易いからです。
					optarg)) { usage(); }	//同じシンボルを二重に指定してはいけない。
				break;
			case 'h':
			default:
				usage();
			}
		}
	}
	//インプレースモードならば…
	if(opt_i) {
		//少なくとも一つ以上、ファイルが指定されていなければいけない。
		if(optind >= argc) { usage(); }
		do {
			FILE* fp;
			{
			    //{{進捗表示用
				fprintf(stderr, "%s\n", argv[optind]);
			    //}}進捗表示用
				fp = fopen(argv[optind], "r");	//入力モードでファイルを開く。
				if(!fp) { error(EXIT_FAILURE, errno, "%sが開けません。", argv[optind]); }
			}
			{
				char *src, *dst;
				//ファイル全体から文字列を読み込む。
				{
					GString* s = g_string_new(NULL);
					int c;
					while((c = fgetc(fp)) != EOF) {
						g_string_append_c(s, c);
					}
					src = g_string_free(s, 0/*free_segment*/);
				}
				//入力モードで開いたファイルを閉じる。
				fclose(fp);
				//コメントを削除した文字列を取得する。
				dst = StripComments(src);
				//'-s'オプションが指定されていたら、行末の空白文字,及び,空行を削除する。
				if(opt_s) {
					char* tmp = TrimSpace(dst);
					free(dst);
					dst = tmp;
				}
				//'-z'オプションが指定されていたら、「#if 0」～「#endif」の区間を削除する。
				if(opt_z) {
					char* tmp = StripIfZero(dst);
					free(dst);
					dst = tmp;
				}
				//出力モードでファイルを開く。
				fp = fopen(argv[optind], "w");
				if(!fp) { error(EXIT_FAILURE, errno, "%sが作成出来ません。", argv[optind]); }
				//コメントを削除した文字列を、出力モードで開いたファイルへ出力する。
				fputs(dst, fp);
				//ファイル全体から読み込んだ文字列を開放する。
				free(src);
				//コメントを削除した文字列を開放する。
				free(dst);
			}
			{
				fclose(fp);	//出力モードで開いたファイルを閉じる。
				optind++;	//次の入力ファイルへ進める。
			}
		} while(optind < argc);
	//インプレースモードでなければ…
	} else {
		do {
			FILE* fp = stdin;	//入力ファイルが指定されていなければ、標準入力を一回処理する。
			if(optind < argc) {	//入力ファイルが指定されていたら…
			    //{{進捗表示用
				fprintf(stderr, "%s\n", argv[optind]);
			    //}}進捗表示用
				fp = fopen(argv[optind], "r");	//入力ファイルを開く。
				if(!fp) { error(EXIT_FAILURE, errno, "%s が開けません。", argv[optind]); }
			}
			{
				char *src, *dst;
				//ファイル全体から文字列を読み込む。
				{
					GString* s = g_string_new(NULL);
					int c;
					while((c = fgetc(fp)) != EOF) {
						g_string_append_c(s, c);
					}
					src = g_string_free(s, 0/*free_segment*/);
				}
				//コメントを削除した文字列を取得する。
				dst = StripComments(src);
				//'-s'オプションが指定されていたら、行末の空白文字,及び,空行を削除する。
				if(opt_s) {
					char* tmp = TrimSpace(dst);
					free(dst);
					dst = tmp;
				}
				//'-z'オプションが指定されていたら、「#if 0」～「#endif」の区間を削除する。
				if(opt_z) {
					char* tmp = StripIfZero(dst);
					free(dst);
					dst = tmp;
				}
				//コメントを削除した文字列を、標準出力へ出力する。
				fputs(dst, stdout);
				//ファイル全体から読み込んだ文字列を開放する。
				free(src);
				//コメントを削除した文字列を開放する。
				free(dst);
			}
			if(optind < argc) {	//入力ファイルが指定されていたら…
				fclose(fp);	//入力ファイルを閉じる。
				optind++;	//次の入力ファイルへ進める。
			}
		} while(optind < argc);
	}
	//'-d'オプションと'-u'オプションのためのハッシュテーブルを開放する。
	ght_finalize(ht_symbol);
	return exitCode;
}
/****************************************************************************
 *	
 ****************************************************************************/
//シフトJIS文字を一文字取得する。
static int GetChar(const char* s, int* p_index) {
	//1バイト目を取得する。
	int c = (unsigned char)s[(*p_index)++];
	//シフトJIS文字の1バイト目ならば…
	if(ismbblead(c)) {
		//2バイト目を取得する。
		int tmp = (unsigned char)s[(*p_index)++];
		//シフトJIS文字の2バイト目である事を確認する。
		if(!ismbbtrail(tmp)) {
			error(EXIT_FAILURE, 0, "シフトJIS文字の2バイト目が不正です。");
		}
		//2バイト文字を組み立てる。
		c = (c << 8) | tmp;
	}
	//1バイト文字,又は,2バイト文字を返す。
	return c;
}
/*--------------------------------------------------------------------------*/
//シフトJIS文字を一文字出力する。
static void PutChar(GString* s, int c) {
	//2バイト文字ならば…
	if(c > UCHAR_MAX) {
		//1バイト目を出力する。
		g_string_append_c(s, c >> 8);
	}
	//1バイト目,又は,2バイト目を出力する。
	g_string_append_c(s, c);
}
/*--------------------------------------------------------------------------*/
#define S_CLEAR			0	//通常状態
#define S_SLASH			1	//スラッシュ保留中
#define S_SLASH_SLASH		2	//行コメント内
#define S_SLASH_SLASH_ESC	3	//行コメント内のバックスラッシュ直後
#define S_SLASH_STAR		4	//ブロックコメント内
#define S_STAR			5	//アスタリスク保留中
#define S_DQUOTE		6	//ダブルクォーテーション内
#define S_DQUOTE_ESC		7	//ダブルクォーテーション内のバックスラッシュ直後
#define S_SQUOTE		8	//シングルクォーテーション内
#define S_SQUOTE_ESC		9	//シングルクォーテーション内のバックスラッシュ直後
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
//入力ストリーム内のC言語コメントを削除して、出力ストリームへ出力する。
static char* StripComments(const char* src) {
	//出力文字列バッファを作成する。
	GString* dst = g_string_new(NULL);
	//入力文字列インデクスを初期化する。
	int index = 0;
	//初期状態を通常状態とする。
	int state = S_CLEAR;
	for(;;) {
		//一文字取得する。
		int c = GetChar(src, &index);
		//終端ならば…
		if(!c) {
//{{2016/08/17変更:行コメントを改行せずに終端に到達した場合もエラーにせず許容する事にしました。
//			//現在の状態が通常状態でなければ、エラー。
//			if(state != S_CLEAR) {
//				error(EXIT_FAILURE, 0, "通常状態以外で、終端に到達しました。");
//			}
//↓2016/08/17変更:行コメントを改行せずに終端に到達した場合もエラーにせず許容する事にしました。
			//現在の状態が、通常状態でも行コメント内でもなければ、エラー。
			if((state != S_CLEAR      ) &&	//通常状態
			   (state != S_SLASH_SLASH)) {	//行コメント内
				error(EXIT_FAILURE, 0, "通常状態,又は,行コメント内以外で、終端に到達しました。");
			}
//}}2016/08/17変更:行コメントを改行せずに終端に到達した場合もエラーにせず許容する事にしました。
			//ループを抜ける。
			break;
		}
		//現在の状態によって…
		switch(state) {
		default:DIE();	//バグ
		//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
		//通常状態
		case S_CLEAR:
			//スラッシュが出現したら…
			if(c == '/') {
				//スラッシュ保留中の状態へ移行する。
				state = S_SLASH;
				//ダブルクォーテーションの出力は保留し、まだ出力しない。
			//ダブルクォーテーションが出現したら…
			} else if(c == '"') {
				//ダブルクォーテーションを出力する。
				PutChar(dst, c);
				//ダブルクォーテーション内の状態へ移行する。
				state = S_DQUOTE;
			//シングルクォーテーションが出現したら…
			} else if(c == '\'') {
				//シングルクォーテーションを出力する。
				PutChar(dst, c);
				//シングルクォーテーション内の状態へ移行する。
				state = S_SQUOTE;
			//それ以外の文字ならば…
			} else {
				//この文字を出力する。
				PutChar(dst, c);
			}
			break;
		//スラッシュ保留中
		case S_SLASH:
			//スラッシュの直後にスラッシュが出現したら…
			if(c == '/') {
				//行コメント内の状態へ移行する。
				state = S_SLASH_SLASH;
			//スラッシュの直後にアスタリスクが出現したら…
			} else if(c == '*') {
				//ブロックコメント内の状態へ移行する。
				state = S_SLASH_STAR;
			//それ以外の文字ならば…
			} else {
				//保留していたダブルクォーテーションを出力する。
				PutChar(dst, '/');
				//この文字を出力する。
				PutChar(dst, c);
				//通常状態へ復帰する。
				state = S_CLEAR;
			}
			break;
		//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
		//行コメント内ならば…
		case S_SLASH_SLASH:
			//バックスラッシュが出現したら…
			if(c == '\\') { 
				//行コメント内のバックスラッシュ直後の状態へ移行する。
				state = S_SLASH_SLASH_ESC;
			//改行文字が出現したら…
			} else if(c == '\n') {
				//改行文字を出力する。
				PutChar(dst, c);
				//通常状態へ移行する。
				state = S_CLEAR;
			}
			break;
		//行コメント内のバックスラッシュ直後
		case S_SLASH_SLASH_ESC:
			//行コメント内の状態へ復帰する。
			state = S_SLASH_SLASH;
			break;
		//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
		//ブロックコメント内ならば…
		case S_SLASH_STAR:
			//アスタリスクが出現したら…
			if(c == '*') {
				//アスタリスク保留中の状態へ移行する。
				state = S_STAR;
			}
			break;
		//アスタリスク保留中
		case S_STAR:
			//アスタリスクの直後にスラッシュが出現したら…
			if(c == '/') {
				//通常状態へ移行する。
				state = S_CLEAR ;
			//アスタリスクの直後にアスタリスクが出現したら…
			} else if(c == '*') {
				//アスタリスク保留状態を継続する。
				/** no job **/
			//それ以外の文字ならば…
			} else {
				//ブロックコメント内の状態へ復帰する。
				state = S_SLASH_STAR;
			}
			break;
		//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
		//ダブルクォーテーション内
		case S_DQUOTE:
			//この文字を出力する。
			PutChar(dst, c);
			//バックスラッシュが出現したら…
			if(c == '\\') { 
				//ダブルクォーテーション内のバックスラッシュ直後の状態へ移行する。
				state = S_DQUOTE_ESC;
			//ダブルクォーテーションが出現したら…
			} else if(c == '"') {
				//通常状態へ移行する。
				state = S_CLEAR;
			}
			break;
		//ダブルクォーテーション内のバックスラッシュ直後
		case S_DQUOTE_ESC:
			//この文字を出力する。
			PutChar(dst, c);
			//ダブルクォーテーション内の状態へ復帰する。
			state = S_DQUOTE;
			break;
		//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
		//シングルクォーテーション内
		case S_SQUOTE:
			//この文字を出力する。
			PutChar(dst, c);
			//バックスラッシュが出現したら…
			if(c == '\\') { 
				//シングルクォーテーション内のバックスラッシュ直後の状態へ移行する。
				state = S_SQUOTE_ESC;
			//シングルクォーテーションが出現したら…
			} else if(c == '\'') {
				//通常状態へ移行する。
				state = S_CLEAR;
			}
			break;
		//シングルクォーテーション内のバックスラッシュ直後
		case S_SQUOTE_ESC:
			//この文字を出力する。
			PutChar(dst, c);
			//シングルクォーテーション内の状態へ復帰する。
			state = S_SQUOTE;
			break;
		//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
		}
	}
	//出力文字列バッファの構造体だけを開放し、文字列を取得する。
	return g_string_free(dst, 0/*free_segment*/);
}
/*--------------------------------------------------------------------------*/
//行末の空白文字,及び,空行を削除する。
static char* TrimSpace(const char* src) {
	//出力文字列バッファを作成する。
	GString* dst = g_string_new(NULL);
	//入力文字列を行に分割する。
	char** strv = strsplit_set(src, "\r\n", 0);
	//トリミングする前に行末にバックスラッシュが有る行を発見したら次の行を削除しないようにするために(next_line_cont=1)にする。
	//こうしないと、
	//│#define FOO BAR \
	//│            BAZ \
	//│
	//│void test() { }
	//の三行目をトリミングしてしまって、間違った結果になってしまう。
	//上記の例の三行目は、二行目の行継続を消費するために必要である。
	int next_line_cont = 0;
	//各行について…
	int i = 0;
	char* s;
	while((s = strv[i++])) {
		//前の行の継続フラグを退避する。
		int prev_line_cont = next_line_cont;
		//この行の継続フラグを取得する。
		next_line_cont = str_has_suffix(s, "\\");
		//行末の空白文字を削除する。
		strchomp(s);
	    //{{Cプリプロセッサの行継続に関する処理
		//この行の継続フラグがオフであったにも関わらず、行末の空白文字を削除した結果、行末がバックスラッシュになったならば、間違った行継続を避けるために空白を一文字補う。
		//│#define FOO BAR \    	←このバックスラッシュは行継続ではないつもり。
		//│                 ~~~~スペース4個
		//が
		//│#define FOO BAR \		←このバックスラッシュは行継続になってしまっている。
		//│                     スペース0個
		//になってしまった場合に、
		//│#define FOO BAR \ 		←このバックスラッシュを行継続でないバックスラッシュに戻す。
		//│                 ~   スペース1個
		//にする、という処理です。
		if(!next_line_cont && str_has_suffix(s, "\\")) {
			int len = strlen(s);
			s[len + 0] = ' ';	//┬少なくとも一文字以上のトリミングが発生しているはずだから、この処理は安全である。
			s[len + 1] = '\0';	//┘
		}
	    //}}Cプリプロセッサの行継続に関する処理
		//前の行の継続フラグがオンだったか,又は,この行が空行でなければ、出力文字列に追加する。
		if(prev_line_cont || *s) {
			g_string_append(dst, s);
			g_string_append_c(dst, '\n');
		}
	}
	//行に分割した入力文字列を開放する。
	strfreev(strv);
	//出力文字列バッファの構造体だけを開放し、文字列を取得する。
	return g_string_free(dst, 0/*free_segment*/);
}
/*--------------------------------------------------------------------------*/
//疑似命令の種類
#define DirectiveKind_OTHER		 0	//下記以外
#define DirectiveKind_IF_0		 1	//「#if 0」
#define DirectiveKind_IF_1		 2	//「#if 1」
#define DirectiveKind_IF_OTHER		 3	//「#if ～」
#define DirectiveKind_IFDEF		 4	//「#ifdef ～」
#define DirectiveKind_IFNDEF		 5	//「#ifndef ～」
#define DirectiveKind_ELIF_0		 6	//「#elif 0」
#define DirectiveKind_ELIF_1		 7	//「#elif 1」
#define DirectiveKind_ELIF_OTHER	 8	//「#elif ～」
#define DirectiveKind_ELSE		 9	//「#else」
#define DirectiveKind_ENDIF		10	//「#endif」
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
//行の文字列から、疑似命令の種類を判定する。
static int GetDirectiveKind(const char* s) {
//{{2016/10/15削除:「#if」「#elif」の式を(限定的ではありますが)評価出来るようにしました。
//	int c;
//}}2016/10/15削除:「#if」「#elif」の式を(限定的ではありますが)評価出来るようにしました。
	while(isblank(*s)) { s++; }						//^[[:blank:]]*
	if(*s++ != '#') { return DirectiveKind_OTHER; }				//^[[:blank:]]*#
	while(isblank(*s)) { s++; }						//^[[:blank:]]*#[[:blank:]]*
	if(!strncmp(s, "if", 2)) {						//^[[:blank:]]*#[[:blank:]]*if
		s += 2;
		if(!strncmp(s, "def", 3)) {					//^[[:blank:]]*#[[:blank:]]*ifdef
			s += 3;
			if(!isblank(*s)) { return DirectiveKind_OTHER; }	//^[[:blank:]]*#[[:blank:]]*ifdef[[:blank:]]
		    //{{'-d','-u'オプション対応
			{
				const char *symbol_start, *symbol_end;
				while( isblank(*s)) { s++; }			//^[[:blank:]]*#[[:blank:]]*ifdef[[:blank:]]+
				symbol_start = s;
				while(!isblank(*s) && *s) { s++; }		//^[[:blank:]]*#[[:blank:]]*ifdef[[:blank:]]+[^[:blank:]]*
				symbol_end   = s;  // ~~ 忘れないで!!
				while( isblank(*s)) { s++; }			//^[[:blank:]]*#[[:blank:]]*ifdef[[:blank:]]+[^[:blank:]]*[[:blank:]]*$
				if(*s) { error(EXIT_FAILURE, 0, "「#ifdef」にシンボルが複数指定されています。"); }
				switch((int)ght_get(ht_symbol, symbol_end - symbol_start, symbol_start)) {
				default:DIE();	//バグ
				case 'd': return DirectiveKind_IF_1;		//'-d'オプションで指定されたシンボルの「#ifdef」ならば、「#if 1」と見なす。
				case 'u': return DirectiveKind_IF_0;		//'-u'オプションで指定されたシンボルの「#ifdef」ならば、「#if 0」と見なす。
				case 0: /** no job **/ break;			//'-d'オプションでも'-u'オプションでも指定されていないシンボルならば、処理を継続する。
				}
			}
		    //}}'-d','-u'オプション対応
			return DirectiveKind_IFDEF;
		} else if(!strncmp(s, "ndef", 4)) {				//^[[:blank:]]*#[[:blank:]]*ifndef
			s += 4;
			if(!isblank(*s)) { return DirectiveKind_OTHER; }	//^[[:blank:]]*#[[:blank:]]*ifndef[[:blank:]]
		    //{{'-d','-u'オプション対応
			{
				const char *symbol_start, *symbol_end;
				while( isblank(*s)) { s++; }			//^[[:blank:]]*#[[:blank:]]*ifdef[[:blank:]]+
				symbol_start = s;
				while(!isblank(*s) && *s) { s++; }		//^[[:blank:]]*#[[:blank:]]*ifdef[[:blank:]]+[^[:blank:]]*
				symbol_end   = s;  // ~~ 忘れないで!!
				while( isblank(*s)) { s++; }			//^[[:blank:]]*#[[:blank:]]*ifdef[[:blank:]]+[^[:blank:]]*[[:blank:]]*$
				if(*s) { error(EXIT_FAILURE, 0, "「#ifndef」にシンボルが複数指定されています。"); }
				switch((int)ght_get(ht_symbol, symbol_end - symbol_start, symbol_start)) {
				default:DIE();	//バグ
				case 'd': return DirectiveKind_IF_0;		//'-d'オプションで指定されたシンボルの「#ifndef」ならば、「#if 0」と見なす。
				case 'u': return DirectiveKind_IF_1;		//'-u'オプションで指定されたシンボルの「#ifndef」ならば、「#if 1」と見なす。
				case 0: /** no job **/ break;			//'-d'オプションでも'-u'オプションでも指定されていないシンボルならば、処理を継続する。
				}
			}
		    //}}'-d','-u'オプション対応
			return DirectiveKind_IFNDEF;
		} else {
//{{2016/10/15追加:「#if」「#elif」の式を(限定的ではありますが)評価出来るようにしました。
//		    //{{「#if(～)」も「#if ～」と同じと見なす。
//			if(*s == '(') { return DirectiveKind_IF_OTHER; }	//^[[:blank:]]*#[[:blank:]]*if\(
//		    //}}「#if(～)」も「#if ～」と同じと見なす。
//			if(!isblank(*s)) { return DirectiveKind_OTHER; }	//^[[:blank:]]*#[[:blank:]]*if[[:blank:]]
//			while(isblank(*s)) { s++; }				//^[[:blank:]]*#[[:blank:]]*if[[:blank:]]+
//			c = *s++;
//			if((c == '0') || (c == '1')) {				//^[[:blank:]]*#[[:blank:]]*if[[:blank:]]+[01]
//				while(isblank(*s)) { s++; }			//^[[:blank:]]*#[[:blank:]]*if[[:blank:]]+[01][[:blank:]]*
//				if(*s) { return DirectiveKind_OTHER; }		//^[[:blank:]]*#[[:blank:]]*if[[:blank:]]+[01][[:blank:]]*$
//				return DirectiveKind_IF_0 + (c - '0');
//			} else {
//				return DirectiveKind_IF_OTHER;			//^[[:blank:]]*#[[:blank:]]*if[[:blank:]]+([^01].*)?$
//			}
//↓2016/10/15変更:「#if」「#elif」の式を(限定的ではありますが)評価出来るようにしました。
			//↓'isblank(*s)'は'!iscsym(*s)'に含まれるので不要。
			if(/*isblank(*s) ||*/					//^[[:blank:]]*#[[:blank:]]*if[[:blank:]]
			   !iscsym(*s)) {					//^[[:blank:]]*#[[:blank:]]*if[^[:csym:]]	//{{「#if(～)」も「#if ～」と同じと見なす。「#if!～」も「#if !～」と同じと見なす。}}
				switch(IfExprEval(s)) {
				default:DIE();	//バグ
				case  0: return DirectiveKind_IF_0;		//「#if」の式が0と評価された場合
				case  1: return DirectiveKind_IF_1;		//「#if」の式が1と評価された場合
				case -1: return DirectiveKind_IF_OTHER;		//「#if」の式が評価不可能だった場合
				}
			} else {
				return DirectiveKind_IF_OTHER;			//^[[:blank:]]*#[[:blank:]]*if[[:blank:]]+([^01].*)?$
			}
//}}2016/10/15追加:「#if」「#elif」の式を(限定的ではありますが)評価出来るようにしました。
		}
	} else if(!strncmp(s, "elif", 4)) {					//^[[:blank:]]*#[[:blank:]]*elif
		s += 4;
//{{2016/10/15変更:「#if」「#elif」の式を(限定的ではありますが)評価出来るようにしました。
//	    //{{「#elif(～)」も「#elif ～」と同じと見なす。
//		if(*s == '(') { return DirectiveKind_ELIF_OTHER; }		//^[[:blank:]]*#[[:blank:]]*elif\(
//	    //}}「#elif(～)」も「#elif ～」と同じと見なす。
//		if(!isblank(*s)) { return DirectiveKind_OTHER; }		//^[[:blank:]]*#[[:blank:]]*elif[[:blank:]]
//		while(isblank(*s)) { s++; }					//^[[:blank:]]*#[[:blank:]]*elif[[:blank:]]+
//		c = *s++;
//		if((c == '0') || (c == '1')) {					//^[[:blank:]]*#[[:blank:]]*elif[[:blank:]]+[01]
//			while(isblank(*s)) { s++; }				//^[[:blank:]]*#[[:blank:]]*elif[[:blank:]]+[01][[:blank:]]*
//			if(*s) { return DirectiveKind_OTHER; }			//^[[:blank:]]*#[[:blank:]]*elif[[:blank:]]+[01][[:blank:]]*$
//			return DirectiveKind_ELIF_0 + (c - '0');
//		} else {
//			return DirectiveKind_ELIF_OTHER;			//^[[:blank:]]*#[[:blank:]]*elif[[:blank:]]+([^01].*)?$
//		}
//↓2016/10/15変更:「#if」「#elif」の式を(限定的ではありますが)評価出来るようにしました。
		//↓'isblank(*s)'は'!iscsym(*s)'に含まれるので不要。
		if(/*isblank(*s) ||*/						//^[[:blank:]]*#[[:blank:]]*elif[[:blank:]]
		   !iscsym(*s)) {						//^[[:blank:]]*#[[:blank:]]*elif[^[:csym:]]	//{{「#elif(～)」も「#elif ～」と同じと見なす。「#elif!～」も「#elif !～」と同じと見なす。}}
			switch(IfExprEval(s)) {
			default:DIE();	//バグ
			case  0: return DirectiveKind_ELIF_0;			//「#elif」の式が0と評価された場合
			case  1: return DirectiveKind_ELIF_1;			//「#elif」の式が1と評価された場合
			case -1: return DirectiveKind_ELIF_OTHER;		//「#elif」の式が評価不可能だった場合
			}
		} else {
			return DirectiveKind_ELIF_OTHER;			//^[[:blank:]]*#[[:blank:]]*elif[[:blank:]]+([^01].*)?$
		}
//}}2016/10/15変更:「#if」「#elif」の式を(限定的ではありますが)評価出来るようにしました。
	} else if(!strncmp(s, "else", 4)) {					//^[[:blank:]]*#[[:blank:]]*else
		s += 4;
	    //{{「#else foo」も許容する事にした。
	    //	while(isblank(*s)) { s++; }					//^[[:blank:]]*#[[:blank:]]*else[[:blank:]]*
	    //	if(*s) { return DirectiveKind_OTHER; }				//^[[:blank:]]*#[[:blank:]]*else[[:blank:]]*$
	    //	return DirectiveKind_ELSE;
	    //↓「#else foo」も許容する事にした。
		if(!*s || isblank(*s)) { return DirectiveKind_ELSE; }		//^[[:blank:]]*#[[:blank:]]*else([[:blank:]].*)?$
		return DirectiveKind_OTHER;
	    //}}「#else foo」も許容する事にした。
	} else if(!strncmp(s, "endif", 5)) {					//^[[:blank:]]*#[[:blank:]]*endif
		s += 5;
	    //{{「#endif foo」も許容する事にした。
	    //	while(isblank(*s)) { s++; }					//^[[:blank:]]*#[[:blank:]]*endif[[:blank:]]*
	    //	if(*s) { return DirectiveKind_OTHER; }				//^[[:blank:]]*#[[:blank:]]*endif[[:blank:]]*$
	    //	return DirectiveKind_ENDIF;
	    //↓「#endif foo」も許容する事にした。
		if(!*s || isblank(*s)) { return DirectiveKind_ENDIF; }		//^[[:blank:]]*#[[:blank:]]*endif([[:blank:]].*)?$
		return DirectiveKind_OTHER;
	    //}}「#endif foo」も許容する事にした。
	}
	return DirectiveKind_OTHER;
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
#undef  MAX_NEST
#define MAX_NEST	32	//ブロックの最大ネストレベル(変更可)
#undef  MAX_DIRECTIVE
#define MAX_DIRECTIVE	32	//一つのブロックを構成する疑似命令の最大数(変更可)
//一つの疑似命令行を表す構造体
typedef struct _ST_Directive {
	int		iDirectiveKind;		//疑似命令の種類(DirectiveKind_*)
	int		iLine;			//疑似命令の行番号(0～)
} ST_Directive;
//「#if」「#ifdef」「#ifndef」～「#elif」「#else」～「#endif」のブロックを管理する構造体
typedef struct _ST_Block {
	int		iDirective;		//最後に疑似命令を格納したTBL_Directive[]のインデクス(=疑似命令の数-1)
	ST_Directive	TBL_Directive[MAX_DIRECTIVE];	//このブロックを構成する疑似命令の配列
} ST_Block;
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
//「#if 0」～「#endif」の区間を削除する。
static char* StripIfZero(const char* src) {
	//出力文字列バッファを作成する。
	GString* dst = g_string_new(NULL);
	//入力文字列を行に分割する。
	char** strv = strsplit_set(src, "\r\n", 0);
	//各行を、ポインタ配列に格納する。
	GPtrArray* array = g_ptr_array_new();
	int i = 0;
	char* s;
	while((s = strv[i++])) {
//{{元々存在しない最後の空行が間違って現れてしないようにするための処理。TrimSpace()の場合は空行を削除するので問題にならなかったが、当関数は空行を削除しないのでここで明示的に除外する必要が有る。
		//"ABC\nDEF\n"を分割すると、{"ABC","DEF",""}になってしまうので、最後の空行はポインタ配列に格納しない。
		if(!*s && !strv[i]) { continue/*breakでも構いません*/; }
//}}元々存在しない最後の空行が間違って現れてしないようにするための処理。TrimSpace()の場合は空行を削除するので問題にならなかったが、当関数は空行を削除しないのでここで明示的に除外する必要が有る。
		g_ptr_array_add(array, s);
	}
	//削除する区間が無くなるまで繰り返す。
L_RESTART:
	{
		int iDirectiveKind;
		ST_Block* pBlock;
		ST_Directive* pDirective;
		//ネスト状態を初期化する。
		ST_Block TBL_Block[MAX_NEST];	//現在のブロックのネスト状態を表す配列。ソースコード全体のブロック構造を表すのではない事に注意せよ。
		int iNest = -1;			//現在のネストレベル(-1～)。TBL_Block[]へのインデクスです。
		//各行について…
		for(i = 0; i < array->len; i++) {
			s = g_ptr_array_index(array, i);
			//疑似命令の種類を判定する。
			iDirectiveKind = GetDirectiveKind(s);
			//疑似命令の種類によって…
			switch(iDirectiveKind) {
			default:DIE();	//バグ
			case DirectiveKind_OTHER:	//下記以外
				/** no job **/
				break;
			case DirectiveKind_IF_0:	//「#if 0」
			case DirectiveKind_IF_1:	//「#if 1」
			case DirectiveKind_IF_OTHER:	//「#if ～」
			case DirectiveKind_IFDEF:	//「#ifdef ～」
			case DirectiveKind_IFNDEF:	//「#ifndef ～」
				//ネストレベルを増やす。
				if(++iNest >= MAX_NEST) { error(EXIT_FAILURE, 0, "疑似命令のネストレベルが深過ぎます。"); }
				//疑似命令の種類と行番号を格納する。
				pBlock = &TBL_Block[iNest];
				pBlock->iDirective = 0;
				pDirective = &pBlock->TBL_Directive[pBlock->iDirective];
				pDirective->iDirectiveKind = iDirectiveKind;
				pDirective->iLine = i;
				break;
			case DirectiveKind_ELIF_0:	//「#elif 0」
			case DirectiveKind_ELIF_1:	//「#elif 1」
			case DirectiveKind_ELIF_OTHER:	//「#elif ～」
			case DirectiveKind_ELSE:	//「#else」
				//疑似命令のブロック内である事を確認する。
				if(iNest < 0) { error(EXIT_FAILURE, 0, "疑似命令のブロック外に「#elif」又は「#else」が有ります。"); }
				//直前の疑似命令が「#else」でない事を確認する。(「#if～#else～#else～#endif」は不可だからです。)
				pBlock = &TBL_Block[iNest];
				pDirective = &pBlock->TBL_Directive[pBlock->iDirective];
				if(pDirective->iDirectiveKind == DirectiveKind_ELSE) { error(EXIT_FAILURE, 0, "「#else」の後に「#else」が有ります。"); }
				//疑似命令の種類と行番号を格納する。
				if(++pBlock->iDirective >= MAX_DIRECTIVE) { error(EXIT_FAILURE, 0, "疑似命令のぶら下がり数が多過ぎます。"); }
				pDirective = &pBlock->TBL_Directive[pBlock->iDirective];
				pDirective->iDirectiveKind = iDirectiveKind;
				pDirective->iLine = i;
				break;
			case DirectiveKind_ENDIF:	//「#endif」
				//疑似命令のブロック内である事を確認する。
				if(iNest < 0) { error(EXIT_FAILURE, 0, "疑似命令のブロック外に「#endif」が有ります。"); }
				//疑似命令の種類と行番号を格納する。
				pBlock = &TBL_Block[iNest];
				if(++pBlock->iDirective >= MAX_DIRECTIVE) { error(EXIT_FAILURE, 0, "疑似命令のぶら下がり数が多過ぎます。"); }
				pDirective = &pBlock->TBL_Directive[pBlock->iDirective];
				pDirective->iDirectiveKind = iDirectiveKind;
				pDirective->iLine = i;
//{{2016/08/18変更:「#elif」を含むブロックに対する削除ルールを追加しました。
//				//「#if 0 ～ #endif」の単純なブロックならば、このブロック全体を削除する。
//				if((pBlock->iDirective == 1) &&									//「#elif」や「#else」を含まない単純なブロックで…
//				   (pBlock->TBL_Directive[0].iDirectiveKind == DirectiveKind_IF_0)) {				//最初の疑似命令が「#if 0」ならば…
//					if(pBlock->TBL_Directive[0].iLine >= pBlock->TBL_Directive[1].iLine) { DIE(); }	//バグ
//					g_ptr_array_remove_range(array,           pBlock->TBL_Directive[0].iLine,		//「#if 0 ～ #endif」
//						(pBlock->TBL_Directive[1].iLine - pBlock->TBL_Directive[0].iLine) + 1);
//					//最初からもう一度繰り返す。
//					goto L_RESTART;
//				}
//				//「#if 1 ～ #endif」の単純なブロックならば、「#if 1」の行と「#endif」の行を削除する。
//				if((pBlock->iDirective == 1) &&									//「#elif」や「#else」を含まない単純なブロックで…
//				   (pBlock->TBL_Directive[0].iDirectiveKind == DirectiveKind_IF_1)) {				//最初の疑似命令が「#if 1」ならば…
//					if(pBlock->TBL_Directive[0].iLine >= pBlock->TBL_Directive[1].iLine) { DIE(); }	//バグ
//					g_ptr_array_remove_index(array, pBlock->TBL_Directive[1].iLine);			//「#endif」		┬行番号がずれないように、後ろから先に削除する事に注意!!
//					g_ptr_array_remove_index(array, pBlock->TBL_Directive[0].iLine);			//「#if 1」		┘
//					//最初からもう一度繰り返す。
//					goto L_RESTART;
//				}
//				//「#if 0 ① #else ② #endif」のブロックならば、②の部分以外を削除する。
//				if((pBlock->iDirective == 2) &&									//「#elif」又は「#else」を一つ含むブロックで…
//				   (pBlock->TBL_Directive[0].iDirectiveKind == DirectiveKind_IF_0) &&				//最初の疑似命令が「#if 0」で…
//				   (pBlock->TBL_Directive[1].iDirectiveKind == DirectiveKind_ELSE)) {				//二つ目の疑似命令が「#else」ならば…
//					if(pBlock->TBL_Directive[0].iLine >= pBlock->TBL_Directive[1].iLine) { DIE(); }	//バグ
//					if(pBlock->TBL_Directive[1].iLine >= pBlock->TBL_Directive[2].iLine) { DIE(); }	//バグ
//					g_ptr_array_remove_index(array, pBlock->TBL_Directive[2].iLine);			//「#endif」		┬行番号がずれないように、後ろから先に削除する事に注意!!
//					g_ptr_array_remove_range(array,           pBlock->TBL_Directive[0].iLine,		//「#if 0 ① #else」	┘
//						(pBlock->TBL_Directive[1].iLine - pBlock->TBL_Directive[0].iLine) + 1);
//					//最初からもう一度繰り返す。
//					goto L_RESTART;
//				}
//				//「#if 1 ① #else ② #endif」のブロックならば、①の部分以外を削除する。
//				if((pBlock->iDirective == 2) &&									//「#elif」又は「#else」を一つ含むブロックで…
//				   (pBlock->TBL_Directive[0].iDirectiveKind == DirectiveKind_IF_1) &&				//最初の疑似命令が「#if 1」で…
//				   (pBlock->TBL_Directive[1].iDirectiveKind == DirectiveKind_ELSE)) {				//二つ目の疑似命令が「#else」ならば…
//					if(pBlock->TBL_Directive[0].iLine >= pBlock->TBL_Directive[1].iLine) { DIE(); }	//バグ
//					if(pBlock->TBL_Directive[1].iLine >= pBlock->TBL_Directive[2].iLine) { DIE(); }	//バグ
//					g_ptr_array_remove_range(array,           pBlock->TBL_Directive[1].iLine,		//「#else ② #endif」	┬行番号がずれないように、後ろから先に削除する事に注意!!
//						(pBlock->TBL_Directive[2].iLine - pBlock->TBL_Directive[1].iLine) + 1);		//			│
//					g_ptr_array_remove_index(array, pBlock->TBL_Directive[0].iLine);			//「#if 1」		┘
//					//最初からもう一度繰り返す。
//					goto L_RESTART;
//				}
//↓2016/08/18変更:「#elif」を含むブロックに対する削除ルールを追加しました。
				//現在のネストレベルで、ブロックの削除が発生したら…
				if(DeleteBlock(array, pBlock)) {
					//最初からもう一度繰り返す。
					goto L_RESTART;
				}
//}}2016/08/18変更:「#elif」を含むブロックに対する削除ルールを追加しました。
				//ネストレベルを減らして、継続する。
				iNest--;
				break;
			}
		}
		if(iNest != -1) { error(EXIT_FAILURE, 0, "疑似命令のブロックが閉じていません。"); }
	}
	//残ったリストの各行を、出力文字列バッファに追加する。
	for(i = 0; i < array->len; i++) {
		s = g_ptr_array_index(array, i);
		g_string_append(dst, s);
		g_string_append_c(dst, '\n');
	}
	//ポインタ配列を開放する。(ポインタはstrvの各行を指しているので、ここで開放する必要は無い。)
	g_ptr_array_unref(array);
	//行に分割した入力文字列を開放する。
	strfreev(strv);
	//出力文字列バッファの構造体だけを開放し、文字列を取得する。
	return g_string_free(dst, 0/*free_segment*/);
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
//{{2016/08/18追加:「#elif」を含むブロックに対する削除ルールを追加しました。
static int DeleteBlock(GPtrArray* array, const struct _ST_Block* pBlock) {
	const ST_Directive* pDirective;
	int iDirective, iLine1, iLine2;
	//最初に出現する、「#if 0」「#elif 0」以外の疑似命令を見つける。
	iDirective = 0;
	for(;;) {
		if(iDirective > pBlock->iDirective) { DIE(); }	//もしここで停止したら当ツールのバグです。少なくとも最後の「#endif」が見つかるはずである。入力テキストに「#endif」が欠落していたら当関数を呼び出す前にエラー検出しているはず。
		pDirective = &pBlock->TBL_Directive[iDirective];
		if((pDirective->iDirectiveKind != DirectiveKind_IF_0  ) &&
		   (pDirective->iDirectiveKind != DirectiveKind_ELIF_0)) { break; }
		iDirective++;
	}
	//最初に出現した、「#if 0」「#elif 0」以外の疑似命令の種類によって…
	switch(pDirective->iDirectiveKind) {
	case DirectiveKind_IF_1:	//「#if 1」
	case DirectiveKind_ELIF_1:	//「#elif 1」
	case DirectiveKind_ELSE:	//「#else」
		//①ブロック先頭の行から、このディレクティブの行までを削除する。
		//②次のディレクティブの行から、ブロック最後の行までを削除する。
		//ただし、行がずれないように、②①の順で行う。
		iLine1 = pBlock->TBL_Directive[iDirective + 1].iLine;		//次のディレクティブの行	┬同じである可能性も有ります。
		iLine2 = pBlock->TBL_Directive[pBlock->iDirective].iLine;	//ブロック最後の行		┘
		g_ptr_array_remove_range(array, iLine1, iLine2 - iLine1 + 1);	//②次のディレクティブの行から、ブロック最後の行までを削除する。
		iLine1 = pBlock->TBL_Directive[0].iLine;			//ブロック先頭の行
		iLine2 = pBlock->TBL_Directive[iDirective].iLine;		//このディレクティブの行
		g_ptr_array_remove_range(array, iLine1, iLine2 - iLine1 + 1);	//①ブロック先頭の行から、このディレクティブの行までを削除する。
		//最初からもう一度繰り返す。
		return 1;
	case DirectiveKind_ENDIF:	//「#endif」
		//ブロック全体を削除する。
		iLine1 = pBlock->TBL_Directive[0].iLine;			//ブロック先頭の行
		iLine2 = pBlock->TBL_Directive[pBlock->iDirective].iLine;	//ブロック最後の行
		g_ptr_array_remove_range(array, iLine1, iLine2 - iLine1 + 1);	//ブロック全体を削除する。
		//最初からもう一度繰り返す。
		return 1;
	}
	//上記以外ならば、現在のネストレベルでの削除を行わない。
	return 0;
}
//}}2016/08/18追加:「#elif」を含むブロックに対する削除ルールを追加しました。
//{{2016/10/15追加:「#if」「#elif」の式を(限定的ではありますが)評価出来るようにしました。
/****************************************************************************
 *	「#if」及び「#elif」の式を評価する処理
 ****************************************************************************/
//clipexpr.cの「ユーティリティ関数　実数版，動的メモリ確保有り」を元にして作成しました。
// - 当処理が対応している演算子は、単項'!','&&','||'のみです。
//   本物のCプリプロセッサは四則演算にも対応しているのですが、当ツールでは実装を簡単にするために上記の演算子しか対応しない事にしました。
//   当ツールの用途では、上記以外の演算子が出現したら、その「#if」や「#elif」は'評価不可能'と判断してしまっても、実用上十分だからです。
// - 元にした、clipexpr.cのexpreval()は、(途中で例外が投入されない限り)明示的にメモリを解放しており、メモリリークは発生しないのですが、
//   当実装は簡単のために明示的なメモリ開放は行っておらず、リークしたメモリの解放はGCに任せています。
//   当ツールの用途では、これでも問題無いはずです。
/*--------------------------------------------------------------------------*/
#define UNEVALUABLE()	SEH_throw(ExprException)
/*--------------------------------------------------------------------------*/
typedef struct _ST_Token {
	int		type;		//exprsort_TokenType_～
} ST_Token;
static ST_Token* Token_New(int type) {
	ST_Token* pToken = calloc(1, sizeof(ST_Token));
	if(!pToken) { DIE(); }		//メモリ不足
	pToken->type = type;
	return pToken;
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
typedef struct _ST_Token_nu {
	int		type;		//exprsort_TokenType_Number
	int		isSymbol;	//値ならば0,シンボルならば1
	union {
		int	value;		//値		(isSymbol==0)の場合	┬exprsort()には変数の概念は有ったがシンボルの概念は無かったので、仕方無くシンボルも数値(exprsort_TokenType_Number)の一種として扱う事にした。
		char	name[0];	//シンボル	(isSymbol==1)の場合	┘
	};
} ST_Token_nu;
static ST_Token* Token_nu_New(int value, const char* name/*NULL可*/) {
	ST_Token_nu* pNuTok;
	if(name) {	//(name!=NULL)ならば、シンボルのトークンを作成する。
		pNuTok = calloc(1, offsetof(ST_Token_nu, name) + strlen(name) + 1/*nul*/);
		if(!pNuTok) { DIE(); }	//メモリ不足
		pNuTok->isSymbol = 1;		//┬シンボル
		strcpy(pNuTok->name, name);	//┘
	} else {	//(name==NULL)ならば、値のトークンを作成する。
		pNuTok = calloc(1, sizeof(ST_Token_nu));
		if(!pNuTok) { DIE(); }	//メモリ不足
		pNuTok->isSymbol = 0;		//┬値
		pNuTok->value = value;		//┘
	}
	pNuTok->type = exprsort_TokenType_Number;
	return (ST_Token*)pNuTok;
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
typedef struct _ST_Token_op {
	int		type;		//exprsort_TokenType_Operator
	int		name;		//'!'(単項!),'&'(&&),'|'(||)
} ST_Token_op;
static ST_Token* Token_op_New(int name) {
	ST_Token_op* pOpTok = calloc(1, sizeof(ST_Token_op));
	if(!pOpTok) { DIE(); }		//メモリ不足
	pOpTok->type = exprsort_TokenType_Operator;
	pOpTok->name = name;
	return (ST_Token*)pOpTok;
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
typedef struct _ST_Token_fn {
	int		type;		//exprsort_TokenType_Function
	int		argc;		//実引数の数
	char		name[0];	//関数名			現在の所、"defined"のみです。
} ST_Token_fn;
static ST_Token* Token_fn_New(const char* name) {
	ST_Token_fn* pFnTok = calloc(1, offsetof(ST_Token_fn, name) + strlen(name) + 1/*nul*/);
	if(!pFnTok) { DIE(); }		//メモリ不足
	pFnTok->type = exprsort_TokenType_Function;
	strcpy(pFnTok->name, name);
	return (ST_Token*)pFnTok;
}
/*--------------------------------------------------------------------------*/
//アプリケーション定義の関数:トークンの種類を返す。
static int GetTokenType(const void* _pToken) {
	ST_Token* pToken = *(ST_Token**)_pToken;
	return pToken->type;
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
//アプリケーション定義の関数:演算子が左結合かを返す。
static int IsOpLeftAssoc(const void* _pOpTok) {
	ST_Token_op* pOpTok = *(ST_Token_op**)_pOpTok;
	switch(pOpTok->name) {
	default:DIE();	//バグ
	case '!':	//'!'(単項!)
		return 0;	//右結合
	case '&':	//'&&'
	case '|':	//'||'
		return 1;	//左結合
	}
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
//アプリケーション定義の関数:演算子の優先順位を較べる。
static int CompareOpPrec(const void* _pOpTok1, const void* _pOpTok2) {
	ST_Token_op* pOpTok[2] = {
		*(ST_Token_op**)_pOpTok1,
		*(ST_Token_op**)_pOpTok2,
	};
	int i, iOpPri[2];
	for(i = 0; i < 2; i++) {
		switch(pOpTok[i]->name) {
		default:DIE();	//バグ
		case '!':	//'!'(単項!)
			iOpPri[i] = 2; break;
		case '&':	//'&&'
			iOpPri[i] = 1; break;
		case '|':	//'||'
			iOpPri[i] = 0; break;
		}
	}
	return iOpPri[0] - iOpPri[1];
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
//アプリケーション定義の関数:関数の実引数の数を通知する。
static void IncrFnNumArgs(void* _pFnTok) {
	ST_Token_fn* pFnTok = *(ST_Token_fn**)_pFnTok;
	pFnTok->argc++;
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
//テキストを中置記法のトークン並びに変換する。
static GPtrArray* PreProcess(const char* expr) {
	GPtrArray* array = g_ptr_array_new();
	for(;;) {
		ST_Token* pToken;
		int c, x;
		while(isspace(c = *expr)) { expr++; }	//空白を読み飛ばす。
		if(!c) { break; }
		if(isdigit(c)) {
			x = strtol(expr, (char**)&expr, 0);
			pToken = Token_nu_New(x, NULL/*ダミー*/);				//数値
		} else if(iscsymf(c)) {
			GString* s = g_string_new(NULL);
			while(iscsym(*expr)) { g_string_append_c(s, *expr++); }
			if(*expr == '(') {	//シンボルの直後に(空白も挟まずに)、'('が有れば関数。
				pToken = Token_fn_New(s->str);					//関数
			} else {		//そうでなければシンボル。
				pToken = Token_nu_New(0/*ダミー*/, s->str);			//シンボル
			}
		} else {
			switch(c) {
			default:UNEVALUABLE();	//それ以外の演算子は当ツールでは未対応なので、'評価不可能'と判断します。
			case '!':
				pToken = Token_op_New(c);					//'!'(単項!)
				break;
			case '&':
			case '|':
				if(*(expr + 1) != c) { UNEVALUABLE(); }	//ビットAnd,及び,ビットOrは当ツールでは未対応なので、'評価不可能'と判断します。
				pToken = Token_op_New(c);					//'&&','||'
				expr++;
				break;
			case '(':
				pToken = Token_New(exprsort_TokenType_LeftParenthesis);		//'('
				break;
			case ')':
				pToken = Token_New(exprsort_TokenType_RightParenthesis);	//')'
				break;
			}
			expr++;
		}
		g_ptr_array_add(array, pToken);
	}
	return array;
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
//逆ポーランド記法のトークン並びを計算する。
static int PostProcess(GPtrArray* array) {
	GPtrArray* stack = g_ptr_array_new();
	ST_Token_nu *pNuTok1, *pNuTok2;
	ST_Token_fn* pFnTok;
	ST_Token_op* pOpTok;
	//逆ポーランド記法のトークン並びが空になるまで…
	while(array->len) {
		//逆ポーランド記法のトークン並びの先頭から、トークンを取り出す。
		ST_Token* pToken = g_ptr_array_remove_index(array, 0);
		//数値,又は,シンボルならば…
		if(pToken->type == exprsort_TokenType_Number) {
			pNuTok1 = (ST_Token_nu*)pToken;
			//そのまま、スタックに積む。
			g_ptr_array_insert(stack, 0, pNuTok1);
		//関数ならば…
		} else if(pToken->type == exprsort_TokenType_Function) {
			pFnTok = (ST_Token_fn*)pToken;
			//スタックに、引数が有る事を確認する。
			if(stack->len < pFnTok->argc) { DIE(); }	//バグ。PreProcess()で引数を積む時にpFnTok->argcを増やすので、ここで引数が不足である事は無いはずです。
			//関数名によって…
			if(!strcmp(pFnTok->name, "defined")) {			//現在の所、"defined"のみです。
				//引数が一つである事を確認する。
				if(pFnTok->argc != 1) { UNEVALUABLE(); }	//defined()の引数が一つでなければ、Cプリプロセッサへの疑似命令として不正であるが、とりあえず'評価不可能'と判断しておく事にします。おそらくコンパイル時に、Cプリプロセッサがエラーを検出するはずです。
				//スタックの先頭から、引数を取り出す。
				pNuTok1 = g_ptr_array_remove_index(stack, 0);
				//引数がシンボルである事を確認する。
				if(!pNuTok1->isSymbol) { UNEVALUABLE(); }	//defined()の引数が数値ならば、Cプリプロセッサへの疑似命令として不正であるが、とりあえず'評価不可能'と判断しておく事にします。おそらくコンパイル時に、Cプリプロセッサがエラーを検出するはずです。
				//シンボルの名前によって…
				switch((int)ght_get(ht_symbol, strlen(pNuTok1->name), pNuTok1->name)) {	//ハッシュテーブルのキーに終端のnulは含めていないので、「strlen(pNuTok1->name) + 1/*nul*/」としないよう注意せよ。
				default:DIE();	//バグ
				case 'd': g_ptr_array_insert(stack, 0, Token_nu_New(1, NULL/*ダミー*/)); break;	//'-d'オプションで指定されたシンボルならば、スタックに1を積む。
				case 'u': g_ptr_array_insert(stack, 0, Token_nu_New(0, NULL/*ダミー*/)); break;	//'-u'オプションで指定されたシンボルならば、スタックに0を積む。
				case 0:   UNEVALUABLE();							//'-d'オプションでも'-u'オプションでも指定されていないシンボルならば、'評価不可能'と判断する。
				}
			} else {
				UNEVALUABLE();	//defined以外の関数(多分マクロ)が使用されていたら、'評価不可能'と判断する事にします。
			}
		//演算子ならば…
		} else if(pToken->type == exprsort_TokenType_Operator) {
			pOpTok = (ST_Token_op*)pToken;
			//演算子の種類によって…
			switch(pOpTok->name) {
			default:DIE();	//バグ
			case '!':	//単項'!'
				//スタックに、引数が有る事を確認する。
				if(stack->len < 1) { UNEVALUABLE(); }		//入力式が間違っていたら、引数が不足している可能性も有ります。Cプリプロセッサへの疑似命令として不正であるが、とりあえず'評価不可能'と判断しておく事にします。おそらくコンパイル時に、Cプリプロセッサがエラーを検出するはずです。
				//スタックの先頭から、引数を取り出す。
				pNuTok1 = g_ptr_array_remove_index(stack, 0);
				//引数が数値である事を確認する。
				if(pNuTok1->isSymbol) { UNEVALUABLE(); }	//当ツールはマクロ展開を考慮しないので、シンボルの数値は判断出来ません。シンボルに対して演算子が適用されていたら、'評価不可能'と判断します。
				//数値を論理否定して、スタックに積む。
				pNuTok1->value = !pNuTok1->value;
				g_ptr_array_insert(stack, 0, pNuTok1);
				break;
			case '&':	//'&&'
				//スタックに、引数が有る事を確認する。
				if(stack->len < 2) { UNEVALUABLE(); }		//入力式が間違っていたら、引数が不足している可能性も有ります。Cプリプロセッサへの疑似命令として不正であるが、とりあえず'評価不可能'と判断しておく事にします。おそらくコンパイル時に、Cプリプロセッサがエラーを検出するはずです。
				//スタックの先頭から、引数を取り出す。
				pNuTok1 = g_ptr_array_remove_index(stack, 0);
				pNuTok2 = g_ptr_array_remove_index(stack, 0);
				//引数が数値である事を確認する。
				if(pNuTok1->isSymbol) { UNEVALUABLE(); }	//当ツールはマクロ展開を考慮しないので、シンボルの数値は判断出来ません。シンボルに対して演算子が適用されていたら、'評価不可能'と判断します。
				if(pNuTok2->isSymbol) { UNEVALUABLE(); }	//当ツールはマクロ展開を考慮しないので、シンボルの数値は判断出来ません。シンボルに対して演算子が適用されていたら、'評価不可能'と判断します。
				//論理積の結果を、スタックに積む。
				g_ptr_array_insert(stack, 0,
					Token_nu_New(
						pNuTok1->value && pNuTok2->value,
						NULL/*ダミー*/));
				break;
			case '|':	//'||'
				//スタックに、引数が有る事を確認する。
				if(stack->len < 2) { UNEVALUABLE(); }		//入力式が間違っていたら、引数が不足している可能性も有ります。Cプリプロセッサへの疑似命令として不正であるが、とりあえず'評価不可能'と判断しておく事にします。おそらくコンパイル時に、Cプリプロセッサがエラーを検出するはずです。
				//スタックの先頭から、引数を取り出す。
				pNuTok1 = g_ptr_array_remove_index(stack, 0);
				pNuTok2 = g_ptr_array_remove_index(stack, 0);
				//引数が数値である事を確認する。
				if(pNuTok1->isSymbol) { UNEVALUABLE(); }	//当ツールはマクロ展開を考慮しないので、シンボルの数値は判断出来ません。シンボルに対して演算子が適用されていたら、'評価不可能'と判断します。
				if(pNuTok2->isSymbol) { UNEVALUABLE(); }	//当ツールはマクロ展開を考慮しないので、シンボルの数値は判断出来ません。シンボルに対して演算子が適用されていたら、'評価不可能'と判断します。
				//論理和の結果を、スタックに積む。
				g_ptr_array_insert(stack, 0,
					Token_nu_New(
						pNuTok1->value || pNuTok2->value,
						NULL/*ダミー*/));
				break;
			}
		} else {
			DIE();
		}
	}
	//スタックに結果だけが残っている事を確認する。
	if(stack->len != 1) { UNEVALUABLE(); }		//入力式が間違っていたら、引数が不足している可能性も有ります。Cプリプロセッサへの疑似命令として不正であるが、とりあえず'評価不可能'と判断しておく事にします。おそらくコンパイル時に、Cプリプロセッサがエラーを検出するはずです。
	//スタックの先頭から、結果を取り出す。
	pNuTok1 = g_ptr_array_remove_index(stack, 0);
	//結果が数値である事を確認する。
	if(pNuTok1->isSymbol) { UNEVALUABLE(); }	//当ツールはマクロ展開を考慮しないので、シンボルの数値は判断出来ません。結果がシンボルならば、'評価不可能'と判断します。
	//結果の数値を返す。
	return pNuTok1->value;
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
//「#if」及び「#elif」の式を評価する処理
//・「#if」又は「#elif」の式が0と評価された場合、0を返す。
//・「#if」又は「#elif」の式が1と評価された場合、1を返す。
//・「#if」又は「#elif」の式が評価不可能だった場合、-1を返す。
static int IfExprEval(const char* expr) {
	SEH_try {
		//テキストを中置記法のトークン並びに変換する。
		GPtrArray* array = PreProcess(expr);
		//中置記法のトークン並びを逆ポーランド記法のトークン並びに変換する。
		int n = exprsort(array->pdata, array->len, sizeof(void*), GetTokenType, IsOpLeftAssoc, CompareOpPrec, IncrFnNumArgs);
		//配列の末尾の、トラッシュバッファの部分を開放する。
		g_ptr_array_set_size(array, n);
		//逆ポーランド記法のトークン並びを計算する。
		return PostProcess(array);	//評価結果(0,又は,1)
	} SEH_catch(ExprException) {
		return -1;			//評価不可能(-1)
	} SEH_end
	/* NOTREACHED */
	DIE();
}
//}}2016/10/15追加:「#if」「#elif」の式を(限定的ではありますが)評価出来るようにしました。
