/*	
 *	clipwkst.c
 *
 *	ワークシート(多階層ハッシュテーブル)
 *
 *	CLiP - Common Library for P/ECE
 *	Copyright (C) 2015-2016 Naoyuki Sawa
 *
 *	* Mon Jul 13 21:22:16 JST 2015 Naoyuki Sawa
 *	- 1st リリース。
 *	* Wed Jul 15 01:12:55 JST 2015 Naoyuki Sawa
 *	- Worksheet_ref_l(),Worksheet_ref_a(),Worksheet_ref_v()を追加しました。
 *	- Worksheet_sum_l(),Worksheet_sum_a(),Worksheet_sum_v()を追加しました。
 *	* Thu Jul 16 22:21:18 JST 2015 Naoyuki Sawa
 *	- Worksheet_sort(),Worksheet_sort_l(),Worksheet_sort_a(),Worksheet_sort_v()を追加しました。
 *	* Sun Jul 19 12:59:05 JST 2015 Naoyuki Sawa
 *	- コメントのみ追記:
 *	  当モジュールの関数は、キーや値に指定できるのは数値のみで、文字列は指定できません。
 *	  キーや値に文字列を指定したい場合は、本日clipstr.cモジュールに追加した、quark_from_string(),quark_to_string()を併用してください。
 *	  <例>
 *	  │Worksheet_set_l(ws,quark_from_string("Value"),quark_from_string("Key"),-1);
 *	  │iVal=Worksheet_get_l(ws,quark_from_string("Key"),-1);
 *	  │if(iVal!=-1){puts(quark_to_string(iVal));}
 *	* Fri Dec 11 21:38:17 JST 2015 Naoyuki Sawa
 *	- Worksheet_incr_l()等が、これまでは戻り値無しでしたが、(1)増加後の値を返すようにしました。
 *	  カウンタとして用いる場合、セルの値を(1)増加して、すぐにその値を参照する事が多いからです。
 *	- 戻り値を無視すれば動作は変わっていないので、既存のアプリケーションへの影響は有りません。
 *	* Sun Jul 17 21:03:07 JST 2016 Naoyuki Sawa
 *	- Worksheet_ref_l(),Worksheet_get_l(),Worksheet_sum_l(),Worksheet_set_l(),Worksheet_unset_l(),Worksheet_incr_l(),Worksheet_first_l(),Worksheet_sort_l(),
 *	  Worksheet_ref_a(),Worksheet_get_a(),Worksheet_sum_a(),Worksheet_set_a(),Worksheet_unset_a(),Worksheet_incr_a(),Worksheet_first_a(),Worksheet_sort_a()をアセンブラ化しました。
 *	  主な目的は、コードサイズの低減です。
 *	  当モジュールコードサイズが、1468バイト⇒1016バイトに減りました。
 *	* Sun Jul 17 22:34:56 JST 2016 Naoyuki Sawa
 *	- USE_WORKSHEET_GTREEシンボルを定義すると、当モジュールがgtree.cモジュールを使用するようにしました。
 *	  USE_WORKSHEET_GTREEシンボルを定義しなければ、これまで通り、ght_hash_table.cモジュールを使用します。
 *	  当面は、USE_WORKSHEET_GTREEシンボルを定義して、gtree.cモジュールを使用するようにして、動作を検証しようと思います。
 *	  万一、動作上の問題が生じたら、ght_hash_table.cモジュールを使用するように戻して下さい。
 *	- gtree.cモジュールを使用するように変更したい理由は、ソート関数(Worksheet_sort()等)が不要になるからです。
 *	  ツリーは常にソートされているので、アプリケーションがイテレーション等の前に、明示的にソートする必要が無くなります。
 *	  USE_WORKSHEET_GTREEが定義されている場合はソート関数はダミー処理となります。
 *	  USE_WORKSHEET_GTREEが定義されている場合はソート関数を無効にしても良いのですが、ソート関数を呼び出している既存のアプリケーションのために残しておく事にしました。
 *	* Mon Jul 18 20:14:53 JST 2016 Naoyuki Sawa
 *	- ワークシート(多階層ハッシュテーブル)の大幅な変更を行いました。
 *	  ・変更前はハッシュテーブルを利用した実装とツリーを利用した実装を選択出来ましたが、変更後はツリーを利用した実装のみとして単純化しました。
 *	  ・ツリーを利用した実装のみとした事に伴い、ソート関数は無意味になったので、ソート関数を完全に廃止しました。
 *	  ・変更前は一度作成されたノードはWorksheet_unset_*()が呼ばれない限り存続し続けましたが、変更後は値が0で子階層も無いノードを自動的に削除して使用メモリを節約するようにしました。
 *	  ・変更前はST_Worksheetは子階層のツリーを常に(空でも)保持していましたが、変更後は子階層のツリーが空ならば未作成のままとして使用メモリを節約するようにしました。
 *	  ・変更前は明示的に0が設定されたノードもイテレートされていましたが、上記の変更に伴い、変更後は値0,かつ,子階層のツリーが空のノードは実体が存在せずイテレートされなくなりました。
 *	  ・ルート階層に対して直接「Worksheet_unset_l(ws,-1)」を呼び出す事は、変更前はエラーでしたが、変更後は可能となりました。ワークシートをクリアする動作となります。
 *	  ・グローバル変数「Worksheet_count」を追加しました。アプリケーション全体で、確保されているST_Worksheetの数を追跡する変数です。(デバッグ用)
 *	- 上述の通り、今回の変更は動作の変更も伴うものです。
 *	  変更前のプログラムは安定して動いた実績があるので、変更前のソースコードを「/clip/keep/18_ワークシート(多階層ハッシュテーブル)変更前保存/」に保存しておきました。	←←←←←←←←2016/07/18以前のソースコードは左記のフォルダを参照して下さい。
 *	* Wed Jul 20 21:23:00 JST 2016 Naoyuki Sawa
 *	- ローカル関数Worksheet_key_compare_func()をアセンブラ化しました。
 *	  小さな関数ですが、ツリーの操作において呼び出される頻度は高いので、多少は高速化の効果が有ると思います。(未計測)
 *	- ヌルポインタを指定してWorksheet_free()を呼び出した場合に、何も解放しないのにWorksheet_countを減らしてしまっていたバグを修正しました。
 *	* Sun Sep 04 21:41:17 JST 2016 Naoyuki Sawa
 *	- (int)値を(void*)にキャストして整数キーとして使用する箇所で、64ビット版ではコンパイラの警告が出ていた問題を回避しました。
 *	  詳細は、Worksheet_key_compare_func(),Worksheet_ref_v(),Worksheet_set_v(),Worksheet_unset_v()内のコメントを参照して下さい。
 *	- 64ビット版で、Worksheet_first_v()とWorksheet_next()において、不正なメモリアクセスが発生していた問題を修正しました。
 *	  詳細は、Worksheet_first_v()とWorksheet_next()内のコメントを参照して下さい。
 */
#include "clip.h"
/****************************************************************************
 *	グローバル変数
 ****************************************************************************/
#ifdef  _DEBUG
int Worksheet_count;		//確保されているST_Worksheetの数を追跡する。	デバッグ用
#endif//_DEBUG
/****************************************************************************
 *	ローカル関数
 ****************************************************************************/
#ifndef PIECE
static int Worksheet_key_compare_func(const void* a, const void* b, void* user_data) {
//{{2016/09/04変更:(int)値を(void*)にキャストして整数キーとして使用する箇所で、64ビット版ではコンパイラの警告が出ていた問題を回避しました。
//	if((int)a < (int)b) { return -1; }
//	if((int)a > (int)b) { return  1; }
//	                    { return  0; }
//↓2016/09/04変更:(int)値を(void*)にキャストして整数キーとして使用する箇所で、64ビット版ではコンパイラの警告が出ていた問題を回避しました。
#ifndef _WIN64
	if((int)a < (int)b) { return -1; }		//┐
	if((int)a > (int)b) { return  1; }		//├32ビット版は、これまでと変更有りません。32ビット版も64ビット版と同じ書き方にしても動作は変わらないのですが、簡潔さを優先してこれまでのままとする事にしました。
	                    { return  0; }		//┘
#else //_WIN64
	if((intptr_t)a < (intptr_t)b) { return -1; }	//┐
	if((intptr_t)a > (intptr_t)b) { return  1; }	//├64ビット版は、(void*)(64ビット)⇒(int)(32ビット)にキャストしようとすると警告が出ます。元の値が32ビットの整数キーなので(int)にキャストしても安全ではあるのですが、警告を回避するために(void*)(64ビット)⇒(intptr_t)(64ビット)にキャストして比較する事にしました。尚、(void*)のまま比較すると符号無し比較になるので、本来の意図と違う比較になってしまい望ましくありません。(当モジュールの用途では実害は無いと思いますが、安全のために(void*)のまま比較する事は避けました。)
	                              { return  0; }	//┘
#endif//_WIN64
//}}2016/09/04変更:(int)値を(void*)にキャストして整数キーとして使用する箇所で、64ビット版ではコンパイラの警告が出ていた問題を回避しました。
}
#else //PIECE
/*static*/ int Worksheet_key_compare_func(const void* a, const void* b, void* user_data);
asm("
		.code
		.align		1
Worksheet_key_compare_func:
		cmp		%r12, %r13				;//%psr := a <=> b
		jrlt.d		Worksheet_key_compare_func_RET		;//if((int)a < (int)b) { return -1 }
		ld.w		%r10, -1				;//					*delay*
		jrgt.d		Worksheet_key_compare_func_RET		;//if((int)a > (int)b) { return  1 }
		ld.w		%r10, 1					;//					*delay*
		ld.w		%r10, 0					;//                    { return  0 }
Worksheet_key_compare_func_RET:						;//
		ret							;//
");
#endif//PIECE
/****************************************************************************
 *	作成/開放
 ****************************************************************************/
//集計表の単一階層を作成する。
// - この階層を列と見なすか行と見なすかは、アプリケーション側の解釈次第です。
ST_Worksheet* Worksheet_new() {
	//集計表の単一階層のメモリを確保する。
	ST_Worksheet* ws = calloc(1, sizeof(ST_Worksheet));
	if(!ws) { DIE(); }
#ifdef  _DEBUG
	Worksheet_count++;	//確保されているST_Worksheetの数を追跡する。	デバッグ用
#endif//_DEBUG
	//集計表の単一階層を返す。
	return ws;
}
/*--------------------------------------------------------------------------*/
//集計表の全階層を開放する。
void Worksheet_free(ST_Worksheet* ws/*NULL可*/) {
	//ヌルポインタに対して呼出された場合は、何もしない。	//この明示的な判定は必須である。この判定を行わないと、この後の処理で何も解放しないのに、Worksheet_countを減らしてしまうバグが生じる。
	if(!ws) { return; }	//ここまで
	//子階層のツリーが有れば…
	if(ws->tKey) {
#ifdef  _DEBUG
		if(!g_tree_nnodes(ws->tKey)) { DIE(); }	//空のツリーはWorksheet_unset_v()で削除されているはず。ここでエラー停止したら当モジュールのバグか、又は、アプリケーションが途中の階層に対してキーの並び(0)個でWorksheet_unset_l()等を実行した可能性が有ります。
#endif//_DEBUG
		//子階層のツリーを開放する。
		g_tree_unref(ws->tKey);
	}
	//この階層を開放する。
	free(ws);
#ifdef  _DEBUG
	Worksheet_count--;	//確保されているST_Worksheetの数を追跡する。	デバッグ用
	if(Worksheet_count < 0) { DIE(); }	//確保されているST_Worksheetの数が0未満になる事は起こり得ない。もしここで停止したら当モジュールのバグです。
#endif//_DEBUG
}
/****************************************************************************
 *	参照
 ****************************************************************************/
//集計表のノードを参照する。
// - 指定されたノードが存在しない場合は、ヌルポインタを返します。
// - 可変引数は、(0)個以上のキーの並びと、終端の(-1)です。
//   キーの並びが(0)個だった場合は、引数wsを返します。
// - キーには、(-1)以外の任意の整数を指定出来ます。
#ifndef PIECE
/*--------------------------------------------------------------------------*/
ST_Worksheet* Worksheet_ref_l(ST_Worksheet* ws/*NULL可*/, ... /*,(-1)*/) {
	va_list ap;
	va_start(ap, ws);
	ws = Worksheet_ref_a(ws, ap);
	va_end(ap);
	return ws;
}
/*--------------------------------------------------------------------------*/
ST_Worksheet* Worksheet_ref_a(ST_Worksheet* ws/*NULL可*/, va_list ap) {
	int aKey[(7+1)/*調整可*/], i = 0;	//実用上、キーと値の階層が7以下(終端(-1)を含めて8以下)と考えて充分だろう。不足した場合は増やせ。
	for(;;) {
		if((aKey[i] = va_arg(ap, int)) == -1) { break; }
		if(++i >= ARRAY_SIZE(aKey)) { DIE(); }	//ここで停止した場合は、aKey[]不足か,又は,呼出し側が終端を忘れている。たぶん後者だろう。
	}
	return Worksheet_ref_v(ws, aKey);
}
/*--------------------------------------------------------------------------*/
#else //PIECE
ST_Worksheet* Worksheet_ref_l(ST_Worksheet* ws/*NULL可*/, ... /*,(-1)*/);
ST_Worksheet* Worksheet_ref_a(ST_Worksheet* ws/*NULL可*/, va_list ap);
asm("
		.code
		.align		1
		.global		Worksheet_ref_l
		.global		Worksheet_ref_a
Worksheet_ref_l:
		;//[%sp+0] := retp
		;//[%sp+4] := ws
		;//[%sp+8] := ...
		xld.w		%r12, [%sp+4]		;//%r12 := ws
		xadd		%r13, %sp, 8		;//%r13 := ap = &...
Worksheet_ref_a:
		;//%r12    := ws
		;//%r13    := ap
		xjp		Worksheet_ref_v
");
#endif//PIECE
/*--------------------------------------------------------------------------*/
ST_Worksheet* Worksheet_ref_v(ST_Worksheet* ws/*NULL可*/, const int* aKey/*={key,key,...,key,-1}*/) {
	int iKey;
	//次のキーを取得する。
	iKey = *aKey++;
	//キーが終端ならば、引数ws/*NULL可*/をそのまま返す。
	if(iKey == -1) { return ws/*NULL可*/; }	//ここまで
	//子階層のツリーが無ければ、ヌルポインタを返す。
	if(!ws || !ws->tKey) { return NULL; }	//ここまで
	//キーに対応する子階層(有れば)を取得する。
//{{2016/09/04変更:(int)値を(void*)にキャストして整数キーとして使用する箇所で、64ビット版ではコンパイラの警告が出ていた問題を回避しました。
//	ws = g_tree_lookup(ws->tKey, (void*)iKey);
//↓2016/09/04変更:(int)値を(void*)にキャストして整数キーとして使用する箇所で、64ビット版ではコンパイラの警告が出ていた問題を回避しました。
#ifndef _WIN64
	ws = g_tree_lookup(ws->tKey, (void*)iKey);		//32ビット版は、これまでと変更有りません。32ビット版も64ビット版と同じ書き方にしても動作は変わらないのですが、簡潔さを優先してこれまでのままとする事にしました。
#else //_WIN64
	ws = g_tree_lookup(ws->tKey, (void*)(intptr_t)iKey);	//64ビット版は、(int)(32ビット)⇒(void*)(64ビット)に直接キャストしようとすると警告が出ます。(int)(32ビット)⇒(intptr_t)(64ビット)⇒(void*)(64ビット)の順でキャストすれば警告は出ません。
#endif//_WIN64
//}}2016/09/04変更:(int)値を(void*)にキャストして整数キーとして使用する箇所で、64ビット版ではコンパイラの警告が出ていた問題を回避しました。
	//キーに対応する子階層(有れば)に対して、再帰する。
	return Worksheet_ref_v(ws/*NULL可*/, aKey);
}
/****************************************************************************
 *	取得
 ****************************************************************************/
//集計表のセルの値を取得する。
// - 集計表のセルの値が未設定だった場合は、(0)を返します。
//   未設定の(0)と、明示的に設定された(0)を、区別する方法は有りません。
// - 可変引数は、(0)個以上のキーの並びと、終端の(-1)です。
//   キーの並びが(0)個だった場合は、この階層の値を返します。
// - キーには、(-1)以外の任意の整数を指定出来ます。
#ifndef PIECE
/*--------------------------------------------------------------------------*/
int Worksheet_get_l(ST_Worksheet* ws/*NULL可*/, ... /*,(-1)*/) {
	int iVal;
	va_list ap;
	va_start(ap, ws);
	iVal = Worksheet_get_a(ws, ap);
	va_end(ap);
	return iVal;
}
/*--------------------------------------------------------------------------*/
int Worksheet_get_a(ST_Worksheet* ws/*NULL可*/, va_list ap) {
	int aKey[(7+1)/*調整可*/], i = 0;	//実用上、キーと値の階層が7以下(終端(-1)を含めて8以下)と考えて充分だろう。不足した場合は増やせ。
	for(;;) {
		if((aKey[i] = va_arg(ap, int)) == -1) { break; }
		if(++i >= ARRAY_SIZE(aKey)) { DIE(); }	//ここで停止した場合は、aKey[]不足か,又は,呼出し側が終端を忘れている。たぶん後者だろう。
	}
	return Worksheet_get_v(ws, aKey);
}
/*--------------------------------------------------------------------------*/
#else //PIECE
int Worksheet_get_l(ST_Worksheet* ws/*NULL可*/, ... /*,(-1)*/);
int Worksheet_get_a(ST_Worksheet* ws/*NULL可*/, va_list ap);
asm("
		.code
		.align		1
		.global		Worksheet_get_l
		.global		Worksheet_get_a
Worksheet_get_l:
		;//[%sp+0] := retp
		;//[%sp+4] := ws
		;//[%sp+8] := ...
		xld.w		%r12, [%sp+4]		;//%r12 := ws
		xadd		%r13, %sp, 8		;//%r13 := ap = &...
Worksheet_get_a:
		;//%r12    := ws
		;//%r13    := ap
		xjp		Worksheet_get_v
");
#endif//PIECE
/*--------------------------------------------------------------------------*/
int Worksheet_get_v(ST_Worksheet* ws/*NULL可*/, const int* aKey/*={key,key,...,key,-1}*/) {
	//指定されたノード(有れば)を取得する。
	ws = Worksheet_ref_v(ws/*NULL可*/, aKey);
	//指定されたノードの値を返す。
	return ws ? ws->iVal : 0;
}
/****************************************************************************
 *	合計
 ****************************************************************************/
//集計表の、指定されたノード以下のツリーの値を合計する。
// - 指定されたノードが存在しない場合は、(0)を返します。
//   上記の(0)と、明示的に設定された値の合計が(0)である場合とを、区別する方法は有りません。
// - 可変引数は、(0)個以上のキーの並びと、終端の(-1)です。
//   キーの並びが(0)個だった場合は、このノード以下のツリーの値を合計します。
// - キーには、(-1)以外の任意の整数を指定出来ます。
// - 指定されたノード自身の値も、合計に含みます。
//   ノードが値とキーを両方持っている場合は、どちらも合計に含みます。
//　　　　　 １ 　　　　
//　　　　　／＼　　　　例えは、左図のツリーの最上位のノードを指定した場合、
//　　　　２　　◇　　　合計値は(1+2+3+4+5=15)となります。
//　　　／　＼　　＼　　
//　　３　　　４　　５　
#ifndef PIECE
/*--------------------------------------------------------------------------*/
int Worksheet_sum_l(ST_Worksheet* ws/*NULL可*/, ... /*,(-1)*/) {
	int iSum;
	va_list ap;
	va_start(ap, ws);
	iSum = Worksheet_sum_a(ws, ap);
	va_end(ap);
	return iSum;
}
/*--------------------------------------------------------------------------*/
int Worksheet_sum_a(ST_Worksheet* ws/*NULL可*/, va_list ap) {
	int aKey[(7+1)/*調整可*/], i = 0;	//実用上、キーと値の階層が7以下(終端(-1)を含めて8以下)と考えて充分だろう。不足した場合は増やせ。
	for(;;) {
		if((aKey[i] = va_arg(ap, int)) == -1) { break; }
		if(++i >= ARRAY_SIZE(aKey)) { DIE(); }	//ここで停止した場合は、aKey[]不足か,又は,呼出し側が終端を忘れている。たぶん後者だろう。
	}
	return Worksheet_sum_v(ws, aKey);
}
/*--------------------------------------------------------------------------*/
#else //PIECE
int Worksheet_sum_l(ST_Worksheet* ws/*NULL可*/, ... /*,(-1)*/);
int Worksheet_sum_a(ST_Worksheet* ws/*NULL可*/, va_list ap);
asm("
		.code
		.align		1
		.global		Worksheet_sum_l
		.global		Worksheet_sum_a
Worksheet_sum_l:
		;//[%sp+0] := retp
		;//[%sp+4] := ws
		;//[%sp+8] := ...
		xld.w		%r12, [%sp+4]		;//%r12 := ws
		xadd		%r13, %sp, 8		;//%r13 := ap = &...
Worksheet_sum_a:
		;//%r12    := ws
		;//%r13    := ap
		xjp		Worksheet_sum_v
");
#endif//PIECE
/*--------------------------------------------------------------------------*/
int Worksheet_sum_v(ST_Worksheet* ws/*NULL可*/, const int* aKey/*={key,key,...,key,-1}*/) {
	ST_Worksheet_Iterator it;
	int iKey, iSum;
	//指定されたノード(有れば)を取得する。
	ws = Worksheet_ref_v(ws/*NULL可*/, aKey);
	//指定されたノードの値を取得する。
	iSum = Worksheet_get_l(ws/*NULL可*/, -1);
	//子階層のキーに対して再帰し、値を加算する。
	for(iKey = Worksheet_first_l(ws/*NULL可*/, &it, -1);
	    iKey != -1;
	    iKey = Worksheet_next(&it)) { iSum += Worksheet_sum_l(ws, iKey, -1); }
	return iSum;
}
/****************************************************************************
 *	設定
 ****************************************************************************/
//集計表のセルの値を設定する。
// - 可変引数は、(0)個以上のキーの並びと、終端の(-1)です。
//   キーの並びが(0)個だった場合は、この階層の値を設定します。
// - キーには、(-1)以外の任意の整数を指定出来ます。
// - 値には、任意の整数を指定出来ます。
#ifndef PIECE
/*--------------------------------------------------------------------------*/
void Worksheet_set_l(ST_Worksheet* ws, int iVal, ... /*,(-1)*/) {
	va_list ap;
	va_start(ap, iVal);
	Worksheet_set_a(ws, iVal, ap);
	va_end(ap);
}
/*--------------------------------------------------------------------------*/
void Worksheet_set_a(ST_Worksheet* ws, int iVal, va_list ap) {
	int aKey[(7+1)/*調整可*/], i = 0;	//実用上、キーと値の階層が7以下(終端(-1)を含めて8以下)と考えて充分だろう。不足した場合は増やせ。
	for(;;) {
		if((aKey[i] = va_arg(ap, int)) == -1) { break; }
		if(++i >= ARRAY_SIZE(aKey)) { DIE(); }	//ここで停止した場合は、aKey[]不足か,又は,呼出し側が終端を忘れている。たぶん後者だろう。
	}
	Worksheet_set_v(ws, iVal, aKey);
}
/*--------------------------------------------------------------------------*/
#else //PIECE
void Worksheet_set_l(ST_Worksheet* ws, int iVal, ... /*,(-1)*/);
void Worksheet_set_a(ST_Worksheet* ws, int iVal, va_list ap);
asm("
		.code
		.align		1
		.global		Worksheet_set_l
		.global		Worksheet_set_a
Worksheet_set_l:
		;//%r12    := ws
		;//[%sp+0] := retp
		;//[%sp+4] := iVal
		;//[%sp+8] := ...
		xld.w		%r13, [%sp+4]		;//%r13 := iVal
		xadd		%r14, %sp, 8		;//%r14 := ap = &...
Worksheet_set_a:
		;//%r12    := ws
		;//%r13    := iVal
		;//%r14    := ap
		xjp		Worksheet_set_v
");
#endif//PIECE
/*--------------------------------------------------------------------------*/
void Worksheet_set_v(ST_Worksheet* ws, int iVal, const int* aKey/*={key,key,...,key,-1}*/) {
	int iKey;
	ST_Worksheet* pChild;
	//ヌルポインタに対して呼出された場合は、エラー停止する。
	if(!ws) { DIE(); }
	//次のキーを取得する。
	iKey = *aKey++;
	//キーが終端ならば…
	if(iKey == -1) {
		//この階層の値を設定する。
		ws->iVal = iVal;
		return;	//ここまで
	}
	//キーに対応する子階層(有れば)を取得する。
	pChild = Worksheet_ref_l(ws, iKey, -1);
	//キーに対応する子階層が無ければ…
	if(!pChild) {
		//子階層のツリーが未作成ならば…
		if(!ws->tKey) {
			//子階層のツリーを作成する。
			ws->tKey = g_tree_new_full(Worksheet_key_compare_func,		//key_compare_func
			                           NULL,				//key_compare_data
			                           NULL,				//key_destroy_func
			                           (void (*)(void*))Worksheet_free);	//value_destroy_func
		}
		//集計表の単一階層を作成する。
		pChild = Worksheet_new();
		//キーに対応する子階層として、追加する。
//{{2016/09/04変更:(int)値を(void*)にキャストして整数キーとして使用する箇所で、64ビット版ではコンパイラの警告が出ていた問題を回避しました。
//		g_tree_insert(ws->tKey, (void*)iKey, pChild);
//↓2016/09/04変更:(int)値を(void*)にキャストして整数キーとして使用する箇所で、64ビット版ではコンパイラの警告が出ていた問題を回避しました。
#ifndef _WIN64
		g_tree_insert(ws->tKey, (void*)iKey, pChild);		//32ビット版は、これまでと変更有りません。32ビット版も64ビット版と同じ書き方にしても動作は変わらないのですが、簡潔さを優先してこれまでのままとする事にしました。
#else //_WIN64
		g_tree_insert(ws->tKey, (void*)(intptr_t)iKey, pChild);	//64ビット版は、(int)(32ビット)⇒(void*)(64ビット)に直接キャストしようとすると警告が出ます。(int)(32ビット)⇒(intptr_t)(64ビット)⇒(void*)(64ビット)の順でキャストすれば警告は出ません。
#endif//_WIN64
//}}2016/09/04変更:(int)値を(void*)にキャストして整数キーとして使用する箇所で、64ビット版ではコンパイラの警告が出ていた問題を回避しました。
	}
	//キーに対応する子階層に対して、再帰する。
	Worksheet_set_v(pChild, iVal, aKey);
	//キーに対応する子階層が、値が0で子階層も無いノードとなったら…
	if(!pChild->iVal && !pChild->tKey) {
		//キーに対応する子階層を削除する。
		Worksheet_unset_l(ws, iKey, -1);
	}
}
/****************************************************************************
 *	削除
 ****************************************************************************/
//集計表の指定されたノード,及び,それ以下の階層を開放する。
// - 可変引数は、(0)個以上のキーの並びと、終端の(-1)です。
//   キーの並びが(0)個だった場合は、この階層の値と子階層をクリアします。
//   キーの並びを(0)個にして呼び出す事が出来るのは、ルート階層に対してのみである事に注意して下さい。	←←←←重要
//   例えば、途中の階層をWorksheet_ref_l()等で取得して、その階層に対して直接「Worksheet_unset_l(ws,-1)」を実行すると、データ構造が破壊されます。
//   ある階層から見て、値が0で子階層も無いノードが、子階層のツリーの中に存在してはいけないからです。
// - キーには、(-1)以外の任意の整数を指定出来ます。
#ifndef PIECE
/*--------------------------------------------------------------------------*/
void Worksheet_unset_l(ST_Worksheet* ws/*NULL可*/, ... /*,(-1)*/) {
	va_list ap;
	va_start(ap, ws);
	Worksheet_unset_a(ws, ap);
	va_end(ap);
}
/*--------------------------------------------------------------------------*/
void Worksheet_unset_a(ST_Worksheet* ws/*NULL可*/, va_list ap) {
	int aKey[(7+1)/*調整可*/], i = 0;	//実用上、キーと値の階層が7以下(終端(-1)を含めて8以下)と考えて充分だろう。不足した場合は増やせ。
	for(;;) {
		if((aKey[i] = va_arg(ap, int)) == -1) { break; }
		if(++i >= ARRAY_SIZE(aKey)) { DIE(); }	//ここで停止した場合は、aKey[]不足か,又は,呼出し側が終端を忘れている。たぶん後者だろう。
	}
	Worksheet_unset_v(ws, aKey);
}
/*--------------------------------------------------------------------------*/
#else //PIECE
void Worksheet_unset_l(ST_Worksheet* ws/*NULL可*/, ... /*,(-1)*/);
void Worksheet_unset_a(ST_Worksheet* ws/*NULL可*/, va_list ap);
asm("
		.code
		.align		1
		.global		Worksheet_unset_l
		.global		Worksheet_unset_a
Worksheet_unset_l:
		;//[%sp+0] := retp
		;//[%sp+4] := ws
		;//[%sp+8] := ...
		xld.w		%r12, [%sp+4]		;//%r12 := ws
		xadd		%r13, %sp, 8		;//%r13 := ap = &...
Worksheet_unset_a:
		;//%r12    := ws
		;//%r13    := ap
		xjp		Worksheet_unset_v
");
#endif//PIECE
/*--------------------------------------------------------------------------*/
void Worksheet_unset_v(ST_Worksheet* ws/*NULL可*/, const int* aKey/*={key,key,...,key,-1}*/) {
	int iKey;
	ST_Worksheet* pChild;
	//ヌルポインタに対して呼出された場合は、何もしない。
	if(!ws) { return; }	//ここまで
	//次のキーを取得する。
	iKey = *aKey++;
	//キーが終端ならば…
	if(iKey == -1) {
		//この階層の値をクリアする。
		ws->iVal = 0;
		//子階層のツリーが有れば…
		if(ws->tKey) {
			//子階層のツリーを解放する。
			g_tree_unref(ws->tKey);
			             ws->tKey = NULL;
		}
		return;	//ここまで
	}
	//キーに対応する子階層(有れば)を取得する。
	pChild = Worksheet_ref_l(ws, iKey, -1);
	//キーに対応する子階層が無ければ、何もしない。
	if(!pChild) { return; }	//ここまで
	//キーに対応する子階層に対して、再帰する。
	Worksheet_unset_v(pChild, aKey);
	//キーに対応する子階層が、値が0で子階層も無いノードとなったら…
	if(!pChild->iVal && !pChild->tKey) {
		//キーに対応する子階層を解放する。
//{{2016/09/04変更:(int)値を(void*)にキャストして整数キーとして使用する箇所で、64ビット版ではコンパイラの警告が出ていた問題を回避しました。
//		g_tree_remove(ws->tKey, (void*)iKey);
//↓2016/09/04変更:(int)値を(void*)にキャストして整数キーとして使用する箇所で、64ビット版ではコンパイラの警告が出ていた問題を回避しました。
#ifndef _WIN64
		g_tree_remove(ws->tKey, (void*)iKey);		//32ビット版は、これまでと変更有りません。32ビット版も64ビット版と同じ書き方にしても動作は変わらないのですが、簡潔さを優先してこれまでのままとする事にしました。
#else //_WIN64
		g_tree_remove(ws->tKey, (void*)(intptr_t)iKey);	//64ビット版は、(int)(32ビット)⇒(void*)(64ビット)に直接キャストしようとすると警告が出ます。(int)(32ビット)⇒(intptr_t)(64ビット)⇒(void*)(64ビット)の順でキャストすれば警告は出ません。
#endif//_WIN64
//}}2016/09/04変更:(int)値を(void*)にキャストして整数キーとして使用する箇所で、64ビット版ではコンパイラの警告が出ていた問題を回避しました。
		//子階層のツリーが空になったら…
		if(!g_tree_nnodes(ws->tKey)) {
			//子階層のツリーを解放する。
			g_tree_unref(ws->tKey);
			             ws->tKey = NULL;
		}
	}
}
/****************************************************************************
 *	計数
 ****************************************************************************/
//集計表のセルの値を(1)増加し、(1)増加後の値を返す。
// - 集計表のセルの値が未設定だった場合は、(1)増加後の値は(1)になります。
// - 可変引数は、(0)個以上のキーの並びと、終端の(-1)です。
//   キーの並びが(0)個だった場合は、この階層の値を(1)増加します。
// - キーには、(-1)以外の任意の整数を指定出来ます。
#ifndef PIECE
/*--------------------------------------------------------------------------*/
int Worksheet_incr_l(ST_Worksheet* ws, ... /*,(-1)*/) {
	int iVal;
	va_list ap;
	va_start(ap, ws);
	iVal = Worksheet_incr_a(ws, ap);
	va_end(ap);
	return iVal;
}
/*--------------------------------------------------------------------------*/
int Worksheet_incr_a(ST_Worksheet* ws, va_list ap) {
	int aKey[(7+1)/*調整可*/], i = 0;	//実用上、キーと値の階層が7以下(終端(-1)を含めて8以下)と考えて充分だろう。不足した場合は増やせ。
	for(;;) {
		if((aKey[i] = va_arg(ap, int)) == -1) { break; }
		if(++i >= ARRAY_SIZE(aKey)) { DIE(); }	//ここで停止した場合は、aKey[]不足か,又は,呼出し側が終端を忘れている。たぶん後者だろう。
	}
	return Worksheet_incr_v(ws, aKey);
}
/*--------------------------------------------------------------------------*/
#else //PIECE
int Worksheet_incr_l(ST_Worksheet* ws, ... /*,(-1)*/);
int Worksheet_incr_a(ST_Worksheet* ws, va_list ap);
asm("
		.code
		.align		1
		.global		Worksheet_incr_l
		.global		Worksheet_incr_a
Worksheet_incr_l:
		;//[%sp+0] := retp
		;//[%sp+4] := ws
		;//[%sp+8] := ...
		xld.w		%r12, [%sp+4]		;//%r12 := ws
		xadd		%r13, %sp, 8		;//%r13 := ap = &...
Worksheet_incr_a:
		;//%r12    := ws
		;//%r13    := ap
		xjp		Worksheet_incr_v
");
#endif//PIECE
/*--------------------------------------------------------------------------*/
int Worksheet_incr_v(ST_Worksheet* ws, const int* aKey/*={key,key,...,key,-1}*/) {
	//現在の値を取得し(1)増加する。
	int iVal = Worksheet_get_v(ws, aKey) + 1;
	//(1)増加後の値を設定する。
	Worksheet_set_v(ws, iVal, aKey);
	//(1)増加後の値を返す。
	return iVal;
}
/****************************************************************************
 *	走査開始
 ****************************************************************************/
//集計表の子階層の最初のキーを取得します。
// - 子階層が無ければ、(-1)を返します。
// - 可変引数は、(0)個以上のキーの並びと、終端の(-1)です。
//   キーの並びが(0)個だった場合は、この階層の子階層の最初のキーを取得します。
// - キーには、(-1)以外の任意の整数を指定出来ます。
// - 当関数が(-1)を返した場合も、そのイテレータに対してWorksheet_next()を呼び出して安全です。
#ifndef PIECE
/*--------------------------------------------------------------------------*/
int Worksheet_first_l(ST_Worksheet* ws/*NULL可*/, ST_Worksheet_Iterator* it, ... /*,(-1)*/) {
	int iKey;
	va_list ap;
	va_start(ap, it);
	iKey = Worksheet_first_a(ws, it, ap);
	va_end(ap);
	return iKey;
}
/*--------------------------------------------------------------------------*/
int Worksheet_first_a(ST_Worksheet* ws/*NULL可*/, ST_Worksheet_Iterator* it, va_list ap) {
	int aKey[(7+1)/*調整可*/], i = 0;	//実用上、キーと値の階層が7以下(終端(-1)を含めて8以下)と考えて充分だろう。不足した場合は増やせ。
	for(;;) {
		if((aKey[i] = va_arg(ap, int)) == -1) { break; }
		if(++i >= ARRAY_SIZE(aKey)) { DIE(); }	//ここで停止した場合は、aKey[]不足か,又は,呼出し側が終端を忘れている。たぶん後者だろう。
	}
	return Worksheet_first_v(ws, it, aKey);
}
/*--------------------------------------------------------------------------*/
#else //PIECE
int Worksheet_first_l(ST_Worksheet* ws/*NULL可*/, ST_Worksheet_Iterator* it, ... /*,(-1)*/);
int Worksheet_first_a(ST_Worksheet* ws/*NULL可*/, ST_Worksheet_Iterator* it, va_list ap);
asm("
		.code
		.align		1
		.global		Worksheet_first_l
		.global		Worksheet_first_a
Worksheet_first_l:
		;//%r12    := ws
		;//[%sp+0] := retp
		;//[%sp+4] := it
		;//[%sp+8] := ...
		xld.w		%r13, [%sp+4]		;//%r13 := it
		xadd		%r14, %sp, 8		;//%r14 := ap = &...
Worksheet_first_a:
		;//%r12    := ws
		;//%r13    := it
		;//%r14    := ap
		xjp		Worksheet_first_v
");
#endif//PIECE
/*--------------------------------------------------------------------------*/
int Worksheet_first_v(ST_Worksheet* ws/*NULL可*/, ST_Worksheet_Iterator* it, const int* aKey/*={key,key,...,key,-1}*/) {
//{{2016/09/04変更:64ビット版で、Worksheet_first_v()とWorksheet_next()において、不正なメモリアクセスが発生していた問題を修正しました。
//	int iKey;
//↓2016/09/04変更:64ビット版で、Worksheet_first_v()とWorksheet_next()において、不正なメモリアクセスが発生していた問題を修正しました。
	// * Sun Sep 04 21:41:17 JST 2016 Naoyuki Sawa
	// - 当関数の最後で、g_tree_first_np()に対して(void*)と見なしてポインタを渡すので、(void*)と同じサイズでなくてはなりません。
	//   もし(int)だと、64ビットでは、32ビット変数に対して64ビット値を格納しようとしてしまい、不正なメモリアクセスが発生します。
	//   上記の問題を防ぐために、(intptr_t)変数としました。
	//   尚、32ビットの場合は(int)も(intptr_t)も同じなので、生成されるコードはこれまでと変わりありません。
	// - 64ビット版で、上記の問題は、VS2015のランタイムエラーチェック機能によって、発見出来ました。
	//   │Run-Time Check Failure #2 - Stack around the variable 'iKey' was corrupted.
	//   VC++6.0ではスタック上の不正なメモリアクセスは発見しづらいため、VS2015のランタイムエラーチェック機能はとても役に立ちます。
	//   ランタイムエラーチェックはデフォルトでOnになっているので、不用意にOffにしたりせずに、Onのままで使用すべきだと思いました。
	intptr_t iKey;
//}}2016/09/04変更:64ビット版で、Worksheet_first_v()とWorksheet_next()において、不正なメモリアクセスが発生していた問題を修正しました。
	//指定されたノード(有れば)を取得する。
	ws = Worksheet_ref_v(ws/*NULL可*/, aKey);
	//指定されたノードの子階層(有れば)を取得する。
	it->tree = ws/*NULL可*/ ? ws->tKey/*NULL可*/ : NULL;
	//指定されたノードの子階層が有れば、最初のイテレーションを行う。
	//指定されたノードの子階層が無ければ、(-1)を返す。
	return (it->tree && g_tree_first_np(it->tree, &it->it, (void**)&iKey, NULL)) ? iKey : -1;	//{{2016/09/04コメント追加:変数iKeyは(void*)と同じサイズでなくてはならない事に注意せよ。上記のコメントを参照して下さい。}}
}
/****************************************************************************
 *	走査継続
 ****************************************************************************/
//集計表の子階層の次のキーを取得します。
// - 次のキーが無ければ、(-1)を返します。
// - 当関数が(-1)を返した場合も、そのイテレータに対して再度Worksheet_next()を呼び出して安全です。
int Worksheet_next(ST_Worksheet_Iterator* it) {
//{{2016/09/04変更:64ビット版で、Worksheet_first_v()とWorksheet_next()において、不正なメモリアクセスが発生していた問題を修正しました。
//	int iKey;
//↓2016/09/04変更:64ビット版で、Worksheet_first_v()とWorksheet_next()において、不正なメモリアクセスが発生していた問題を修正しました。
	// * Sun Sep 04 21:41:17 JST 2016 Naoyuki Sawa
	// - 当関数の最後で、g_tree_first_np()に対して(void*)と見なしてポインタを渡すので、(void*)と同じサイズでなくてはなりません。
	//   もし(int)だと、64ビットでは、32ビット変数に対して64ビット値を格納しようとしてしまい、不正なメモリアクセスが発生します。
	//   上記の問題を防ぐために、(intptr_t)変数としました。
	//   尚、32ビットの場合は(int)も(intptr_t)も同じなので、生成されるコードはこれまでと変わりありません。
	// - 64ビット版で、上記の問題は、VS2015のランタイムエラーチェック機能によって、発見出来ました。
	//   │Run-Time Check Failure #2 - Stack around the variable 'iKey' was corrupted.
	//   VC++6.0ではスタック上の不正なメモリアクセスは発見しづらいため、VS2015のランタイムエラーチェック機能はとても役に立ちます。
	//   ランタイムエラーチェックはデフォルトでOnになっているので、不用意にOffにしたりせずに、Onのままで使用すべきだと思いました。
	intptr_t iKey;
//}}2016/09/04変更:64ビット版で、Worksheet_first_v()とWorksheet_next()において、不正なメモリアクセスが発生していた問題を修正しました。
	//指定されたノードの子階層が有れば、次のイテレーションを行う。
	//指定されたノードの子階層が無ければ、(-1)を返す。
	return (it->tree && g_tree_next_np(it->tree, &it->it, (void**)&iKey, NULL)) ? iKey : -1;	//{{2016/09/04コメント追加:変数iKeyは(void*)と同じサイズでなくてはならない事に注意せよ。上記のコメントを参照して下さい。}}
}
/****************************************************************************
 *	ダンプ
 ****************************************************************************/
static void Worksheet_dump_indent(int indent) {
	while(indent--) { putchar('\t'); }
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
static void Worksheet_dump_iter(ST_Worksheet* ws/*NULL可*/, int indent) {
	ST_Worksheet_Iterator it;
	int iKey;
	//子階層を走査する。
	for(iKey = Worksheet_first_l(ws/*NULL可*/, &it, -1);
	    iKey != -1;
	    iKey = Worksheet_next(&it)) {
		ST_Worksheet* pChild = Worksheet_ref_l(ws, iKey, -1);
		//子階層の値が設定されていたら、値を出力する。
		if(pChild->iVal) {
			Worksheet_dump_indent(indent);
			printf("%d\t=\t%d\n", iKey, pChild->iVal);
		}
		//子階層のツリーが有れば、キーを出力する。
		if(pChild->tKey) {
#ifdef  _DEBUG
			if(!g_tree_nnodes(ws->tKey)) { DIE(); }	//空のツリーはWorksheet_unset_v()で削除されているはず。ここでエラー停止したら当モジュールのバグか、又は、アプリケーションが途中の階層に対してキーの並び(0)個でWorksheet_unset_l()等を実行した可能性が有ります。
#endif//_DEBUG
			Worksheet_dump_indent(indent);
			printf("%d\n", iKey);
			Worksheet_dump_iter(pChild, indent + 1);
			Worksheet_dump_indent(indent);
			printf("end\n");
		}
	}
}
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
//集計表の全階層を、レジストリトクリプト形式でダンプします。
// - レジストリスクリプトの制限は考慮しません。
//   具体的には、レジストリスクリプトのキーと値の範囲は(0)〜(65535)であるのに対し、
//   当モジュールのキーの範囲は(-1)を除く任意の整数,値の範囲は(-1)も含む任意の整数です。
//   レジストリスクリプトの制限から外れるこれらの値も、そのまま出力します。
//   その場合、レジストリスクリプトをコンパイルした時点でエラーとなります。
// - ルート階層に直接設定した値は出力しません。
//   レジストリスクリプトの構造上、'ルート階層の値'は表現出来ないからです。
void Worksheet_dump(ST_Worksheet* ws/*NULL可*/) {
	Worksheet_dump_iter(ws, 0);
}
/****************************************************************************
 *	使用例
 ****************************************************************************/
#if 0
void test() {
	ST_Worksheet* ws = Worksheet_new();
	int i, s = 0;
	for(i = 0; i < 1000; i++) {
		int a[8], j, n, v;
		//---
		v = RND16_RANGE(s, 0, 3);
		n = RND16_RANGE(s, 0, 7);
		for(j = 0; j <= n; j++) { a[j] = RND16_RANGE(s, 0, 3); }
		switch(n) {
		default:DIE();
		case 0: Worksheet_set_l(ws, v, a[0], -1); break;
		case 1: Worksheet_set_l(ws, v, a[0], a[1], -1); break;
		case 2: Worksheet_set_l(ws, v, a[0], a[1], a[2], -1); break;
		case 3: Worksheet_set_l(ws, v, a[0], a[1], a[2], a[3], -1); break;
		case 4: Worksheet_set_l(ws, v, a[0], a[1], a[2], a[3], a[4], -1); break;
		case 5: Worksheet_set_l(ws, v, a[0], a[1], a[2], a[3], a[4], a[5], -1); break;
		case 6: Worksheet_set_l(ws, v, a[0], a[1], a[2], a[3], a[4], a[5], a[6], -1); break;
		}
		//---
		n = RND16_RANGE(s, 0, 7);
		for(j = 0; j <= n; j++) { a[j] = RND16_RANGE(s, 0, 3); }
		switch(n) {
		default:DIE();
		case 0: Worksheet_incr_l(ws, a[0], -1); break;
		case 1: Worksheet_incr_l(ws, a[0], a[1], -1); break;
		case 2: Worksheet_incr_l(ws, a[0], a[1], a[2], -1); break;
		case 3: Worksheet_incr_l(ws, a[0], a[1], a[2], a[3], -1); break;
		case 4: Worksheet_incr_l(ws, a[0], a[1], a[2], a[3], a[4], -1); break;
		case 5: Worksheet_incr_l(ws, a[0], a[1], a[2], a[3], a[4], a[5], -1); break;
		case 6: Worksheet_incr_l(ws, a[0], a[1], a[2], a[3], a[4], a[5], a[6], -1); break;
		}
		//---
		n = RND16_RANGE(s, 0, 7);
		for(j = 0; j <= n; j++) { a[j] = RND16_RANGE(s, 0, 3); }
		switch(n) {
		default:DIE();
		case 0: Worksheet_unset_l(ws, a[0], -1); break;
		case 1: Worksheet_unset_l(ws, a[0], a[1], -1); break;
		case 2: Worksheet_unset_l(ws, a[0], a[1], a[2], -1); break;
		case 3: Worksheet_unset_l(ws, a[0], a[1], a[2], a[3], -1); break;
		case 4: Worksheet_unset_l(ws, a[0], a[1], a[2], a[3], a[4], -1); break;
		case 5: Worksheet_unset_l(ws, a[0], a[1], a[2], a[3], a[4], a[5], -1); break;
		case 6: Worksheet_unset_l(ws, a[0], a[1], a[2], a[3], a[4], a[5], a[6], -1); break;
		}
	}
	Worksheet_dump(ws);
	Worksheet_free(ws);
}
#endif
