/*****************************************************************************
 * 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である事を示します。
/*---------------------------------------------------------------------------*/
//{{2017/02/27追加:'-xc','-xcs'オプションを追加しました。
//言語選択
static char* opt_x = "c";		//"c"=C言語形式,"cs"=C#形式
//}}2017/02/27追加:'-xc','-xcs'オプションを追加しました。
/*---------------------------------------------------------------------------*/
static ST_KeyBody* g_keyBody;				//トップレベルのKeyBody
static char* TBL_Label[UINT16_MAX+1];			//名前として使用されたラベルを登録する。データとして使用されたラベルは登録しない。
static int nLabel;					//名前として使用されたラベルの登録数。
static uint32_t TBL_UsedExpr[(UINT16_MAX+1)/32];	//符号無し16ビット値の内、明示的な数値として使用されたビットに1を立てる。0のまま残った箇所にラベルの数値を割り当てる。
static uint16_t TBL_LabelExpr[UINT16_MAX+1];		//符号無し16ビット値の内、明示的な数値として使用されなかった数値をラベルに割り当てる。
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
#define String_Max	(1<<(24-2))			//文字列データの最大数。レジストリの最大容量が(1<<24)，文字列データへの距離がが3バイト，文字列データが最短でも1バイトなので、文字列データの数がレジストリの最大容量の1/4を超える事は無い。
static char* TBL_String[String_Max];			//文字列データを重複無く登録する配列。
static int nString;					//文字列データの登録数。
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
#define Blob_Max	(1<<(24-2))			//BLOBデータの最大数。レジストリの最大容量が(1<<24)，BLOBデータへの距離がが3バイト，BLOBデータが最短でも1バイトなので、BLOBデータの数がレジストリの最大容量の1/4を超える事は無い。
static ST_Blob* TBL_Blob[Blob_Max];			//BLOBデータを重複無く登録する配列。
static int nBlob;					//BLOBデータの登録数。
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
static ght_hash_table_t* ht_String;			//TBL_String[]に登録済みかの判断を高速化するためのハッシュ。	String_intern()の中だけで使用する。		それ以外の関数から参照すると依存関係が増えすぎて今後の保守が難しくなるので、それ以外の関数からは参照しないようにせよ。
static ght_hash_table_t* ht_Blob;			//TBL_Blob[]に登録済みかの判断を高速化するためのハッシュ。	Blob_intern()の中だけで使用する。		それ以外の関数から参照すると依存関係が増えすぎて今後の保守が難しくなるので、それ以外の関数からは参照しないようにせよ。
static ght_hash_table_t* ht_str2num;			//文字列を重複無く(1<<24)以上の数値に変換する。			str2num()の中だけで使用する。			それ以外の関数から参照すると依存関係が増えすぎて今後の保守が難しくなるので、それ以外の関数からは参照しないようにせよ。
static ght_hash_table_t* ht_num2str;			//変換した(1<<24)以上の数値を文字列に戻す。			str2num()とnum2str()の中だけで使用する。	それ以外の関数から参照すると依存関係が増えすぎて今後の保守が難しくなるので、それ以外の関数からは参照しないようにせよ。
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
//ラベル名のソート方法
int (*fnLabCmp)(const void*, const void*) = strcmp;	//'n'オプションが指定されたらstrnatcmpに変更する。
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
//{{進捗表示用
static int g_nKeyBodyAll;	//最適化開始前の全キーボディ数
static int g_nKeyBodyNow;	//最適化後に生き残っているキーボディ数(進捗)
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 optimize1();
static void optimize2();
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::x: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;
//{{2017/02/27追加:'-xc','-xcs'オプションを追加しました。
		case 'x':
			opt_x = optarg;		//言語選択を設定する。
			if(strcmp(opt_x, "c") && strcmp(opt_x, "cs")) { usage(); }
			break;
//}}2017/02/27追加:'-xc','-xcs'オプションを追加しました。
		case 'q':
			opt_q = 1;
			break;
		case 'h':
		default:
			usage();
		}
	}
	//ハッシュテーブルを作成する。
	ght_set_rehash((ht_String  = ght_create(0)), 1);
	ght_set_rehash((ht_Blob    = ght_create(0)), 1);
	ght_set_rehash((ht_str2num = ght_create(0)), 1);
	ght_set_rehash((ht_num2str = 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(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, "dRegTblC - レジストリテーブルコンパイラ (%s)\n", VERSION);
	fprintf(stderr, "Copyright (C) 2015-2017 Naoyuki Sawa\n");
	fprintf(stderr, "\n");
	fprintf(stderr, "USAGE:\n");
	fprintf(stderr, "  dRegTblC [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ファイル名 レジストリテーブルのバイナリデータを、バイナリ形式でファイルへ出力します。\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");
//{{2017/02/27追加:'-xc','-xcs'オプションを追加しました。
	fprintf(stderr, "  -xc          出力ソースをC言語形式とします。(既定値)\n");
	fprintf(stderr, "  -xcs         出力ソースをC#形式とします。\n");
//}}2017/02/27追加:'-xc','-xcs'オプションを追加しました。
	fprintf(stderr, "  -q           進捗表示を抑制します。\n");
	fprintf(stderr, "  -h           使い方を表示します。\n");
	fprintf(stderr, "\n");
	fprintf(stderr, "EXAMPLE:\n");
	fprintf(stderr, "  ①dRegTblC.exe RegTbl.txt > RegTbl.c\n");
	fprintf(stderr, "  　RegTbl.txtをコンパイルして、RegTbl.cへ出力します。\n");
	fprintf(stderr, "  ②dRegTblC.exe -oRegTbl.bin RegTbl.txt > RegTbl.h\n");
	fprintf(stderr, "  　RegTbl.txtをコンパイルして、ラベルをRegTbl.hへ出力します。\n");
	fprintf(stderr, "  　レジストリテーブルのバイナリデータを、RegTbl.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;
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
//これまでは数値データが16ビットだったのでWin32のアドレスの特性でポインタと数値を区別できたが、今後は数値データが24ビットなので区別できなくなった。区別するためにハッシュに登録して区別出来る値を返す事にした。
static int str2num(char* str) {
	int n = strlen(str) + 1/*nul*/;			//(文字列+ヌル文字)のバイト数
	int x = (int)ght_get(ht_str2num, n, str);	//この文字列は既に登録されているか?
	if(!x) {					//未登録ならば…
		static int y = (1<<31);	//数値データは24ビット符号無しなのでその範囲と被らなければok。numの初期値は(1<<24)以上ならば何でも構いません。デバッグ時に判り易いよう(1<<31)から割り当てる事にしました。
		x = y++;				//(1<<31)+0,(1<<31)+1,(1<<31)+2,...
		//文字列⇒数値の辞書に登録する。
		if(ght_insert(ht_num2str,      str, sizeof x,  &x)) { DIE(); }	//バグ
		//数値⇒文字列の辞書に登録する。
		if(ght_insert(ht_str2num, (void*)x,        n, str)) { DIE(); }	//バグ
	}
	return x;
}
static char* num2str(int num) {
	char* str;
	if((unsigned)num >= (1<<24)) {		//(1<<24)以上ならば文字列を変換した数値である。
		if(!(str = ght_get(ht_num2str, sizeof num, &num))) { DIE(); }	//バグ
	} else {				//(1<<24)未満ならば数値である。
		str = strdup_printf("%u", num);	//数値の文字列表現を作成する。
	}
	return str;
}
static int isnumstr(int num) {
	return (unsigned)num >= (1<<24);	//(1<<24)以上ならば文字列である。
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
void add_keyBody(ST_KeyBody* keyBody) {
//{{2017/03/05修正:dRegTblC.exeの引数にスクリプトファイル名を二個以上指定すると、最後のスクリプトファイルの内容しか出力されていなかった、バグを修正しました。
//	//トップレベルのKeyBodyを格納する。
//	g_keyBody = keyBody;
//↓2017/03/05修正:dRegTblC.exeの引数にスクリプトファイル名を二個以上指定すると、最後のスクリプトファイルの内容しか出力されていなかった、バグを修正しました。
	//まだトップレベルのKeyBodyが格納されていなければ…(=一個目のスクリプトファイルならば…)
	if(!g_keyBody) {
		//トップレベルのKeyBodyを格納する。
		g_keyBody = keyBody;
	//既にトップレベルのKeyBodyが格納されていたら…(=二個目以降のスクリプトファイルならば…)
	} else {
		//トップレベルのKeyBodyに、今回のKeyBodyをマージする。
	    //{{この範囲の処理は、KeyBody_addKey()の処理とほとんど同じです。
		int i;
		for(i = 0; i < keyBody->nKey; i++) {
			KeyBody_addKey(g_keyBody, keyBody->TBL_Key[i]);			//キーをマージする。
		}
		for(i = 0; i < keyBody->nValueRow; i++) {
			KeyBody_addValueRow(g_keyBody, keyBody->TBL_ValueRow[i]);	//値をマージする。
		}
		for(i = 0; i < keyBody->nStringRow; i++) {
			KeyBody_addStringRow(g_keyBody, keyBody->TBL_StringRow[i]);	//文字列をマージする。
		}
		for(i = 0; i < keyBody->nBlobRow; i++) {
			KeyBody_addBlobRow(g_keyBody, keyBody->TBL_BlobRow[i]);		//BLOBをマージする。
		}
	    //}}この範囲の処理は、KeyBody_addKey()の処理とほとんど同じです。
	}
//}}2017/03/05修正:dRegTblC.exeの引数にスクリプトファイル名を二個以上指定すると、最後のスクリプトファイルの内容しか出力されていなかった、バグを修正しました。
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
ST_KeyBody* KeyBody_new() {
	ST_KeyBody* keyBody = calloc(1, sizeof(ST_KeyBody));
	if(!keyBody) { DIE(); }
	ght_set_rehash((keyBody->ht_Key       = ght_create(0)), 1);
	ght_set_rehash((keyBody->ht_ValueRow  = ght_create(0)), 1);
	ght_set_rehash((keyBody->ht_StringRow = ght_create(0)), 1);
	ght_set_rehash((keyBody->ht_BlobRow   = ght_create(0)), 1);
	obstack_init(&keyBody->o_Key      );
	obstack_init(&keyBody->o_ValueRow );
	obstack_init(&keyBody->o_StringRow);
	obstack_init(&keyBody->o_BlobRow  );
	return keyBody;
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
ST_KeyBody* KeyBody_addKey(ST_KeyBody* keyBody, ST_Key* key) {
	int name = key->nameRow->name, i;
    //{{進捗表示用
	if(!opt_q) {
		newMsgLen = fprintf(stderr, "\rpass1 %s", num2str(name));
		if(oldMsgLen > newMsgLen) { fprintf(stderr, "%*s", oldMsgLen - newMsgLen, ""); } oldMsgLen = newMsgLen;
	}
    //}}進捗表示用
	//同じ階層に存在する同じ名前のキーを検索する。(登録してみて失敗したら存在すると見なす。)
	//尚、キーと値と文字列とBLOBは別の名前空間と考えるので、ここでは値や文字列やBLOBは検索しない。
	if(ght_insert(keyBody->ht_Key, key, sizeof name, &name)) {
		ST_Key* key2 = ght_get(keyBody->ht_Key, sizeof name, &name);
		if(!key2) { DIE(); }	//バグ
		//スクリプト上での表現としては、同じ階層に同じ名前のキーが存在しても構わない。出力データではマージされて一つのキーとなる。
		//ただし、マージするキー同士の同じ階層に同じ名前で異なるデータの値や文字列やBLOBが存在していた場合はエラーとする。(KeyBody_addValueRow(),KeyBody_addStringRow(),KeyBody_addBlobRow()にて判定する。)
		for(i = 0; i < key->keyBody->nKey; i++) {
			KeyBody_addKey(key2->keyBody, key->keyBody->TBL_Key[i]);		//キーをマージする。
		}
		for(i = 0; i < key->keyBody->nValueRow; i++) {
			KeyBody_addValueRow(key2->keyBody, key->keyBody->TBL_ValueRow[i]);	//値をマージする。
		}
		for(i = 0; i < key->keyBody->nStringRow; i++) {
			KeyBody_addStringRow(key2->keyBody, key->keyBody->TBL_StringRow[i]);	//文字列をマージする。
		}
		for(i = 0; i < key->keyBody->nBlobRow; i++) {
			KeyBody_addBlobRow(key2->keyBody, key->keyBody->TBL_BlobRow[i]);	//BLOBをマージする。
		}
		return keyBody;	//ここまで
	}
	if(keyBody->nKey > UINT16_MAX) {
		yyerror("キーの数が制限を超えました。(%s)", num2str(name));
		DIE();	//継続しても無駄なので終了する。
	}
	//高速化のためにo_Keyを使ってTBL_Keyを伸張する事にした。
	               obstack_ptr_grow(&keyBody->o_Key, key);
	keyBody->TBL_Key = obstack_base(&keyBody->o_Key);	//最後にobstack_finish()する手間を省くために毎回アドレスを取得する事にした。
	keyBody->nKey++;
	return keyBody;
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
ST_KeyBody* KeyBody_addValueRow(ST_KeyBody* keyBody, ST_ValueRow* valueRow) {
	int name = valueRow->name;
    //{{進捗表示用
	if(!opt_q) {
		newMsgLen = fprintf(stderr, "\rpass1 %s", num2str(name));
		if(oldMsgLen > newMsgLen) { fprintf(stderr, "%*s", oldMsgLen - newMsgLen, ""); } oldMsgLen = newMsgLen;
	}
    //}}進捗表示用
	//同じ階層に存在する同じ名前の値を検索する。(登録してみて失敗したら存在すると見なす。)
	//尚、キーと値と文字列とBLOBは別の名前空間と考えるので、ここではキーや文字列やBLOBは検索しない。
	if(ght_insert(keyBody->ht_ValueRow, valueRow, sizeof name, &name)) {
		ST_ValueRow* valueRow2 = ght_get(keyBody->ht_ValueRow, sizeof name, &name);
		if(!valueRow2) { DIE(); }	//バグ
		//同じ階層に同じ名前で異なるデータの値が存在してはいけない。エラーとする。
		//ただし、同じ階層に同じ名前で同じデータの値は存在していても構わない。
		//つまり単純に二回目以降の同じ(値:名前=データ)の定義は無視すれば良い。(格納してはいけない事に注意せよ)
		if(valueRow->data == valueRow2->data) { return keyBody; }	//ここまで
		yyerror("同じ階層に同じ名前で異なるデータの値が存在してはいけません。(%s)", num2str(name));
		yynerrs++;	//呼び出し元(main)に知らせるために
		//全てのセマンティクスエラーを報告するために継続する。
	}
	if(keyBody->nValueRow > UINT16_MAX) {
		yyerror("値の数が制限を超えました。(%s)", num2str(name));
		DIE();	//継続しても無駄なので終了する。
	}
	//高速化のためにo_ValueRowを使ってTBL_ValueRowを伸張する事にした。
	                    obstack_ptr_grow(&keyBody->o_ValueRow, valueRow);
	keyBody->TBL_ValueRow = obstack_base(&keyBody->o_ValueRow);	//最後にobstack_finish()する手間を省くために毎回アドレスを取得する事にした。
	keyBody->nValueRow++;
	return keyBody;
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
ST_KeyBody* KeyBody_addStringRow(ST_KeyBody* keyBody, ST_StringRow* stringRow) {
	int name = stringRow->name;
    //{{進捗表示用
	if(!opt_q) {
		newMsgLen = fprintf(stderr, "\rpass1 %s", num2str(name));
		if(oldMsgLen > newMsgLen) { fprintf(stderr, "%*s", oldMsgLen - newMsgLen, ""); } oldMsgLen = newMsgLen;
	}
    //}}進捗表示用
	//同じ階層に存在する同じ名前の文字列を検索する。(登録してみて失敗したら存在すると見なす。)
	//尚、キーと値と文字列とBLOBは別の名前空間と考えるので、ここではキーや値やBLOBは検索しない。
	if(ght_insert(keyBody->ht_StringRow, stringRow, sizeof name, &name)) {
		ST_StringRow* stringRow2 = ght_get(keyBody->ht_StringRow, sizeof name, &name);
		if(!stringRow2) { DIE(); }	//バグ
		//同じ階層に同じ名前で異なるデータの文字列が存在してはいけない。エラーとする。
		//ただし、同じ階層に同じ名前で同じデータの文字列は存在していても構わない。
		//つまり単純に二回目以降の同じ(文字列:名前=データ)の定義は無視すれば良い。(格納してはいけない事に注意せよ)
		if(stringRow->s == stringRow2->s) { return keyBody; }	//ここまで	//文字列データはインターンしてあるのでポインタ比較可能です。
		yyerror("同じ階層に同じ名前で異なるデータの文字列が存在してはいけません。(%s)", num2str(name));
		yynerrs++;	//呼び出し元(main)に知らせるために
		//全てのセマンティクスエラーを報告するために継続する。
	}
	if(keyBody->nStringRow > UINT16_MAX) {
		yyerror("文字列の数が制限を超えました。(%s)", num2str(name));
		DIE();	//継続しても無駄なので終了する。
	}
	//高速化のためにo_StringRowを使ってTBL_StringRowを伸張する事にした。
	                     obstack_ptr_grow(&keyBody->o_StringRow, stringRow);
	keyBody->TBL_StringRow = obstack_base(&keyBody->o_StringRow);	//最後にobstack_finish()する手間を省くために毎回アドレスを取得する事にした。
	keyBody->nStringRow++;
	return keyBody;
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
ST_KeyBody* KeyBody_addBlobRow(ST_KeyBody* keyBody, ST_BlobRow* blobRow) {
	int name = blobRow->name;
    //{{進捗表示用
	if(!opt_q) {
		newMsgLen = fprintf(stderr, "\rpass1 %s", num2str(name));
		if(oldMsgLen > newMsgLen) { fprintf(stderr, "%*s", oldMsgLen - newMsgLen, ""); } oldMsgLen = newMsgLen;
	}
    //}}進捗表示用
	//同じ階層に存在する同じ名前のBLOBを検索する。(登録してみて失敗したら存在すると見なす。)
	//尚、キーと値と文字列とBLOBは別の名前空間と考えるので、ここではキーや値や文字列は検索しない。
	if(ght_insert(keyBody->ht_BlobRow, blobRow, sizeof name, &name)) {
		ST_BlobRow* blobRow2 = ght_get(keyBody->ht_BlobRow, sizeof name, &name);
		if(!blobRow2) { DIE(); }	//バグ
		//同じ階層に同じ名前で異なるデータのBLOBが存在してはいけない。エラーとする。
		//ただし、同じ階層に同じ名前で同じデータのBLOBは存在していても構わない。
		//つまり単純に二回目以降の同じ(BLOB:名前=データ)の定義は無視すれば良い。(格納してはいけない事に注意せよ)
		if(blobRow->blob == blobRow2->blob) { return keyBody; }	//ここまで	//BLOBデータはインターンしてあるのでポインタ比較可能です。
		yyerror("同じ階層に同じ名前で異なるデータのBLOBが存在してはいけません。(%s)", num2str(name));
		yynerrs++;	//呼び出し元(main)に知らせるために
		//全てのセマンティクスエラーを報告するために継続する。
	}
	if(keyBody->nBlobRow > UINT16_MAX) {
		yyerror("BLOBの数が制限を超えました。(%s)", num2str(name));
		DIE();	//継続しても無駄なので終了する。
	}
	//高速化のためにo_BlobRowを使ってTBL_BlobRowを伸張する事にした。
	                   obstack_ptr_grow(&keyBody->o_BlobRow, blobRow);
	keyBody->TBL_BlobRow = obstack_base(&keyBody->o_BlobRow);	//最後にobstack_finish()する手間を省くために毎回アドレスを取得する事にした。
	keyBody->nBlobRow++;
	return keyBody;
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
ST_Key* Key_new(ST_NameRow* nameRow, ST_KeyBody* keyBody) {
	ST_Key* key = calloc(1, sizeof(ST_Key));
	if(!key) { DIE(); }
	key->nameRow	= nameRow;
	key->keyBody	= keyBody;
	return key;
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
ST_NameRow* NameRow_new(int name) {
	ST_NameRow* nameRow = calloc(1, sizeof(ST_NameRow));
	if(!nameRow) { DIE(); }
	nameRow->line	= yylineno;	//行番号。エラーメッセージのためだけに使用する。
	nameRow->name	= name;
	return nameRow;
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
ST_ValueRow* ValueRow_new(int name, int data) {
	ST_ValueRow* valueRow = calloc(1, sizeof(ST_ValueRow));
	if(!valueRow) { DIE(); }
	valueRow->line	= yylineno;	//行番号。エラーメッセージのためだけに使用する。
	valueRow->name	= name;
	valueRow->data	= data;
	return valueRow;
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
ST_StringRow* StringRow_new(int name, char* s) {
	ST_StringRow* stringRow = calloc(1, sizeof(ST_StringRow));
	if(!stringRow) { DIE(); }
	stringRow->line	= yylineno;	//行番号。エラーメッセージのためだけに使用する。
	stringRow->name	= name;
	stringRow->s	= s;
	return stringRow;
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
ST_BlobRow* BlobRow_new(int name, ST_Blob* blob) {
	ST_BlobRow* blobRow = calloc(1, sizeof(ST_BlobRow));
	if(!blobRow) { DIE(); }
	blobRow->line	= yylineno;	//行番号。エラーメッセージのためだけに使用する。
	blobRow->name	= name;
	blobRow->blob	= blob;
	return blobRow;
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
int Name_newExpr(int expr) {
	if((unsigned)expr > UINT16_MAX) {	//名前の数値は16ビットまで
		yyerror("名前が範囲外です。(%d)", expr);
		yynerrs++;	//呼び出し元(main)に知らせるために
		//全てのセマンティクスエラーを報告するために継続する。
		expr = 0;	//処理を継続するために、範囲内の仮の値に置き換える。
	}
	//符号無し16ビット値の内、明示的な数値として使用されたビットに1を立てる。
	if((unsigned)expr <= UINT16_MAX) {
		TBL_UsedExpr[expr/32] |= (1<<(expr%32));
	}
	return expr;
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
int Name_newLabel(char* label) {
	//名前として使用されたラベルを登録する。
	int iLabel;
	for(iLabel = 0; iLabel < nLabel; iLabel++) {
		if(!strcmp(TBL_Label[iLabel], label)) { break; }	//既に登録されていたら抜ける。		//ここではfnLabCmpを使用してはいけない。文字列が厳密に一致しているかを判断する目的だから。
	}
	if(iLabel == nLabel) {	//まだ登録されていなければ…
		if(nLabel > UINT16_MAX) {
			yyerror("ラベル定義数が制限を超えました。(%s)", label);
			DIE();	//継続しても無駄なので終了する。
		}
		TBL_Label[nLabel++] = label;
	}
	//文字列を数値に変換して返す。
	return str2num(label);
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
int Data_newExpr(int expr) {
	if((unsigned)expr >= (1<<24)) {		//データの数値は24ビットまで
		yyerror("データが範囲外です。(%d)", expr);
		yynerrs++;	//呼び出し元(main)に知らせるために
		//全てのセマンティクスエラーを報告するために継続する。
		expr = 0;	//処理を継続するために、範囲内の仮の値に置き換える。
	}
	//符号無し16ビット値の内、明示的な数値として使用されたビットに1を立てる。
	if((unsigned)expr <= UINT16_MAX) {
		TBL_UsedExpr[expr/32] |= (1<<(expr%32));
	}
	return expr;
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
int Data_newLabel(char* label) {
	//文字列を数値に変換して返す。
	return str2num(label);
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
int Bit_new(int expr) {
	if((unsigned)expr > 1) {
		yyerror("bit値が範囲外です。(%d)", expr);
		yynerrs++;	//呼び出し元(main)に知らせるために
		//全てのセマンティクスエラーを報告するために継続する。
		expr = 0;	//処理を継続するために、範囲内の仮の値に置き換える。
	}
	return expr;
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
int Byte_new(int expr) {
	if((unsigned)expr > UINT8_MAX) {
		yyerror("byte値が範囲外です。(%d)", expr);
		yynerrs++;	//呼び出し元(main)に知らせるために
		//全てのセマンティクスエラーを報告するために継続する。
		expr = 0;	//処理を継続するために、範囲内の仮の値に置き換える。
	}
	return expr;
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
int Half_new(int expr) {
	if((unsigned)expr > UINT16_MAX) {
		yyerror("half値が範囲外です。(%d)", expr);
		yynerrs++;	//呼び出し元(main)に知らせるために
		//全てのセマンティクスエラーを報告するために継続する。
		expr = 0;	//処理を継続するために、範囲内の仮の値に置き換える。
	}
	return expr;
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
//{{2017/02/16追加:BLOBのデータ定義に'exp10'のプレフィクス指定を追加しました。
int Exp10_new(int expr) {
	//整数値を、指数表現にエンコードする。
	int retval = Exp10_Encode(expr);
	//エンコード出来なければ…
	if(retval == -1) {
		yyerror("exp10値が範囲外です。(%d)", expr);
		yynerrs++;	//呼び出し元(main)に知らせるために
		//全てのセマンティクスエラーを報告するために継続する。
		retval = 0;	//処理を継続するために、範囲内の仮の値に置き換える。
	}
	return retval;
}
//}}2017/02/16追加:BLOBのデータ定義に'exp10'のプレフィクス指定を追加しました。
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
char* String_intern(char* s) {
	//ht_Stringに登録済みか? (登録してみて失敗したら存在すると見なす。)
	int n = strlen(s) + 1/*nul*/;
	if(ght_insert(ht_String, s, n, s)) {
		//登録済みだった場合…
		char* s2 = ght_get(ht_String, n, s);	//ht_Stringに登録されているポインタはTBL_String[]に登録されているポインタと同じ物なのでht_Stringから取得して問題無い。
		if(!s2) { DIE(); }	//バグ
		return s2;		//登録済みの文字列を返す。今回渡された's'を返さないように注意せよ。
	}
	//未登録だった場合…
	if(nString >= String_Max) {
		yyerror("文字列データの数が制限を超えました。(%s)", s);
		DIE();	//継続しても無駄なので終了する。
	}
	TBL_String[nString++] = s;	//ht_Stringには上の処理で登録成功したので、TBL_String[]にも追加する。
	return s;
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
ST_Blob* Blob_newBit(int bit) {
	ST_Blob* blob = calloc(1, sizeof(ST_Blob));
	if(!blob) { DIE(); }
	obstack_init(&blob->o_data);
	return Blob_addBit(blob, bit);
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
ST_Blob* Blob_addBit(ST_Blob* blob, int bit) {
	//最終バイトの残りビット数が無ければ…
	if(!blob->bitsLeft) {
		if((blob->size + 1) > (1<<24)) {	//BLOBのサイズは(1<<24)バイトまで
			yyerror("BLOBデータのサイズが制限を超えました。");
			DIE();	//継続しても無駄なので終了する。
		}
		             obstack_grow(&blob->o_data, &bit, 1);		//符号無し8ビットLE、LSB先行
		blob->data = obstack_base(&blob->o_data);	//最後にobstack_finish()する手間を省くために毎回アドレスを取得する事にした。
		blob->size += 1;
		blob->bitsLeft = 7;	//最終バイトの残りビット数を残り7ビットとする。
	//最終バイトの残りビット数が有れば…
	} else {
		blob->data[blob->size - 1] |= (bit << (8 - blob->bitsLeft));	//符号無し8ビットLE、LSB先行
		blob->bitsLeft--;	//最終バイトの残りビット数を減らす。
	}
	return blob;
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
ST_Blob* Blob_newByte(int byte) {
	ST_Blob* blob = calloc(1, sizeof(ST_Blob));
	if(!blob) { DIE(); }
	obstack_init(&blob->o_data);
	return Blob_addByte(blob, byte);
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
ST_Blob* Blob_addByte(ST_Blob* blob, int byte) {
	blob->bitsLeft = 0;	//このフィールドが1～7の時にbyte,half,word,fp16,fp32,fp64の格納を行う場合はバイト境界まで進める(即ち確実にbitsLeft=0とする)。
	if((blob->size + 1) > (1<<24)) {	//BLOBのサイズは(1<<24)バイトまで
		yyerror("BLOBデータのサイズが制限を超えました。");
		DIE();	//継続しても無駄なので終了する。
	}
	             obstack_grow(&blob->o_data, &byte, 1);	//符号無し8ビットLE
	blob->data = obstack_base(&blob->o_data);	//最後にobstack_finish()する手間を省くために毎回アドレスを取得する事にした。
	blob->size += 1;
	return blob;
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
ST_Blob* Blob_newHalf(int half) {
	ST_Blob* blob = calloc(1, sizeof(ST_Blob));
	if(!blob) { DIE(); }
	obstack_init(&blob->o_data);
	return Blob_addHalf(blob, half);
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
ST_Blob* Blob_addHalf(ST_Blob* blob, int half) {
	blob->bitsLeft = 0;	//このフィールドが1～7の時にbyte,half,word,fp16,fp32,fp64の格納を行う場合はバイト境界まで進める(即ち確実にbitsLeft=0とする)。
	if((blob->size + 2) > (1<<24)) {	//BLOBのサイズは(1<<24)バイトまで
		yyerror("BLOBデータのサイズが制限を超えました。");
		DIE();	//継続しても無駄なので終了する。
	}
	             obstack_grow(&blob->o_data, &half, 2);	//符号無し16ビットLE
	blob->data = obstack_base(&blob->o_data);	//最後にobstack_finish()する手間を省くために毎回アドレスを取得する事にした。
	blob->size += 2;
	return blob;
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
ST_Blob* Blob_newWord(int word) {
	ST_Blob* blob = calloc(1, sizeof(ST_Blob));
	if(!blob) { DIE(); }
	obstack_init(&blob->o_data);
	return Blob_addWord(blob, word);
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
ST_Blob* Blob_addWord(ST_Blob* blob, int word) {
	blob->bitsLeft = 0;	//このフィールドが1～7の時にbyte,half,word,fp16,fp32,fp64の格納を行う場合はバイト境界まで進める(即ち確実にbitsLeft=0とする)。
	if((blob->size + 4) > (1<<24)) {	//BLOBのサイズは(1<<24)バイトまで
		yyerror("BLOBデータのサイズが制限を超えました。");
		DIE();	//継続しても無駄なので終了する。
	}
	             obstack_grow(&blob->o_data, &word, 4);	//符号無し32ビットLE
	blob->data = obstack_base(&blob->o_data);	//最後にobstack_finish()する手間を省くために毎回アドレスを取得する事にした。
	blob->size += 4;
	return blob;
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
ST_Blob* Blob_newFp16(double d) {
	ST_Blob* blob = calloc(1, sizeof(ST_Blob));
	if(!blob) { DIE(); }
	obstack_init(&blob->o_data);
	return Blob_addFp16(blob, d);
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
ST_Blob* Blob_addFp16(ST_Blob* blob, double d) {
	fp16 tmp = truncdfhf2(d);
	blob->bitsLeft = 0;	//このフィールドが1～7の時にbyte,half,word,fp16,fp32,fp64の格納を行う場合はバイト境界まで進める(即ち確実にbitsLeft=0とする)。
	if((blob->size + 4) > (1<<24)) {	//BLOBのサイズは(1<<24)バイトまで
		yyerror("BLOBデータのサイズが制限を超えました。");
		DIE();	//継続しても無駄なので終了する。
	}
	             obstack_grow(&blob->o_data, &tmp, 2);	//半精度浮動小数点数LE
	blob->data = obstack_base(&blob->o_data);	//最後にobstack_finish()する手間を省くために毎回アドレスを取得する事にした。
	blob->size += 2;
	return blob;
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
ST_Blob* Blob_newFp32(double d) {
	ST_Blob* blob = calloc(1, sizeof(ST_Blob));
	if(!blob) { DIE(); }
	obstack_init(&blob->o_data);
	return Blob_addFp32(blob, d);
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
ST_Blob* Blob_addFp32(ST_Blob* blob, double d) {
	float tmp = d;
	blob->bitsLeft = 0;	//このフィールドが1～7の時にbyte,half,word,fp16,fp32,fp64の格納を行う場合はバイト境界まで進める(即ち確実にbitsLeft=0とする)。
	if((blob->size + 4) > (1<<24)) {	//BLOBのサイズは(1<<24)バイトまで
		yyerror("BLOBデータのサイズが制限を超えました。");
		DIE();	//継続しても無駄なので終了する。
	}
	             obstack_grow(&blob->o_data, &tmp, 4);	//単精度浮動小数点数LE
	blob->data = obstack_base(&blob->o_data);	//最後にobstack_finish()する手間を省くために毎回アドレスを取得する事にした。
	blob->size += 4;
	return blob;
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
ST_Blob* Blob_newFp64(double d) {
	ST_Blob* blob = calloc(1, sizeof(ST_Blob));
	if(!blob) { DIE(); }
	obstack_init(&blob->o_data);
	return Blob_addFp64(blob, d);
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
ST_Blob* Blob_addFp64(ST_Blob* blob, double d) {
	blob->bitsLeft = 0;	//このフィールドが1～7の時にbyte,half,word,fp16,fp32,fp64の格納を行う場合はバイト境界まで進める(即ち確実にbitsLeft=0とする)。
	if((blob->size + 8) > (1<<24)) {	//BLOBのサイズは(1<<24)バイトまで
		yyerror("BLOBデータのサイズが制限を超えました。");
		DIE();	//継続しても無駄なので終了する。
	}
	             obstack_grow(&blob->o_data, &d, 8);	//倍精度浮動小数点数LE
	blob->data = obstack_base(&blob->o_data);	//最後にobstack_finish()する手間を省くために毎回アドレスを取得する事にした。
	blob->size += 8;
	return blob;
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
ST_Blob* Blob_newFile(char* file) {
	ST_Blob* blob = calloc(1, sizeof(ST_Blob));
	if(!blob) { DIE(); }
	obstack_init(&blob->o_data);
	return Blob_addFile(blob, file);
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
ST_Blob* Blob_addFile(ST_Blob* blob, char* file) {
	FILE* fp = fopen(file, "rb");	//バイナリモードで開く。
	if(!fp) {
		yyerror("%s が開けません。", file);
		yynerrs++;	//呼び出し元(main)に知らせるために			//必要。YYERRORマクロ呼び出しでyynerrsは増やされないようだ。	←←←←←←←←←←■■■■重要■■■■
		return NULL;	//y.tab.yの処理でYYERRORを行う。
	}
	{
		int c;
		while((c = fgetc(fp)) != EOF) {
			blob = Blob_addByte(blob, c);
			if(!blob) { break; }	//Blob_addByte()がNULL(エラー)を返したらこのループを抜けてファイルを閉じblob(==NULL)を返す。	現在の所Blob_addByte()がNULL(エラー)を返す事は無いが、今後もしBlob_addByte()がNULL(エラー)を返すように変更した場合のために判定しておく事にした。その場合'yynerrs++'はBlob_addByte()内で行うだろうからここで'yynerrs++'する必要は無い。
		}
	}
	fclose(fp);
	return blob;
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
ST_Blob* Blob_intern(ST_Blob* blob) {
	//空のblobBodyならばエラーとする。
	// - 普通は、blobBodyの変換規則が空要素を認めていないから空のblobBodyが生成される事は無いのだが、唯一、file要素のみから成るblobBodyで,かつ,そのファイルが0バイトの時に空のblobBodyが生成されてしまう。
	//   そのようなケースはy.tab.yで検出出来ないので、Blob_intern()で検出してエラーとする事にした。
	if(!blob->size) {
		yyerror("空のblobBodyは作成出来ません。");
		yynerrs++;	//呼び出し元(main)に知らせるために			//必要。YYERRORマクロ呼び出しでyynerrsは増やされないようだ。	←←←←←←←←←←■■■■重要■■■■
		return NULL;	//y.tab.yの処理でYYERRORを行う。
	}
	//ht_Blobに登録済みか? (登録してみて失敗したら存在すると見なす。)
	if(ght_insert(ht_Blob, blob, blob->size, blob->data)) {
		//登録済みだった場合…
		ST_Blob* blob2 = ght_get(ht_Blob, blob->size, blob->data);	//ht_Blobに登録されているポインタはTBL_Blob[]に登録されているポインタと同じ物なのでht_Blobから取得して問題無い。
		if(!blob2) { DIE(); }	//バグ
		return blob2;		//登録済みのBLOBを返す。今回渡された'blob'を返さないように注意せよ。
	}
	//未登録だった場合…
	if(nBlob >= Blob_Max) {
		yyerror("BLOBデータの数が制限を超えました。");
		DIE();	//継続しても無駄なので終了する。
	}
	TBL_Blob[nBlob++] = blob;	//ht_Blobには上の処理で登録成功したので、TBL_Blob[]にも追加する。
	return blob;
}
/*---------------------------------------------------------------------------*/
//2パス目の処理
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
static int Label_compar(const void* _x, const void* _y) {
	char* x = *(char**)_x;
	char* y = *(char**)_y;
	if(x[0] == '.') {
		if(y[0] == '.') {
			return (*fnLabCmp)(x, y);	//xもyもローカルラベルならば、文字列比較する。
		} else {
			return  1;			//xがローカルラベルでyがグローバルラベルならば、xを後ろにする。
		}
	} else {
		if(y[0] == '.') {
			return -1;			//xがグローバルラベルでyがローカルラベルならば、yを後ろにする。
		} else {
			return (*fnLabCmp)(x, y);	//xもyもグローバルラベルならば、文字列比較する。
		}
	}
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
static int String_compar(const void* _x, const void* _y) {
	char* x = *(char**)_x;
	char* y = *(char**)_y;
	return strcmp(x, y);				//ここではfnLabCmpを使用しない事にした。ラベルの比較ではなく文字列データの比較だからです。文字列データを自然順アルゴリズムで比較するのが間違いと言う訳ではないが、万一空白のみの違いで大小が曖昧になってしまう危険を避けるために、単純にstrcmp()を使用する方が安全だろう。
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
static int Blob_compar(const void* _x, const void* _y) {
	ST_Blob* x = *(ST_Blob**)_x;
	ST_Blob* y = *(ST_Blob**)_y;
	if(x->size < y->size) { return -1; }		//┬小さいBLOBを優先する。一番大きなBLOBを最後に出力する方が配列要素からBLOBの先頭への距離が小さく抑えられる可能性が高い。(僅かではあるが…)
	if(x->size > y->size) { return  1; }		//┘
	return memcmp(x->data, y->data, x->size);	//サイズが同じならばBLOBの内容で比較する。デバッグ時の見易さのためだけでありこの比較条件に動作上の意味は無い。
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
static int getLabelExpr(int label) {
	char* str;
	int iLabel;
	if(!isnumstr(label)) { DIE(); }	//バグ
	str = num2str(label);
	for(iLabel = 0; iLabel < nLabel; iLabel++) {
		if(!strcmp(TBL_Label[iLabel], str)) {	//ここではfnLabCmpを使用してはいけない。文字列が厳密に一致しているかを判断する目的だから。
			return TBL_LabelExpr[iLabel];	//見つかったらラベルに割り当てた数値を返す。
		}
	}
	return -1;	//見つからなければ(-1)を返す。
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
static int Key_compar(const void* _x, const void* _y) {
	ST_Key* x = *(ST_Key**)_x;
	ST_Key* y = *(ST_Key**)_y;
	if(x->nameRow->name < y->nameRow->name) { return -1; }
	if(x->nameRow->name > y->nameRow->name) { return  1; }
	                                        { return  0; }
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
static int ValueRow_compar(const void* _x, const void* _y) {
	ST_ValueRow* x = *(ST_ValueRow**)_x;
	ST_ValueRow* y = *(ST_ValueRow**)_y;
	if(x->name < y->name) { return -1; }
	if(x->name > y->name) { return  1; }
	                      { return  0; }
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
static int StringRow_compar(const void* _x, const void* _y) {
	ST_StringRow* x = *(ST_StringRow**)_x;
	ST_StringRow* y = *(ST_StringRow**)_y;
	if(x->name < y->name) { return -1; }
	if(x->name > y->name) { return  1; }
	                      { return  0; }
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
static int BlobRow_compar(const void* _x, const void* _y) {
	ST_BlobRow* x = *(ST_BlobRow**)_x;
	ST_BlobRow* y = *(ST_BlobRow**)_y;
	if(x->name < y->name) { return -1; }
	if(x->name > y->name) { return  1; }
	                      { return  0; }
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
static void resolveLabel(ST_KeyBody* keyBody) {
	int iKey, iValueRow, iStringRow, iBlobRow;
	//--- キー ---
	//キーの名前を数値に変換する。
	for(iKey = 0; iKey < keyBody->nKey; iKey++) {
		int name = keyBody->TBL_Key[iKey]->nameRow->name;
		yylineno = keyBody->TBL_Key[iKey]->nameRow->line;	//yyerrorのために
	    //{{進捗表示用
		if(!opt_q) {
			newMsgLen = fprintf(stderr, "\rpass2 %s", num2str(name));
			if(oldMsgLen > newMsgLen) { fprintf(stderr, "%*s", oldMsgLen - newMsgLen, ""); } oldMsgLen = newMsgLen;
		}
	    //}}進捗表示用
		//キーの名前
		if(isnumstr(name)) {
			name = getLabelExpr(name);
			if(name < 0) { DIE(); }	//名前は1パス目で登録しているはず。ここで止まったらバグ。
			keyBody->TBL_Key[iKey]->nameRow->name = name;
		}
		//サブキー(再帰)
		resolveLabel(keyBody->TBL_Key[iKey]->keyBody);
	}
	//キー配列を名前の数値の昇順にソートする。
	qsort(keyBody->TBL_Key, keyBody->nKey, sizeof(ST_Key*), Key_compar);
	//--- 値 ---
	//値の名前とデータを数値に変換する。
	for(iValueRow = 0; iValueRow < keyBody->nValueRow; iValueRow++) {
		int name = keyBody->TBL_ValueRow[iValueRow]->name;
		int data = keyBody->TBL_ValueRow[iValueRow]->data;
		yylineno = keyBody->TBL_ValueRow[iValueRow]->line;	//yyerrorのために
	    //{{進捗表示用
		if(!opt_q) {
			newMsgLen = fprintf(stderr, "\rpass2 %s", num2str(name));
			if(oldMsgLen > newMsgLen) { fprintf(stderr, "%*s", oldMsgLen - newMsgLen, ""); } oldMsgLen = newMsgLen;
		}
	    //}}進捗表示用
		//値の名前
		if(isnumstr(name)) {
			name = getLabelExpr(name);
			if(name < 0) { DIE(); }	//名前は1パス目で登録しているはず。ここで止まったらバグ。
			keyBody->TBL_ValueRow[iValueRow]->name = name;
		}
		//値のデータ
		if(isnumstr(data)) {
			data = getLabelExpr(data);
			if(data < 0) {
				yyerror("ラベルが定義されていません。(%s)", num2str(keyBody->TBL_ValueRow[iValueRow]->data));
				yynerrs++;	//呼び出し元(main)に知らせるために
				//全てのセマンティクスエラーを報告するために継続する。
				data = 0;	//処理を継続するために、範囲内の仮の値に置き換える。
			}
			keyBody->TBL_ValueRow[iValueRow]->data = data;
		}
	}
	//値の配列を名前の数値の昇順にソートする。
	qsort(keyBody->TBL_ValueRow, keyBody->nValueRow, sizeof(ST_ValueRow*), ValueRow_compar);
	//--- 文字列 ---
	//文字列の名前を数値に変換する。
	for(iStringRow = 0; iStringRow < keyBody->nStringRow; iStringRow++) {
		int name = keyBody->TBL_StringRow[iStringRow]->name;
		yylineno = keyBody->TBL_StringRow[iStringRow]->line;	//yyerrorのために
	    //{{進捗表示用
		if(!opt_q) {
			newMsgLen = fprintf(stderr, "\rpass2 %s", num2str(name));
			if(oldMsgLen > newMsgLen) { fprintf(stderr, "%*s", oldMsgLen - newMsgLen, ""); } oldMsgLen = newMsgLen;
		}
	    //}}進捗表示用
		//文字列の名前
		if(isnumstr(name)) {
			name = getLabelExpr(name);
			if(name < 0) { DIE(); }	//名前は1パス目で登録しているはず。ここで止まったらバグ。
			keyBody->TBL_StringRow[iStringRow]->name = name;
		}
	}
	//文字列の配列を名前の数値の昇順にソートする。
	qsort(keyBody->TBL_StringRow, keyBody->nStringRow, sizeof(ST_StringRow*), StringRow_compar);
	//--- BLOB ---
	//BLOBの名前を数値に変換する。
	for(iBlobRow = 0; iBlobRow < keyBody->nBlobRow; iBlobRow++) {
		int name = keyBody->TBL_BlobRow[iBlobRow]->name;
		yylineno = keyBody->TBL_BlobRow[iBlobRow]->line;	//yyerrorのために
	    //{{進捗表示用
		if(!opt_q) {
			newMsgLen = fprintf(stderr, "\rpass2 %s", num2str(name));
			if(oldMsgLen > newMsgLen) { fprintf(stderr, "%*s", oldMsgLen - newMsgLen, ""); } oldMsgLen = newMsgLen;
		}
	    //}}進捗表示用
		//BLOBの名前
		if(isnumstr(name)) {
			name = getLabelExpr(name);
			if(name < 0) { DIE(); }	//名前は1パス目で登録しているはず。ここで止まったらバグ。
			keyBody->TBL_BlobRow[iBlobRow]->name = name;
		}
	}
	//BLOBの配列を名前の数値の昇順にソートする。
	qsort(keyBody->TBL_BlobRow, keyBody->nBlobRow, sizeof(ST_BlobRow*), BlobRow_compar);
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
static void pass2(int bOptimize) {
	//ラベル定義を名前の昇順にソートする。				//┬必須
	qsort(TBL_Label, nLabel, sizeof(char*), Label_compar);		//┘
	//文字列データを昇順にソートする。				//┬こちらは必須では無いが、デバッグのし易さのためにこうする事にした。
	qsort(TBL_String, nString, sizeof(char*), String_compar);	//┘
	//BLOBデータを昇順にソートする。				//┬こちらは必須では無いが、デバッグのし易さのためにこうする事にした。
	qsort(TBL_Blob, nBlob, sizeof(ST_Blob*), Blob_compar);		//┘
	//明示的な数値として使用されなかった数値をラベルに割り当てる。
	//[note]
	//│「明示的な数値として使用されなかった数値をラベルに割り当てる」ようにした理由は、偶然のバグを検出し易くするためです。
	//│厳密には、同一階層のキーの名前の数値にさえ重複しなければどんな値でも良く、全体で使用されていない数値にする必要は無いのですが、
	//│もし、値のデータとしてキーの数値を指定する使い方をしている場合に、(例:'valueName = keyName')、
	//│そういう使い方でない、普通のデータを持っている値(例:'valueName = parameter')のparameterがkeyNameに一致して、
	//│それを使ってキー参照を行い、たまたまそれが成功すると、発見しづらいバグになります。
	//│ラベルに自動的に割り当てる数値に対して、全体で使用されていない数値を割り当てれば、少しでもバグを発見し易くなると思いました。
	//│もっとも実際には、ほとんどの場合はキー名の数値は自動割当でなく、ユーザーが明示的に(外部の#define等で)指定するでしょうから、
	//│この対策が有効に働くのはごく限られたケースだとは思いますが、少なくとも害にはならないと思います。
	if(nLabel) {								//ラベルが1個以上使用されていたら…
		int iExpr, iLabel = 0;
		for(iExpr = /*0*/1; iExpr <= UINT16_MAX; iExpr++) {		//1～UINT16_MAXの各数値について…	/*'0'はもし使用されていなくてもラベルに割り当てない事にした。判り易さのためだけであり必須の仕様ではない。*/
			if(!(TBL_UsedExpr[iExpr/32] & (1<<(iExpr%32)))) {	//この数値が明示的に使用されていなければ…
				TBL_LabelExpr[iLabel++] = iExpr;		//ラベルに数値を割り当てる。
				if(iLabel >= nLabel) { break; }			//全てのラベルに値を割り当てたら抜ける。
			}
		}
		if(iLabel < nLabel) {
			yyerror("ラベルに割り当てるための未使用の数値が不足しています。");
			DIE();	//継続しても無駄なので終了する。
		}
	}
	//キーの名前と,値の名前と,値のラベル型データと,文字列の名前と,BLOBの名前を、数値に変換して、その数値の昇順にソートする。
	resolveLabel(g_keyBody);
	//最適化を行う。
	if(bOptimize) {
		//■最適化処理1
		//ルートのキーボディから階層を辿って、空のキー(値もキーも無いキー)を削除する。
		// - ただし、ルートのキーボディ自身は空になっても削除しない。
		//   ルートのキーボディを削除すると、出力処理が行えないから。
		optimize1();
		//■最適化処理2
		//同じ内容のキーボディを統合する。
		// - 同じ内容のキーボディとは、以下の条件を満たすキーボディの事です:
		//   ・値の配列が一致する事。値の配列をバイナリ比較する事で判断できます。
		//   ・キーの配列が一致する事。キーボディはリダイレクト後のポインタで判断する必要が有るのでバイナリ比較してはいけません。
		optimize2();
	}
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
static int getKeyBodyCount(int count, ST_KeyBody* keyBody) {
	int iKey;
	//最適化されたキーボディは数えない。
	if(keyBody->redir) { return count; }
	//サブキー(再帰)
	for(iKey = 0; iKey < keyBody->nKey; iKey++) {
		count = getKeyBodyCount(count, keyBody->TBL_Key[iKey]->keyBody);
	}
	return count + 1/*自分自身の分*/;
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
static void optimizeKeyBody1(ST_KeyBody* keyBody) {
	int iKey, nKey = 0;
	//各キーについて…
	for(iKey = 0; iKey < keyBody->nKey; iKey++) {
		ST_Key* key = keyBody->TBL_Key[iKey];
		//このキーのキーボディを最適化する。
		optimizeKeyBody1(key->keyBody);
		//このキーのキーボディが空でなければ、登録する。
		//このキーのキーボディが空になったら、登録しない(=削除する)。
		if(key->keyBody->nKey ||	//キーか、
		   key->keyBody->nValueRow ||	//値か、
		   key->keyBody->nStringRow ||	//文字列か、
		   key->keyBody->nBlobRow) {	//BLOBの、少なくともどれかが1個以上有れば空ではない。
			keyBody->TBL_Key[nKey++] = key;
		}
	}
	//このキーボディのキーの数を更新する。
	keyBody->nKey = nKey;
    //{{進捗表示用
	if(keyBody->nKey ||		//キーか、
	   keyBody->nValueRow ||	//値か、
	   keyBody->nStringRow ||	//文字列か、
	   keyBody->nBlobRow) {		//BLOBの、少なくともどれかが1個以上有れば空ではない。
		g_nKeyBodyNow++;	//最適化後に生き残っているキーボディ数(進捗)を増やす
		if(!opt_q) {
			newMsgLen = fprintf(stderr, "\rpass2 最適化1 %d/%d", g_nKeyBodyNow, g_nKeyBodyAll);
			if(oldMsgLen > newMsgLen) { fprintf(stderr, "%*s", oldMsgLen - newMsgLen, ""); } oldMsgLen = newMsgLen;
		}
	}
    //}}進捗表示用
}
//■最適化処理1について
//│'-d'オプションを指定しなければ、最適化処理1を行います。(デフォルト)
//│最適化処理1は、ルートのキーボディから階層を辿って、空のキー(値もキーも無いキー)を削除します。
//│ただし、以下の点について注意してください。
//│---
//│最適化処理1によって削除されたキーのラベルも、'#define'には出力されます。たとえば、
//││Key1
//││end
//│というキーを定義した場合、このキーは最適化によって削除されますが、'#define Key1'は出力されます。
//│このようにした理由は、最適化の影響によって'#define'に出力されるラベルが無くなると、アプリケーションプログラムがコンパイルできなくなる可能性が有るからです。
//│アプリケーションプログラムは、最適化されたキーをREG_open_key()で開こうとした時に開けない事で、最適化によって削除された事を(実行時に)判断してください。
//│---
//│最適化処理1によって削除されたキーのラベルを参照している値のデータが存在する場合、そのデータは残ります。たとえば、
//││Key1
//││  x=Key2
//││end
//││Key2
//││end
//│という値を定義した場合、出力されるデータ構造は以下のようになります。
//││#define Key1 1
//││#define Key2 2
//││1
//││ 10=2
//│Key2が削除されるが、Key2を参照している値のデータは残っている事に注意してください。
//│この場合も前述のケースと同様に、アプリケーションプログラムは、参照先のキーをREG_open_key()で開こうとした時に開けない事で、最適化によって削除された事を(実行時に)判断してください。
//│---
static void optimize1() {
    //{{進捗表示用
	g_nKeyBodyAll = getKeyBodyCount(0, g_keyBody);	//最適化開始前の全キーボディ数
	g_nKeyBodyNow = 0;				//最適化後に生き残っているキーボディ数(進捗)
	if(!opt_q) {
		newMsgLen = fprintf(stderr, "\rpass2 最適化1 %d/%d", g_nKeyBodyNow, g_nKeyBodyAll);
		if(oldMsgLen > newMsgLen) { fprintf(stderr, "%*s", oldMsgLen - newMsgLen, ""); } oldMsgLen = newMsgLen;
	}
    //}}進捗表示用
	//ルートのキーボディから階層を辿って、空のキー(値もキーも無いキー)を削除する。
	optimizeKeyBody1(g_keyBody);
    //{{進捗表示用
	if(!opt_q) {
		newMsgLen = fprintf(stderr, "\rpass2 最適化1 %d/%d", g_nKeyBodyNow, g_nKeyBodyAll);
		if(oldMsgLen > newMsgLen) { fprintf(stderr, "%*s", oldMsgLen - newMsgLen, ""); } putc('\n', stderr); oldMsgLen = 0;
	}
    //}}進捗表示用
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
static int setKeyBodyIndex(int index, ST_KeyBody* keyBody) {
	int iKey;
	//このキーボディのインデクスを格納する。
	keyBody->index = index;
	//このキーボディの分、インデクスを進める。
	index++;
	//サブキー(再帰)
	for(iKey = 0; iKey < keyBody->nKey; iKey++) {
		index = setKeyBodyIndex(index, keyBody->TBL_Key[iKey]->keyBody);
	}
	return index;
}
//このキーボディが最適化されているならば、統合先のキーボディに対して再帰する。
//このキーボディが最適化されていなければ、このキーボディ自身を返す。
static ST_KeyBody* redirKeyBody(ST_KeyBody* keyBody) {
	//リダイレクト先が有ればリダイレクト先に対して再帰する。リダイレクト先が無ければそのまま返す。
	return keyBody->redir ? redirKeyBody(keyBody->redir) : keyBody;
}
//keyBody1をルートとするツリーの中で、keyBody2と同じ内容のキーボディを検索し、有ればそのポインタを返す。無ければNULLを返す。
static ST_KeyBody* searchKeyBody(ST_KeyBody* keyBody1/*比較先*/, ST_KeyBody* keyBody2/*比較元*/) {
	int iKey, iValueRow, iStringRow, iBlobRow;
	if(keyBody2->redir) { DIE(); }	//バグ。optimizeKeyBody2()からの呼び出しの流れで、既に最適化されたキーボディに対してこの関数は呼ばれないはずである。
	//keyBody2に対してg_keyBodyから深さ優先探索で検索して来てkeyBody2に到達したら、マッチしなかった事になるのでNULLを返す。
	if(keyBody1->index >= keyBody2->index) { return NULL; }
	//最適化されたキーボディにはマッチしないし、サブキーの検索も行わない。
	// - もし一致するならばもっと早く、最適化されたキーのリダイレクト先キーボディにマッチしているであろう。
	//   最適化は深さ優先探索で前方のキーボディに対して行われるから。
	if(keyBody1->redir) { return NULL; }
	//キーの配列が一致するか?
	if(keyBody1->nKey !=
	   keyBody2->nKey) { goto L_SEARCH_SUB_KEY; }						//このキーボディ自身に一致しなければサブキーの検索へ。
	for(iKey = 0; iKey < keyBody1->nKey; iKey++) {
		if(keyBody1->TBL_Key[iKey]->nameRow->name !=
		   keyBody2->TBL_Key[iKey]->nameRow->name) { goto L_SEARCH_SUB_KEY; }		//このキーボディ自身に一致しなければサブキーの検索へ。
		if(redirKeyBody(keyBody1->TBL_Key[iKey]->keyBody) !=											//┬キー配列の比較は比較先,比較元ともリダイレクト結果で比較する事に注意せよ。
		   redirKeyBody(keyBody2->TBL_Key[iKey]->keyBody)) { goto L_SEARCH_SUB_KEY; }	//このキーボディ自身に一致しなければサブキーの検索へ。	//┘
	}
	//値の配列が一致するか?
	if(keyBody1->nValueRow !=
	   keyBody2->nValueRow) { goto L_SEARCH_SUB_KEY; }					//このキーボディ自身に一致しなければサブキーの検索へ。
	for(iValueRow = 0; iValueRow < keyBody1->nValueRow; iValueRow++) {
		if(keyBody1->TBL_ValueRow[iValueRow]->name !=
		   keyBody2->TBL_ValueRow[iValueRow]->name) { goto L_SEARCH_SUB_KEY; }		//このキーボディ自身に一致しなければサブキーの検索へ。
		if(keyBody1->TBL_ValueRow[iValueRow]->data !=
		   keyBody2->TBL_ValueRow[iValueRow]->data) { goto L_SEARCH_SUB_KEY; }		//このキーボディ自身に一致しなければサブキーの検索へ。
	}
	//文字列の配列が一致するか?
	if(keyBody1->nStringRow !=
	   keyBody2->nStringRow) { goto L_SEARCH_SUB_KEY; }					//このキーボディ自身に一致しなければサブキーの検索へ。
	for(iStringRow = 0; iStringRow < keyBody1->nStringRow; iStringRow++) {
		if(keyBody1->TBL_StringRow[iStringRow]->name !=
		   keyBody2->TBL_StringRow[iStringRow]->name) { goto L_SEARCH_SUB_KEY; }	//このキーボディ自身に一致しなければサブキーの検索へ。
		if(keyBody1->TBL_StringRow[iStringRow]->s !=												//┬文字列データはインターンしてあるのでポインタ比較可能です。
		   keyBody2->TBL_StringRow[iStringRow]->s) { goto L_SEARCH_SUB_KEY; }		//このキーボディ自身に一致しなければサブキーの検索へ。	//┘
	}
	//BLOBの配列が一致するか?
	if(keyBody1->nBlobRow !=
	   keyBody2->nBlobRow) { goto L_SEARCH_SUB_KEY; }					//このキーボディ自身に一致しなければサブキーの検索へ。
	for(iBlobRow = 0; iBlobRow < keyBody1->nBlobRow; iBlobRow++) {
		if(keyBody1->TBL_BlobRow[iBlobRow]->name !=
		   keyBody2->TBL_BlobRow[iBlobRow]->name) { goto L_SEARCH_SUB_KEY; }		//このキーボディ自身に一致しなければサブキーの検索へ。
		if(keyBody1->TBL_BlobRow[iBlobRow]->blob !=												//┬BLOBデータはインターンしてあるのでポインタ比較可能です。
		   keyBody2->TBL_BlobRow[iBlobRow]->blob) { goto L_SEARCH_SUB_KEY; }		//このキーボディ自身に一致しなければサブキーの検索へ。	//┘
	}
	//keyBody1とkeyBody2の内容が一致したので、keyBody1を返す。
	return keyBody1;
L_SEARCH_SUB_KEY:
	//keyBody1とkeyBody2の内容が一致しなければ、keyBody1のサブキーを探索する。
	for(iKey = 0; iKey < keyBody1->nKey; iKey++) {
		ST_KeyBody* keyBody1sub = keyBody1->TBL_Key[iKey]->keyBody;	//keyBody1のサブキーを取得する。
		if(!keyBody1sub->redir) {	//最適化されたキーボディにはマッチしないし、サブキーの検索も行わない。もし一致するならばもっと早く、最適化されたキーのリダイレクト先キーボディにマッチしているであろう。最適化は深さ優先探索で前方のキーボディに対して行われるから。
		    //{{この判断必要!!こうしないとキー配列の1個目に対して検索した時に同じキー配列内の2個目にマッチしてしまう。正しくはキー配列の1個目自身に到達したら打ち切るのが正しい。この関数の最初の方で行っているのと同様の処理をここでも行う必要が有る。
			//keyBody2に対してg_keyBodyから深さ優先探索で検索して来てkeyBody2に到達したら、マッチしなかった事になるのでNULLを返す。
			if(keyBody1sub->index >= keyBody2->index) { return NULL; }
		    //}}この判断必要!!こうしないとキー配列の1個目に対して検索した時に同じキー配列内の2個目にマッチしてしまう。正しくはキー配列の1個目自身に到達したら打ち切るのが正しい。この関数の最初の方で行っているのと同様の処理をここでも行う必要が有る。
			keyBody1sub = searchKeyBody(keyBody1sub, keyBody2);
			if(keyBody1sub) { return keyBody1sub; }	//keyBody1sub(=keyBody1のサブキー)とkeyBody2の内容が一致したので、keyBody1sub(=keyBody1のサブキー)を返す。
		}
	}
	//keyBody1をルートとするツリーの中で、keyBody2と同じ内容のキーボディが無かったので、NULLを返す。
	return NULL;
}
static void optimizeKeyBody2(ST_KeyBody* keyBody) {
	int iKey;
	//このキーボディが既に最適化されていたら、何もしない。
	if(keyBody->redir) { return; }
	//各キーについて…
	for(iKey = 0; iKey < keyBody->nKey; iKey++) {
		ST_Key* key = keyBody->TBL_Key[iKey];
		//このキーのキーボディを最適化する。
		optimizeKeyBody2(key->keyBody);
	}
	//このキーボディに一致するキーボディを検索する。有ればリダイレクト先に格納して最適化する。無ければ最適化しないままとする。
	// - ツリーを深さ優先探索して、前方に有るキーボディを検索対象とする。
	keyBody->redir = searchKeyBody(g_keyBody/*比較先*/, keyBody/*比較元*/);
	if(redirKeyBody(keyBody)->index > keyBody->index) { DIE(); }	//バグ。最適化されなければ(keyBody->redir==NULL)なので左の条件は'=='になるはず。最適化されたら左の条件は'<'になるはず。いずれにしても'>'になる事はないはずなのでもしそうなったらバグ。
    //{{進捗表示用
	if(!keyBody->redir) {		//リダイレクトされなかったら…
		g_nKeyBodyNow++;	//最適化後に生き残っているキーボディ数(進捗)を増やす
		if(!opt_q) {
			newMsgLen = fprintf(stderr, "\rpass2 最適化2 %d/%d", g_nKeyBodyNow, g_nKeyBodyAll);
			if(oldMsgLen > newMsgLen) { fprintf(stderr, "%*s", oldMsgLen - newMsgLen, ""); } oldMsgLen = newMsgLen;
		}
	}
    //}}進捗表示用
}
//■最適化処理2について
//│'-d'オプションを指定しなければ、最適化処理1を行います。(デフォルト)
//│最適化処理2は、内容が同じキーを統合します。
//│<例>
//││Key1
//││  a=b
//││end
//││Key2
//││  Key3
//││    a=b
//││  end
//││  c=d
//││end
//│Key3の中の'a=b'は、Key1の中の'a=b'に統合されます。
//│出力データ構造の中で統合されるだけであり、統合されている事を明示的に意識する必要は有りません。
//│出力データ構造の仕様に沿って参照すれば、自動的に統合先のキーを参照する事が出来ます。
//│---
static void optimize2() {
    //{{進捗表示用
	g_nKeyBodyAll = getKeyBodyCount(0, g_keyBody);	//最適化開始前の全キーボディ数
	g_nKeyBodyNow = 0;				//最適化後に生き残っているキーボディ数(進捗)
	if(!opt_q) {
		newMsgLen = fprintf(stderr, "\rpass2 最適化2 %d/%d", g_nKeyBodyNow, g_nKeyBodyAll);
		if(oldMsgLen > newMsgLen) { fprintf(stderr, "%*s", oldMsgLen - newMsgLen, ""); } oldMsgLen = newMsgLen;
	}
    //}}進捗表示用
	//全てのキーボディに走査順のインデクスを振る。
	//最適化する際、前方(インデクスが小さい方)のキーボディにのみ一致させ、後方(インデクスが大きい方)には一致させないためです。
	setKeyBodyIndex(0, g_keyBody);
	//内容が同じキーを統合します。
	optimizeKeyBody2(g_keyBody);
    //{{進捗表示用
	if(!opt_q) {
		newMsgLen = fprintf(stderr, "\rpass2 最適化2 %d/%d", g_nKeyBodyNow, g_nKeyBodyAll);
		if(oldMsgLen > newMsgLen) { fprintf(stderr, "%*s", oldMsgLen - newMsgLen, ""); } putc('\n', stderr); oldMsgLen = 0;
	}
    //}}進捗表示用
}
/*---------------------------------------------------------------------------*/
//出力処理
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
static int setKeyBodyAddr(int addr, ST_KeyBody* keyBody) {
	int iKey;
	//最適化されたキーボディは出力しないのでアドレスも割り当てない。
	if(keyBody->redir) { return addr; }
	//このキーボディのアドレスを格納する。
	keyBody->addr = addr;
	//このキーボディのバイト数の分、アドレスを進める。
	addr += 1/*mask*/;
	if(keyBody->nKey) {
		addr += 2/*nKey*/;							//符号無し16ビットBE
		addr += keyBody->nKey * (2/*keyName*/ + 3/*keyBodyDist*/);		//符号無し16ビットBE+符号有り24ビットBE
	}
	if(keyBody->nValueRow) {
		addr += 2/*nValue*/;							//符号無し16ビットBE
		addr += keyBody->nValueRow * (2/*valueName*/ + 3/*valueData*/);		//符号無し16ビットBE+符号無し24ビットBE
	}
	if(keyBody->nStringRow) {
		addr += 2/*nString*/;							//符号無し16ビットBE
		addr += keyBody->nStringRow * (2/*stringName*/ + 3/*stringDist*/);	//符号無し16ビットBE+符号有り24ビットBE
	}
	if(keyBody->nBlobRow) {
		addr += 2/*nBlob*/;							//符号無し16ビットBE
		addr += keyBody->nBlobRow * (2/*blobName*/ + 3/*blobDist*/);		//符号無し16ビットBE+符号有り24ビットBE
	}
	//サブキー(再帰)
	for(iKey = 0; iKey < keyBody->nKey; iKey++) {
		addr = setKeyBodyAddr(addr, keyBody->TBL_Key[iKey]->keyBody);
	}
	return addr;
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
static int outputBinBE(int addr, int size, int _data, FILE* fp) {
	unsigned char data[4];
	*(int*)data = _data;
	while(--size >= 0) {			//┬ビッグエンディアン(BE)で出力する事にした。実行時にアライメントされていないバイト列から符号無し16ビット値,又は,符号有り24ビット値を組み立てる時にBEの方がインターロックが発生せずに処理できるから。(1バイト目ロード⇒2バイト目ロード⇒1バイト目シフト⇒論理和)
		fwrite(&data[size], 1, 1, fp);	//┘
		addr++;
	}
	return addr;
}
static int outputKeyBodyBin(int addr, ST_KeyBody* keyBody, FILE* fp) {
	int mask;
	//最適化されたキーボディは出力しない。
	if(keyBody->redir) { return addr; }
	//このキーボディが持っている配列タイプのマスク
	mask = 0;
	if(keyBody->nKey)       { mask |= (1<<7); }	//キー
	if(keyBody->nValueRow)  { mask |= (1<<6); }	//値
	if(keyBody->nStringRow) { mask |= (1<<5); }	//文字列
	if(keyBody->nBlobRow)   { mask |= (1<<4); }	//BLOB
	addr = outputBinBE(addr, 1, mask, fp);								//符号無し8ビット
	//キー
	if(keyBody->nKey) {
		int iKey, keyBodyDist;
		addr = outputBinBE(addr, 2, keyBody->nKey - 1/*要素数-1*/, fp);				//符号無し16ビットBE
		for(iKey = 0; iKey < keyBody->nKey; iKey++) {
			ST_Key* key = keyBody->TBL_Key[iKey];
			ST_KeyBody* keyBodyRedir;
			//キーの名前
			addr = outputBinBE(addr, 2, key->nameRow->name, fp);				//符号無し16ビットBE
			//最適化先のキーボディへの距離
			keyBodyRedir = redirKeyBody(key->keyBody);
			keyBodyDist  = keyBodyRedir->addr/*最適化先のキーボディのアドレス*/ - (addr + 3)/*このフィールドの次のアドレス*/;
			if((keyBodyDist < -((1<<(24-1))-1)) ||
			   (keyBodyDist >  ((1<<(24-1))-1))) {
				yyerror("相対位置が範囲外です。");
				DIE();	//継続しても無駄なので終了する。
			}
			addr = outputBinBE(addr, 3, keyBodyDist, fp);					//符号有り24ビットBE
		}
	}
	//値
	if(keyBody->nValueRow) {
		int iValueRow;
		addr = outputBinBE(addr, 2, keyBody->nValueRow - 1/*要素数-1*/, fp);			//符号無し16ビットBE
		for(iValueRow = 0; iValueRow < keyBody->nValueRow; iValueRow++) {
			ST_ValueRow* valueRow = keyBody->TBL_ValueRow[iValueRow];
			//値の名前
			addr = outputBinBE(addr, 2, valueRow->name, fp);				//符号無し16ビットBE
			//値のデータ
			addr = outputBinBE(addr, 3, valueRow->data, fp);				//符号無し24ビットBE
		}
	}
	//文字列
	if(keyBody->nStringRow) {
		int iStringRow, stringDist;
		addr = outputBinBE(addr, 2, keyBody->nStringRow - 1/*要素数-1*/, fp);			//符号無し16ビットBE
		for(iStringRow = 0; iStringRow < keyBody->nStringRow; iStringRow++) {
			ST_StringRow* stringRow = keyBody->TBL_StringRow[iStringRow];
			ENTRY item, *pEntry;
			//文字列の名前
			addr = outputBinBE(addr, 2, stringRow->name, fp);				//符号無し16ビットBE
			//文字列への距離
			item.key = stringRow->s;
			if(!(pEntry = hsearch(item, FIND))) { DIE(); }	//バグ
			stringDist = (int)pEntry->data - (addr + 3)/*このフィールドの次のアドレス*/;
			if((stringDist < -((1<<(24-1))-1)) ||
			   (stringDist >  ((1<<(24-1))-1))) {
				yyerror("相対位置が範囲外です。");
				DIE();	//継続しても無駄なので終了する。
			}
			addr = outputBinBE(addr, 3, stringDist, fp);					//符号有り24ビットBE
		}
	}
	//BLOB
	if(keyBody->nBlobRow) {
		int iBlobRow, blobDist;
		addr = outputBinBE(addr, 2, keyBody->nBlobRow - 1/*要素数-1*/, fp);			//符号無し16ビットBE
		for(iBlobRow = 0; iBlobRow < keyBody->nBlobRow; iBlobRow++) {
			ST_BlobRow* blobRow = keyBody->TBL_BlobRow[iBlobRow];
			//BLOBの名前
			addr = outputBinBE(addr, 2, blobRow->name, fp);					//符号無し16ビットBE
			//BLOBのデータ本体への距離
			blobDist = (blobRow->blob->addr + 3/*size*/) - (addr + 3)/*このフィールドの次のアドレス*/;
			if((blobDist < -((1<<(24-1))-1)) ||
			   (blobDist >  ((1<<(24-1))-1))) {
				yyerror("相対位置が範囲外です。");
				DIE();	//継続しても無駄なので終了する。
			}
			addr = outputBinBE(addr, 3, blobDist, fp);					//符号有り24ビットBE
		}
	}
	//サブキー(再帰)
	{
		int iKey;
		for(iKey = 0; iKey < keyBody->nKey; iKey++) {
			ST_Key* key = keyBody->TBL_Key[iKey];
			addr = outputKeyBodyBin(addr, key->keyBody, fp);
		}
	}
	return addr;
}
static int outputStringBin(int addr, char* s, FILE* fp) {
	int c;
	do {
		c = *s++;
		addr = outputBinBE(addr, 1, c, fp);
	} while(c);
	return addr;
}
static int outputBlobBin(int addr, ST_Blob* blob, FILE* fp) {
	int i;
	addr = outputBinBE(addr, 3, blob->size - 1/*データ本体のバイト数-1*/, fp);			//符号無し24ビットBE
	for(i = 0; i < blob->size; i++) {
		addr = outputBinBE(addr, 1, blob->data[i], fp);
	}
	return addr;
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
static int outputTxtBE(int addr, int size, int _data) {
	unsigned char data[4];
	*(int*)data = _data;
	while(--size >= 0) {
		if(  addr      ) { putchar(',' ); }
		if(!(addr & 31)) { putchar('\n'); }
		printf("0x%02X", data[size]);
		addr++;
	}
	return addr;
}
static int outputKeyBodyTxt(int addr, ST_KeyBody* keyBody) {
	int mask;
	//最適化されたキーボディは出力しない。
	if(keyBody->redir) { return addr; }
	//このキーボディが持っている配列タイプのマスク
	mask = 0;
	if(keyBody->nKey)       { mask |= (1<<7); }	//キー
	if(keyBody->nValueRow)  { mask |= (1<<6); }	//値
	if(keyBody->nStringRow) { mask |= (1<<5); }	//文字列
	if(keyBody->nBlobRow)   { mask |= (1<<4); }	//BLOB
	addr = outputTxtBE(addr, 1, mask);								//符号無し8ビット
	//キー
	if(keyBody->nKey) {
		int iKey, keyBodyDist;
		addr = outputTxtBE(addr, 2, keyBody->nKey - 1/*要素数-1*/);				//符号無し16ビットBE
		for(iKey = 0; iKey < keyBody->nKey; iKey++) {
			ST_Key* key = keyBody->TBL_Key[iKey];
			ST_KeyBody* keyBodyRedir;
			//キーの名前
			addr = outputTxtBE(addr, 2, key->nameRow->name);				//符号無し16ビットBE
			//最適化先のキーボディへの距離
			keyBodyRedir = redirKeyBody(key->keyBody);
			keyBodyDist  = keyBodyRedir->addr/*最適化先のキーボディのアドレス*/ - (addr + 3)/*このフィールドの次のアドレス*/;
			if((keyBodyDist < -((1<<(24-1))-1)) ||
			   (keyBodyDist >  ((1<<(24-1))-1))) {
				yyerror("相対位置が範囲外です。");
				DIE();	//継続しても無駄なので終了する。
			}
			addr = outputTxtBE(addr, 3, keyBodyDist);					//符号有り24ビットBE
		}
	}
	//値
	if(keyBody->nValueRow) {
		int iValueRow;
		addr = outputTxtBE(addr, 2, keyBody->nValueRow - 1/*要素数-1*/);			//符号無し16ビットBE
		for(iValueRow = 0; iValueRow < keyBody->nValueRow; iValueRow++) {
			ST_ValueRow* valueRow = keyBody->TBL_ValueRow[iValueRow];
			//値の名前
			addr = outputTxtBE(addr, 2, valueRow->name);					//符号無し16ビットBE
			//値のデータ
			addr = outputTxtBE(addr, 3, valueRow->data);					//符号無し24ビットBE
		}
	}
	//文字列
	if(keyBody->nStringRow) {
		int iStringRow, stringDist;
		addr = outputTxtBE(addr, 2, keyBody->nStringRow - 1/*要素数-1*/);			//符号無し16ビットBE
		for(iStringRow = 0; iStringRow < keyBody->nStringRow; iStringRow++) {
			ST_StringRow* stringRow = keyBody->TBL_StringRow[iStringRow];
			ENTRY item, *pEntry;
			//文字列の名前
			addr = outputTxtBE(addr, 2, stringRow->name);					//符号無し16ビットBE
			//文字列への距離
			item.key = stringRow->s;
			if(!(pEntry = hsearch(item, FIND))) { DIE(); }	//バグ
			stringDist = (int)pEntry->data - (addr + 3)/*このフィールドの次のアドレス*/;
			if((stringDist < -((1<<(24-1))-1)) ||
			   (stringDist >  ((1<<(24-1))-1))) {
				yyerror("相対位置が範囲外です。");
				DIE();	//継続しても無駄なので終了する。
			}
			addr = outputTxtBE(addr, 3, stringDist);					//符号有り24ビットBE
		}
	}
	//BLOB
	if(keyBody->nBlobRow) {
		int iBlobRow, blobDist;
		addr = outputTxtBE(addr, 2, keyBody->nBlobRow - 1/*要素数-1*/);				//符号無し16ビットBE
		for(iBlobRow = 0; iBlobRow < keyBody->nBlobRow; iBlobRow++) {
			ST_BlobRow* blobRow = keyBody->TBL_BlobRow[iBlobRow];
			//BLOBの名前
			addr = outputTxtBE(addr, 2, blobRow->name);					//符号無し16ビットBE
			//BLOBのデータ本体への距離
			blobDist = (blobRow->blob->addr + 3/*size*/) - (addr + 3)/*このフィールドの次のアドレス*/;
			if((blobDist < -((1<<(24-1))-1)) ||
			   (blobDist >  ((1<<(24-1))-1))) {
				yyerror("相対位置が範囲外です。");
				DIE();	//継続しても無駄なので終了する。
			}
			addr = outputTxtBE(addr, 3, blobDist);						//符号有り24ビットBE
		}
	}
	//サブキー(再帰)
	{
		int iKey;
		for(iKey = 0; iKey < keyBody->nKey; iKey++) {
			ST_Key* key = keyBody->TBL_Key[iKey];
			addr = outputKeyBodyTxt(addr, key->keyBody);
		}
	}
	return addr;
}
static int outputStringTxt(int addr, char* s) {
	int c;
	do {
		c = *s++;
		addr = outputTxtBE(addr, 1, c);
	} while(c);
	return addr;
}
static int outputBlobTxt(int addr, ST_Blob* blob) {
	int i;
	addr = outputTxtBE(addr, 3, blob->size - 1/*データ本体のバイト数-1*/);				//符号無し24ビットBE
	for(i = 0; i < blob->size; i++) {
		addr = outputTxtBE(addr, 1, blob->data[i]);
	}
	return addr;
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
static void output(char* sBinOut) {
	int addr, iLabel, iString, iBlob;
	//--- ラベルを標準出力に出力する ---
//{{2017/02/27変更:'-xc','-xcs'オプションを追加しました。
//	for(iLabel = 0; iLabel < nLabel; iLabel++) {	//名前の昇順
//		char* label = TBL_Label[iLabel];
//		if(label[0] != '.') {	//ローカルラベルは出力しない。
//			printf("#define RegKey_%s %d\n", label, TBL_LabelExpr[iLabel]);
//		}
//	}
//↓2017/02/27変更:'-xc','-xcs'オプションを追加しました。
	//C言語形式
	if(!strcmp(opt_x, "c")) {
		for(iLabel = 0; iLabel < nLabel; iLabel++) {	//名前の昇順
			char* label = TBL_Label[iLabel];
			if(label[0] != '.') {	//ローカルラベルは出力しない。
				printf("#define RegKey_%s %d\n", label, TBL_LabelExpr[iLabel]);
			}
		}
	//C#形式
	} else if(!strcmp(opt_x, "cs")) {
	  #if 0	//□enumのメンバ形式で出力する場合は、こちらのブロックを有効にして下さい。ただし、C#ではenum型からint型への暗黙のキャストは行われないので、「libclip.REG_get_value(TBL.RegTbl,0,(int)RegKey.Foo)」のようなキャストが必要になってしまいます。後述の、classの定数メンバ形式で出力する方が良いと思います。
		puts("enum RegKey {");
		for(iLabel = 0; iLabel < nLabel; iLabel++) {	//名前の昇順
			char* label = TBL_Label[iLabel];
			if(label[0] != '.') {	//ローカルラベルは出力しない。
				if(iscsymf(*label)) {
					printf("%s=%d,\n", label, TBL_LabelExpr[iLabel]);
				} else {
					printf("_%s=%d,\n", label, TBL_LabelExpr[iLabel]);
				}	//      ~ ラベルの一文字目がCシンボル文字でない場合(スクリプトで「`1234」のように指定した場合)は、C#のenumのメンバ形式の出力ソースでは、先頭に'_'を付与する事にしました。尚、C言語形式や、C#のclassの定数メンバ形式ならば、先頭に'RegKey_'が付くので問題ありません。
			}
		}
		puts("}");
	  #else	//□classの定数メンバ形式で出力する場合は、こちらのブロックを有効にして下さい。前述のenumのメンバ形式での出力と違って、こちらならばキャストは不要で「libclip.REG_get_value(TBL.RegTbl,0,Const.RegKey_Foo)」と書けます。こちらの方が良いと思うので、2017/02/27現在はこちらのブロックを有効にしています。
		puts("static partial class Const {");
		for(iLabel = 0; iLabel < nLabel; iLabel++) {	//名前の昇順
			char* label = TBL_Label[iLabel];
			if(label[0] != '.') {	//ローカルラベルは出力しない。
				printf("public const int RegKey_%s=%d;\n", label, TBL_LabelExpr[iLabel]);
			}
		}
		puts("}");
	  #endif
	} else {
		DIE();	//バグ
	}
//}}2017/02/27変更:'-xc','-xcs'オプションを追加しました。
	//--- キーボディのアドレスを設定する ---
	addr = setKeyBodyAddr(0, g_keyBody);
	//--- 文字列データのアドレスを設定する ---
	if(nString) {	//hcreate(0)はエラーになるので、この判断必要。
		hcreate(nString);
		for(iString = 0; iString < nString; iString++) {
			ENTRY item;
			item.key  = TBL_String[iString];
			item.data = (void*)addr;
			if(!hsearch(item, ENTER)) { DIE(); }	//バグ
			addr += (strlen(TBL_String[iString]) + 1/*nul*/);
		}
	}
	//--- BLOBデータのアドレスを設定する ---
	for(iBlob = 0; iBlob < nBlob; iBlob++) {
		ST_Blob* blob = TBL_Blob[iBlob];
	    //{{BLOBデータ本体のワードアライメント
		//必須ではありませんが、BLOBのデータ本体開始位置をワードアライメントする事にしました。
		//C言語の構造体形式のバイナリをそのままBLOBとして格納した場合、ワードアライメントされていればそのままアクセス出来て便利だからです。
		//もしワードアライメントが保証されていなければ、アプリケーションプログラムからはバイトアクセスする必要が生じて面倒です。
		//1個のBLOB当り、最大3バイト無駄になる可能性が有りますが、普通の使用方法では細かなBLOBをたくさん作る事は無いと思うので、さほど無駄は生じないと思います。
		//今回BLOBタイプを追加した目的は、利便性と高速化のためでしたから、その方針を重視して、ワードアライメントする事に決定しました。
		//尚、BLOBの構造は「データサイズ(符号なし24ビットBE)＋データ本体」ですから、データサイズの1バイト目が(4N+1)から始まるようにします。
		while((addr & 3) != 1) { addr++; }
	    //}}BLOBデータ本体のワードアライメント
		blob->addr = addr;
		addr += (3/*size*/ + TBL_Blob[iBlob]->size);
	}
	//--- データを出力する ---
	//バイナリ形式の場合…
	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()の文字列の最後に余計な改行が付いていたのを削除しました。}}
		addr = outputKeyBodyBin(0, g_keyBody, fp);
		for(iString = 0; iString < nString; iString++) {
			addr = outputStringBin(addr, TBL_String[iString], fp);
		}
		for(iBlob = 0; iBlob < nBlob; iBlob++) {
		    //{{BLOBデータ本体のワードアライメント
			while((addr & 3) != 1) { addr = outputBinBE(addr, 1, 0, fp); }
		    //}}BLOBデータ本体のワードアライメント
			addr = outputBlobBin(addr, TBL_Blob[iBlob], fp);
		}
		fclose(fp);
	//テキスト形式の場合…
	} else {
//{{2017/02/27変更:'-xc','-xcs'オプションを追加しました。
//		printf("const unsigned char TBL_RegTbl[%d]={", addr);
//		addr = outputKeyBodyTxt(0, g_keyBody);
//		for(iString = 0; iString < nString; iString++) {
//			addr = outputStringTxt(addr, TBL_String[iString]);
//		}
//		for(iBlob = 0; iBlob < nBlob; iBlob++) {
//		    //{{BLOBデータ本体のワードアライメント
//			while((addr & 3) != 1) { addr = outputTxtBE(addr, 1, 0); }
//		    //}}BLOBデータ本体のワードアライメント
//			addr = outputBlobTxt(addr, TBL_Blob[iBlob]);
//		}
//		puts("};");
//↓2017/02/27変更:'-xc','-xcs'オプションを追加しました。
		//C言語形式
		if(!strcmp(opt_x, "c")) {
			printf("const unsigned char TBL_RegTbl[%d]={", addr);
			addr = outputKeyBodyTxt(0, g_keyBody);
			for(iString = 0; iString < nString; iString++) {
				addr = outputStringTxt(addr, TBL_String[iString]);
			}
			for(iBlob = 0; iBlob < nBlob; iBlob++) {
			    //{{BLOBデータ本体のワードアライメント
				while((addr & 3) != 1) { addr = outputTxtBE(addr, 1, 0); }
			    //}}BLOBデータ本体のワードアライメント
				addr = outputBlobTxt(addr, TBL_Blob[iBlob]);
			}
			puts("};");
		//C#形式
		} else if(!strcmp(opt_x, "cs")) {
			puts("static partial class Const {");
			printf("public static readonly byte[/*%d*/] TBL_RegTbl={", addr);
			addr = outputKeyBodyTxt(0, g_keyBody);
			for(iString = 0; iString < nString; iString++) {
				addr = outputStringTxt(addr, TBL_String[iString]);
			}
			for(iBlob = 0; iBlob < nBlob; iBlob++) {
			    //{{BLOBデータ本体のワードアライメント
				while((addr & 3) != 1) { addr = outputTxtBE(addr, 1, 0); }
			    //}}BLOBデータ本体のワードアライメント
				addr = outputBlobTxt(addr, TBL_Blob[iBlob]);
			}
			puts("};");
			puts("}");
		} else {
			DIE();	//バグ
		}
//}}2017/02/27変更:'-xc','-xcs'オプションを追加しました。
	}
}
