/*****************************************************************************
 * 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_phr;	//フレーズ名⇒(ST_Phr*)
//ラベル名のソート方法
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(const char* sPhrOut, const char* sEnmOut, const char* sRegOut);
static void output_phr(FILE* fp);
static void output_enm(FILE* fp);
static void output_reg(FILE* fp);
/*---------------------------------------------------------------------------*/
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'オプションが指定されなければ最適化を行う。
	const char* sPhrOut = NULL;	//'-p'オプションが指定されたらフレーズ定義ヘッダのファイル名,又は,"-"(標準出力)を設定する。	─P/ECE専用
	const char* sEnmOut = NULL;	//'-u'オプションが指定されたら列挙定義スクリプトのファイル名,又は,"-"(標準出力)を設定する。	┬汎用
	const 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, "dp::u::r::enw::qh")) != -1) {
		switch(opt) {
		case 'd':
			bOptimize = 0;				//最適化を行わない。
			break;
		case 'p':
			sPhrOut = optarg ? optarg : "-";	//フレーズ定義ヘッダのファイル名,又は,"-"(標準出力)を設定する。
			break;
		case 'u':
			sEnmOut = optarg ? optarg : "-";	//列挙定義スクリプトのファイル名,又は,"-"(標準出力)を設定する。
			break;
		case 'r':
			sRegOut = optarg ? 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_phr = 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);
//{{2017/03/16変更:複数の入力ファイルを指定した時に、最後以外のファイルでエラーが発生した場合、当ツールの終了ステータスが0になっていたバグを修正しました。
//			if((yyparse() != 0) /*|| (yynerrs != 0)*/) { error(EXIT_FAILURE, 0, "%s: エラーが発生しました。", argv[i]); }	//{{2016/09/21変更:die(～)で終了していた箇所を、error(EXIT_FAILURE,～)で終了するように変更しました。}}	//{{2016/09/28変更:error()の文字列の最後に余計な改行が付いていたのを削除しました。}}
//↓2017/03/16変更:複数の入力ファイルを指定した時に、最後以外のファイルでエラーが発生した場合、当ツールの終了ステータスが0になっていたバグを修正しました。
			{
				int save_yynerrs = yynerrs;	//yyparse()がyynerrsをクリアするので、その前に現在のyynerrsを保存しておく。
				if((yyparse() != 0) /*|| (yynerrs != 0)*/) { error(EXIT_FAILURE, 0, "%s: エラーが発生しました。", argv[i]); }	//{{2016/09/21変更:die(～)で終了していた箇所を、error(EXIT_FAILURE,～)で終了するように変更しました。}}	//{{2016/09/28変更:error()の文字列の最後に余計な改行が付いていたのを削除しました。}}
				yynerrs += save_yynerrs;	//今回のyyparse()が0から数えたであろうyynerrsに対して、先ほど保存しておいたyynerrsを足し込む。
			}
//}}2017/03/16変更:複数の入力ファイルを指定した時に、最後以外のファイルでエラーが発生した場合、当ツールの終了ステータスが0になっていたバグを修正しました。
			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(sPhrOut, sEnmOut, 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, "dPhrDefC - フレーズ定義コンパイラ (%s)\n", VERSION);
	fprintf(stderr, "Copyright (C) 2016-2017 Naoyuki Sawa\n");
	fprintf(stderr, "\n");
	fprintf(stderr, "USAGE:\n");
	fprintf(stderr, "  dPhrDefC [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, "  -p[ファイル名] P/ECE用のフレーズ定義ヘッダを、ファイルへ出力します。\n");
	fprintf(stderr, "                 ファイル名を指定しなければ、標準出力へ出力します。\n");
	fprintf(stderr, "                 このオプションを指定しなければ、フレーズ定義ヘッダを出力しません。\n");
	fprintf(stderr, "  -u[ファイル名] 汎用の列挙定義スクリプトを、ファイルへ出力します。\n");
	fprintf(stderr, "                 ファイル名を指定しなければ、標準出力へ出力します。\n");
	fprintf(stderr, "                 このオプションを指定しなければ、列挙定義スクリプトを出力しません。\n");
	fprintf(stderr, "  -r[ファイル名] 汎用のレジストリスクリプトを、ファイルへ出力します。\n");
	fprintf(stderr, "                 ファイル名を指定しなければ、標準出力へ出力します。\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, "  ①dPhrDefC.exe -p PhrDef.txt\n");
	fprintf(stderr, "  　PhrDef.txtをコンパイルします。\n");
	fprintf(stderr, "  　P/ECE用のフレーズ定義ヘッダを、標準出力へ出力します。\n");
	fprintf(stderr, "  ②dPhrDefC.exe -psound.h PhrDef.txt\n");
	fprintf(stderr, "  　PhrDef.txtをコンパイルします。\n");
	fprintf(stderr, "  　P/ECE用のフレーズ定義ヘッダを、sound.hへ出力します。\n");
	fprintf(stderr, "  ③dPhrDefC.exe -uEnmPhrDef.txt -rRegPhrDef.txt PhrDef.txt\n");
	fprintf(stderr, "  　PhrDef.txtをコンパイルします。\n");
	fprintf(stderr, "  　汎用の列挙定義スクリプトを、EnmPhrDef.txtへ出力します。\n");
	fprintf(stderr, "  　汎用のレジストリスクリプトを、RegPhrDef.txtへ出力します。\n");
	fprintf(stderr, "\n");
	fprintf(stderr, "※注意\n");
	fprintf(stderr, "  P/ECE用のフレーズ番号は既存のマクロ仕様のため0ベースです。\n");
	fprintf(stderr, "  汎用のフレーズ番号は使用性を考慮して1ベースとしてあります。\n");
	fprintf(stderr, "  両者の番号は一致していないので一つのアプリケーション内で両者を併用しないで下さい。\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;
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
ST_PhrSeq* PhrSeq_new(ST_Phr* pPhr) {
	ST_PhrSeq* pPhrSeq = calloc(1, sizeof(ST_PhrSeq));
	if(!pPhrSeq) { DIE(); }
	return PhrSeq_addPhr(pPhrSeq, pPhr);
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
ST_PhrSeq* PhrSeq_addPhr(ST_PhrSeq* pPhrSeq, ST_Phr* pPhr) {
	pPhrSeq = realloc(pPhrSeq, sizeof(ST_PhrSeq) + (sizeof pPhrSeq->aPhr[0]) * (pPhrSeq->nPhr + 1));
	if(!pPhrSeq) { DIE(); }
	//直前のフレーズが有れば…
	if(pPhrSeq->nPhr) {
		ST_Phr* pPhr2 = pPhrSeq->aPhr[pPhrSeq->nPhr - 1];
		//直前のフレーズの、次のフレーズが設定されていたら…
		if(pPhr2->pNext) {
			//次のフレーズ名が違っていたら…
			if(strcmp(pPhr2->pNext, pPhr->pPhrLab)) {
				yyerror("フレーズ名が同じで次のフレーズ名が違っています。(%s)", pPhr2->pPhrLab);
				yynerrs++;	//呼び出し元(main)に知らせるために。	//必要。YYERRORマクロ呼び出しでyynerrsは増やされないようだ。	←←←←←←←←←←■■■■重要■■■■
				return NULL;	//y.tab.yの処理でYYERRORを行う。
			}
		//直前のフレーズの、次のフレーズが設定されていなければ…
		} else {
			//直前のフレーズに次のフレーズ名を設定する。
			pPhr2->pNext		= pPhr->pPhrLab;
		}
	}
	pPhrSeq->aPhr[pPhrSeq->nPhr++]	= pPhr;
	return pPhrSeq;
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
ST_PhrSeq* PhrSeq_markResource(ST_PhrSeq* pPhrSeq, int bResource) {
	int iPhr;
	for(iPhr = 0; iPhr < pPhrSeq->nPhr; iPhr++) {
		ST_Phr* pPhr = pPhrSeq->aPhr[iPhr];
		pPhr->bResource		|= bResource;	//このフレーズシーケンスに属するフレーズのリソースフラグに、指定されたリソースフラグをマージする。
	}
	return pPhrSeq;
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
ST_Phr* Phr_new(char* pPhrLab/*片方のみNULL可*/, char* pPath/*片方のみNULL可*/, char* pNext/*NULL可*/) {
	ST_Phr* pPhr = calloc(1, sizeof(ST_Phr));
	if(!pPhr) { DIE(); }
	if(!pPhrLab && !pPath) { DIE(); }	//両方NULLで呼び出される事は無いはず。もしここで停止したらy.tab.yのバグである。
	//ラベルが省略されていたら、ファイル名からラベルを自動生成する。
	if(!pPhrLab) {
		char fname[MAX_FNAME];
		int i;
		splitpath(pPath, NULL, NULL, fname, NULL);
//{{2016/01/27変更:ファイル名からラベルを自動生成する処理を変更しました。
//		for(i = 0; iscsym(fname[i]); i++) { /** no job **/ }
//↓2016/01/27変更:ファイル名からラベルを自動生成する処理を変更しました。
		// * Wed Jan 27 22:56:05 JST 2016 Naoyuki Sawa
		// - ファイル名からラベルを自動生成する処理を変更しました。
		// - 本日、dTexDefC.exeのファイル名からラベルを自動生成する処理を変更して、dPhrDefC.exeも同じ動作に統一するのが良いと思ったからです。
		//   dTexDefC.exeの処理を変更したのは、必要が有ってそうしたのですが、dPhrDefC.exeも変更する事は現在の所必須では有りません。
		//   しかし、両ツールのファイル名からラベルを自動生成する処理を統一しておく方が判り易いと思ったので、統一する事にしました。
		// - 詳細は、/clip/tool/dTexDefC/winapp.h の同日のコメントを参照して下さい。
		//   尚、dTexDefC.exeでのdpbmpcnv.exe用のフォーマット指定('-'以降)を切り取る処理は、dPhrDefC.exeではdppcmcnv.exe用のフォーマット指定('-'以降)を切り取る処理となります。
		for(i = 0; fname[i]; i++) {
			if(fname[i] == '-') { break; }			//②'-'が出現したら、そこで打ち切る。							┬この順序は必須です。
			if(!iscsym(fname[i])) { fname[i] = '_'; }	//①'-'が出現するまでの間、Cシンボル以外の文字が出現したら、'_'に置き換えて継続する。	┘
		}
//}}2016/01/27変更:ファイル名からラベルを自動生成する処理を変更しました。
		if(!i) {
			yyerror("ファイル名からラベルを自動生成出来ません。(%s)", pPath);
			yynerrs++;	//呼び出し元(main)に知らせるために。	//必要。YYERRORマクロ呼び出しでyynerrsは増やされないようだ。	←←←←←←←←←←■■■■重要■■■■
			return NULL;	//y.tab.yの処理でYYERRORを行う。
		}
		pPhrLab = strndup(fname, i);
		if(!pPhrLab) { DIE(); }
		strupr(pPhrLab);
	}
	{
		//同じ名前のフレーズが既に登録されていたら…
		ST_Phr* pPhr2 = ght_get(ht_phr,
			strlen(pPhrLab) + 1/*nul*/,
			       pPhrLab);
		if(pPhr2) {
			//--- ファイル名をマージする ---
			if(pPath) {
				//今回のフレーズと、既存のフレーズの、両方ともファイル名が設定されていたら…
				if(pPhr2->pPath) {
					//ファイル名が違っていたら…
					if(strcmp(pPath, pPhr2->pPath)) {
						yyerror("フレーズ名が同じでファイル名が違っています。(%s)", pPhrLab);
						yynerrs++;	//呼び出し元(main)に知らせるために。	//必要。YYERRORマクロ呼び出しでyynerrsは増やされないようだ。	←←←←←←←←←←■■■■重要■■■■
						return NULL;	//y.tab.yの処理でYYERRORを行う。
					}
				//今回のフレーズにファイル名が設定されていて、既存のフレーズにファイル名が設定されていなければ…
				} else {
					//既存のフレーズにファイル名を設定する。
					pPhr2->pPath		= pPath;
				}
			} else {
				//今回のフレーズにファイル名が設定されていなくて、既存のフレーズにファイル名が設定されていたら…
				if(pPhr2->pPath) {
					/** no job **/
				//今回のフレーズと、既存のフレーズの、両方ともファイル名が設定されていなければ…
				} else {
					/** no job **/
				}
			}
			//--- 次のフレーズ名をマージする ---
			if(pNext) {
				//今回のフレーズと、既存のフレーズの、両方とも次のフレーズ名が設定されていたら…
				if(pPhr2->pNext) {
					//次のフレーズ名が違っていたら…
					if(strcmp(pNext, pPhr2->pNext)) {
						yyerror("フレーズ名が同じで次のフレーズ名が違っています。(%s)", pPhrLab);
						yynerrs++;	//呼び出し元(main)に知らせるために。	//必要。YYERRORマクロ呼び出しでyynerrsは増やされないようだ。	←←←←←←←←←←■■■■重要■■■■
						return NULL;	//y.tab.yの処理でYYERRORを行う。
					}
				//今回のフレーズに次のフレーズ名が設定されていて、既存のフレーズに次のフレーズ名が設定されていなければ…
				} else {
					//既存のフレーズに次のフレーズ名を設定する。
					pPhr2->pNext		= pNext;
				}
			} else {
				//今回のフレーズに次のフレーズ名が設定されていなくて、既存のフレーズに次のフレーズ名が設定されていたら…
				if(pPhr2->pNext) {
					/** no job **/
				//今回のフレーズと、既存のフレーズの、両方とも次のフレーズ名が設定されていなければ…
				} else {
					/** no job **/
				}
			}
			//既存のフレーズを返す。
			return pPhr2;
		}
		//今回のフレーズを登録する。
		if(ght_insert(ht_phr,
			       pPhr,
			strlen(pPhrLab) + 1/*nul*/,
			       pPhrLab)) { DIE(); }	//ここで停止する事は有りえないはず。
	    //{{進捗表示用
		if(!opt_q) {
			newMsgLen = fprintf(stderr, "\rpass1 %s", pPhrLab);
			if(oldMsgLen > newMsgLen) { fprintf(stderr, "%*s", oldMsgLen - newMsgLen, ""); } oldMsgLen = newMsgLen;
		}
	    //}}進捗表示用
	}
	pPhr->pPhrLab		= pPhrLab;	//この時点でpPhrLabはNULLではない。
	pPhr->pPath		= pPath;	//この時点でpPathはNULLの可能性有り。別途、同じ名前のフレーズ定義行でファイル名を設定する。addTex()を参照せよ。
	pPhr->pNext		= pNext;	//NULL(通常フレーズ,又は,未決定),又は,"$loop"(ループフレーズ),又は,"$complete"(完全フレーズ)。
//不要	pPhr->bResource		= 0;		//PhrSeq_markResource()で設定する。
	return pPhr;
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
ST_Phr* Phr_newPN(int PN, char* pNext/*NULL可*/) {
	ST_Phr* pPhr;
	char* pName;
	if((unsigned)PN > UINT16_MAX) {
		yyerror("PN指定が範囲外です。(%d)", PN);
		yynerrs++;	//呼び出し元(main)に知らせるために。	//必要。YYERRORマクロ呼び出しでyynerrsは増やされないようだ。	←←←←←←←←←←■■■■重要■■■■
		return NULL;	//y.tab.yの処理でYYERRORを行う。
	}
	//PNから仮のラベル,兼,仮のファイル名を自動生成する。
	pName = strdup_printf("$%d", PN);
	//ラベルとファイル名が指定された時と同様に、フレーズを作成する。
	pPhr = Phr_new(pName, pName, pNext);
	if(pPhr) {	//Phr_new()がエラーを検出してNULLを返した場合は以下のブロックの処理を行わない。
		//PN指定の場合は1パス目でフレーズ番号を格納する。
		// - Phr_new()の処理で既に登録されていたフレーズを返した場合はフレーズ番号は既に格納されているが、同じ値なので上書きして問題無い。
		if(pPhr->iPhrNo && (pPhr->iPhrNo != PN)) { DIE(); }	//ここで停止したら当ツールのバグ。新規作成でなければ既に格納されているフレーズ番号は今回のPNと同じ値のはずである。
		pPhr->iPhrNo		= PN;
	}
	return pPhr;
}
/*---------------------------------------------------------------------------*/
//2パス目の処理
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
static int Phr_compar(LIST_ENTRY* _x, LIST_ENTRY* _y) {
	//当関数の処理は、ght_hash_table.h/cモジュールの動作に依存しています。
	//外部からght_hash_table_t.le_iteratorの順番を並び替えても、動作上問題有りません。
	ST_Phr* x = CONTAINING_RECORD(_x, ght_hash_entry_t, le_iterator)->p_entry_data;
	ST_Phr* y = CONTAINING_RECORD(_y, ght_hash_entry_t, le_iterator)->p_entry_data;
	if(*x->pPhrLab == '$') {
		if(*y->pPhrLab == '$') {
			//両方ともPN指定ならば、PNで比較する。
			return x->iPhrNo - y->iPhrNo;	//PNは[0～65535]の範囲なので単純に減算して良い。オーバーフローの心配は無い。
		} else {
			//xがPN指定,yがラベル指定ならば、xを優先する。
			return -1;
		}
	} else {
		if(*y->pPhrLab == '$') {
			//xがラベル指定,yがPN指定ならば、yを優先する。
			return  1;
		} else {
			//両方ともラベル指定ならば、ラベルで比較する。
			return (*fnLabCmp)(x->pPhrLab, y->pPhrLab);
		}
	}
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
//フレーズシーケンスが無限ループになっていないかを検査する。
// - フレーズ名pRootから検索して、再度、pRootが出現しないかを確認する。
//   pRootは必ずしもフレーズシーケンスの先頭でなくても、アルゴリズム上は問題ない。
//   フレーズシーケンスの途中からの検査でも、正しく動作する。
//   全てのフレーズに対してこの検査を行うので、(無駄は生じるが)見落としが生じる事は無い。
static int DetectPhrSeqLoop(char* pRoot, char* pNext/*NULL可*/) {
	ST_Phr* pPhr;
	if(!pNext ||					//通常フレーズ,又は,
	   !strcmp(pNext, "$loop") ||			//ループフレーズ,又は,
	   !strcmp(pNext, "$complete")) {		//完全フレーズならば…
		return 0;				//無限ループになっていない。正常なフレーズシーケンスである。
	}
	if(!strcmp(pRoot, pNext)) { return 1; }		//無限ループになっている。不正なフレーズシーケンスである。
	pPhr = ght_get(ht_phr,
		strlen(pNext) + 1/*nul*/,
		       pNext);
	if(!pPhr) { DIE(); }	//当ツールのバグ
	return DetectPhrSeqLoop(pRoot, pPhr->pNext);	//次のフレーズに対して再帰する。
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
static void pass2(int bOptimize) {
	int iPhrNo = 0;		//PN指定のグループの中のフレーズ番号の最大値を求めるための変数。
	ght_iterator_t it;
	ST_Phr* pPhr;
	char* pPhrLab;
	//フレーズを昇順にソートする。		//■■■注意：ght_hash_table_tの実装依存の処理です。■■■
	// - PN指定を優先し、ラベル指定を後にする。つまり、PN指定のグループが全部出現した後に、ラベル指定のグループが全部出現する並びとなる。
	//   PN指定同士ならば、PNが小さい方を優先する。ラベル指定同士ならば、ラベルで比較する。
	SortList(&ht_phr->le_iterator, Phr_compar);
	//各フレーズについて…
	for(pPhr = ght_first(ht_phr, &it, &pPhrLab);
	    pPhr;
	    pPhr = ght_next( ht_phr, &it, &pPhrLab)) {
		//フレーズのファイル名が設定されている事を確認する。
		// - フレーズのファイル名を省略する形式でフレーズシーケンスを定義した場合は、別途、フレーズ定義行でフレーズのファイル名を指定しなければいけない。
		if(!pPhr->pPath) {
			yyerror("フレーズのファイル名が設定されていません。(%s)", pPhrLab);
			yynerrs++;	//呼び出し元(main)に知らせるために。
			//全てのセマンティクスエラーを報告するために継続する。
		}
		//フレーズシーケンスが無限ループになっていないかを検査する。
		// - フレーズシーケンスは、通常フレーズ,ループフレーズ,完全フレーズのいずれかで終端していなければいけない。
		//   <例>
		//    │PHR1 PHR2 PHR3		ok
		//    │PHR1 PHR2 PHR3 loop	ok
		//    │PHR1 PHR2 PHR3 complete	ok
		//    │PHR1 PHR2 PHR1		err!!
		//    │PHR1 PHR2 PHR2		err!!
		if(DetectPhrSeqLoop(pPhrLab, pPhr->pNext)) {
			yyerror("フレーズシーケンスの無限ループを検出しました。(%s)", pPhrLab);
			yynerrs++;	//呼び出し元(main)に知らせるために。
			//全てのセマンティクスエラーを報告するために継続する。
		}
		//PN指定ならば…
		if(*pPhrLab == '$') {
			//フレーズ番号の最大値を更新する。
			iPhrNo		= pPhr->iPhrNo;
		//ラベル指定ならば…
		} else {
			//フレーズ番号を格納する。
			// - 先行するPN指定のフレーズによってフレーズ番号の最大値が更新されていなければ、1ベースでの連番となる。
			//   先行するPN指定のフレーズによってフレーズ番号の最大値が更新されていた場合は、PN指定のフレーズ番号の最大値の次からの連番となる。
			pPhr->iPhrNo	= ++iPhrNo;
		}
	}
}
/*---------------------------------------------------------------------------*/
//出力処理
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
static void output(const char* sPhrOut, const char* sEnmOut, const char* sRegOut) {
	int nOut = 0;
	//フレーズ定義ヘッダのファイル名,又は,"-"(標準出力)が指定されていたら…
	if(sPhrOut) {
		FILE* fp = strcmp(sPhrOut, "-") ? fopen(sPhrOut, "w") : stdout;
		if(!fp) { error(EXIT_FAILURE, errno, "%s が作成出来ません。", sPhrOut); }	//{{2016/09/21変更:die(～)で終了していた箇所を、error(EXIT_FAILURE,～)で終了するように変更しました。}}	//{{2016/09/28変更:error()の文字列の最後に余計な改行が付いていたのを削除しました。}}
		output_phr(fp);
		if(fp != stdout) { fclose(fp); }
		nOut++;
	}
	//列挙定義スクリプトのファイル名,又は,"-"(標準出力)を指定されていたら…
	if(sEnmOut) {
		FILE* fp = strcmp(sEnmOut, "-") ? fopen(sEnmOut, "w") : stdout;
		if(!fp) { error(EXIT_FAILURE, errno, "%s が作成出来ません。", sEnmOut); }	//{{2016/09/21変更:die(～)で終了していた箇所を、error(EXIT_FAILURE,～)で終了するように変更しました。}}	//{{2016/09/28変更:error()の文字列の最後に余計な改行が付いていたのを削除しました。}}
		output_enm(fp);
		if(fp != stdout) { fclose(fp); }
		nOut++;
	}
	//レジストリスクリプトのファイル名,又は,"-"(標準出力)が指定されていたら…
	if(sRegOut) {
		FILE* fp = strcmp(sRegOut, "-") ? fopen(sRegOut, "w") : stdout;
		if(!fp) { error(EXIT_FAILURE, errno, "%s が作成出来ません。", sRegOut); }	//{{2016/09/21変更:die(～)で終了していた箇所を、error(EXIT_FAILURE,～)で終了するように変更しました。}}	//{{2016/09/28変更:error()の文字列の最後に余計な改行が付いていたのを削除しました。}}
		output_reg(fp);
		if(fp != stdout) { fclose(fp); }
		nOut++;
	}
	if(!nOut) {
		fprintf(stderr, "-p/-u/-rオプションが指定されなかったので、出力を行いませんでした。\n");
	}
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
//フレーズ定義ヘッダを出力する。
static void output_phr(FILE* fp) {
	ght_iterator_t it;
	ST_Phr* pPhr;
	char* pPhrLab;
	//各フレーズについて…
	for(pPhr = ght_first(ht_phr, &it, &pPhrLab);
	    pPhr;
	    pPhr = ght_next( ht_phr, &it, &pPhrLab)) {
		//PN指定ならば…
		if(*pPhrLab == '$') {
			yyerror("P/ECE環境ではPN指定を使用出来ません。(%s)", pPhrLab);
			yynerrs++;	//呼び出し元(main)に知らせるために。
			return;		//ここまで
		//ラベル指定ならば…
		} else {
			//リソースフラグがオンならば…
			if(pPhr->bResource) {
				//サービスとして、パス名をP/ECEリソースに適したファイル名に変換して出力する。
				char fname[MAX_FNAME];
				splitpath(pPhr->pPath, NULL, NULL, fname, NULL);
				fprintf(fp, "SOUND_RESOURCE_(%s,\"%s.ppd\")\n",
					pPhrLab,
					fname);
			//リソースフラグがオフならば…
			} else {
				fprintf(fp, "SOUND_(%s)\n",
					pPhrLab);
			}
		}
	}
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
//列挙定義スクリプトを出力する。
static void output_enm(FILE* fp) {
	int iPhrNo = -1;	//フレーズが空,又は,全てPN指定の場合は列挙定義スクリプトが空なので「PhrNo～end」も出力してはいけない。列挙定義コンパイラがエラーになるから。これはそれを判定して最初の行の直前に「PhrNo～」を表示するための変数です。
	ght_iterator_t it;
	ST_Phr* pPhr;
	char* pPhrLab;
	//各フレーズについて…
	for(pPhr = ght_first(ht_phr, &it, &pPhrLab);
	    pPhr;
	    pPhr = ght_next( ht_phr, &it, &pPhrLab)) {
		//PN指定ならば…
		if(*pPhrLab == '$') {
			//PN指定のフレーズに対しては列挙定義を行わない。
			//(フレーズ番号==PN番号)なので、アプリケーションはPN番号をそのまま使用せよ。
			//PN番号は個々のアプリケーション依存の値なので、当ツールは関知しない。
		//ラベル指定ならば…
		} else {
			if(iPhrNo == -1) {	//一行目の列挙定義の行を出力する時に…
				fprintf(fp, "PhrNo\n");
			}
			if(isdigit(*pPhrLab)) {
				fprintf(fp, "\t`%s", pPhrLab);		//フレーズ定義スクリプトで「`1234」のようなラベルを指定した場合は、列挙定義スクリプトにも「`」を付けて出力しないと、列挙定義コンパイラがエラーになる。
			} else {
				fprintf(fp, "\t%s", pPhrLab);
			}
			if(++iPhrNo != pPhr->iPhrNo) {			//┐
				fprintf(fp, "\t%d", pPhr->iPhrNo);	//├現在の所ラベル定義のフレーズ番号は連番だが、もし今後飛び番を許可した場合も適切な列挙定義スクリプトを生成出来るように、このように処理する事にした。
				iPhrNo = pPhr->iPhrNo;			//┘
			}
			fputc('\n', fp);
		}
	}
	if(iPhrNo != -1) {	//列挙定義の行を一行以上出力していたならば…
		fprintf(fp, "end\n");
	}
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
//レジストリスクリプトを出力する。
static void output_reg(FILE* fp) {
	//フレーズ
	if(ght_size(ht_phr)) {
		ght_iterator_t it;
		ST_Phr* pPhr;
		char* pPhrLab;
		fprintf(fp, "%s\t\t\t\t#//\t%s\n", "PhrDef", "PhrDef");
		//各フレーズについて…
		for(pPhr = ght_first(ht_phr, &it, &pPhrLab);
		    pPhr;
		    pPhr = ght_next( ht_phr, &it, &pPhrLab)) {
			//ラベル指定ならば、次のフレーズ名が設定されているかどうかに関係無く、このフレーズに対するキーを生成する。
			//PN指定ならば、次のフレーズ名が設定されている場合のみ、このフレーズに対するキーを生成する。
			if((*pPhrLab != '$') || pPhr->pNext) {
				fprintf(fp, "\t%d\t\t\t#//\t\t%s\n", pPhr->iPhrNo, pPhrLab);
				//ラベル指定ならば、ファイル名を出力する。
				//PN指定ならば、ファイル名を出力しない。
				if(*pPhrLab != '$') {
					fprintf(fp, "\t\t%s\t= \"%s\"\t#//\t\t\t%s\t= \"%s\"\n", "Path", pPhr->pPath, "Path", pPhr->pPath);
				}
				//次のフレーズ名が設定されていたら…
				if(pPhr->pNext) {
					//■NOTE:アプリケーションは戻り値に関係無く(((int)(x<<8))>>8)のように符号拡張してTAPシーケンサに渡せば良い。■
					//完全フレーズならば…
					if(!strcmp(pPhr->pNext, "$complete")) {
						fprintf(fp, "\t\t%s\t= %s\t#//\t\t\t%s\t= %s\n", "Next", "(1<<24)-2", "Next", "complete");
					//ループフレーズならば…
					} else if(!strcmp(pPhr->pNext, "$loop")) {
						fprintf(fp, "\t\t%s\t= %d\t#//\t\t\t%s\t= %s\n", "Next", pPhr->iPhrNo, "Next", "loop");
					//中間フレーズならば…
					} else {
						ST_Phr* pPhr2 = ght_get(ht_phr,
							strlen(pPhr->pNext) + 1/*nul*/,
							       pPhr->pNext);
						if(!pPhr2) { DIE(); }	//当ツールのバグ
						fprintf(fp, "\t\t%s\t= %d\t#//\t\t\t%s\t= %s\n", "Next", pPhr2->iPhrNo, "Next", pPhr->pNext);
					}
				}
				fprintf(fp, "\t%s\t\t\t#//\t\t%s\n", "end", "end");
			}
		}
		fprintf(fp, "%s\t\t\t\t#//\t%s\n", "end", "end");
	}
}
