/*****************************************************************************
 * Bison/Flexプログラム部分をテストするための、Windowsアプリケーションの雛型 *
 * □ビルド・実行方法:                                                       *
 * ┃"C:\Program Files (x86)\Microsoft Visual Studio\VC98\Bin\VCVARS32.BAT"  *
 * ┃make -f winapp.mk run                                                   *
 *****************************************************************************/
#include "winapp.h"
//-----------------------------------------------------------------------------
//警告定義
enum {
	WARN_CAST,			//暗黙の型変換で誤差が生じたら、警告を表示し最終的にエラー終了します。このオプションを指定しなければ、警告表示もエラー終了もしません。
};
static const char* const warn_tokens[]={
	"cast",				//暗黙の型変換で誤差が生じたら、警告を表示し最終的にエラー終了します。このオプションを指定しなければ、警告表示もエラー終了もしません。
NULL};//終端
int warn;				//各警告(WARN_*)について(warn&(1<<WARN_*))がセットされていたらその警告がOnである事を示します。
/*---------------------------------------------------------------------------*/
static ght_hash_table_t* ht_label;	//ラベル⇒(ST_Enum*)
//ラベル名のソート方法
int (*fnLabCmp)(const void*, const void*) = strcmp;	//'n'オプションが指定されたらstrnatcmpに変更する。
//{{進捗表示用
static int opt_q;
static int newMsgLen, oldMsgLen = 0;
//}}進捗表示用
/*---------------------------------------------------------------------------*/
//エラーログ用
static char err_path[_MAX_PATH];	//'-e'オプションが指定されたら、エラーログファイル名を格納する。
static FILE* fp_err;			//最初のエラーログを記録する時に、エラーログファイルを作成する。
//{{2016/09/24追加:エラーメッセージに、現在の行番号だけでなく、現在のファイル名も表示するようにしました。
typedef struct _ST_Location {
	const char*	path;					//push_location()が呼び出された直前のファイル名
	int		line;					//push_location()が呼び出された直前の行番号
} ST_Location;
static ST_Location TBL_Location[16/*調整可*/];			//ロケーションスタック
static nLocation;						//ロケーションスタックの格納数
static const char* filename = "-";				//現在のファイル名(popen時はコマンド名)。現在の行番号はyylinenoを参照して下さい。
static void init_location(const char* path) {
	nLocation = 0;						//ロケーションスタックを空にする。
	filename = strdup(path);				//現在のファイル名をコピーして保持する。
	yylineno = 1;						//現在の行番号を初期化する。	yylex_init()でも初期化しているのでこの行は無くても構わないのだが、判り易さのためにここでも初期化する事にした。
}
void push_location(const char* path) {
	if(nLocation >= ARRAY_SIZE(TBL_Location)) { DIE(); }	//ロケーションスタックがフルでない事を確認する。
	TBL_Location[nLocation].path = filename;		//現在のファイル名をロケーションスタックに退避する。
	TBL_Location[nLocation].line = yylineno;		//現在の行番号をロケーションスタックに退避する。
	nLocation++;						//ロケーションスタックの格納数を増やす。
	filename = strdup(path);				//現在のファイル名をコピーして保持する。
	yylineno = 1;						//現在の行番号を初期化する。
}
void pop_location() {
	if(!nLocation) { DIE(); }				//ロケーションスタックが空でない事を確認する。
	nLocation--;						//ロケーションスタックの格納数を減らす。
	filename = TBL_Location[nLocation].path;		//現在のファイル名をロケーションスタックから復元する。
	yylineno = TBL_Location[nLocation].line;		//現在の行番号をロケーションスタックから復元する。
}
//}}2016/09/24追加:エラーメッセージに、現在の行番号だけでなく、現在のファイル名も表示するようにしました。
void yyerror(const char* fmt, ...) {	//←{{2015/03/06:'-e'オプション対応のためにwinapp.cから移動しました。}}
	//'-e'オプションが指定されていて、最初のエラーログを記録する時ならば…
	if(*err_path && !fp_err) {
		//エラーログファイルを作成する。
		fp_err = fopen(err_path, "w");
		if(!fp_err) { error(EXIT_FAILURE, errno, "%s が作成できません。", err_path); }	//{{2016/09/21変更:die(～)で終了していた箇所を、error(EXIT_FAILURE,～)で終了するように変更しました。}}	//{{2016/09/28変更:error()の文字列の最後に余計な改行が付いていたのを削除しました。}}
		//エラーログファイルの出力ストリームは、バッファリング無しとする。
		// - die()で終了した時(abort()時)に、バッファ分がフラッシュされないようなので、こうしておく必要が有る。
		//   別の案として、この下の処理で毎回fflush()する方法も有るが、バッファリング無しとする方が簡単だろう。
		setbuf(fp_err, NULL);
	}
	//エラーメッセージ(,及び,エラーログ)を出力する。
//{{2016/09/24変更:エラーメッセージに、現在の行番号だけでなく、現在のファイル名も表示するようにしました。
//	             fprintf(stderr, "### error at line %d : ", yylineno);	//エラーメッセージ
//	if(fp_err) { fprintf(fp_err, "### error at line %d : ", yylineno); }	//エラーログ
//↓2016/09/24変更:エラーメッセージに、現在の行番号だけでなく、現在のファイル名も表示するようにしました。
	             fprintf(stderr, "%s(%d) : ", filename, yylineno);		//エラーメッセージ
	if(fp_err) { fprintf(fp_err, "%s(%d) : ", filename, yylineno); }	//エラーログ
//}}2016/09/24変更:エラーメッセージに、現在の行番号だけでなく、現在のファイル名も表示するようにしました。
	{
		va_list ap;
		va_start(ap, fmt);
		             vfprintf(stderr, fmt, ap);		//エラーメッセージ
		if(fp_err) { vfprintf(fp_err, fmt, ap); }	//エラーログ
		va_end(ap);
	}
	             fputc('\n', stderr);	//エラーメッセージ
	if(fp_err) { fputc('\n', fp_err); }	//エラーログ
}
/*---------------------------------------------------------------------------*/
/* ローカル関数宣言 */
__declspec(noreturn) static void usage();
static int subr(int argc, char* argv[]);
static void pass2(int bOptimize);
static void output(char* sRegOut);
/*---------------------------------------------------------------------------*/
int main(int argc, char* argv[]) {
	int exitCode;
	SEH_init();
	exitCode = subr(argc, argv);
	return exitCode;
}
/*---------------------------------------------------------------------------*/
static int subr(int argc, char* argv[]) {
	int opt, bOptimize = 1;	//'-d'オプションが指定されなければ最適化を行う。
	char* sRegOut = NULL;	//'-r'オプションが指定されたらレジストリスクリプトファイル名を設定する。
#if     (YYDEBUG!=0)
//	yydebug = 1; //nonzero means print parse trace
#endif/*(YYDEBUG!=0)*/
	//コマンドラインオプションを処理する。
	opterr = 0; //オプションエラーは当プログラム内で処理するので、getopt()内でのエラー表示は不要。
	while((opt = getopt(argc, argv, "dr:enw::qh")) != -1) {
		switch(opt) {
		case 'd':
			bOptimize = 0;		//最適化を行わない。
			break;
		case 'r':
			sRegOut = optarg;	//レジストリスクリプトファイル名を設定する。
			break;
		case 'e':
			{
				//エラーログファイル名を作成する。
				// - カレントフォルダに'実行ファイル名.err'という名前で生成する事にした。この動作は、'lk33.exe'の仕様を参考にした。
				char fname[_MAX_FNAME];
				_splitpath(argv[0], NULL, NULL, fname, NULL);
				_makepath(err_path, NULL, NULL, fname, ".err");
				//'-e'オプションが指定されたら、最初に確実に、既存のエラーログファイルを削除しておく。
				//前回に生成したエラーログが残っていて、今回生成したエラーログと混同する危険を避けるためである。
				//尚、'-e'オプションが指定されなかった場合は、既存のエラーログファイルを削除する処理は行わない。
				remove(err_path);
			}
			break;
		case 'n':
			fnLabCmp = strnatcmp;	//ラベル名のソートにstrnatcmp()を使用する。
			break;
		case 'w':
			if(optarg) {
				char *optionp = optarg, *valuep;
				while(*optionp) {
					switch(getsubopt(&optionp, (char* const*)warn_tokens, &valuep)) {
					case WARN_CAST:
						if(valuep) { usage(); }
						warn |= (1<<WARN_CAST);
						break;
					default:
						usage();
					}
				}
			} else {
				warn = -1;
			}
			break;
		case 'q':
			opt_q = 1;
			break;
		case 'h':
		default:
			usage();
		}
	}
	//ハッシュテーブルを作成する。
	ght_set_rehash((ht_label = ght_create(0)), 1);
	//--- 1パス目の処理 ---
	//ファイルが指定されていなければ、標準入力から読み込む。
	if(optind == argc) {
		init_location("-");	//{{2016/09/24追加:エラーメッセージに、現在の行番号だけでなく、現在のファイル名も表示するようにしました。}}
		yylex_init(stdin);
		if((yyparse() != 0) /*|| (yynerrs != 0)*/) { error(EXIT_FAILURE, 0, "エラーが発生しました。"); }	//{{2016/09/21変更:die(～)で終了していた箇所を、error(EXIT_FAILURE,～)で終了するように変更しました。}}	//{{2016/09/28変更:error()の文字列の最後に余計な改行が付いていたのを削除しました。}}
	//ファイルが指定されていたら、各ファイルを読み込む。
	} else {
		int i;
		for(i = optind; i < argc; i++) {
			FILE* fp = fopen(argv[i], "r");
			if(!fp) { error(EXIT_FAILURE, errno, "%s が開けません。", argv[i]); }	//{{2016/09/21変更:die(～)で終了していた箇所を、error(EXIT_FAILURE,～)で終了するように変更しました。}}	//{{2016/09/28変更:error()の文字列の最後に余計な改行が付いていたのを削除しました。}}
			init_location(argv[i]);	//{{2016/09/24追加:エラーメッセージに、現在の行番号だけでなく、現在のファイル名も表示するようにしました。}}
			yylex_init(fp);
			if((yyparse() != 0) /*|| (yynerrs != 0)*/) { error(EXIT_FAILURE, 0, "%s: エラーが発生しました。", argv[i]); }	//{{2016/09/21変更:die(～)で終了していた箇所を、error(EXIT_FAILURE,～)で終了するように変更しました。}}	//{{2016/09/28変更:error()の文字列の最後に余計な改行が付いていたのを削除しました。}}
			fclose(fp);
		}
	}
    //{{進捗表示用
	if(!opt_q) {
		newMsgLen = fprintf(stderr, "\rpass1 ok");
		if(oldMsgLen > newMsgLen) { fprintf(stderr, "%*s", oldMsgLen - newMsgLen, ""); } putc('\n', stderr); oldMsgLen = 0;
	}
    //}}進捗表示用
	//1パス目の処理でエラーが発生しても、2パス目の処理を行って、全てのセマンティクスエラーを報告する。
	//--- 2パス目の処理 ---
	//2パス目の処理を行う。
	pass2(bOptimize);
    //{{進捗表示用
	if(!opt_q) {
		newMsgLen = fprintf(stderr, "\rpass2 ok");
		if(oldMsgLen > newMsgLen) { fprintf(stderr, "%*s", oldMsgLen - newMsgLen, ""); } putc('\n', stderr); oldMsgLen = 0;
	}
    //}}進捗表示用
	if(yynerrs != 0) { error(EXIT_FAILURE, 0, "エラーが発生しました。"); }	//1パス目,又は,2パス目の処理でエラーが発生していたら、出力処理を行わない。	//{{2016/09/21変更:die(～)で終了していた箇所を、error(EXIT_FAILURE,～)で終了するように変更しました。}}	//{{2016/09/28変更:error()の文字列の最後に余計な改行が付いていたのを削除しました。}}
	//--- 出力処理 ---
	//出力処理を行う。
	output(sRegOut);
	if(yynerrs != 0) { error(EXIT_FAILURE, 0, "エラーが発生しました。"); }	//{{2016/09/21変更:die(～)で終了していた箇所を、error(EXIT_FAILURE,～)で終了するように変更しました。}}	//{{2016/09/28変更:error()の文字列の最後に余計な改行が付いていたのを削除しました。}}
	return EXIT_SUCCESS;
}
/*---------------------------------------------------------------------------*/
//使い方を表示して終了する。
__declspec(noreturn) static void usage() {
	fprintf(stderr, "dEnmDefC - 列挙定義コンパイラ (%s)\n", VERSION);
	fprintf(stderr, "Copyright (C) 2016 Naoyuki Sawa\n");
	fprintf(stderr, "\n");
	fprintf(stderr, "USAGE:\n");
	fprintf(stderr, "  dEnmDefC [option ...] [filename ...] \n");
	fprintf(stderr, "  ・filenameを指定しなければ、標準入力から読み込みます。\n");
	fprintf(stderr, "  ・filenameは、ワイルドカードによる複数指定も可能です。\n");
	fprintf(stderr, "\n");
	fprintf(stderr, "OPTIONS:\n");
	fprintf(stderr, "  -d           最適化を行いません。\n");
	fprintf(stderr, "               このオプションを指定しなければ、最適化を行います。\n");
	fprintf(stderr, "  -rファイル名 列挙定義のレジストリスクリプトを、ファイルへ出力します。\n");
	fprintf(stderr, "               このオプションを指定しなければ、標準出力へ出力します。\n");
	fprintf(stderr, "  -e           エラーログファイルを生成します。\n");
	fprintf(stderr, "               このオプションを指定しても、エラーが発生しなければ生成されません。\n");
	fprintf(stderr, "  -n           ラベル名のソートに、strnatcmp()を使用します。\n");
	fprintf(stderr, "               このオプションを指定しなければ、strcmp()を使用します。\n");
	fprintf(stderr, "  -w           全ての警告を有効にします。\n");
	fprintf(stderr, "               このオプションを指定しなければ、個別に設定した警告のみが有効になります。\n");
	fprintf(stderr, "  -wcast       暗黙の型変換で誤差が生じたら、警告を表示し最終的にエラー終了します。\n");
	fprintf(stderr, "               このオプションを指定しなければ、警告表示もエラー終了もしません。\n");
	fprintf(stderr, "  -q           進捗表示を抑制します。\n");
	fprintf(stderr, "  -h           使い方を表示します。\n");
	fprintf(stderr, "\n");
	fprintf(stderr, "EXAMPLE:\n");
	fprintf(stderr, "  ①dEnmDefC.exe EnmDef.txt > EnmDef.out\n");
	fprintf(stderr, "  　EnmDef.txtをコンパイルして、EnmDef.outへ出力します。\n");
	fprintf(stderr, "  ②dEnmDefC.exe -rRegEnmDef.txt EnmDef.txt > EnmDef.h\n");
	fprintf(stderr, "  　EnmDef.txtをコンパイルして、ラベルをEnmDef.hへ出力します。\n");
	fprintf(stderr, "  　列挙定義のレジストリスクリプトを、RegEnmDef.txtへ出力します。\n");
	exit(EXIT_FAILURE);
}
/*---------------------------------------------------------------------------*/
//1パス目の処理
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
int cast_dbl_to_int(double d) {
	int i = d;
	if(i != d) {
		if(warn & (1<<WARN_CAST)) {
			yyerror("切り捨て誤差が発生しました: %f → %d", d, i);
			yynerrs++;	//呼び出し元(main)に知らせるために。
			//全てのセマンティクスエラーを報告するために継続する。
		}
	}
	return i;
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
void addEnum(ST_Enum* pEnum) {
    //{{進捗表示用
	if(!opt_q) {
		newMsgLen = fprintf(stderr, "\rpass1 %s", pEnum->pLabel);
		if(oldMsgLen > newMsgLen) { fprintf(stderr, "%*s", oldMsgLen - newMsgLen, ""); } oldMsgLen = newMsgLen;
	}
    //}}進捗表示用
	//ラベルが重複していない事を確認し、列挙型を登録する。
	if(ght_insert(ht_label,
		       pEnum,
		strlen(pEnum->pLabel) + 1/*nul*/,
		       pEnum->pLabel)) {
		yyerror("ラベルが重複しています。(%s)", pEnum->pLabel);
		yynerrs++;	//呼び出し元(main)に知らせるために。
		return;		//全てのセマンティクスエラーを報告するために継続する。
	}
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
static int Enum_new_compar(const void* _x, const void* _y) {
	const ST_EnumLine* x = *(const ST_EnumLine**)_x;
	const ST_EnumLine* y = *(const ST_EnumLine**)_y;
	if(x->iVal < y->iVal) { return -1; }
	if(x->iVal > y->iVal) { return  1; }
	DIE();	//EnumBody_addEnumLine()で検査済みなので同じ値は無いはず。もしここで止まったら当ツールのバグです。
}
ST_Enum* Enum_new(char* pLabel, ST_EnumBody* pEnumBody) {
	ST_Enum* pEnum = calloc(1, sizeof(ST_Enum));
	//列挙型ライン配列を値の昇順にソートする。
	// - 必須ではありません。出力時に見易くするためだけです。意味上はソートしてもしなくても同じです。
	qsort(pEnumBody->aEnumLine,
	      pEnumBody->nEnumLine,
	      sizeof pEnumBody->aEnumLine[0],
	      Enum_new_compar);
	pEnum->pLabel		= pLabel;
	pEnum->pEnumBody	= pEnumBody;
	return pEnum;
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
//{{2016/07/07変更:空の列挙型を許容するように変更しました。
//ST_EnumBody* EnumBody_new(ST_EnumLine* pEnumLine) {
//	ST_EnumBody* pEnumBody = calloc(1, sizeof(ST_EnumBody));
//	return EnumBody_addEnumLine(pEnumBody, pEnumLine);
//}
//↓2016/07/07変更:空の列挙型を許容するように変更しました。
ST_EnumBody* EnumBody_new() {
	ST_EnumBody* pEnumBody = calloc(1, sizeof(ST_EnumBody));
	if(!pEnumBody) { DIE(); }
	return pEnumBody;
}
//}}2016/07/07変更:空の列挙型を許容するように変更しました。
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
ST_EnumBody* EnumBody_addEnumLine(ST_EnumBody* pEnumBody, ST_EnumLine* pEnumLine) {
	int i;
	pEnumBody = realloc(pEnumBody, sizeof(ST_EnumBody) + (sizeof pEnumBody->aEnumLine[0]) * (pEnumBody->nEnumLine + 1));
	if(!pEnumBody) { DIE(); }
	//ラインがオートインクリメントならば…
	if(pEnumLine->bIncr) {
		//前のラインが有れば…
		if(pEnumBody->nEnumLine) {
			//(前のラインの値+1)とする。
			pEnumLine->iVal = pEnumBody->aEnumLine[pEnumBody->nEnumLine - 1]->iVal + 1;
		//前のラインが無ければ…
		} else {
			//(iVal=0)とする。
			// - (bIncr=1)であるラインは既に(iVal=0)になっているはずである。y.tab.yのlineの処理を参照せよ。
			if(pEnumLine->iVal) { DIE(); }	//もしここで止まったら当ツールのバグです。
		}
		//値が範囲内である事を確認する。
		// - 値はレジストリキーの名前としても使用するので、0～65535の範囲でなければならない。
		if((unsigned)pEnumLine->iVal > UINT16_MAX) {
			yyerror("値が範囲外です。(%s,%d)", pEnumLine->pSym, pEnumLine->iVal);
			yynerrs++;	//呼び出し元(main)に知らせるために。	//必要。YYERRORマクロ呼び出しでyynerrsは増やされないようだ。	←←←←←←←←←←■■■■重要■■■■
			return NULL;	//y.tab.yの処理でYYERRORを行う。
		}
	}
	//シンボル,又は,値が重複していない事を確認する。
	for(i = 0; i < pEnumBody->nEnumLine; i++) {
		ST_EnumLine* pEnumLine2 = pEnumBody->aEnumLine[i];
		if(!strcmp(pEnumLine->pSym, pEnumLine2->pSym)) {
			yyerror("シンボルが重複しています。(%s)", pEnumLine->pSym);
			yynerrs++;	//呼び出し元(main)に知らせるために。	//必要。YYERRORマクロ呼び出しでyynerrsは増やされないようだ。	←←←←←←←←←←■■■■重要■■■■
			return NULL;	//y.tab.yの処理でYYERRORを行う。
		}
		if(pEnumLine->iVal == pEnumLine2->iVal) {
			yyerror("値が重複しています。(%d)", pEnumLine->iVal);
			yynerrs++;	//呼び出し元(main)に知らせるために。	//必要。YYERRORマクロ呼び出しでyynerrsは増やされないようだ。	←←←←←←←←←←■■■■重要■■■■
			return NULL;	//y.tab.yの処理でYYERRORを行う。
		}
	}
	pEnumBody->aEnumLine[pEnumBody->nEnumLine++]	= pEnumLine;
	return pEnumBody;
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
ST_EnumLine* EnumLine_new(char* pSym, int bIncr, int iVal, char* pDesc) {
	ST_EnumLine* pEnumLine = calloc(1, sizeof(ST_EnumLine));
	pEnumLine->pSym		= pSym;
	pEnumLine->bIncr	= bIncr;
	pEnumLine->iVal		= iVal;
	pEnumLine->pDesc	= pDesc;
	return pEnumLine;
}
/*---------------------------------------------------------------------------*/
//2パス目の処理
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
static int Label_compar(LIST_ENTRY* _x, LIST_ENTRY* _y) {
	//当関数の処理は、ght_hash_table.h/cモジュールの動作に依存しています。
	//外部からght_hash_table_t.le_iteratorの順番を並び替えても、動作上問題有りません。
	ght_hash_entry_t* x = CONTAINING_RECORD(_x, ght_hash_entry_t, le_iterator);
	ght_hash_entry_t* y = CONTAINING_RECORD(_y, ght_hash_entry_t, le_iterator);
	return (*fnLabCmp)(x->key_data, y->key_data);
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
static void pass2(int bOptimize) {
	//列挙定義のラベルを名前の昇順にソートする。	//■■■注意：ght_hash_table_tの実装依存の処理です。■■■
	// - 意味上の影響は有りません。出力テキストの見易さのためだけです。
	//   列挙定義のラベルはレジストリコンパイラによって値が割り振られるので、順番は関係無いからです。
	// - 尚、列挙定義の値の順番には意味が有るので、ソートしてはいけません。
	//   列挙定義スクリプトに記述された順番のまま、値を振る必要が有ります。
	SortList(&ht_label->le_iterator, Label_compar);
}
/*---------------------------------------------------------------------------*/
//出力処理
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
static void output(char* sRegOut) {
	ght_iterator_t it;
	char* pLabel;
	ST_Enum* pEnum;
	int i;
	//標準出力
	{
		FILE* fp = stdout;
		//列挙定義の値の出力
		for(pEnum = ght_first(ht_label, &it, &pLabel);
		    pEnum;
		    pEnum = ght_next( ht_label, &it, &pLabel)) {
			fprintf(fp, "//%s\n", pLabel);														//'//',ラベル
			{
				ST_EnumBody* pEnumBody = pEnum->pEnumBody;
				int iValMax = -1;	//ダミー
				for(i = 0; i < pEnumBody->nEnumLine; i++) {
					ST_EnumLine* pEnumLine = pEnumBody->aEnumLine[i];
					fprintf(fp, "#define %s_%s\t%d", pLabel, pEnumLine->pSym, pEnumLine->iVal);						//ラベル,'_',シンボル,値
		if(*pEnumLine->pDesc) { fprintf(fp, "\t//%s", pEnumLine->pDesc); }										//'//',説明文
					fputc('\n', stdout);
					if(pEnumLine->iVal > iValMax) { iValMax = pEnumLine->iVal; }
				}
				fprintf(fp, "#define %s_%s\t%d\n", pLabel, "Max", iValMax);									//ラベル,'_','Max',最大値		{{2016/07/07コメント追記:空の列挙型の場合は、「#define ラベル_Max -1」となります。}}
//{{2016/07/07追加:空の列挙型を許容するように変更しました。
				//空の列挙型を許容するように変更しましたが、空の列挙型を記述するのはあくまでもレアケースです。
				//間違って列挙型の中身を書き忘れて、空の列挙型になってしまっている恐れがあるので、空の列挙型に対しては常に警告を表示する事にしました。
				//意図的に空の列挙型を記述した事が判っている場合は、この警告は無視して下さい。
				if(!pEnumBody->nEnumLine) {
    //{{2016/09/11修正:空の列挙型の警告メッセージが、進捗表示で上書きされて消えてしまっていた問題を修正しました。
    //					fprintf(stderr, "警告: 空の列挙型 %s を出力しました。", pLabel);
    //↓2016/09/11修正:空の列挙型の警告メッセージが、進捗表示で上書きされて消えてしまっていた問題を修正しました。
					fprintf(stderr, "警告: 空の列挙型 %s を出力しました。\n", pLabel);
    //}}2016/09/11修正:空の列挙型の警告メッセージが、進捗表示で上書きされて消えてしまっていた問題を修正しました。
				}
//}}2016/07/07追加:空の列挙型を許容するように変更しました。
			}
		}
	}
	//標準出力,又は,レジストリスクリプトファイル
	{
		FILE* fp = stdout;
		//レジストリスクリプトファイル名が指定されていたら…
		if(sRegOut) {
			//レジストリスクリプトファイルを作成する。
			fp = fopen(sRegOut, "w");
			if(!fp) { error(EXIT_FAILURE, errno, "%s が作成できません。", sRegOut); }	//{{2016/09/21変更:die(～)で終了していた箇所を、error(EXIT_FAILURE,～)で終了するように変更しました。}}	//{{2016/09/28変更:error()の文字列の最後に余計な改行が付いていたのを削除しました。}}
		}
		fprintf(fp, "%s\t\t\t\t\t#//\t%s\n", "EnmDef", "EnmDef");											//EnmDef
		{
			for(pEnum = ght_first(ht_label, &it, &pLabel);
			    pEnum;
			    pEnum = ght_next( ht_label, &it, &pLabel)) {
				ST_EnumBody* pEnumBody = pEnum->pEnumBody;
				fprintf(fp, "\t%s\t\t\t\t#//\t\t%s\n", pLabel, pLabel);
				for(i = 0; i < pEnumBody->nEnumLine; i++) {
					ST_EnumLine* pEnumLine = pEnumBody->aEnumLine[i];
					fprintf(fp, "\t\t%d\t\t\t#//\t\t\t%s\n", pEnumLine->iVal, pEnumLine->pSym);						//値,シンボル
					fprintf(fp, "\t\t\t%d\t= \"%s\"\t#//\t\t\t\t%s\t= \"%s\"\n", Enm_name, pEnumLine->pSym, "name", pEnumLine->pSym);	//Enm_name,シンボル
		if(*pEnumLine->pDesc) { fprintf(fp, "\t\t\t%d\t= \"%s\"\t#//\t\t\t\t%s\t= \"%s\"\n", Enm_desc, pEnumLine->pDesc, "desc", pEnumLine->pDesc); }	//Enm_desc,説明文
					fprintf(fp, "\t\t%s\t\t\t#//\t\t\t%s\n", "end",  "end");								//end
				}
				fprintf(fp, "\t%s\t\t\t\t#//\t\t%s\n", "end",  "end");										//end
			}
		}
		fprintf(fp, "%s\t\t\t\t\t#//\t%s\n", "end", "end");												//end
		//レジストリスクリプトファイル名が指定されていたら…
		if(sRegOut) {
			//レジストリスクリプトファイルを閉じる。
			fclose(fp);
		}
	}
}
