/*****************************************************************************
 * 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_ncs;	//NCS補間名⇒(ST_Ncs*)
//ラベル名のソート方法
int (*fnLabCmp)(const void*, const void*) = strcmp;	//'n'オプションが指定されたらstrnatcmpに変更する。
//{{進捗表示用
static int opt_q;
static int newMsgLen, oldMsgLen = 0;
//}}進捗表示用
static int nTotalHalfSize;	//全NCS補間データハーフワードサイズの合計
/*---------------------------------------------------------------------------*/
//エラーログ用
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 optimize();
static void output(char* sBinOut);
/*---------------------------------------------------------------------------*/
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* sBinOut = NULL;	//'-o'オプションが指定されたらバイナリ出力ファイル名を設定する。
#if     (YYDEBUG!=0)
//	yydebug = 1; //nonzero means print parse trace
#endif/*(YYDEBUG!=0)*/
	//コマンドラインオプションを処理する。
	opterr = 0; //オプションエラーは当プログラム内で処理するので、getopt()内でのエラー表示は不要。
	while((opt = getopt(argc, argv, "do:enw::qh")) != -1) {
		switch(opt) {
		case 'd':
			bOptimize = 0;		//最適化を行わない。
			break;
		case 'o':
			sBinOut = 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_ncs = 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(sBinOut);
	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, "dNcsSeqC - ステートレスNCS補間シーケンサコンパイラ (%s)\n", VERSION);
	fprintf(stderr, "Copyright (C) 2016 Naoyuki Sawa\n");
	fprintf(stderr, "\n");
	fprintf(stderr, "USAGE:\n");
	fprintf(stderr, "  dNcsSeqC [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, "  -oファイル名 NCS補間のバイナリデータを、バイナリ形式でファイルへ出力します。\n");
	fprintf(stderr, "               このオプションを指定しなければ、Cソース形式で標準出力へ出力します。\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, "  ①dNcsSeqC.exe NcsSeq.txt > NcsSeq.c\n");
	fprintf(stderr, "  　NcsSeq.txtをコンパイルして、NcsSeq.cへ出力します。\n");
	fprintf(stderr, "  ②dNcsSeqC.exe -oNcsSeq.bin NcsSeq.txt > NcsSeq.h\n");
	fprintf(stderr, "  　NcsSeq.txtをコンパイルして、ラベルをNcsSeq.hへ出力します。\n");
	fprintf(stderr, "  　NCS補間のバイナリデータを、NcsSeq.binへ出力します。\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 addNcs(ST_Ncs* pNcs) {
    //{{進捗表示用
	if(!opt_q) {
		newMsgLen = fprintf(stderr, "\rpass1 %s", pNcs->pNcsLab);
		if(oldMsgLen > newMsgLen) { fprintf(stderr, "%*s", oldMsgLen - newMsgLen, ""); } oldMsgLen = newMsgLen;
	}
    //}}進捗表示用
	//ラベルが重複していない事を確認し、NCS補間を登録する。
	if(ght_insert(ht_ncs,
		       pNcs,
		strlen(pNcs->pNcsLab) + 1/*nul*/,
		       pNcs->pNcsLab)) {
		yyerror("ラベルが重複しています。(%s)", pNcs->pNcsLab);
		yynerrs++;	//呼び出し元(main)に知らせるために。
		return;		//全てのセマンティクスエラーを報告するために継続する。
	}
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
ST_RelExpr RelExpr_New(int iVal, int bRel) {
	ST_RelExpr relExpr;
	relExpr.iVal		= iVal;
	relExpr.bRel		= bRel;
	return relExpr;
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
ST_ExprList* ExprList_New(ST_RelExpr relExpr) {
	ST_ExprList* pExprList = calloc(1, sizeof(ST_ExprList));
	if(!pExprList) { DIE(); }
	return ExprList_Add(pExprList, relExpr);
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
ST_ExprList* ExprList_Add(ST_ExprList* pExprList, ST_RelExpr relExpr) {
	pExprList = realloc(pExprList,
		sizeof(ST_ExprList) +
		(sizeof pExprList->aExpr[0]) * (pExprList->nExpr + 1));
	if(!pExprList) { DIE(); }
	pExprList->aExpr[pExprList->nExpr++]	= relExpr;
	return pExprList;
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
ST_Ncs* Ncs_New(char* pNcsLab, ST_NodeRows* pNodeRows) {
	ST_Ncs* pNcs = calloc(1, sizeof(ST_Ncs));
	if(!pNcs) { DIE(); }
	{
		int iNodeRow, nNodeRow = pNodeRows->nNodeRow;
		int iExpr, nExpr = pNodeRows->aNodeRow[0]->pExprList->nExpr;			//万一(nNodeRow<=0)だった場合はこの時点で存在しない要素にアクセスしてしまっているが、いずれにせよバグなので考慮しなくて良いだろう。
		if((nNodeRow <= 0) || (nExpr <= 0)) { DIE(); }					//空のノードリストや空の数値リストが作成されない事は、y.tab.yのルールで保障されているはず。もしここで停止したら当ツールのバグです。
		//--- 相対値⇒絶対値 変換処理 ---
		for(iNodeRow = 0; iNodeRow < nNodeRow; iNodeRow++) {
			ST_NodeRow* pNodeRow = pNodeRows->aNodeRow[iNodeRow];
			if(pNodeRow->pExprList->nExpr != nExpr) { DIE(); }			//軸の数が異なる場合は既にNodeRows_Add()で検出しているはず。もしここで停止したら当ツールのバグです。
			if(iNodeRow) {
				ST_NodeRow* pPrevRow = pNodeRows->aNodeRow[iNodeRow - 1];
				if(pPrevRow->pExprList->nExpr != nExpr) { DIE(); }		//軸の数が異なる場合は既にNodeRows_Add()で検出しているはず。もしここで停止したら当ツールのバグです。
				if(pPrevRow->t.bRel) { DIE(); }					//前のノードは既に絶対値に変換されているはず。もしここで停止したら当ツールのバグです。
				if(pNodeRow->t.bRel) {
					pNodeRow->t.iVal += pPrevRow->t.iVal;			//┬相対値⇒絶対値 変換処理
					pNodeRow->t.bRel = 0;					//┘
				}
				for(iExpr = 0; iExpr < nExpr; iExpr++) {
					if(pPrevRow->pExprList->aExpr[iExpr].bRel) { DIE(); }	//前のノードは既に絶対値に変換されているはず。もしここで停止したら当ツールのバグです。
					if(pNodeRow->pExprList->aExpr[iExpr].bRel) {
						pNodeRow->pExprList->aExpr[iExpr].iVal += pPrevRow->pExprList->aExpr[iExpr].iVal;	//┬相対値⇒絶対値 変換処理
						pNodeRow->pExprList->aExpr[iExpr].bRel = 0;						//┘
					}
				}
			} else {
				pNodeRow->t.bRel = 0;						//┐
				for(iExpr = 0; iExpr < nExpr; iExpr++) {			//├先頭要素の前のノードの値は(擬似的に)0と見なすので、先頭要素は相対値でも絶対値でも同じです。
					pNodeRow->pExprList->aExpr[iExpr].bRel = 0;		//│つまり、先頭要素が相対値であっても、そのままの値で絶対値とします。
				}								//┘
			}
		}
		//--- エラー検出処理 ---
		for(iNodeRow = 0; iNodeRow < nNodeRow; iNodeRow++) {
			ST_NodeRow* pNodeRow = pNodeRows->aNodeRow[iNodeRow];
			if(pNodeRow->pExprList->nExpr != nExpr) { DIE(); }			//軸の数が異なる場合は既にNodeRows_Add()で検出しているはず。もしここで停止したら当ツールのバグです。
			if(pNodeRow->t.bRel) { DIE(); }						//相対値⇒絶対値 変換処理にて、既に絶対値に変換されているはず。もしここで停止したら当ツールのバグです。
			if(iNodeRow) {
				ST_NodeRow* pPrevRow = pNodeRows->aNodeRow[iNodeRow - 1];
				if(pPrevRow->pExprList->nExpr != nExpr) { DIE(); }		//軸の数が異なる場合は既にNodeRows_Add()で検出しているはず。もしここで停止したら当ツールのバグです。
				if(pPrevRow->t.bRel) { DIE(); }					//相対値⇒絶対値 変換処理にて、既に絶対値に変換されているはず。もしここで停止したら当ツールのバグです。
				if(pPrevRow->t.iVal >= pNodeRow->t.iVal) {
					yyerror("時刻が昇順になっていません。(%d,%d)", pPrevRow->t.iVal, pNodeRow->t.iVal);
					yynerrs++;	//呼び出し元(main)に知らせるために	//必要。YYERRORマクロ呼び出しでyynerrsは増やされないようだ。	←←←←←←←←←←■■■■重要■■■■
					return NULL;	//y.tab.yの処理でYYERRORを行う。
				}
			}
			if((unsigned)pNodeRow->t.iVal > UINT16_MAX) {
				yyerror("時刻が範囲外です。(%d)", pNodeRow->t.iVal);
				yynerrs++;	//呼び出し元(main)に知らせるために		//必要。YYERRORマクロ呼び出しでyynerrsは増やされないようだ。	←←←←←←←←←←■■■■重要■■■■
				return NULL;	//y.tab.yの処理でYYERRORを行う。
			}
			for(iExpr = 0; iExpr < nExpr; iExpr++) {
				if(pNodeRow->pExprList->aExpr[iExpr].bRel) { DIE(); }		//相対値⇒絶対値 変換処理にて、既に絶対値に変換されているはず。もしここで停止したら当ツールのバグです。
				if((pNodeRow->pExprList->aExpr[iExpr].iVal < INT16_MIN) ||
				   (pNodeRow->pExprList->aExpr[iExpr].iVal > INT16_MAX)) {
					yyerror("座標が範囲外です。(%d)", pNodeRow->pExprList->aExpr[iExpr].iVal);
					yynerrs++;	//呼び出し元(main)に知らせるために。	//必要。YYERRORマクロ呼び出しでyynerrsは増やされないようだ。	←←←←←←←←←←■■■■重要■■■■
					return NULL;	//y.tab.yの処理でYYERRORを行う。
				}
			}
		}
	}
	pNcs->pNcsLab		= pNcsLab;
	pNcs->pNodeRows		= pNodeRows;
	return pNcs;
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
ST_NodeRows* NodeRows_Add(ST_NodeRows* pNodeRows/*NULL可*/, ST_NodeRow* pNodeRow) {
	if(pNodeRows) {
		ST_NodeRow* pPrevRow = pNodeRows->aNodeRow[pNodeRows->nNodeRow - 1];
		if(pPrevRow->pExprList->nExpr != pNodeRow->pExprList->nExpr) {
			yyerror("軸の数が一致していません。(%d,%d)", pPrevRow->pExprList->nExpr, pNodeRow->pExprList->nExpr);
			yynerrs++;	//呼び出し元(main)に知らせるために	//必要。YYERRORマクロ呼び出しでyynerrsは増やされないようだ。	←←←←←←←←←←■■■■重要■■■■
			return NULL;	//y.tab.yの処理でYYERRORを行う。
		}
	} else {
		pNodeRows = calloc(1, sizeof(ST_NodeRows));
		if(!pNodeRows) { DIE(); }
	}
	if((unsigned)(pNodeRows->nNodeRow + 1) > (UINT8_MAX + 2)) {		//clipncss.cのNcsSeq_New()を参照せよ。nSeg=(nNode-1),ST_NcsSeq.nSeg_1=(nSeg-1)なので、nNodeの上限は(UINT8_MAX+2)である。
		yyerror("ノード数が多過ぎます。(%d)", (pNodeRows->nNodeRow + 1));
		yynerrs++;	//呼び出し元(main)に知らせるために		//必要。YYERRORマクロ呼び出しでyynerrsは増やされないようだ。	←←←←←←←←←←■■■■重要■■■■
		return NULL;	//y.tab.yの処理でYYERRORを行う。
	}
	pNodeRows = realloc(pNodeRows,
		sizeof(ST_NodeRows) +
		(sizeof pNodeRows->aNodeRow[0]) * (pNodeRows->nNodeRow + 1));
	if(!pNodeRows) { DIE(); }
	pNodeRows->aNodeRow[pNodeRows->nNodeRow++]	= pNodeRow;
	return pNodeRows;
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
ST_NodeRow* NodeRow_New(ST_RelExpr t, ST_ExprList* pExprList) {
	ST_NodeRow* pNodeRow = calloc(1, sizeof(ST_NodeRow));
	if(!pNodeRow) { DIE(); }
	pNodeRow->t		= t;
	pNodeRow->pExprList	= pExprList;
	return pNodeRow;
}
/*---------------------------------------------------------------------------*/
//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 int fnGetTim(int iNode, void* arg) {
	ST_Ncs* pNcs = arg;
	ST_NodeRows* pNodeRows = pNcs->pNodeRows;
	ST_NodeRow* pNodeRow;
	if((unsigned)iNode >= (unsigned)pNodeRows->nNodeRow) { DIE(); }		//NcsSeq_New()に指定したノード数の範囲外のノードが要求される事は無いはず。もしここで停止したらclipncss.cモジュールのバグの可能性が高い。
	pNodeRow = pNodeRows->aNodeRow[iNode];
	if(pNodeRow->t.bRel) { DIE(); }						//既に絶対値に変換されているはず。もしここで停止したら当ツールのバグです。
	return pNodeRow->t.iVal;
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
static int fnGetPos(int iNode, int iAxs, void* arg) {
	ST_Ncs* pNcs = arg;
	ST_NodeRows* pNodeRows = pNcs->pNodeRows;
	ST_NodeRow* pNodeRow;
	if((unsigned)iNode >= (unsigned)pNodeRows->nNodeRow) { DIE(); }		//NcsSeq_New()に指定したノード数の範囲外のノードが要求される事は無いはず。もしここで停止したらclipncss.cモジュールのバグの可能性が高い。
	pNodeRow = pNodeRows->aNodeRow[iNode];
	if((unsigned)iAxs >= (unsigned)pNodeRow->pExprList->nExpr) { DIE(); }	//NcsSeq_New()に指定した軸の数の範囲外の軸が要求される事は無いはず。もしここで停止したらclipncss.cモジュールのバグの可能性が高い。
	if(pNodeRow->pExprList->aExpr[iAxs].bRel) { DIE(); }			//既に絶対値に変換されているはず。もしここで停止したら当ツールのバグです。
	return pNodeRow->pExprList->aExpr[iAxs].iVal;
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
static void pass2(int bOptimize) {
	ght_iterator_t it;
	ST_Ncs* pNcs;
	char* pNcsLab;
	//--- 最適化処理 ---
	{
		//最適化処理を行う。
		if(bOptimize) { optimize(); }
	}
	//NCS補間を名前の昇順にソートする。					//■■■注意：ght_hash_table_tの実装依存の処理です。■■■
	SortList(&ht_ncs->le_iterator, Label_compar);
	//--- 2パス目で格納するフィールドを格納する ---
	{
		int iNcsNo = 0;	//NCS補間番号(1ベース)	ここでの初期値は0だが、プレインクリメントするので、1ベースになります。
		//各NCS補間について…
		for(pNcs = ght_first(ht_ncs, &it, &pNcsLab);
		    pNcs;
		    pNcs = ght_next( ht_ncs, &it, &pNcsLab)) {
			//NCS補間番号(1ベース)を格納する。
			pNcs->iNcsNo		= ++iNcsNo;	//1ベースとするために、プレインクリメントする。
			if((unsigned)iNcsNo > UINT16_MAX) {
				yyerror("NCS補間数が多過ぎます。(%d)", iNcsNo);
				DIE();	//継続しても無駄なので終了する。
			}
		}
	}
	//--- NCS補間データを作成する ---
	{
		//各NCS補間について…
		for(pNcs = ght_first(ht_ncs, &it, &pNcsLab);
		    pNcs;
		    pNcs = ght_next( ht_ncs, &it, &pNcsLab)) {
		    //{{進捗表示用
			if(!opt_q) {
				newMsgLen = fprintf(stderr, "\rpass2 %s", pNcsLab);
				if(oldMsgLen > newMsgLen) { fprintf(stderr, "%*s", oldMsgLen - newMsgLen, ""); } oldMsgLen = newMsgLen;
			}
		    //}}進捗表示用
			//NCS補間データを作成する。
			{
				ST_NodeRows* pNodeRows = pNcs->pNodeRows;
				int nNodeRows = pNodeRows->nNodeRow;
				ST_NodeRow* pNodeRow = pNodeRows->aNodeRow[0];
				int nExpr = pNodeRow->pExprList->nExpr;
				//NCS補間データを作成する。
				ST_NcsSeq* pNcsSeq = NcsSeq_New(fnGetTim, fnGetPos, nNodeRows, nExpr, pNcs);
				int nSeg = pNcsSeq->nSeg_1 + 1;
				int nAxs = pNcsSeq->nAxs_1 + 1;
				//ST_NcsSeg構造体のバイト数を求める。
				int SegBytes = sizeof(ST_NcsSeg) -
				               sizeof(ST_NcsAxs)/*ST_NcsSeg.TBL_Axs[0]の分*/ +
				              (sizeof(ST_NcsAxs) * nAxs);
				//NCS補間データのバイト数を求める。
				int NcsBytes = sizeof(ST_NcsSeq) -
				               sizeof(ST_NcsSeg)/*ST_NcsSeq.TBL_Seg[0]の分*/ +
				               (SegBytes * nSeg);
				//NCS補間データをコピーする。
				obstack_init(&pNcs->o);
				obstack_grow(&pNcs->o, pNcsSeq, NcsBytes);
				//NCS補間データを開放する。
				NcsSeq_Free(pNcsSeq);
			}
		}
	}
	//--- アドレスを設定する ---
	{
		int iHalfAddr = 0;	//ハーフワードアドレス
		//各NCS補間について…
		for(pNcs = ght_first(ht_ncs, &it, &pNcsLab);
		    pNcs;
		    pNcs = ght_next( ht_ncs, &it, &pNcsLab)) {
			int nByteSize, nHalfSize;
			//NCS補間データのバイトサイズを求める。
			nByteSize = obstack_object_size(&pNcs->o);
			if(nByteSize % sizeof(unsigned short)) { DIE(); }	//NCS補間データは(unsigned short)の倍数サイズであるはず。
			//NCS補間データのハーフワードサイズを求める。
			nHalfSize = nByteSize / sizeof(unsigned short);
			//NCS補間データのハーフワードアドレスを格納する。
			pNcs->iHalfAddr = iHalfAddr;
			//ハーフワードアドレスを進める。
			iHalfAddr += nHalfSize;
		}
		//全NCS補間データハーフワードサイズの合計を格納する。
		nTotalHalfSize = iHalfAddr;
	}
}
/*---------------------------------------------------------------------------*/
//最適化処理
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
static void optimize() {
	/** no job **/
}
/*---------------------------------------------------------------------------*/
//出力処理
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
static void output(char* sBinOut) {
	ght_iterator_t it;
	ST_Ncs* pNcs;
	char* pNcsLab;
	int* TBL_HalfAddr/*[nNcsSeq+1]*/;	//アドレステーブル
	//NCS補間の数を取得しておく。
	const int nNcsSeq = ght_size(ht_ncs);
	//--- ラベルを標準出力に出力する ---
	for(pNcs = ght_first(ht_ncs, &it, &pNcsLab);
	    pNcs;
	    pNcs = ght_next( ht_ncs, &it, &pNcsLab)) {
		printf("#define NcsSeqNo_%s %d\n", pNcsLab, pNcs->iNcsNo);		//ラベルは1ベース
	}
	printf("#define NcsSeqNo_Max %d\n", nNcsSeq);					//ラベルの最大値	ラベルは1ベースなので、(ラベルの最大値==NCS補間の数) となる。
	//--- アドレステーブルを作成する ---
	{
		struct obstack o;
		obstack_init(&o);					//アドレステーブルを作成するための、バッファを初期化する。
		obstack_int_grow(&o, nNcsSeq + 1/*[0]自身の分*/);	//アドレステーブルの[0]に要素数を格納する。([0]自身も含む)
		for(pNcs = ght_first(ht_ncs, &it, &pNcsLab);
		    pNcs;
		    pNcs = ght_next( ht_ncs, &it, &pNcsLab)) {
			obstack_int_grow(&o, pNcs->iHalfAddr);		//[1]～[nNcsSeq]
		}
		TBL_HalfAddr = obstack_finish(&o);
		//この下でTBL_HalfAddrを参照するので、'obstack_free(&o,NULL)'を行ってはいけない。
	}
	//--- データを出力する ---
	//バイナリ形式の場合…
	if(sBinOut) {
		FILE* fp = fopen(sBinOut, "wb");
		if(!fp) { error(EXIT_FAILURE, errno, "%s が作成できません。", sBinOut); }	//{{2016/09/21変更:die(～)で終了していた箇所を、error(EXIT_FAILURE,～)で終了するように変更しました。}}	//{{2016/09/28変更:error()の文字列の最後に余計な改行が付いていたのを削除しました。}}
		//アドレステーブルを出力する。
		fwrite(TBL_HalfAddr, sizeof(int), nNcsSeq + 1/*[0]自身の分*/, fp);
		//NCS補間データを出力する。
		for(pNcs = ght_first(ht_ncs, &it, &pNcsLab);
		    pNcs;
		    pNcs = ght_next( ht_ncs, &it, &pNcsLab)) {
			unsigned short* pNcsDat = obstack_base(&pNcs->o);
			int nByteSize, nHalfSize;
			nByteSize = obstack_object_size(&pNcs->o);
			if(nByteSize % sizeof(unsigned short)) { DIE(); }
			nHalfSize = nByteSize / sizeof(unsigned short);
			fwrite(pNcsDat, sizeof(unsigned short), nHalfSize, fp);
		}
		fclose(fp);
	//テキスト形式の場合…
	} else {
		int i;
		//アドレステーブルを出力する。
		printf("const unsigned short TBL_NcsSeqNo[%d]={", nNcsSeq + 1/*[0]自身の分*/);
		for(i = 0; i < nNcsSeq + 1/*[0]自身の分*/; i++) {
			if(  i      ) { putchar(',' ); }
			if(!(i & 15)) { putchar('\n'); }
			printf("0x%08X", TBL_HalfAddr[i]);
		}
		puts("};");
		//NCS補間データを出力する。
		i = 0;
		printf("const unsigned short TBL_NcsSeq[%d]={", nTotalHalfSize);
		for(pNcs = ght_first(ht_ncs, &it, &pNcsLab);
		    pNcs;
		    pNcs = ght_next( ht_ncs, &it, &pNcsLab)) {
			unsigned short* pNcsDat = obstack_base(&pNcs->o);
			int nByteSize, nHalfSize, iNcsDat;
			nByteSize = obstack_object_size(&pNcs->o);
			if(nByteSize % sizeof(unsigned short)) { DIE(); }
			nHalfSize = nByteSize / sizeof(unsigned short);
			for(iNcsDat = 0; iNcsDat < nHalfSize; iNcsDat++) {
				if(  i      ) { putchar(',' ); }
				if(!(i & 31)) { putchar('\n'); }
				printf("0x%04X", pNcsDat[iNcsDat]);
				i++;
			}
		}
		puts("};");
	}
}
