/*****************************************************************************
 * 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 int nLedCh;				//LEDチャネル数。DataRows_add()が初回に呼ばれた時に決定する。
static ST_SeqTbl* TBL_SeqTbl[UINT16_MAX];	//SeqTbl配列
static int nSeqTbl;				//SeqTbl配列の格納数
static ST_DatTbl* TBL_DatTbl[UINT16_MAX];	//DatTbl配列
static int nDatTbl;				//DatTbl配列の格納数
static int endAddr;				//出力データバイト数(=最終バイトのアドレス+1)
static char* TBL_Label[UINT16_MAX];		//ラベル配列		//SeqTblのグローバルラベルを登録する。
static int nLabel;				//ラベル配列の格納数
static int nAtmarkLocalLabel;			//'@'ローカルラベルの出現数		//'def @@'が出現したら、'@%d'という形式の内部ローカルラベルに変換して、ハッシュテーブルに登録する。その後、+1する。要するに、次に出現する'@'ローカルラベルの番号を表していると見なせるので、'goto @F'や'call @F'からは、nAtmarkLocalLabelで参照できる。'goto @B'や'call @B'からは、(nAtmarkLocalLabel-1)で参照できる。
static char* sDotLocalLabelPrefix = "";		//'.'ローカルラベルのプレフィクス	//'def .～'が出現したら、'%s.～'という形式の内部ローカルラベルに変換して、ハッシュテーブルに登録する。グローバルラベルの定義が出現したら、sDotLocalLabelPrefixを更新する。結果として、グローバルラベル定義～グローバルラベル定義の間でのローカルラベルとなる。
//ラベル名のソート方法
int (*fnLabCmp)(const void*, const void*) = strcmp;	//'n'オプションが指定されたらstrnatcmpに変更する。
//{{2015/11/14追加:"-s"オプションを追加しました。
//エンコード関数
int (*fnEncode)(const void* _src, int srcLen, void* _dst, int dstCap);	//BytePairEncodingEncoder_EncodeST,又は,BytePairEncodingEncoder_EncodeMT
//}}2015/11/14追加:"-s"オプションを追加しました。
//{{進捗表示用
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 ST_SeqTbl* IsSeqTbl(void* seqTbl);	//引数がST_SeqTblならば引数をそのまま返す。引数がST_SeqTblでなければNULLを返す。
static ST_DatTbl* IsDatTbl(void* datTbl);	//引数がST_DatTblならば引数をそのまま返す。引数がST_DatTblでなければNULLを返す。
static void pass2(int bOptimize);
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:ensw::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;
	    //{{2015/11/14追加:"-s"オプションを追加しました。
		case 's':
			fnEncode = BytePairEncodingEncoder_EncodeST;	//エンコード時に、強制的にシングルスレッド版を使用する。
			break;
	    //}}2015/11/14追加:"-s"オプションを追加しました。
		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;
			BytePairEncodingEncoder_PrintProgress = 0;	//BytePairEncodingEncoder_EncodeST(),及び,BytePairEncodingEncoder_EncodeMT()の進捗表示を抑制する。詳細はclipbpe.cの'Fri Sep 16 21:30:10 JST 2016'のコメントを参照せよ。
			break;
		case 'h':
		default:
			usage();
		}
	}
    //{{2015/11/14追加:"-s"オプションを追加しました。
	//'-s'オプションが指定されていなければ、コア数によって自動的に選択する。
	if(!fnEncode) {
		SYSTEM_INFO si;
		GetSystemInfo(&si);
		fnEncode = (si.dwNumberOfProcessors < 2) ? BytePairEncodingEncoder_EncodeST	//コア数が2未満ならば、シングルスレッド版を使用する。
							 : BytePairEncodingEncoder_EncodeMT;	//コア数が2以上ならば、マルチスレッド版を使用する。
	}
    //}}2015/11/14追加:"-s"オプションを追加しました。
	//ラベルを登録するためのハッシュテーブルを作成する。
	hcreate(UINT16_MAX);
	//--- 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(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, "dLedSeqC - LEDシーケンサコンパイラ (%s)\n", VERSION);
	fprintf(stderr, "Copyright (C) 2015-2017 Naoyuki Sawa\n");
	fprintf(stderr, "\n");
	fprintf(stderr, "USAGE:\n");
	fprintf(stderr, "  dLedSeqC [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ファイル名 LED制御のバイナリデータを、バイナリ形式でファイルへ出力します。\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, "  -s           エンコード時に、強制的にシングルスレッド版を使用します。\n");
	fprintf(stderr, "               このオプションを指定しなければ、コア数によって自動的に選択します。\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, "  ①dLedSeqC.exe LedDef.txt > LedDef.c\n");
	fprintf(stderr, "  　LedDef.txtをコンパイルして、LedDef.cへ出力します。\n");
	fprintf(stderr, "  ②dLedSeqC.exe -oLedDef.bin LedDef.txt > LedDef.h\n");
	fprintf(stderr, "  　LedDef.txtをコンパイルして、ラベルをLedDef.hへ出力します。\n");
	fprintf(stderr, "  　LED制御のバイナリデータを、LedDef.binへ出力します。\n");
	exit(EXIT_FAILURE);
}
/*---------------------------------------------------------------------------*/
//引数がST_SeqTblならば引数をそのまま返す。引数がST_SeqTblでなければNULLを返す。
static ST_SeqTbl* IsSeqTbl(void* seqTbl) {
	int i;
	for(i = 0; i < nSeqTbl; i++) {
		if(TBL_SeqTbl[i] == seqTbl) { return seqTbl; }
	}
	return NULL;
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
//引数がST_DatTblならば引数をそのまま返す。引数がST_DatTblでなければNULLを返す。
static ST_DatTbl* IsDatTbl(void* datTbl) {
	int i;
	for(i = 0; i < nDatTbl; i++) {
		if(TBL_DatTbl[i] == datTbl) { return datTbl; }
	}
	return NULL;
}
/*---------------------------------------------------------------------------*/
//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 add_seqTbl(ST_SeqTbl* seqTbl) {
	char* lab = seqTbl->labelRow->lab;
	ENTRY item, *pEntry;
    //{{進捗表示用
	if(!opt_q) {
		newMsgLen = fprintf(stderr, "\rpass1 %s", lab);
		if(oldMsgLen > newMsgLen) { fprintf(stderr, "%*s", oldMsgLen - newMsgLen, ""); } oldMsgLen = newMsgLen;
	}
    //}}進捗表示用
	//--- SeqTbl登録処理 ---
	//SeqTblを登録する。
	if(nSeqTbl >= UINT16_MAX) {
		yyerror("SeqTbl数が制限を超えました。(%s)", lab);
		DIE();	//継続しても無駄なので終了する。
	}
	TBL_SeqTbl[nSeqTbl++] = seqTbl;
	//--- ハッシュ更新処理 ---
	item.key = lab;
	if(!(pEntry = hsearch(item, FIND))) { DIE(); }		//バグ
	pEntry->data = seqTbl;					//LabDef_new()で設定したダミー値を置き換える。(ラベルが重複していた場合はエラーだがとりあえず上書きして継続する事にした。先に現れたラベルはハッシュ内から消える事になる。)
	//--- ラベル定義処理 ---
	if((lab[0] == '@') || strchr(lab, '.')) { return; }	//ローカルラベルは出力しない。
	if(nLabel < UINT16_MAX) {
		TBL_Label[nLabel++] = lab;			//hsearch()はイテレーションの機能が無いので、後でイテレートするためにはキー配列を別に作成しておく必要が有る。
	}
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
void add_datTbl(ST_DatTbl* datTbl) {
	char* lab = datTbl->labelRow->lab;
	ENTRY item, *pEntry;
    //{{進捗表示用
	if(!opt_q) {
		newMsgLen = fprintf(stderr, "\rpass1 %s", lab);
		if(oldMsgLen > newMsgLen) { fprintf(stderr, "%*s", oldMsgLen - newMsgLen, ""); } oldMsgLen = newMsgLen;
	}
    //}}進捗表示用
	//--- DatTbl登録処理 ---
	//DatTblを登録する。
	if(nDatTbl >= UINT16_MAX) {
		yyerror("DatTbl数が制限を超えました。(%s)", lab);
		DIE();	//継続しても無駄なので終了する。
	}
	TBL_DatTbl[nDatTbl++] = datTbl;
	//--- ハッシュ更新処理 ---
	item.key = lab;
	if(!(pEntry = hsearch(item, FIND))) { DIE(); }		//バグ
	pEntry->data = datTbl;					//LabDef_new()で設定したダミー値を置き換える。(ラベルが重複していた場合はエラーだがとりあえず上書きして継続する事にした。先に現れたラベルはハッシュ内から消える事になる。)
	//--- ラベル定義処理 ---
	/** no job **/						//DatTblはTBL_Label[]に登録しない。
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
ST_SeqTbl* SeqTbl_new(ST_LabelRow* labelRow, ST_PlayRows* playRows0, ST_PlayRows* playRows1) {
	ST_SeqTbl* seqTbl = calloc(1, sizeof(ST_SeqTbl));
	seqTbl->labelRow			= labelRow;
	seqTbl->playRows[0]			= playRows0;
	seqTbl->playRows[1]			= playRows1;
	return seqTbl;
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
ST_DatTbl* DatTbl_new(ST_LabelRow* labelRow, ST_DataRows* dataRows, int loopIndex) {
	ST_DatTbl* datTbl = calloc(1, sizeof(ST_DatTbl));
	if(loopIndex == INT_MAX) {					//ループ無しならば…
		loopIndex = dataRows->n;				//ループフレーム=フレーム数とする。
	} else {							//ループ有りならば…
		if(loopIndex < 0) {					//(最終フレーム+1)からの相対ならば…
			loopIndex += dataRows->n;			//先頭フレームからの相対に変換する。
		}
		if((unsigned)loopIndex >= (unsigned)dataRows->n) {	//ループフレームが最終フレームを超えていたら…
			yyerror("ループ指定が範囲外です。(%s)", labelRow->lab);
			yynerrs++;	/* 呼び出し元(main)に知らせるために */
			/* 全てのセマンティクスエラーを報告するために継続する */
			loopIndex = 0;	//処理を継続するために、範囲内の仮の値に置き換える。
		}
	}
	datTbl->labelRow			= labelRow;
	datTbl->dataRows			= dataRows;
	datTbl->loopIndex			= loopIndex;
	return datTbl;
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
ST_LabelRow* LabelRow_new(char* lab) {
	ST_LabelRow* labelRow = calloc(1, sizeof(ST_LabelRow));
	labelRow->line				= yylineno;	//行番号。エラーメッセージのためだけに使用する。
	labelRow->lab				= lab;
	return labelRow;
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
char* LabDef_new(char* lab) {
	ENTRY item;
    //{{ローカルラベルの処理
	if(lab[0] == '@') {
		if(lab[1] == '@') {	//'@@'
			lab = strdup_printf("@%d", nAtmarkLocalLabel++);	//内部ローカルラベルに変換して、ハッシュテーブルに登録する。その後、+1する。
		} else {		//'@B','@F'
			yyerror("defのラベル定義が不正です。(%s)", lab);
			yynerrs++;	//呼び出し元(main)に知らせるために
			//全てのセマンティクスエラーを報告するために継続する
		}
	} else if(lab[0] == '.') {
		lab = strconcat(sDotLocalLabelPrefix, lab, NULL);		//内部ローカルラベルに変換して、ハッシュテーブルに登録する。
	} else {
		sDotLocalLabelPrefix = lab;					//グローバルラベルの定義が出現したら、sDotLocalLabelPrefixを更新する。
	}
    //}}ローカルラベルの処理
	item.key = lab;
	if(hsearch(item, FIND)) {
		yyerror("ラベル定義が重複しています。(%s)", lab);
		yynerrs++;	//呼び出し元(main)に知らせるために
		//全てのセマンティクスエラーを報告するために継続する
	}
	item.data = NULL;	//値はダミー。add_seqTbl(),又は,add_datTbl()にて、ST_SeqTbl*,又は,ST_DatTbl*に置き換える。
	if(!hsearch(item, ENTER)) {
		yyerror("ラベル定義数が制限を超えました。(%s)", lab);
		DIE();	//継続しても無駄なので終了する。
	}
	return lab;
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
ST_PlayRows* PlayRows_add(ST_PlayRows* playRows, char* lab) {
	if(!playRows) { playRows = calloc(1, sizeof(ST_PlayRows)); }
	playRows->TBL_PlayRow			= realloc(playRows->TBL_PlayRow, sizeof(char*) * (playRows->n + 1));
	playRows->TBL_PlayRow[playRows->n++]	= lab;
	return playRows;
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
char* LabRef_new(char* lab) {
    //{{ローカルラベルの処理
	if(lab[0] == '@') {
		if(lab[1] == 'B') {		//'@B'
			lab = strdup_printf("@%d", nAtmarkLocalLabel - 1);	//'play @B'や'play @B'からは、(nAtmarkLocalLabel-1)で参照できる。もし'def @@'が出現する前に@Bを参照した場合は-1になってしまうが、2パス目に未定義ラベルとしてエラー検出できるはずであり、安全です。
		} else if(lab[1] == 'F') {	//'@F'
			lab = strdup_printf("@%d", nAtmarkLocalLabel);		//'play @F'や'play @F'からは、nAtmarkLocalLabelで参照できる。もしこの後に'@@'が出現しなければ存在しない内部ローカルラベルを参照してしまうが、2パス目に未定義ラベルとしてエラー検出できるはずであり、安全です
		} else {			//'@@'
			yyerror("callのラベル定義が不正です。(%s)", lab);
			yynerrs++;	/* 呼び出し元(main)に知らせるために */
			/* 全てのセマンティクスエラーを報告するために継続する */
		}
	} else if(lab[0] == '.') {
		lab = strconcat(sDotLocalLabelPrefix, lab, NULL);		//内部ローカルラベルに変換して、ハッシュテーブルに登録する。
	}
    //}}ローカルラベルの処理
	return lab;
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
ST_DataRows* DataRows_add(ST_DataRows* dataRows, ST_DataRow* dataRow) {
    //{{'data'命令の要素数(=LEDチャネル数)が全て同じである事を確認する処理
	if(!nLedCh) { nLedCh = dataRow->n; }	//初回にnLedChを決定する。
	if(dataRow->n != nLedCh) {
		yyerror("dataの要素数が一致しません。(%d)", dataRow->n);
		yynerrs++;	//呼び出し元(main)に知らせるために		//必要。YYERRORマクロ呼び出しでyynerrsは増やされないようだ。	←←←←←←←←←←■■■■重要■■■■
		return NULL;	//y.tab.yのdataRowsの処理でYYERRORを行う。
	}
    //}}'data'命令の要素数(=LEDチャネル数)が全て同じである事を確認する処理
	if(!dataRows) { dataRows = calloc(1, sizeof(ST_DataRows)); }
	dataRows->TBL_DataRow			= realloc(dataRows->TBL_DataRow, sizeof(ST_DataRow*) * (dataRows->n + 1));
	dataRows->TBL_DataRow[dataRows->n++]	= dataRow;
	return dataRows;
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
ST_DataRow* DataRow_add(ST_DataRow* dataRow, int num) {
	if(!dataRow) { dataRow = calloc(1, sizeof(ST_DataRow)); }
	dataRow->TBL_Dimmer			= realloc(dataRow->TBL_Dimmer, sizeof(int) * (dataRow->n + 1));
	dataRow->TBL_Dimmer[dataRow->n++]	= num;
	return dataRow;
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
int Dimmer_new(int num) {
	if((unsigned)num > UINT8_MAX) {	//0～255	0を透明と見なすかどうか,及び,何段階で調光を行うかは、LEDシーケンサの実装次第です。たとえば「clip/clipleds.c」では、0を透明と見なし,1(=消灯),2(=最低輝度)～31(=最大輝度)の31段階で調光を行いますが、当ツールでは関知しません。当ツールは、LEDチャネル数にも調光の段階数にも非依存です。
		yyerror("輝度が範囲外です。(%d)", num);
		yynerrs++;	//呼び出し元(main)に知らせるために
		//全てのセマンティクスエラーを報告するために継続する
		num = 0;	//処理を継続するために、範囲内の仮の値に置き換える。
	}
	return num;
}
/*---------------------------------------------------------------------------*/
//2パス目の処理
static void pass2(int bOptimize) {
	int i, addr;
    //{{2015/06/05追加:出力データ形式を少し変更しました。これまでは、常に全LEDチャネルのデータを出力していましたが、今後は、不透明な輝度データが存在するLEDチャネル範囲の輝度データのみを出力するように変更しました。ある程度まとまった範囲のLEDチャネルのみを使用して、それ以外のLEDは全フレーム透明であるようなデータに対し、処理速度の向上とデータ容量の節約の効果が有ります。
	int iLedCh_Lo, iLedCh_Hi;
    //}}2015/06/05追加:出力データ形式を少し変更しました。これまでは、常に全LEDチャネルのデータを出力していましたが、今後は、不透明な輝度データが存在するLEDチャネル範囲の輝度データのみを出力するように変更しました。ある程度まとまった範囲のLEDチャネルのみを使用して、それ以外のLEDは全フレーム透明であるようなデータに対し、処理速度の向上とデータ容量の節約の効果が有ります。
	unsigned char* p;
	ENTRY item, *pEntry;
    //{{2015/11/13追加:どのSeqTblからも参照されていないDatTblを削除する、最適化処理を追加しました。
	//--- 最適化処理 ---
	if(bOptimize) {
		int iSeqTbl, iPlayRow;
		for(iSeqTbl = 0; iSeqTbl < nSeqTbl; iSeqTbl++) {
			ST_SeqTbl* seqTbl = TBL_SeqTbl[iSeqTbl];
			yylineno = seqTbl->labelRow->line;	//yyerrorのために
		    //{{進捗表示用
			if(!opt_q) {
				newMsgLen = fprintf(stderr, "\rpass2 %s", seqTbl->labelRow->lab);
				if(oldMsgLen > newMsgLen) { fprintf(stderr, "%*s", oldMsgLen - newMsgLen, ""); } oldMsgLen = newMsgLen;
			}
		    //}}進捗表示用
			//[0]=repeatよりも前,[1]=repeatよりも後
			for(i = 0; i < 2; i++) {
				ST_PlayRows* playRows = seqTbl->playRows[i];
				for(iPlayRow = 0; iPlayRow < playRows->n; iPlayRow++) {
					char* lab = playRows->TBL_PlayRow[iPlayRow];
					ST_DatTbl* datTbl;
					item.key = lab;
					if((pEntry = hsearch(item, FIND)) &&		//ラベルを検索する。
					   (datTbl = IsDatTbl(pEntry->data))) {		//SeqTblのラベルではなく、DatTblのラベルである事を確認する。
						datTbl->used = 1;			//このDatTblがSeqTblから参照された事をマークする。
					} else {
						yyerror("ラベルが未定義です。(%s)", lab);
						yynerrs++;	//呼び出し元(main)に知らせるために
						//全てのセマンティクスエラーを報告するために継続する
					}
				}
			}
		}
	}
    //}}2015/11/13追加:どのSeqTblからも参照されていないDatTblを削除する、最適化処理を追加しました。
	//SeqTbl,DatTblの順で出力するために、先に、SeqTblの総サイズ(=DatTblの開始アドレス)を求める事にした。
	//DatTbl,SeqTblの順で出力すれば一回で済むのだが、SeqTbl,DatTblの順の方が見た目に分り易いからです。(前方にテーブル,後方にデータとなるから)
	//データ形式の仕様上も、動作上も、DatTbl,SeqTblの順でも問題は無いのですが、上記の理由のために、SeqTbl,DatTblの順で出力する事にしました。
	//データ変換時(=当ツール)に少し手間が増えても、実行時のデータが判り易い方が、後々のデバッグ時に効率が良いと思ったからです。
	addr = 1/*判り易さのためにTBL_LedSeq[0]にダミーを置く分*/;
	{
		int iSeqTbl;
		for(iSeqTbl = 0; iSeqTbl < nSeqTbl; iSeqTbl++) {
			ST_SeqTbl* seqTbl = TBL_SeqTbl[iSeqTbl];
			addr += (seqTbl->playRows[0]->n/*play*/ + seqTbl->playRows[1]->n/*play*/ + 1/*goto|end*/) * 3/*1命令3バイト固定長*/;
		}
	}
	//--- DatTblの処理 ---
	//	ｲﾝﾄﾛﾌﾞﾛｯｸの圧縮ﾃﾞｰﾀ		#(BPE圧縮ﾃﾞｰﾀ)			ｲﾝﾄﾛ部分が無ければ､空ﾃﾞｰﾀのBPE圧縮で､1ﾊﾞｲﾄの｢0｣になる｡
	//	ﾙｰﾌﾟﾌﾞﾛｯｸの圧縮ﾃﾞｰﾀ		#(BPE圧縮ﾃﾞｰﾀ)			ﾙｰﾌﾟ部分が無ければ､空ﾃﾞｰﾀのBPE圧縮で､1ﾊﾞｲﾄの｢0｣になる｡
	{
		int iDatTbl;
		int iFrame, frameLen[2];	//[0]=イントロフレーム数,[1]=ループフレーム数
		int iLedCh;
		int srcLen, dstLen[2];		//[0]=イントロブロックの圧縮データサイズ,[1]=ループブロックの圧縮データサイズ
		unsigned char *src, *dst[2];	//[0]=イントロブロックの圧縮データ,[1]=ループブロックの圧縮データ
		for(iDatTbl = 0; iDatTbl < nDatTbl; iDatTbl++) {
			ST_DatTbl* datTbl = TBL_DatTbl[iDatTbl];
			ST_DataRows* dataRows = datTbl->dataRows;
			yylineno = datTbl->labelRow->line;	//yyerrorのために
		    //{{進捗表示用
			if(!opt_q) {
				newMsgLen = fprintf(stderr, "\rpass2 %s", datTbl->labelRow->lab);
				if(oldMsgLen > newMsgLen) { fprintf(stderr, "%*s", oldMsgLen - newMsgLen, ""); } oldMsgLen = newMsgLen;
			}
		    //}}進捗表示用
		    //{{2015/11/13追加:どのSeqTblからも参照されていないDatTblを削除する、最適化処理を追加しました。
			//--- 最適化処理 ---
			if(bOptimize) {	//この判断は必須です。最適化無しの時はdatTbl->usedは設定されず0のままだからです。
				//どのSeqTblからも参照されていないDatTblならば、'datTbl->outLen=0'のままで処理を飛ばす。
				//'datTbl->outLen=0'のままなので、この後の処理でこのDatTblは出力されない(=削除される)。
				if(!datTbl->used) {
				    //{{進捗表示用
					if(!opt_q) {
						newMsgLen = fprintf(stderr, "\r削除 %s", datTbl->labelRow->lab);
						if(oldMsgLen > newMsgLen) { fprintf(stderr, "%*s", oldMsgLen - newMsgLen, ""); } putc('\n', stderr); oldMsgLen = 0;
					}
				    //}}進捗表示用
					continue;
				}
			}
		    //}}2015/11/13追加:どのSeqTblからも参照されていないDatTblを削除する、最適化処理を追加しました。
			//アドレスを格納する。
			datTbl->addr = addr;
			//イントロフレーム数とループフレーム数を計算する。
			frameLen[0] =               datTbl->loopIndex;	//イントロフレーム数
			frameLen[1] = dataRows->n - datTbl->loopIndex;	//ループフレーム数
			if((frameLen[0] < 0) || (frameLen[1] < 0)) { DIE(); }	//バグ
	    //{{2015/06/05変更:出力データ形式を少し変更しました。これまでは、常に全LEDチャネルのデータを出力していましたが、今後は、不透明な輝度データが存在するLEDチャネル範囲の輝度データのみを出力するように変更しました。ある程度まとまった範囲のLEDチャネルのみを使用して、それ以外のLEDは全フレーム透明であるようなデータに対し、処理速度の向上とデータ容量の節約の効果が有ります。
	    //		//全フレームの輝度データをリニアに展開する。
	    //		src = malloc(nLedCh * dataRows->n);
	    //		if(!src) { DIE(); }	//メモリ不足
	    //		p = src;
	    //		for(iFrame = 0; iFrame < dataRows->n; iFrame++) {
	    //			ST_DataRow* dataRow = dataRows->TBL_DataRow[iFrame];
	    //			if(dataRow->n != nLedCh) { DIE(); }	//バグ
	    //			for(iLedCh = 0; iLedCh < nLedCh; iLedCh++) {
	    //				*p++ = dataRow->TBL_Dimmer[iLedCh];
	    //			}
	    //		}
	    //		//リニアに展開した輝度データの中から、イントロブロックとループブロックの範囲を、それぞれ個別に圧縮する。
	    //		p = src;
	    //		for(i = 0; i < 2; i++) {
	    //			srcLen = nLedCh * frameLen[i];	//srcLen=0ならば、この下の処理は空データのBPE圧縮処理となり、圧縮データは1バイトの「0」になる。
	    //			//1st try
	    //		    //{{--
	    //		    //	dst[i] = malloc(srcLen);
	    //		    //	if(!dst[i]) { DIE(); }	//メモリ不足
	    //		    //	dstLen[i] = BytePairEncodingEncoder_Encode(p, srcLen, dst[i], srcLen);
	    //		    //	if(dstLen[i] > srcLen) {
	    //		    //↑動作上はこれでも問題無いのだがsrcLen=0の時に2nd tryが発生し過ぎてBytePairEncodingEncoder_Encode()の進捗表示が出過ぎて見辛いので、↓のように変更してsrcLen=0の時に1st tryで完了するようにした。
	    //			dst[i] = malloc(srcLen ? srcLen : 1);
	    //			if(!dst[i]) { DIE(); }	//メモリ不足
	    //			dstLen[i] = BytePairEncodingEncoder_Encode(p, srcLen, dst[i], (srcLen ? srcLen : 1));
	    //			if(dstLen[i] > (srcLen ? srcLen : 1)) {
	    //		    //}}--
	    //				//2nd try
	    //				dst[i] = malloc(dstLen[i]);
	    //				if(!dst[i]) { DIE(); }	//メモリ不足
	    //				if(BytePairEncodingEncoder_Encode(p, srcLen, dst[i], dstLen[i]) != dstLen[i]) { DIE(); }	//バグ
	    //			}
	    //			p += srcLen;
	    //		}
	    //		//出力データを作成する。
	    //		datTbl->outLen = dstLen[0]/*①*/ + dstLen[1]/*②*/;
	    //		datTbl->outBuf = malloc(datTbl->outLen);
	    //		if(!datTbl->outBuf) { DIE(); }	//メモリ不足
	    //		p = datTbl->outBuf;
	    //		memcpy(p, dst[0], dstLen[0]);	//┬①イントロブロックの圧縮データ
	    //		p += dstLen[0];			//┘
	    //		memcpy(p, dst[1], dstLen[1]);	//┬②ループブロックの圧縮データ
	    //	//不要	p += dstLen[1];			//┘
	    //↓2015/06/05変更:出力データ形式を少し変更しました。これまでは、常に全LEDチャネルのデータを出力していましたが、今後は、不透明な輝度データが存在するLEDチャネル範囲の輝度データのみを出力するように変更しました。ある程度まとまった範囲のLEDチャネルのみを使用して、それ以外のLEDは全フレーム透明であるようなデータに対し、処理速度の向上とデータ容量の節約の効果が有ります。
			//不透明な輝度データが存在するLEDチャネル番号の下限と上限を検索する。
			iLedCh_Lo = nLedCh - 1;
			iLedCh_Hi = 0;
			for(iFrame = 0; iFrame < dataRows->n; iFrame++) {
				ST_DataRow* dataRow = dataRows->TBL_DataRow[iFrame];
				if(dataRow->n != nLedCh) { DIE(); }	//バグ
				for(iLedCh = 0; iLedCh < nLedCh; iLedCh++) {
					if(dataRow->TBL_Dimmer[iLedCh]) {
						if(iLedCh < iLedCh_Lo) { iLedCh_Lo = iLedCh; }
						if(iLedCh > iLedCh_Hi) { iLedCh_Hi = iLedCh; }
					}
				}
			}
			//全フレームの全ての輝度データが透明ならば、ダミーとしてLEDチャネル#0の輝度データを全フレーム透明で出力する事にした。
			//再生期間にも意味が有り、各フレームに少なくとも1チャネル分以上のデータを出力しないと、終端を判断できないからである。
			if(iLedCh_Lo > iLedCh_Hi) { iLedCh_Lo = iLedCh_Hi = 0; }
			//全フレームの、不透明な輝度データが存在するLEDチャネル範囲の輝度データを、リニアに展開する。
			src = malloc((iLedCh_Hi - iLedCh_Lo + 1) * dataRows->n);
			if(!src) { DIE(); }	//メモリ不足
			p = src;
			for(iFrame = 0; iFrame < dataRows->n; iFrame++) {
				ST_DataRow* dataRow = dataRows->TBL_DataRow[iFrame];
				if(dataRow->n != nLedCh) { DIE(); }	//バグ
				for(iLedCh = iLedCh_Lo; iLedCh <= iLedCh_Hi; iLedCh++) {
					*p++ = dataRow->TBL_Dimmer[iLedCh];
				}
			}
			//リニアに展開した輝度データの中から、イントロブロックとループブロックの範囲を、それぞれ個別に圧縮する。
			p = src;
			for(i = 0; i < 2; i++) {
				srcLen = (iLedCh_Hi - iLedCh_Lo + 1) * frameLen[i];	//srcLen=0ならば、この下の処理は空データのBPE圧縮処理となり、圧縮データは1バイトの「0」になる。
				//1st try
			//{{--
			//	dst[i] = malloc(srcLen);
			//	if(!dst[i]) { DIE(); }	//メモリ不足
			//	dstLen[i] = BytePairEncodingEncoder_Encode(p, srcLen, dst[i], srcLen);
			//	if(dstLen[i] > srcLen) {
			//↑動作上はこれでも問題無いのだがsrcLen=0の時に2nd tryが発生し過ぎてBytePairEncodingEncoder_Encode()の進捗表示が出過ぎて見辛いので、↓のように変更してsrcLen=0の時に1st tryで完了するようにした。
				dst[i] = malloc(srcLen ? srcLen : 1);
				if(!dst[i]) { DIE(); }	//メモリ不足
			    //{{2015/11/14追加:"-s"オプションを追加しました。
			    //	dstLen[i] = BytePairEncodingEncoder_Encode(p, srcLen, dst[i], (srcLen ? srcLen : 1));
			    //↓2015/11/14追加:"-s"オプションを追加しました。
				dstLen[i] = (*fnEncode)(p, srcLen, dst[i], (srcLen ? srcLen : 1));
			    //}}2015/11/14追加:"-s"オプションを追加しました。
				if(dstLen[i] > (srcLen ? srcLen : 1)) {
			//}}--
					//2nd try
					dst[i] = malloc(dstLen[i]);
					if(!dst[i]) { DIE(); }	//メモリ不足
				    //{{2015/11/14追加:"-s"オプションを追加しました。
				    //	if(BytePairEncodingEncoder_Encode(p, srcLen, dst[i], dstLen[i]) != dstLen[i]) { DIE(); }	//バグ
				    //↓2015/11/14追加:"-s"オプションを追加しました。
					if((*fnEncode)(p, srcLen, dst[i], dstLen[i]) != dstLen[i]) { DIE(); }	//バグ
				    //}}2015/11/14追加:"-s"オプションを追加しました。
				}
				p += srcLen;
			}
			//出力データを作成する。
			datTbl->outLen = 2/*iLedCh_Lo*/ + 2/*iLedCh_Hi*/ + dstLen[0]/*①*/ + dstLen[1]/*②*/;
			datTbl->outBuf = malloc(datTbl->outLen);
			if(!datTbl->outBuf) { DIE(); }	//メモリ不足
			p = datTbl->outBuf;
			*p++ = iLedCh_Lo >> 8;		//┬iLedCh_Lo	ビッグエンディアンで格納する。
			*p++ = iLedCh_Lo;		//┘
			*p++ = iLedCh_Hi >> 8;		//┬iLedCh_Hi	ビッグエンディアンで格納する。
			*p++ = iLedCh_Hi;		//┘
			memcpy(p, dst[0], dstLen[0]);	//┬①イントロブロックの圧縮データ
			p += dstLen[0];			//┘
			memcpy(p, dst[1], dstLen[1]);	//┬②ループブロックの圧縮データ
		//不要	p += dstLen[1];			//┘
	    //}}2015/06/05変更:出力データ形式を少し変更しました。これまでは、常に全LEDチャネルのデータを出力していましたが、今後は、不透明な輝度データが存在するLEDチャネル範囲の輝度データのみを出力するように変更しました。ある程度まとまった範囲のLEDチャネルのみを使用して、それ以外のLEDは全フレーム透明であるようなデータに対し、処理速度の向上とデータ容量の節約の効果が有ります。
			//--- 最適化処理 ---
			if(bOptimize) {
				int iDatTbl2;
				//以前に作成したDatTblと比較して…
				for(iDatTbl2 = 0; iDatTbl2 < iDatTbl; iDatTbl2++) {
					ST_DatTbl* datTbl2 = TBL_DatTbl[iDatTbl2];
					//出力データが一致したら…
					if((       datTbl2->outLen == datTbl->outLen                ) &&	//もしdatTbl2が既に最適化されていたら、datTbl2->outLen=0でありdatTbl->outLenと一致しないので、この行の判定で除外できる。
					   !memcmp(datTbl2->outBuf,   datTbl->outBuf, datTbl->outLen)) {
					    //{{進捗表示用
						if(!opt_q) {
							newMsgLen = fprintf(stderr, "\r最適化 %s → %s %d byte 削減", datTbl->labelRow->lab, datTbl2->labelRow->lab, datTbl->outLen);
							if(oldMsgLen > newMsgLen) { fprintf(stderr, "%*s", oldMsgLen - newMsgLen, ""); } putc('\n', stderr); oldMsgLen = 0;
						}
					    //}}進捗表示用
						datTbl->addr = datTbl2->addr;	//このDatTblのアドレスを、以前に作成したDatTblのアドレスと同じにする。
						datTbl->outLen = 0;		//┐
						datTbl->outBuf = NULL;		//┴このDatTblの出力データを空にする。こうすれば、この後の出力処理において、このDatTblの出力データは出力されない。
						break;	//ここまで
					}
				}
			}
			//アドレスを進める。
			addr += datTbl->outLen;	//最適化された場合はdatTbl->outLen=0になっていてaddrは進まない。
		}
	}
	//出力データバイト数(=最終バイトのアドレス+1)を格納する。
	endAddr = addr;
	//SeqTblの開始位置へリワインドする。詳細は、当関数の最初の方のコメントを参照せよ。
	addr = 1/*判り易さのためにTBL_LedSeq[0]にダミーを置く分*/;
	//--- SeqTblの処理 ---
	//	play 相対位置		#0sssssss ssssssss ssssssss	次の命令を基準とした相対距離	┬ﾋﾞｯｸﾞｴﾝﾃﾞｨｱﾝで格納する｡
	//	goto 相対位置		#1sssssss ssssssss ssssssss	次の命令を基準とした相対距離	┘
	//	end			#11111111 11111111 11111111	goto命令自身の3バイト内部へのgotoは有り得ないので区別できる｡
	{
		int iSeqTbl, iPlayRow, playAddr, gotoAddr, distance;
		for(iSeqTbl = 0; iSeqTbl < nSeqTbl; iSeqTbl++) {
			ST_SeqTbl* seqTbl = TBL_SeqTbl[iSeqTbl];
			yylineno = seqTbl->labelRow->line;	//yyerrorのために
		    //{{進捗表示用
			if(!opt_q) {
				newMsgLen = fprintf(stderr, "\rpass2 %s", seqTbl->labelRow->lab);
				if(oldMsgLen > newMsgLen) { fprintf(stderr, "%*s", oldMsgLen - newMsgLen, ""); } oldMsgLen = newMsgLen;
			}
		    //}}進捗表示用
			//アドレスを格納する。
			seqTbl->addr = addr;
			//出力データを作成する。
			seqTbl->outLen = (seqTbl->playRows[0]->n/*play*/ + seqTbl->playRows[1]->n/*play*/ + 1/*goto|end*/) * 3/*1命令3バイト固定長*/;
			seqTbl->outBuf = malloc(seqTbl->outLen);
			if(!seqTbl->outBuf) { DIE(); }	//メモリ不足
			p = seqTbl->outBuf;
			//[0]=repeatよりも前,[1]=repeatよりも後
			for(i = 0; i < 2; i++) {
				ST_PlayRows* playRows = seqTbl->playRows[i];
				for(iPlayRow = 0; iPlayRow < playRows->n; iPlayRow++) {
					char* lab = playRows->TBL_PlayRow[iPlayRow];
					ST_DatTbl* datTbl;
					item.key = lab;
					if((pEntry = hsearch(item, FIND)) &&		//ラベルを検索する。
					   (datTbl = IsDatTbl(pEntry->data))) {		//SeqTblのラベルではなく、DatTblのラベルである事を確認する。
						playAddr = datTbl->addr;		//DatTblのアドレスを取得する。
					} else {
						yyerror("ラベルが未定義です。(%s)", lab);
						yynerrs++;	//呼び出し元(main)に知らせるために
						//全てのセマンティクスエラーを報告するために継続する
						playAddr = 0;				//この後の処理が継続できるように、ダミーとしてアドレス0を指しておく。
					}
					distance = playAddr - (addr + 3/*次の命令位置からの相対*/);
					if((distance < (-1<<(24-2))) ||
					   (distance > ( 1<<(24-2)))) {
						yyerror("playの距離が範囲外です。(%s)", lab);
						yynerrs++;	/* 呼び出し元(main)に知らせるために */
						/* 全てのセマンティクスエラーを報告するために継続する */
					}
					distance &= ~(1<<(24-1));	//bit23=0(play)
					*p++ = distance >> 16;	//┐
					*p++ = distance >>  8;	//├ビッグエンディアンで格納する。
					*p++ = distance;	//┘
					//アドレスを進める。
					addr += 3;
				}
				if(i == 0) {
					//[0]の終了アドレス(=repeat位置)を記憶しておく。repeatが無い場合はダミーとなる。
					gotoAddr = addr;
				} else {
					//repeat位置への相対距離を求める。repeatが無い場合は-1(end)とする。
					if(playRows->n) {
						distance = gotoAddr - (addr + 3/*次の命令位置からの相対*/);
					} else {
						distance = -1;
					}
					if((distance < (-1<<(24-2))) ||
					   (distance > ( 1<<(24-2)))) {
						yyerror("repeatの距離が範囲外です。(%s)", seqTbl->labelRow->lab);
						yynerrs++;	/* 呼び出し元(main)に知らせるために */
						/* 全てのセマンティクスエラーを報告するために継続する */
					}
					distance |=  (1<<(24-1));	//bit23=1(goto)
					*p++ = distance >> 16;	//┐
					*p++ = distance >>  8;	//├ビッグエンディアンで格納する。
					*p++ = distance;	//┘
					//アドレスを進める。
					addr += 3;
				}
			}
		}
	}
}
/*---------------------------------------------------------------------------*/
//出力処理
static int Label_compar(const void* x, const void* y) {
	return (*fnLabCmp)(*(char**)x, *(char**)y);
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
static void output(char* sBinOut) {
	int iDatTbl, iSeqTbl, iLabel, i;
	ENTRY item, *pEntry;
	//ラベル定義を名前の昇順にソートする。
	qsort(TBL_Label, nLabel, sizeof(char*), Label_compar);
	//--- ラベルを標準出力に出力する ---
	for(iLabel = 0; iLabel < nLabel; iLabel++) {					//名前の昇順	ラベル=0(ダミー)は定義しない。
		char* lab = TBL_Label[iLabel];
		printf("#define LedSeqNo_%s %d\n", lab, iLabel + 1);			//ラベルは1ベース
	}
	printf("#define LedSeqNo_Max %d\n", nLabel);					//最大値
	//利便のために、LEDチャネル番号の最大値(この値も含む)も出力しておく事にした。
	//LEDシーケンサ側,又は,アプリケーション側でも定義している可能性が有るが、同じ値であるはずなので、Cプリプロセッサの警告は出ないので問題無い。
	//もし違う値なら、Cプリプロセッサが警告を出すので、バグの防止にもなるだろう。
	printf("#define LedCh_Max %d\n", nLedCh - 1);
	//--- データを出力する ---
	//バイナリ形式の場合…
	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()の文字列の最後に余計な改行が付いていたのを削除しました。}}
		//アドレステーブルを出力する。
		for(iLabel = -1; iLabel < nLabel; iLabel++) {
			int addr;
			if(iLabel < 0) {
    //{{2015/03/02変更:TBL_LedSeqNo[0]に((LedSeqNo_Max+1)|(LedCh_Max+1)<<16)を格納するようにしました。
    //				addr = 0;						//ラベル番号=0(ダミー)
    //{{2015/03/02変更:TBL_LedSeqNo[0]に((LedSeqNo_Max+1)|(LedCh_Max+1)<<16)を格納するようにしました。
				addr = (nLabel + 1) | (nLedCh << 16);			//TBL_LedSeqNo[0]=((LedSeqNo_Max+1)|(LedCh_Max+1)<<16)		ラベルは1ベースなのでnLabelではなく(nLabel+1)とする事に注意せよ。	LEDチャネル番号は0ベースなのでnLedChで良い。
    //{{2015/03/02変更:TBL_LedSeqNo[0]に((LedSeqNo_Max+1)|(LedCh_Max+1)<<16)を格納するようにしました。
			} else {
				ST_SeqTbl* seqTbl;
				char* lab = TBL_Label[iLabel];
				item.key = lab;
				if(!(pEntry = hsearch(item, FIND)) ||			//ラベルを検索する。
				   !(seqTbl = IsSeqTbl(pEntry->data))) { DIE(); }	//DatTblのラベルではなく、SeqTblのラベルである事を確認する。
				addr = seqTbl->addr;
			}
			fwrite(&addr, sizeof(int), 1, fp);
		}
		//判り易さのためにTBL_LedSeq[0]にダミーを置く分
		putc(0, fp);
		//SeqTblの出力を行う。
		for(iSeqTbl = 0; iSeqTbl < nSeqTbl; iSeqTbl++) {
			ST_SeqTbl* seqTbl = TBL_SeqTbl[iSeqTbl];
			for(i = 0; i < seqTbl->outLen; i++) {
				int data = seqTbl->outBuf[i];
				fwrite(&data, sizeof(unsigned char), 1, fp);
			}
		}
		//DatTblの出力を行う。
		for(iDatTbl = 0; iDatTbl < nDatTbl; iDatTbl++) {
			ST_DatTbl* datTbl = TBL_DatTbl[iDatTbl];
			for(i = 0; i < datTbl->outLen; i++) {	//最適化された場合はdatTbl->outLen=0になっていて出力しない。
				int data = datTbl->outBuf[i];
				fwrite(&data, sizeof(unsigned char), 1, fp);
			}
		}
		fclose(fp);
	//テキスト形式の場合…
	} else {
		//アドレステーブルを出力する。
		printf("const int TBL_LedSeqNo[%d]={", nLabel + 1);
		//アドレステーブルを出力する。
		for(iLabel = -1; iLabel < nLabel; iLabel++) {
			int addr;
			if(iLabel < 0) {
    //{{2015/03/02変更:TBL_LedSeqNo[0]に((LedSeqNo_Max+1)|(LedCh_Max+1)<<16)を格納するようにしました。
    //				addr = 0;						//ラベル番号=0(ダミー)
    //{{2015/03/02変更:TBL_LedSeqNo[0]に((LedSeqNo_Max+1)|(LedCh_Max+1)<<16)を格納するようにしました。
				addr = (nLabel + 1) | (nLedCh << 16);			//TBL_LedSeqNo[0]=((LedSeqNo_Max+1)|(LedCh_Max+1)<<16)		ラベルは1ベースなのでnLabelではなく(nLabel+1)とする事に注意せよ。	LEDチャネル番号は0ベースなのでnLedChで良い。
    //{{2015/03/02変更:TBL_LedSeqNo[0]に((LedSeqNo_Max+1)|(LedCh_Max+1)<<16)を格納するようにしました。
			} else {
				ST_SeqTbl* seqTbl;
				char* lab = TBL_Label[iLabel];
				item.key = lab;
				if(!(pEntry = hsearch(item, FIND)) ||			//ラベルを検索する。
				   !(seqTbl = IsSeqTbl(pEntry->data))) { DIE(); }	//DatTblのラベルではなく、SeqTblのラベルである事を確認する。
				addr = seqTbl->addr;
			}
			if((iLabel & 15) == 15)    { putchar('\n'); }
			printf("0x%08X", addr);
			if(iLabel != (nLabel - 1)) { putchar(',' ); }
		}
		puts("};");
		printf("const unsigned char TBL_LedSeq[%d]={", endAddr);
		//判り易さのためにTBL_LedSeq[0]にダミーを置く分
		printf("\n0x%02X", 0);
		//SeqTblの出力を行う。
		for(iSeqTbl = 0; iSeqTbl < nSeqTbl; iSeqTbl++) {
			ST_SeqTbl* seqTbl = TBL_SeqTbl[iSeqTbl];
			for(i = 0; i < seqTbl->outLen; i++) {
				int data = seqTbl->outBuf[i];
				if(   seqTbl->addr + i       ) { putchar(',' ); }
				if(!((seqTbl->addr + i) & 31)) { putchar('\n'); }
				printf("0x%02X", data);
			}
		}
		//DatTblの出力を行う。
		for(iDatTbl = 0; iDatTbl < nDatTbl; iDatTbl++) {
			ST_DatTbl* datTbl = TBL_DatTbl[iDatTbl];
			for(i = 0; i < datTbl->outLen; i++) {	//最適化された場合はdatTbl->outLen=0になっていて出力しない。
				int data = datTbl->outBuf[i];
				if(   datTbl->addr + i       ) { putchar(',' ); }
				if(!((datTbl->addr + i) & 31)) { putchar('\n'); }
				printf("0x%02X", data);
			}
		}
		puts("};");
	}
}
/*===========================================================================*/
//■ｽｸﾘﾌﾟﾄ書式
//│
//│	#ﾗﾍﾞﾙには､@@,@F,@B,`～,.～の書式も使用できる｡詳細は､dDirSeqCを参照せよ｡
//│
//│	#--- ｼｰｹﾝｽ定義 ---
//│
//│	#部分ﾙｰﾌﾟの例
//│	ｼｰｹﾝｽﾗﾍﾞﾙ
//│			play		ﾃﾞｰﾀﾗﾍﾞﾙ
//│			…
//│			play		ﾃﾞｰﾀﾗﾍﾞﾙ
//│			repeat
//│			play		ﾃﾞｰﾀﾗﾍﾞﾙ	#┐
//│			…				#├この範囲を繰り返す
//│			play		ﾃﾞｰﾀﾗﾍﾞﾙ	#┘
//│			end				#出力ﾃﾞｰﾀでは､repeat位置へのgoto命令として出力される｡
//│
//│	#全体ﾙｰﾌﾟの例
//│	ｼｰｹﾝｽﾗﾍﾞﾙ
//│			repeat
//│			play		ﾃﾞｰﾀﾗﾍﾞﾙ	#┐
//│			…				#├この範囲を繰り返す
//│			play		ﾃﾞｰﾀﾗﾍﾞﾙ	#┘
//│			end				#出力ﾃﾞｰﾀでは､repeat位置へのgoto命令として出力される｡
//│
//│	#ﾙｰﾌﾟ無しの例
//│	ｼｰｹﾝｽﾗﾍﾞﾙ
//│			play		ﾃﾞｰﾀﾗﾍﾞﾙ
//│			…
//│			play		ﾃﾞｰﾀﾗﾍﾞﾙ
//│			end				#出力ﾃﾞｰﾀでは､end命令として出力される｡
//│
//│	#--- ﾃﾞｰﾀ定義 ---
//│
//│	#部分ﾙｰﾌﾟの例1
//│	ﾃﾞｰﾀﾗﾍﾞﾙ
//│			data		輝度,…,輝度
//│			…
//│			data		輝度,…,輝度
//│			loop		10		#先頭から10ﾌﾚｰﾑ目の位置から繰り返す｡
//│
//│	#部分ﾙｰﾌﾟの例2
//│	ﾃﾞｰﾀﾗﾍﾞﾙ
//│			data		輝度,…,輝度
//│			…
//│			data		輝度,…,輝度
//│			loop		-10		#最後の10ﾌﾚｰﾑ分を繰り返す｡
//│
//│	#全体ﾙｰﾌﾟの例
//│	ﾃﾞｰﾀﾗﾍﾞﾙ
//│			data		輝度,…,輝度
//│			…
//│			data		輝度,…,輝度
//│			loop				#'loop 0'と書いても同じです｡
//│
//│	#最終ﾌﾚｰﾑ維持の例
//│	ﾃﾞｰﾀﾗﾍﾞﾙ
//│			data		輝度,…,輝度
//│			…
//│			data		輝度,…,輝度
//│			stay				#'loop -1'と書いても同じです｡
//│
//│	#ﾙｰﾌﾟ無しの例
//│	ﾃﾞｰﾀﾗﾍﾞﾙ
//│			data		輝度,…,輝度
//│			…
//│			data		輝度,…,輝度
//│			end
//│
//│	#輝度の数は､ｽｸﾘﾌﾟﾄ内の全てのdata命令で､同じでなければならない｡
//│	#当ﾂｰﾙは､輝度の数から､LEDﾁｬﾈﾙ数を判断する｡
