// ■変更履歴
// * Mon Dec 01 13:44:05 JST 2014 Naoyuki Sawa
// - 1st リリース。
//   「/clip/tool/dRudSeqS」を元にして作成しました。
// * Fri Dec 12 21:12:36 JST 2014 Naoyuki Sawa
// - 条件分岐'if on','if off'を追加したことに伴い、シーケンサの命令セット,命令コード,命令コード定義用マクロを変更しました。
// * Mon Apr 13 21:23:45 JST 2015 Naoyuki Sawa
// - dRudSeqC.exeの出力形式を2015/02/24に大幅に変更したことに対応して、当ツールも変更後の出力形式を扱うように変更しました。
//   主な変更点は、以下の通りです。
//   ・<Data>要素のデータの中に、インデクステーブルも含めるようにしました。(dRudSeqC.exeのバイナリ出力形式と同じ)
//   ・'-x'オプションで指定する値を、データ内のハーフワードインデクスでなく、'RudSeqNo_*'に対応する連番指定に変更しました。
//   ・入力するxmlファイルの<Data>要素を、C言語形式でなく、Base64形式に変更しました。
//     以下の方法で変換してください。尚、'-c76'は読みやすさのためだけであり、指定しなくても構いません。
//     ＞ dbase64enc.exe -c76 RudDef.bin
// * Sun Feb 21 21:02:01 JST 2016 Naoyuki Sawa
// - 「Boehm GC」をマルチスレッド対応にした事,及び,それに伴ってclipbpe.cモジュールを修正した事に伴い、当ツールもプロジェクト設定の変更を行ってリビルドしました。
//   詳細は、/clip/tool/gc/README.TXT と /clip/clipbpe.c の2015/11/03のコメントを参照して下さい。
// - libclipのライブラリ化の伴い、以下の変更を行いました。
//   ・extclip.hとextclip.cを削除しました。extclip.hとextclip.cはlibclipに含まれるようになったからです。
//   ・"CLiP Files"を削除しました。CLiPライブラリの各モジュールはlibclipに含まれるようになったからです。
//   当ツールの動作には変更有りません。
#include <clip.h>
/*---------------------------------------------------------------------------*/
#define VERSION "20160221"	/* バージョン定義 */
/*---------------------------------------------------------------------------*/
int iDataLen;
uint16_t TBL_Data[1000000/*調整可*/];
/*---------------------------------------------------------------------------*/
void LoadXml(const char* pFileName);
void loadData(TiXmlElement* pRoot);
void simMoveDirect(uint32_t iInst);
/*---------------------------------------------------------------------------*/
__declspec(noreturn) static void usage() {
	fprintf(stderr, "dRudSeqD - パルスモーターの台形制御シーケンサ逆コンパイラ (%s)\n", VERSION);
	fprintf(stderr, "Copyright (C) 2015-2016 Naoyuki Sawa\n");
	fprintf(stderr, "\n");
	fprintf(stderr, "USAGE:\n");
	fprintf(stderr, "  dRudSeqD filename.xml\n");
	fprintf(stderr, "\n");
	fprintf(stderr, "OPTIONS:\n");
	fprintf(stderr, "  -h     使い方を表示します。\n");
	fprintf(stderr, "\n");
	fprintf(stderr, "EXAMPLE:\n");
	fprintf(stderr, "  dRudSeqD sample.xml\n");
	fprintf(stderr, "  sample.xmlを読込み、逆コンパイルを実行し、標準出力へ出力します。\n");
	fprintf(stderr, "  入力ファイル形式は、dRudSeqSと同じです。\n");
	fprintf(stderr, "\n");
	fprintf(stderr, "eXtension:\n");
	fprintf(stderr, "  プロジェクト固有の拡張として、\n");
	fprintf(stderr, "  dRudSeqD -x実行するデータ番号 filename.xml\n");
	fprintf(stderr, "  直接制御のシミュレーションを実行します。\n");
	exit(EXIT_FAILURE);
}
/*---------------------------------------------------------------------------*/
int main(int argc, char* argv[]) {
	int opt, iRudSeqNo = 0;
	/* コマンドラインオプションを処理する。 */
	opterr = 0; /* オプションエラーは当プログラム内で処理するので、getopt()内でのエラー表示は不要。 */
	while((opt = getopt(argc, argv, "hx:")) != -1) {
		switch(opt) {
		default:
		case 'h': usage();
		case 'x': iRudSeqNo = atoi(optarg); if(!iRudSeqNo) { usage(); } break;
		}
	}
	if(optind != (argc - 1)) { usage(); }
	/* XMLファイルを読み込む。 */
	LoadXml(argv[optind]);
	{
		int numRudSeqNo = GET_LEWORD(&TBL_Data[0]);
		/* '-x'オプションが指定されていたら… */
		if(iRudSeqNo) {
			int iInst;
			if((unsigned)iRudSeqNo >= (unsigned)numRudSeqNo) { die("-xオプションの引数が不正です。"); }
			iInst  = GET_LEWORD(((uint32_t*)TBL_Data) + iRudSeqNo);		//TBL_Data[]のデータ開始位置からのハーフワードインデクス。
			iInst += numRudSeqNo * (sizeof(uint32_t) / sizeof(uint16_t));	//TBL_Data[]にはアドレステーブルも含まれているが、アドレステーブルも(仮想的に)ハーフワード単位の命令と仮定して、TBL_Data[]先頭からの命令インデクスを求める。
			/* 直接制御のシミュレーションを実行する。 */
			simMoveDirect(iInst);
		/* '-x'オプションが指定されていなければ… */
		} else {
			uint16_t* pTBL_RudSeq = (uint16_t*)&TBL_Data[numRudSeqNo * (sizeof(uint32_t) / sizeof(uint16_t))];	//TBL_Data[]のデータ開始位置を求める。
			int     actualDataLen =          iDataLen - (numRudSeqNo * (sizeof(uint32_t) / sizeof(uint16_t)));	//TBL_Data[]のデータ開始位置から終端までののハーフワード単位のサイズを求める。
			/* 逆コンパイルを実行する。 */
			RudSeq_Decompile(pTBL_RudSeq, actualDataLen);
		}
	}
	return EXIT_SUCCESS;
}
/*---------------------------------------------------------------------------*/
void LoadXml(const char* pFileName) {
	TiXmlDocument* pDoc = TiXmlDocument_new(NULL);
	TiXmlDocument_LoadFile(pDoc, pFileName);
	{
		TiXmlElement* pRoot = TiXmlNode_FirstChildElement(TiXmlDocument_super(pDoc), "Root");
		if(!pRoot) { DIE(); }
		loadData(pRoot);
	}
}
/*---------------------------------------------------------------------------*/
void loadData(TiXmlElement* pRoot) {
	TiXmlElement* pData = TiXmlNode_FirstChildElement(TiXmlElement_super(pRoot), "Data");
	if(!pData) { DIE(); }
	{
		TiXmlNode* pNode = TiXmlNode_FirstChild(TiXmlElement_super(pData), NULL);
		if(!pNode) { DIE(); }
		{
			TiXmlText* pText = TiXmlNode_ToText(pNode);
			if(!pText) { DIE(); }
			{
				const char* p = TiXmlNode_Value(TiXmlText_super(pText));
				p = strtrim(p, 3, NULL);	/* 文字列内にある全ての改行と空白文字を除去する。base64_decode()は改行や空白文字を除去しないのでこの処理は必須である。 */
				iDataLen = base64_decode(TBL_Data, sizeof TBL_Data, p);
				if(iDataLen & 1) { DIE(); }	//データバグ
				iDataLen >>= 1;			//バイト単位⇒ハーフワード単位に変換
			}
		}
	}
}
/*===========================================================================*
 *	直接制御のシミュレーションを実行する。
 *===========================================================================*/
//「clip/clipruds.c」のRudSeqCh_Exec()をシミュレーション用に改造したものです。
//「clip/clipruds.c」のRudSeqCh_Exec()を変更する際は当関数も変更してください。
/*---------------------------------------------------------------------------*/
static uint16_t Speed;		/* 分周比(0=最速) */
static uint8_t StackLevel;	/* スタックレベル(0〜RudSeqCh_StackSize) */
typedef struct _ST_Stack {
	uint32_t iInst;		/* 戻り先の命令ポインタ */
	 int16_t Repeat;	/* 0=Callスタック,0以外=Repeatスタックの残り繰り返し回数(負=無限) */
} ST_Stack;
static ST_Stack TBL_Stack[RudSeqCh_StackSize];
/*--------------------------------------------------------------------------*/
void simMoveDirect(uint32_t iInst) {
	int Hi, Lo;
	for(;;) {
		/* 命令コードを取得する。 */
		Hi = TBL_Data[iInst];
		Lo = (Hi & 0xFFF);	/* 命令コードの[11: 0] */
		Hi >>= 12;		/* 命令コードの[15:12] */
		/* 命令コードの[15:12]によって… */
		switch(Hi) {
		case 0:
			/* 命令コードの[11: 0]によって… */
			switch(Lo) {
			case 0x0FFA: /* if on */
				/* センサ安定状態がOn でなければ次の命令を飛ばす。 */
				printf("%s\n", "if on");					//※TODO:実際には､ﾚﾎﾟｰﾄ形式としてはこれでは問題が有る｡ｺﾏﾝﾄﾞﾗｲﾝｵﾌﾟｼｮﾝでｾﾝｻ安定状態を指定し､それに沿って次の命令を飛ばすかどうかを決定し､制御せよ｡'if on','if off'を出力する必要は無いはずだ｡尚､左記の案は､ﾚﾎﾟｰﾄ形式の用途では､ｼｰｹﾝｽの最初でしか条件分岐しないという前提である｡
				break;
			case 0x0FFB: /* if off */
				/* センサ安定状態がOffでなければ次の命令を飛ばす。 */
				printf("%s\n", "if off");					//※TODO:実際には､ﾚﾎﾟｰﾄ形式としてはこれでは問題が有る｡ｺﾏﾝﾄﾞﾗｲﾝｵﾌﾟｼｮﾝでｾﾝｻ安定状態を指定し､それに沿って次の命令を飛ばすかどうかを決定し､制御せよ｡'if on','if off'を出力する必要は無いはずだ｡尚､左記の案は､ﾚﾎﾟｰﾄ形式の用途では､ｼｰｹﾝｽの最初でしか条件分岐しないという前提である｡
				break;
			case 0x0FFC: /* wait stop */
				printf("%s\n", "wait stop");
				break;
			case 0x0FFD: /* move stop */
			case 0x0FFE: /* move init */
				die("%04X 当拡張機能は直接制御以外のｼﾐｭﾚｰｼｮﾝを想定していません｡\n", iInst);
			case 0x0FFF: /* end */
				/* スタックが有れば… */
				if(StackLevel) {
					/* スタックを減らす。 */
					StackLevel--;
					/* Repeatスタックならば… */
					if(TBL_Stack[StackLevel].Repeat) {
						/* 無限,又は,残り繰り返し回数を減らして0でなければ… */
						if((TBL_Stack[StackLevel].Repeat < 0) ||
						 (--TBL_Stack[StackLevel].Repeat)) {
							/* 命令ポインタを設定する。 */
							iInst = TBL_Stack[StackLevel].iInst;
							/* スタックを増やす。 */
							StackLevel++;
						}
					/* Callスタックならば… */
					} else {
						/* 命令ポインタを設定する。 */
						iInst = TBL_Stack[StackLevel].iInst;
					}
				/* スタックが無ければ… */
				} else {
					/* 再生終了する。 */
					return; /* ここまで */
				}
				break;
			default: /* user u */
				printf("%s %u\n", "user", Lo);
				break;
			}
			break;
		case 1: /* wait rel */
			/* 直前の命令完了からの相対時間が、指定された相対時間に到達していなければ… */
			printf("%s %u\n", "wait rel", Lo);
			break;
		case 2: /* goto */
			/* 命令ポインタを設定する。 */
			if(!Lo) { DIE(); } /* データバグ */					//goto命令自身へのgotoは明らかにデータバグである。
			iInst += (((int16_t)(Lo<<4))>>4) - 1/*共通処理の「+1」を相殺*/;
			break;
		case 3: /* call */
			if((unsigned)StackLevel >= RudSeqCh_StackSize) { DIE(); }		/* RudSeqCh_StackSizeを調整せよ */
			/* 戻り先の命令ポインタを格納する。 */
			TBL_Stack[StackLevel].iInst = iInst;					//「〜=iInst+1/*次の命令*/;」ではないことに注意せよ。「+1」は戻り時のendの実行後の共通処理で行われるから。
			/* Callスタックであることを示す。 */
			TBL_Stack[StackLevel].Repeat = 0;
			/* スタックを増やす。 */
			StackLevel++;
			/* 命令ポインタを設定する。 */
			if(!Lo) { DIE(); } /* データバグ */					//call命令自身へのcallは明らかにデータバグである。
			iInst += (((int16_t)(Lo<<4))>>4) - 1/*共通処理の「+1」を相殺*/;
			break;
		case 4:
			Hi = Lo;
			Lo = (Hi & 0x3FF);	/* 命令コードの[ 9: 0] */
			Hi >>= 10;		/* 命令コードの[11:10] */
			/* 命令コードの[11:10]によって… */
			switch(Hi) {
			case 0: /* repeat */
				if((unsigned)StackLevel >= RudSeqCh_StackSize) { DIE(); }	/* RudSeqCh_StackSizeを調整せよ */
				/* 戻り先の命令ポインタを格納する。 */
				TBL_Stack[StackLevel].iInst = iInst;				//「〜=iInst+1/*次の命令*/;」ではないことに注意せよ。「+1」は戻り時のendの実行後の共通処理で行われるから。
				/* Repeatスタックの残り繰り返し回数を格納する。 */
				TBL_Stack[StackLevel].Repeat = Lo ? Lo : -1/*無限*/;
/*{{ｼﾐｭﾚｰｼｮﾝ特有}}*/		if(TBL_Stack[StackLevel].Repeat == -1) { TBL_Stack[StackLevel].Repeat = 10; }	//ｼﾐｭﾚｰｼｮﾝが終わらなくなることを防ぐため､無限ﾙｰﾌﾟは10回に置換えてｼﾐｭﾚｰｼｮﾝを行うことにした｡
				/* スタックを増やす。 */
				StackLevel++;
				break;
			case 1: /* speed */
				/* 分周比を設定する。 */
				Speed = Lo;
				printf("%s %u\n", "speed", Lo);
				break;
			case 2: /* move rel */
			default: /* move abs */
				die("%04X 当拡張機能は直接制御以外のｼﾐｭﾚｰｼｮﾝを想定していません｡\n", iInst);
				break;
			}
			break;
		case 5: /* move direct if on */
		case 6: /* move direct if off */
		case 7: /* move direct always */
			/* 台形制御チャネルの動作を実行する。 */
			if(Speed && Lo) {
				static const char* TBL_CondIf[]  ={""," if on"     ," if off"    ," always"    };
				static const char* TBL_CondWhen[]={""," until rise"," until fall"," until edge"};
				printf("%s %d%s%s\n", "move direct", ((int16_t)(Lo<<6))>>6, TBL_CondIf[(Lo>>12)&3], TBL_CondWhen[(Lo>>10)&3]);
			} else {
				printf("%s\n", "move stop direct");
			}
			break;
		default: /* wait abs */
			/* データ再生開始からの絶対時間が、指定された絶対時間に到達していなければ… */
			printf("%s %u\n", "wait abs", ((Hi&7)<<12)|Lo);
			break;
		}
		/* 命令ポインタを進める。 */
		iInst++;									//end,goto,callの直後も命令ポインタが進むことに注意せよ。
	}
}
/*==========================================================================*/
